Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dev 170 sprint validation #90

Merged
merged 2 commits into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import dev.corn.cornbackend.entities.sprint.interfaces.SprintRepository;
import dev.corn.cornbackend.entities.user.User;
import dev.corn.cornbackend.utils.exceptions.project.ProjectDoesNotExistException;
import dev.corn.cornbackend.utils.exceptions.sprint.InvalidSprintDateException;
import dev.corn.cornbackend.utils.exceptions.sprint.SprintDoesNotExistException;
import dev.corn.cornbackend.utils.exceptions.sprint.SprintEndDateMustBeAfterStartDate;
import lombok.RequiredArgsConstructor;
Expand All @@ -30,7 +31,7 @@
@RequiredArgsConstructor
public class SprintServiceImpl implements SprintService {
public static final int SPRINTS_PER_PAGE = 20;
public static final int FUTURE_SPRINTS_PER_PAGE = 5;
private static final int FUTURE_SPRINTS_PER_PAGE = 5;
private static final String UPDATED_SPRINT = "Updated sprint: {}";
private static final String FOUND_SPRINT_TO_UPDATE = "Found sprint to update: {}";
private static final String PROJECT_NOT_FOUND = "Project with projectId: %d does not exist";
Expand All @@ -45,6 +46,10 @@ public final SprintResponse addNewSprint(SprintRequest sprintRequest, User user)
if (sprintRequest.endDate().isBefore(sprintRequest.startDate())) {
throw new SprintEndDateMustBeAfterStartDate(sprintRequest.startDate(), sprintRequest.endDate());
}
if (sprintRepository.existsBetweenStartDateAndEndDate(sprintRequest.startDate(),
sprintRequest.endDate(), sprintRequest.projectId())) {
throw new InvalidSprintDateException("Sprint with given dates already exists");
}

Project project = projectRepository.findByProjectIdAndOwner(sprintRequest.projectId(), user)
.orElseThrow(() -> new ProjectDoesNotExistException(
Expand All @@ -54,8 +59,8 @@ public final SprintResponse addNewSprint(SprintRequest sprintRequest, User user)
.builder()
.project(project)
.sprintName(sprintRequest.name())
.sprintStartDate(sprintRequest.startDate())
.sprintEndDate(sprintRequest.endDate())
.startDate(sprintRequest.startDate())
.endDate(sprintRequest.endDate())
.sprintDescription(sprintRequest.description())
.build();
log.info("Instantiated sprint: {}", sprint);
Expand Down Expand Up @@ -132,15 +137,18 @@ public final SprintResponse updateSprintsDescription(String description, long sp
public final SprintResponse updateSprintsStartDate(LocalDate startDate, long sprintId, User user) {
log.info("Updating sprint with id: {} startDate to: {}", sprintId, startDate);

if (sprintRepository.existsEndDateBeforeDate(startDate)) {
throw new InvalidSprintDateException("Start date cannot be after any existing sprint's end date");
}
Sprint sprintToUpdate = sprintRepository.findByIdWithProjectOwner(sprintId, user)
.orElseThrow(() -> new SprintDoesNotExistException(sprintId));

log.info(FOUND_SPRINT_TO_UPDATE, sprintToUpdate);

if (sprintToUpdate.isEndBefore(startDate)) {
throw new SprintEndDateMustBeAfterStartDate(startDate, sprintToUpdate.getSprintEndDate());
throw new SprintEndDateMustBeAfterStartDate(startDate, sprintToUpdate.getEndDate());
}
sprintToUpdate.setSprintStartDate(startDate);
sprintToUpdate.setStartDate(startDate);

Sprint updatedSprint = sprintRepository.save(sprintToUpdate);

Expand All @@ -153,15 +161,18 @@ public final SprintResponse updateSprintsStartDate(LocalDate startDate, long spr
public final SprintResponse updateSprintsEndDate(LocalDate endDate, long sprintId, User user) {
log.info("Updating sprint with id: {} endDate to: {}", sprintId, endDate);

if (sprintRepository.existsEndDateBeforeDate(endDate)) {
throw new InvalidSprintDateException("End date cannot be before any existing sprint's end date");
}
Sprint sprintToUpdate = sprintRepository.findByIdWithProjectOwner(sprintId, user)
.orElseThrow(() -> new SprintDoesNotExistException(sprintId));

log.info(FOUND_SPRINT_TO_UPDATE, sprintToUpdate);

if (sprintToUpdate.isStartAfter(endDate)) {
throw new SprintEndDateMustBeAfterStartDate(sprintToUpdate.getSprintStartDate(), endDate);
throw new SprintEndDateMustBeAfterStartDate(sprintToUpdate.getStartDate(), endDate);
}
sprintToUpdate.setSprintEndDate(endDate);
sprintToUpdate.setEndDate(endDate);

Sprint updatedSprint = sprintRepository.save(sprintToUpdate);

Expand Down Expand Up @@ -201,7 +212,7 @@ public List<SprintResponse> getCurrentAndFutureSprints(long projectId, User user
Sort.Direction.ASC, SPRINT_START_DATE_FIELD_NAME)
);

Page<Sprint> sprints = sprintRepository.findAllByProjectAndSprintEndDateAfter(project,
Page<Sprint> sprints = sprintRepository.findAllByProjectAndEndDateAfter(project,
LocalDate.now(), pageable);

log.info(SPRINTS_ON_PAGE, sprints.getNumberOfElements());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@

@Builder
public record SprintResponse(long sprintId, long projectId, String sprintName, String sprintDescription,
LocalDate sprintStartDate, LocalDate sprintEndDate) {
LocalDate startDate, LocalDate endDate) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,15 @@ public void run(String... args) {

List<ProjectMember> members = projectMemberRepository.findAll();

for(int i = 0; i < 30; i++) {
LocalDate prevDate = LocalDate.now();

for (int i = 0; i < 30; i++) {
sprintService.addNewSprint(new SprintRequest(
project.getProjectId(), String.format("Sprint %d", i), LocalDate.now(), LocalDate.now().plusDays(7),
project.getProjectId(), String.format("Sprint %d", i), prevDate, prevDate.plusDays(7),
String.format("Sprintd %d description", i)
), projectOwner);

prevDate = prevDate.plusDays(8L);
}

List<Sprint> allSprints = sprintRepository.findAll();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,30 +57,30 @@ public class Sprint implements Jsonable, DateController {

@NotNull(message = SprintConstants.SPRINT_START_DATE_NULL_MSG)
@FutureOrPresent(message = SprintConstants.SPRINT_START_DATE_FUTURE_OR_PRESENT_MSG)
private LocalDate sprintStartDate;
private LocalDate startDate;

@NotNull(message = SprintConstants.SPRINT_END_DATE_NULL_MSG)
@Future(message = SprintConstants.SPRINT_END_DATE_FUTURE_MSG)
private LocalDate sprintEndDate;
private LocalDate endDate;

@Override
public final boolean isStartBefore(LocalDate date) {
return sprintStartDate.isBefore(date);
return startDate.isBefore(date);
}

@Override
public final boolean isStartAfter(LocalDate date) {
return sprintStartDate.isAfter(date);
return startDate.isAfter(date);
}

@Override
public final boolean isEndBefore(LocalDate date) {
return sprintEndDate.isBefore(date);
return endDate.isBefore(date);
}

@Override
public final boolean isEndAfter(LocalDate date) {
return sprintEndDate.isAfter(date);
return endDate.isAfter(date);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ public final SprintResponse toSprintResponse(Sprint sprint) {
.projectId(sprint.getProject().getProjectId())
.sprintName(sprint.getSprintName())
.sprintDescription(sprint.getSprintDescription())
.sprintStartDate(sprint.getSprintStartDate())
.sprintEndDate(sprint.getSprintEndDate())
.startDate(sprint.getStartDate())
.endDate(sprint.getEndDate())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package dev.corn.cornbackend.entities.sprint.constants;

public final class SprintConstants {
public static final String SPRINT_FIRST_DATE_GETTER_NAME = "getSprintStartDate";
public static final String SPRINT_SECOND_DATE_GETTER_NAME = "getSprintEndDate";
public static final String SPRINT_FIRST_DATE_GETTER_NAME = "getStartDate";
public static final String SPRINT_SECOND_DATE_GETTER_NAME = "getEndDate";
public static final String SPRINT_LATER_THAN_MSG = "Sprint end date must be later than start date";

public static final String SPRINT_PROJECT_FIELD_NAME = "project";
Expand All @@ -18,11 +18,11 @@ public final class SprintConstants {
public static final String SPRINT_DESCRIPTION_WRONG_SIZE_MSG = "Description must consist of max 500 characters";
public static final int SPRINT_DESCRIPTION_MAX_SIZE = 500;

public static final String SPRINT_START_DATE_FIELD_NAME = "sprintStartDate";
public static final String SPRINT_START_DATE_FIELD_NAME = "startDate";
public static final String SPRINT_START_DATE_NULL_MSG = "Sprint start Date cannot be null";
public static final String SPRINT_START_DATE_FUTURE_OR_PRESENT_MSG = "Sprint start date has to be present or future date";

public static final String SPRINT_END_DATE_FIELD_NAME = "sprintEndDate";
public static final String SPRINT_END_DATE_FIELD_NAME = "endDate";
public static final String SPRINT_END_DATE_NULL_MSG = "Sprint end date cannot be null";
public static final String SPRINT_END_DATE_FUTURE_MSG = "Sprint end date has to be future date";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,48 +22,90 @@ public interface SprintRepository extends JpaRepository<Sprint, Long> {
/**
* Finds all Sprints associated with Project of given projectId and checks if the user is an assignee
* or the owner of the project
*
* @param projectId id of Project
* @param user user requesting access
* @param pageable pageable
* @param user user requesting access
* @param pageable pageable
* @return Page containing sprints associated with Project if user's criteria are met, empty Page otherwise
*/
@Query("SELECT s FROM Sprint s WHERE s.project.projectId = :projectId AND (s.project.owner = :user OR :user IN (SELECT pm.user FROM ProjectMember pm WHERE pm.project = s.project))")
@Query("""
SELECT s FROM Sprint s
WHERE s.project.projectId = :projectId AND
(s.project.owner = :user OR :user IN (SELECT pm.user FROM ProjectMember pm WHERE pm.project = s.project))
""")
Page<Sprint> findAllByProjectId(@Param("projectId") long projectId, @Param("user") User user, Pageable pageable);

/**
* Finds a Sprint by id and checks if the user is a assignee or the owner of the project associated
* with the Sprint
* @param id id of Sprint
*
* @param id id of Sprint
* @param user user requesting access
* @return an Optional containing the found Sprint if it exists, empty Optional otherwise
*/
@Query("SELECT s FROM Sprint s WHERE s.sprintId = :id AND (s.project.owner = :user OR :user IN (SELECT pm.user FROM ProjectMember pm WHERE pm.project = s.project))")
@Query("""
SELECT s FROM Sprint s
WHERE s.sprintId = :id AND
(s.project.owner = :user OR :user IN (SELECT pm.user FROM ProjectMember pm WHERE pm.project = s.project))
""")
Optional<Sprint> findByIdWithProjectMember(@Param("id") long id, @Param("user") User user);

/**
* Finds a Sprint by id and by project
*
* @param sprintId id of Sprint
* @param project project
* @param project project
* @return an Optional containing the found Sprint if it exists, empty Optional otherwise
*/
Optional<Sprint> findBySprintIdAndProject(long sprintId, Project project);

/**
* Finds a Sprint by id and checks if the user is the owner of the project associated with the Sprint
* @param id id of Sprint
*
* @param id id of Sprint
* @param user user requesting access
* @return an Optional containing the found Sprint if it exists, empty Optional otherwise
*/
@Query("SELECT s FROM Sprint s WHERE s.sprintId = :id AND s.project.owner = :user")
Optional<Sprint> findByIdWithProjectOwner(@Param("id") long id, @Param("user") User user);

/**
* Checks if there is a Sprint with start date between specified dates
*
* @param startDate start date to check
* @param endDate end date to check
* @param projectId id of Project
* @return true if there is a Sprint with start date between specified dates, false otherwise
*/
@Query("""
select count(s) > 0 from Sprint s
WHERE s.project.projectId = :projectId AND
((s.startDate <= :startDate AND s.endDate >= :startDate) OR
(s.startDate <= :endDate AND s.endDate >= :endDate) OR
(s.startDate >= :startDate AND s.endDate <= :endDate))
""")
boolean existsBetweenStartDateAndEndDate(LocalDate startDate, LocalDate endDate, long projectId);

/**
* Checks if there is a Sprint with end date before specified date
*
* @param date date to check
* @return true if there is a Sprint with end date before specified date, false otherwise
*/
@Query("""
select count(*) > 0 from Sprint s
where s.endDate < :date
""")
boolean existsEndDateBeforeDate(LocalDate date);

/**
* Finds all sprints with given project that have end date after specified date and pages them
* @param project project to find sprints associated with
* @param date date after which found sprints should end
*
* @param project project to find sprints associated with
* @param date date after which found sprints should end
* @param pageable pageable to page and sort sprints
* @return Page of found sprints
*/
Page<Sprint> findAllByProjectAndSprintEndDateAfter(Project project, LocalDate date, Pageable pageable);
Page<Sprint> findAllByProjectAndEndDateAfter(Project project, LocalDate date, Pageable pageable);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package dev.corn.cornbackend.utils.exceptions.sprint;

import dev.corn.cornbackend.utils.exceptions.AbstractException;
import org.springframework.http.HttpStatus;

import java.io.Serial;

/**
* Exception thrown when the end date of a sprint is before the start date
*/
public class InvalidSprintDateException extends AbstractException {
@Serial
private static final long serialVersionUID = -3276513929931218115L;

public InvalidSprintDateException(String reason) {
super(HttpStatus.BAD_REQUEST, reason);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import dev.corn.cornbackend.test.sprint.SprintTestDataBuilder;
import dev.corn.cornbackend.test.sprint.data.AddNewSprintData;
import dev.corn.cornbackend.utils.exceptions.project.ProjectDoesNotExistException;
import dev.corn.cornbackend.utils.exceptions.sprint.InvalidSprintDateException;
import dev.corn.cornbackend.utils.exceptions.sprint.SprintDoesNotExistException;
import dev.corn.cornbackend.utils.exceptions.sprint.SprintEndDateMustBeAfterStartDate;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -71,11 +72,11 @@ final void test_addNewSprint_shouldAddNewSprint() {
assertEquals(expected, actual, SPRINT_RESPONSE_SHOULD_BE_EQUAL_TO_EXPECTED);
verify(sprintRepository).save(ADD_SPRINT_DATA.asSprint());
}

@Test
final void test_addNewSprint_shouldThrowWhenProjectNotFound() {
// given
SprintResponse expected = MAPPER.toSprintResponse(ADD_SPRINT_DATA.asSprint());
SprintRequest sprintRequest = ADD_SPRINT_DATA.asSprintRequest();
User owner = ADD_SPRINT_DATA.project().getOwner();

// when
Expand Down Expand Up @@ -260,7 +261,7 @@ final void test_updateSprintsStartDate_shouldThrowSprintDoesNotExistException()
final void test_updateSprintsStartDate_shouldThrowWhenEndIsBeforeStart() {
// given
Sprint sprint = ADD_SPRINT_DATA.asSprint();
LocalDate newStartDate = sprint.getSprintEndDate().plusDays(10);
LocalDate newStartDate = sprint.getEndDate().plusDays(10);
User owner = ADD_SPRINT_DATA.project().getOwner();

// when
Expand Down Expand Up @@ -315,7 +316,7 @@ final void test_updateSprintsEndDate_shouldThrowSprintDoesNotExistException() {
final void test_updateSprintsEndDate_shouldThrowWhenEndIsBeforeStart() {
// given
Sprint sprint = ADD_SPRINT_DATA.asSprint();
LocalDate newEndDate = sprint.getSprintStartDate().minusDays(10);
LocalDate newEndDate = sprint.getStartDate().minusDays(10);
User owner = ADD_SPRINT_DATA.project().getOwner();

// when
Expand Down Expand Up @@ -394,8 +395,8 @@ final void test_getCurrentAndFutureSprints_shouldReturnSprintResponseList() {
// when
when(projectRepository.findByIdWithProjectMember(projectId, user))
.thenReturn(Optional.of(ADD_SPRINT_DATA.project()));
when(sprintRepository.findAllByProjectAndSprintEndDateAfter(
any(),any(), any()))
when(sprintRepository.findAllByProjectAndEndDateAfter(
any(), any(), any()))
.thenReturn(new PageImpl<>(List.of(ADD_SPRINT_DATA.asSprint())));
when(MAPPER.toSprintResponse(ADD_SPRINT_DATA.asSprint()))
.thenReturn(ADD_SPRINT_DATA.asSprintResponse());
Expand All @@ -419,4 +420,38 @@ final void test_getCurrentAndFutureSprints_shouldThrowProjectDoesNotExistExcepti
assertThrows(ProjectDoesNotExistException.class, () ->
sprintService.getCurrentAndFutureSprints(projectId, user));
}
}

@Test
final void test_addNewSprint_shouldThrowInvalidSprintDateException() {
// given
SprintRequest expected = ADD_SPRINT_DATA.asSprintRequest();
User owner = ADD_SPRINT_DATA.project().getOwner();

// when
when(sprintRepository.existsBetweenStartDateAndEndDate(ADD_SPRINT_DATA.asSprint().getStartDate(),
ADD_SPRINT_DATA.asSprint().getEndDate(), ADD_SPRINT_DATA.project().getProjectId()))
.thenReturn(true);

// then
assertThrows(InvalidSprintDateException.class, () -> {
sprintService.addNewSprint(expected, owner);
});
}

@Test
final void test_updateSprintsStartDate_shouldThrowInvalidSprintDateException() {
// given
LocalDate newStartDate = LocalDate.now();
final long sprintId = 1L;
User owner = ADD_SPRINT_DATA.project().getOwner();

// when
when(sprintRepository.existsEndDateBeforeDate(newStartDate))
.thenReturn(true);

// then
assertThrows(InvalidSprintDateException.class, () -> {
sprintService.updateSprintsStartDate(newStartDate, sprintId, owner);
});
}
}
Loading
Loading