diff --git a/src/functionalTest/java/uk/gov/hmcts/reform/preapi/controllers/BookingControllerFT.java b/src/functionalTest/java/uk/gov/hmcts/reform/preapi/controllers/BookingControllerFT.java index 8fae44d188..c07cfc8018 100644 --- a/src/functionalTest/java/uk/gov/hmcts/reform/preapi/controllers/BookingControllerFT.java +++ b/src/functionalTest/java/uk/gov/hmcts/reform/preapi/controllers/BookingControllerFT.java @@ -121,7 +121,12 @@ void recordingScheduleDateShouldNotBeAmendedToThePast() throws JsonProcessingExc // set scheduledFor to yesterday createBooking.setScheduledFor(Timestamp.from(OffsetDateTime.now().minusDays(1).toInstant())); - var putResponse = putBooking(createBooking); + putBooking(createBooking); + var putResponse = doPutRequest( + BOOKINGS_ENDPOINT + "/" + createBooking.getId(), + OBJECT_MAPPER.writeValueAsString(createBooking), + TestingSupportRoles.LEVEL_1 + ); assertResponseCode(putResponse, 400); } @@ -447,14 +452,18 @@ void createBookingWithScheduledForThePast() throws JsonProcessingException { var putCase = doPutRequest( CASES_ENDPOINT + "/" + caseEntity.getId(), OBJECT_MAPPER.writeValueAsString(caseEntity), - TestingSupportRoles.SUPER_USER + TestingSupportRoles.LEVEL_1 ); assertResponseCode(putCase, 201); - var putBooking = putBooking(booking); + var putBooking = doPutRequest( + BOOKINGS_ENDPOINT + "/" + booking.getId(), + OBJECT_MAPPER.writeValueAsString(booking), + TestingSupportRoles.LEVEL_1 + ); assertResponseCode(putBooking, 400); - assertThat(putBooking.body().jsonPath().getString("scheduledFor")) - .isEqualTo("must not be before today"); + assertThat(putBooking.body().jsonPath().getString("message")) + .isEqualTo("Scheduled date must not be in the past"); } @DisplayName("Create a booking with a participant that is not part of the case") @@ -607,7 +616,12 @@ void upsertBookingForClosedCase() throws JsonProcessingException { // attempt update booking.setScheduledFor(Timestamp.valueOf(LocalDate.now().atStartOfDay().plusDays(1))); - var putBooking2 = putBooking(booking); + var putBooking2 = doPutRequest( + BOOKINGS_ENDPOINT + "/" + booking.getId(), + OBJECT_MAPPER.writeValueAsString(booking), + TestingSupportRoles.LEVEL_1 + ); + assertResponseCode(putBooking2, 400); assertThat(putBooking2.body().jsonPath().getString("message")) .isEqualTo( @@ -617,7 +631,11 @@ void upsertBookingForClosedCase() throws JsonProcessingException { // attempt create var booking2 = createBooking(caseEntity.getId(), participants); - var putBooking3 = putBooking(booking2); + var putBooking3 = doPutRequest( + BOOKINGS_ENDPOINT + "/" + booking2.getId(), + OBJECT_MAPPER.writeValueAsString(booking2), + TestingSupportRoles.LEVEL_1 + ); assertResponseCode(putBooking3, 400); assertThat(putBooking3.body().jsonPath().getString("message")) .isEqualTo( @@ -654,12 +672,20 @@ void upsertBookingForPendingClosureCase() throws JsonProcessingException { // close case caseEntity.setState(CaseState.PENDING_CLOSURE); caseEntity.setClosedAt(Timestamp.from(Instant.now().minusSeconds(36000))); - var putCase2 = putCase(caseEntity); + var putCase2 = doPutRequest( + CASES_ENDPOINT + "/" + caseEntity.getId(), + OBJECT_MAPPER.writeValueAsString(caseEntity), + TestingSupportRoles.LEVEL_1 + ); assertResponseCode(putCase2, 204); // attempt update booking.setScheduledFor(Timestamp.valueOf(LocalDate.now().atStartOfDay().plusDays(1))); - var putBooking2 = putBooking(booking); + var putBooking2 = doPutRequest( + BOOKINGS_ENDPOINT + "/" + booking.getId(), + OBJECT_MAPPER.writeValueAsString(booking), + TestingSupportRoles.LEVEL_1 + ); assertResponseCode(putBooking2, 400); assertThat(putBooking2.body().jsonPath().getString("message")) .isEqualTo( @@ -669,7 +695,11 @@ void upsertBookingForPendingClosureCase() throws JsonProcessingException { // attempt create var booking2 = createBooking(caseEntity.getId(), participants); - var putBooking3 = putBooking(booking2); + var putBooking3 = doPutRequest( + BOOKINGS_ENDPOINT + "/" + booking2.getId(), + OBJECT_MAPPER.writeValueAsString(booking2), + TestingSupportRoles.LEVEL_1 + ); assertResponseCode(putBooking3, 400); assertThat(putBooking3.body().jsonPath().getString("message")) .isEqualTo( diff --git a/src/functionalTest/java/uk/gov/hmcts/reform/preapi/controllers/CaseControllerFT.java b/src/functionalTest/java/uk/gov/hmcts/reform/preapi/controllers/CaseControllerFT.java index 962173753d..d169461131 100644 --- a/src/functionalTest/java/uk/gov/hmcts/reform/preapi/controllers/CaseControllerFT.java +++ b/src/functionalTest/java/uk/gov/hmcts/reform/preapi/controllers/CaseControllerFT.java @@ -58,7 +58,11 @@ void updateCaseClosedBadRequest() throws JsonProcessingException { // attempt update case dto.setTest(true); - var putCase2 = putCase(dto); + var putCase2 = doPutRequest( + CASES_ENDPOINT + "/" + dto.getId(), + OBJECT_MAPPER.writeValueAsString(dto), + TestingSupportRoles.LEVEL_1 + ); assertResponseCode(putCase2, 400); assertThat(putCase2.body().jsonPath().getString("message")) .isEqualTo("Resource Case(" diff --git a/src/main/java/uk/gov/hmcts/reform/preapi/controllers/BookingController.java b/src/main/java/uk/gov/hmcts/reform/preapi/controllers/BookingController.java index d5176791f9..5104e2e851 100644 --- a/src/main/java/uk/gov/hmcts/reform/preapi/controllers/BookingController.java +++ b/src/main/java/uk/gov/hmcts/reform/preapi/controllers/BookingController.java @@ -247,7 +247,6 @@ public ResponseEntity undeleteBooking(@PathVariable UUID bookingId) { return ok().build(); } - private void validateRequestWithBody(UUID bookingId, CreateBookingDTO createBookingDTO) { if (!bookingId.equals(createBookingDTO.getId())) { throw new PathPayloadMismatchException("bookingId", "bookingDTO.id"); diff --git a/src/main/java/uk/gov/hmcts/reform/preapi/dto/CreateBookingDTO.java b/src/main/java/uk/gov/hmcts/reform/preapi/dto/CreateBookingDTO.java index 91d8973930..48e8ecd4c2 100644 --- a/src/main/java/uk/gov/hmcts/reform/preapi/dto/CreateBookingDTO.java +++ b/src/main/java/uk/gov/hmcts/reform/preapi/dto/CreateBookingDTO.java @@ -6,7 +6,6 @@ import jakarta.validation.constraints.NotNull; import lombok.Data; import lombok.NoArgsConstructor; -import uk.gov.hmcts.reform.preapi.dto.validators.BookingScheduledForNotPastOrNotChangedConstraint; import uk.gov.hmcts.reform.preapi.dto.validators.ParticipantTypeConstraint; import uk.gov.hmcts.reform.preapi.entities.Booking; @@ -20,7 +19,6 @@ @NoArgsConstructor @Schema(description = "CreateBookingDTO") @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) -@BookingScheduledForNotPastOrNotChangedConstraint(message = "scheduled_for is required and must not be before today") public class CreateBookingDTO { @Schema(description = "CreateBookingId") @NotNull(message = "id is required") diff --git a/src/main/java/uk/gov/hmcts/reform/preapi/dto/validators/NotPastDateConstraint.java b/src/main/java/uk/gov/hmcts/reform/preapi/dto/validators/NotPastDateConstraint.java deleted file mode 100644 index 26d0b52b20..0000000000 --- a/src/main/java/uk/gov/hmcts/reform/preapi/dto/validators/NotPastDateConstraint.java +++ /dev/null @@ -1,20 +0,0 @@ -package uk.gov.hmcts.reform.preapi.dto.validators; - -import jakarta.validation.Constraint; -import jakarta.validation.Payload; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Documented -@Constraint(validatedBy = NotPastDateValidator.class) -@Target({ ElementType.METHOD, ElementType.FIELD }) -@Retention(RetentionPolicy.RUNTIME) -public @interface NotPastDateConstraint { - String message() default "Date must not be in the past"; - Class[] groups() default {}; - Class[] payload() default {}; -} diff --git a/src/main/java/uk/gov/hmcts/reform/preapi/dto/validators/NotPastDateValidator.java b/src/main/java/uk/gov/hmcts/reform/preapi/dto/validators/NotPastDateValidator.java deleted file mode 100644 index 22231c8571..0000000000 --- a/src/main/java/uk/gov/hmcts/reform/preapi/dto/validators/NotPastDateValidator.java +++ /dev/null @@ -1,27 +0,0 @@ -package uk.gov.hmcts.reform.preapi.dto.validators; - -import jakarta.validation.ConstraintValidator; -import jakarta.validation.ConstraintValidatorContext; - -import java.sql.Timestamp; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.ZoneId; - -public class NotPastDateValidator implements ConstraintValidator { - @Override - public void initialize(NotPastDateConstraint date) { - } - - @Override - public boolean isValid(Timestamp dateField, ConstraintValidatorContext cxt) { - if (dateField == null) { - return false; - } - - var localDateField = LocalDateTime.ofInstant(dateField.toInstant(), ZoneId.of("Europe/London")).toLocalDate(); - var today = LocalDate.now(); - - return !localDateField.isBefore(today); - } -} diff --git a/src/main/java/uk/gov/hmcts/reform/preapi/security/AuthorisationService.java b/src/main/java/uk/gov/hmcts/reform/preapi/security/AuthorisationService.java index d82a5f55b4..0f03edfeab 100644 --- a/src/main/java/uk/gov/hmcts/reform/preapi/security/AuthorisationService.java +++ b/src/main/java/uk/gov/hmcts/reform/preapi/security/AuthorisationService.java @@ -170,7 +170,8 @@ public boolean hasUpsertAccess(UserAuthentication authentication, CreateRecordin public boolean hasUpsertAccess(UserAuthentication authentication, CreateShareBookingDTO dto) { return authentication.getUserId().equals(dto.getSharedByUser()) - && (authentication.isAdmin() || hasBookingAccess(authentication, dto.getBookingId())); + && (authentication.isAdmin() || hasBookingAccess(authentication, dto.getBookingId())) + || authentication.hasRole("ROLE_SUPER_USER"); } public boolean hasUpsertAccess(UserAuthentication authentication, CreateCaseDTO dto) { diff --git a/src/main/java/uk/gov/hmcts/reform/preapi/services/BookingService.java b/src/main/java/uk/gov/hmcts/reform/preapi/services/BookingService.java index 372242b089..460d4eaa7a 100644 --- a/src/main/java/uk/gov/hmcts/reform/preapi/services/BookingService.java +++ b/src/main/java/uk/gov/hmcts/reform/preapi/services/BookingService.java @@ -19,6 +19,7 @@ import uk.gov.hmcts.reform.preapi.enums.CaseState; import uk.gov.hmcts.reform.preapi.enums.RecordingStatus; import uk.gov.hmcts.reform.preapi.enums.UpsertResult; +import uk.gov.hmcts.reform.preapi.exception.BadRequestException; import uk.gov.hmcts.reform.preapi.exception.NotFoundException; import uk.gov.hmcts.reform.preapi.exception.ResourceInDeletedStateException; import uk.gov.hmcts.reform.preapi.exception.ResourceInWrongStateException; @@ -30,6 +31,8 @@ import java.sql.Timestamp; import java.time.Instant; import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; import java.time.temporal.ChronoUnit; import java.util.List; import java.util.Optional; @@ -126,6 +129,17 @@ public Page searchBy( @Transactional @PreAuthorize("@authorisationService.hasUpsertAccess(authentication, #createBookingDTO)") public UpsertResult upsert(CreateBookingDTO createBookingDTO) { + var auth = ((UserAuthentication) SecurityContextHolder.getContext().getAuthentication()); + + var localDateField = LocalDateTime.ofInstant(createBookingDTO.getScheduledFor().toInstant(), + ZoneId.of("Europe/London")).toLocalDate(); + var today = LocalDate.now(); + + if (localDateField.isBefore(today) + && !auth.hasRole("ROLE_SUPER_USER")) { + throw new BadRequestException("Scheduled date must not be in the past"); + } + if (bookingAlreadyDeleted(createBookingDTO.getId())) { throw new ResourceInDeletedStateException("BookingDTO", createBookingDTO.getId().toString()); } @@ -136,7 +150,7 @@ public UpsertResult upsert(CreateBookingDTO createBookingDTO) { var caseEntity = caseRepository.findByIdAndDeletedAtIsNull(createBookingDTO.getCaseId()) .orElseThrow(() -> new NotFoundException("Case: " + createBookingDTO.getCaseId())); - if (caseEntity.getState() != CaseState.OPEN) { + if (caseEntity.getState() != CaseState.OPEN && !auth.hasRole("ROLE_SUPER_USER")) { throw new ResourceInWrongStateException( "Booking", createBookingDTO.getId(), diff --git a/src/main/java/uk/gov/hmcts/reform/preapi/services/CaseService.java b/src/main/java/uk/gov/hmcts/reform/preapi/services/CaseService.java index e449c86532..4bebf0c4d3 100644 --- a/src/main/java/uk/gov/hmcts/reform/preapi/services/CaseService.java +++ b/src/main/java/uk/gov/hmcts/reform/preapi/services/CaseService.java @@ -123,12 +123,15 @@ public UpsertResult upsert(CreateCaseDTO createCaseDTO) { var isCaseClosureCancellation = false; var isCasePendingClosure = false; + var auth = ((UserAuthentication) SecurityContextHolder.getContext().getAuthentication()); + if (isUpdate) { if (foundCase.get().isDeleted()) { throw new ResourceInDeletedStateException("CaseDTO", createCaseDTO.getId().toString()); } if (foundCase.get().getState() != CaseState.OPEN && foundCase.get().getState() == createCaseDTO.getState() + && !auth.hasRole("ROLE_SUPER_USER") ) { throw new ResourceInWrongStateException( "Resource Case(" diff --git a/src/test/java/uk/gov/hmcts/reform/preapi/controller/BookingControllerTest.java b/src/test/java/uk/gov/hmcts/reform/preapi/controller/BookingControllerTest.java index 260adef307..caa53c3a54 100644 --- a/src/test/java/uk/gov/hmcts/reform/preapi/controller/BookingControllerTest.java +++ b/src/test/java/uk/gov/hmcts/reform/preapi/controller/BookingControllerTest.java @@ -39,7 +39,6 @@ import java.sql.Timestamp; import java.text.SimpleDateFormat; -import java.time.Instant; import java.time.OffsetDateTime; import java.time.temporal.ChronoUnit; import java.util.List; @@ -413,62 +412,6 @@ void createBookingEndpoint400ScheduledForMissing() throws Exception { .isEqualTo("{\"scheduledFor\":\"scheduled_for is required and must not be before today\"}"); } - @DisplayName("Should fail to create a booking with 400 response code as scheduledFor is before today") - @Test - void createBookingEndpoint400ScheduledForInThePast() throws Exception { - - var caseId = UUID.randomUUID(); - var bookingId = UUID.randomUUID(); - var booking = new CreateBookingDTO(); - booking.setId(bookingId); - booking.setCaseId(caseId); - booking.setParticipants(getCreateParticipantDTOs()); - booking.setScheduledFor(Timestamp.from(OffsetDateTime.now().minusWeeks(1).toInstant())); - - CaseDTO mockCaseDTO = new CaseDTO(); - when(caseService.findById(caseId)).thenReturn(mockCaseDTO); - - MvcResult response = mockMvc.perform(put(getPath(bookingId)) - .with(csrf()) - .content(OBJECT_MAPPER.writeValueAsString(booking)) - .contentType(MediaType.APPLICATION_JSON_VALUE) - .accept(MediaType.APPLICATION_JSON_VALUE)) - .andExpect(status().isBadRequest()) - .andReturn(); - - assertThat(response.getResponse().getContentAsString()) - .isEqualTo( - "{\"scheduledFor\":\"must not be before today\"}" - ); - } - - @DisplayName("Should fail to create a booking with 400 response code as scheduledFor is in the past same day") - @Test - void createBookingEndpointScheduledForInThePastButToday() throws Exception { - - var caseId = UUID.randomUUID(); - var bookingId = UUID.randomUUID(); - var booking = new CreateBookingDTO(); - booking.setId(bookingId); - booking.setCaseId(caseId); - booking.setParticipants(getCreateParticipantDTOs()); - booking.setScheduledFor( - Timestamp.from(Instant.now().truncatedTo(ChronoUnit.DAYS)) - ); - booking.setParticipants(getCreateParticipantDTOs()); - - CaseDTO mockCaseDTO = new CaseDTO(); - when(caseService.findById(caseId)).thenReturn(mockCaseDTO); - when(bookingService.upsert(any(CreateBookingDTO.class))).thenReturn(UpsertResult.CREATED); - - mockMvc.perform(put(getPath(bookingId)) - .with(csrf()) - .content(OBJECT_MAPPER.writeValueAsString(booking)) - .contentType(MediaType.APPLICATION_JSON_VALUE) - .accept(MediaType.APPLICATION_JSON_VALUE)) - .andExpect(status().isCreated()); - } - @DisplayName("Should fail to update a booking with 400 response code as its already deleted") @Test void updateBookingEndpoint400() throws Exception { diff --git a/src/test/java/uk/gov/hmcts/reform/preapi/dto/validators/NotPastDateValidatorTest.java b/src/test/java/uk/gov/hmcts/reform/preapi/dto/validators/NotPastDateValidatorTest.java deleted file mode 100644 index 181edec2eb..0000000000 --- a/src/test/java/uk/gov/hmcts/reform/preapi/dto/validators/NotPastDateValidatorTest.java +++ /dev/null @@ -1,56 +0,0 @@ -package uk.gov.hmcts.reform.preapi.dto.validators; - -import jakarta.validation.ConstraintValidatorContext; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import java.sql.Timestamp; -import java.time.ZoneId; -import java.time.ZonedDateTime; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class NotPastDateValidatorTest { - private NotPastDateValidator validator; - private ConstraintValidatorContext constraintValidatorContext; - - @BeforeEach - void setUp() { - validator = new NotPastDateValidator(); - constraintValidatorContext = null; - } - - @DisplayName("Should return false when value is null") - @Test - void isValidWithDateNull() { - assertFalse(validator.isValid(null, constraintValidatorContext)); - } - - @DisplayName("Should return false when value is in the past") - @Test - void isValidWithDateInPast() { - ZonedDateTime pastDateTime = ZonedDateTime.now(ZoneId.of("Europe/London")) - .minusDays(1); - Timestamp pastTimestamp = Timestamp.from(pastDateTime.toInstant()); - assertFalse(validator.isValid(pastTimestamp, constraintValidatorContext)); - } - - @DisplayName("Should return true when value is not in the past") - @Test - void isValidWithDateToday() { - ZonedDateTime currentDateTime = ZonedDateTime.now(ZoneId.of("Europe/London")); - Timestamp currentTimestamp = Timestamp.from(currentDateTime.toInstant()); - assertTrue(validator.isValid(currentTimestamp, constraintValidatorContext)); - } - - @DisplayName("Should return true when value is in the future") - @Test - void givenFutureDate_ReturnsTrue() { - ZonedDateTime futureDateTime = ZonedDateTime.now(ZoneId.of("Europe/London")) - .plusDays(1); - Timestamp futureTimestamp = Timestamp.from(futureDateTime.toInstant()); - assertTrue(validator.isValid(futureTimestamp, constraintValidatorContext)); - } -} diff --git a/src/test/java/uk/gov/hmcts/reform/preapi/security/AuthorisationServiceTest.java b/src/test/java/uk/gov/hmcts/reform/preapi/security/AuthorisationServiceTest.java index 325d097cae..6fefba5d58 100644 --- a/src/test/java/uk/gov/hmcts/reform/preapi/security/AuthorisationServiceTest.java +++ b/src/test/java/uk/gov/hmcts/reform/preapi/security/AuthorisationServiceTest.java @@ -778,6 +778,21 @@ void hasUpsertAccessUserIsNotSharing() { assertFalse(authorisationService.hasUpsertAccess(authenticationUser, dto)); } + @DisplayName("Should grant upsert access when the authenticated super user is not the one sharing the booking,") + @Test + void hasUpsertAccessSuperUserIsNotSharing() { + var dto = new CreateShareBookingDTO(); + + dto.setSharedByUser(UUID.randomUUID()); + dto.setBookingId(UUID.randomUUID()); + + when(authenticationUser.getUserId()).thenReturn(UUID.randomUUID()); + when(authenticationUser.isAdmin()).thenReturn(false); + when(authenticationUser.hasRole("ROLE_SUPER_USER")).thenReturn(true); + + assertTrue(authorisationService.hasUpsertAccess(authenticationUser, dto)); + } + @DisplayName("Should grant upsert access when the authenticated user is an admin") @Test void hasUpsertAccessUserIsAdmin() { diff --git a/src/test/java/uk/gov/hmcts/reform/preapi/services/BookingServiceTest.java b/src/test/java/uk/gov/hmcts/reform/preapi/services/BookingServiceTest.java index 2d0dca865d..5b724d04ad 100644 --- a/src/test/java/uk/gov/hmcts/reform/preapi/services/BookingServiceTest.java +++ b/src/test/java/uk/gov/hmcts/reform/preapi/services/BookingServiceTest.java @@ -19,6 +19,7 @@ import uk.gov.hmcts.reform.preapi.enums.ParticipantType; import uk.gov.hmcts.reform.preapi.enums.RecordingStatus; import uk.gov.hmcts.reform.preapi.enums.UpsertResult; +import uk.gov.hmcts.reform.preapi.exception.BadRequestException; import uk.gov.hmcts.reform.preapi.exception.NotFoundException; import uk.gov.hmcts.reform.preapi.exception.ResourceInDeletedStateException; import uk.gov.hmcts.reform.preapi.exception.ResourceInWrongStateException; @@ -33,6 +34,7 @@ import java.time.Instant; import java.time.LocalDate; import java.time.LocalTime; +import java.time.Period; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -198,6 +200,7 @@ void upsertBookingSuccessCreated() { caseEntity.setId(caseId); bookingModel.setId(UUID.randomUUID()); bookingModel.setCaseId(caseId); + bookingModel.setScheduledFor(Timestamp.from(Instant.now().plus(Period.ofWeeks(1)))); var participantModel = new CreateParticipantDTO(); participantModel.setId(UUID.randomUUID()); participantModel.setParticipantType(ParticipantType.WITNESS); @@ -229,6 +232,7 @@ void upsertCreateBookingCaseNotFound() { bookingModel.setId(UUID.randomUUID()); bookingModel.setCaseId(caseId); bookingModel.setParticipants(Set.of()); + bookingModel.setScheduledFor(Timestamp.from(Instant.now().plus(Period.ofWeeks(1)))); when(caseRepository.findByIdAndDeletedAtIsNull(caseId)).thenReturn(Optional.empty()); when(bookingRepository.findById(bookingModel.getId())).thenReturn(Optional.empty()); @@ -248,11 +252,14 @@ void upsertCreateBookingCaseNotFound() { @DisplayName("Create/update a booking when case is not OPEN") @Test void upsertCreateBookingCaseNotOpen() { + setAuthentication(); + var bookingModel = new CreateBookingDTO(); var caseId = UUID.randomUUID(); bookingModel.setId(UUID.randomUUID()); bookingModel.setCaseId(caseId); bookingModel.setParticipants(Set.of()); + bookingModel.setScheduledFor(Timestamp.from(Instant.now().plus(Period.ofWeeks(1)))); var aCase = new Case(); aCase.setId(UUID.randomUUID()); @@ -277,6 +284,97 @@ void upsertCreateBookingCaseNotOpen() { verify(bookingRepository, never()).save(any()); } + @DisplayName("Create/update a booking when case is not OPEN but user is Super Admin") + @Test + void upsertCreateBookingCaseNotOpenWithSuperAdmin() { + var mockAuth = mock(UserAuthentication.class); + when(mockAuth.isAdmin()).thenReturn(true); + when(mockAuth.isAppUser()).thenReturn(true); + when(mockAuth.hasRole("ROLE_SUPER_USER")).thenReturn(true); + SecurityContextHolder.getContext().setAuthentication(mockAuth); + + var bookingModel = new CreateBookingDTO(); + var caseId = UUID.randomUUID(); + bookingModel.setId(UUID.randomUUID()); + bookingModel.setCaseId(caseId); + bookingModel.setParticipants(Set.of()); + bookingModel.setScheduledFor(Timestamp.from(Instant.now().plus(Period.ofWeeks(1)))); + + var aCase = new Case(); + aCase.setId(UUID.randomUUID()); + aCase.setState(CaseState.CLOSED); + + var bookingEntity = new Booking(); + when(caseRepository.findByIdAndDeletedAtIsNull(caseId)).thenReturn(Optional.of(aCase)); + when(bookingRepository.findById(bookingModel.getId())).thenReturn(Optional.of(bookingEntity)); + when(bookingRepository.existsById(bookingModel.getId())).thenReturn(true); + when(caseRepository.findByIdAndDeletedAtIsNull(bookingModel.getCaseId())).thenReturn(Optional.of(new Case())); + when(bookingRepository.save(bookingEntity)).thenReturn(bookingEntity); + + assertThat(bookingService.upsert(bookingModel)).isEqualTo(UpsertResult.UPDATED); + } + + @DisplayName("Create a booking in the past as a superuser") + @Test + void upsertBookingInPastSuperuserCreated() { + var mockAuth = mock(UserAuthentication.class); + when(mockAuth.isAdmin()).thenReturn(true); + when(mockAuth.isAppUser()).thenReturn(true); + when(mockAuth.hasRole("ROLE_SUPER_USER")).thenReturn(true); + SecurityContextHolder.getContext().setAuthentication(mockAuth); + + var bookingModel = new CreateBookingDTO(); + var caseId = UUID.randomUUID(); + var caseEntity = new Case(); + caseEntity.setId(caseId); + bookingModel.setId(UUID.randomUUID()); + bookingModel.setCaseId(caseId); + bookingModel.setScheduledFor(Timestamp.from(Instant.now().minus(Period.ofWeeks(1)))); + bookingModel.setParticipants(Set.of()); + + var bookingEntity = new Booking(); + + when(caseRepository.findByIdAndDeletedAtIsNull(caseId)).thenReturn(Optional.of(caseEntity)); + when(bookingRepository.findById(bookingModel.getId())).thenReturn(Optional.empty()); + when(bookingRepository.existsByIdAndDeletedAtIsNotNull(bookingModel.getId())).thenReturn(false); + when(bookingRepository.existsById(bookingModel.getId())).thenReturn(false); + when(caseRepository.findByIdAndDeletedAtIsNull(bookingModel.getCaseId())).thenReturn(Optional.of(new Case())); + when(bookingRepository.save(bookingEntity)).thenReturn(bookingEntity); + assertThat(bookingService.upsert(bookingModel)).isEqualTo(UpsertResult.CREATED); + } + + @DisplayName("Create a booking in the past as a standard user") + @Test + void upsertBookingInPastWithoutSuperuserCreated() { + var mockAuth = mock(UserAuthentication.class); + when(mockAuth.isAdmin()).thenReturn(false); + when(mockAuth.isAppUser()).thenReturn(true); + when(mockAuth.hasRole("ROLE_SUPER_USER")).thenReturn(false); + SecurityContextHolder.getContext().setAuthentication(mockAuth); + + var bookingModel = new CreateBookingDTO(); + var caseId = UUID.randomUUID(); + var caseEntity = new Case(); + caseEntity.setId(caseId); + bookingModel.setId(UUID.randomUUID()); + bookingModel.setCaseId(caseId); + bookingModel.setScheduledFor(Timestamp.from(Instant.now().minus(Period.ofWeeks(1)))); + bookingModel.setParticipants(Set.of()); + + var bookingEntity = new Booking(); + + when(bookingRepository.findById(bookingModel.getId())).thenReturn(Optional.of(bookingEntity)); + when(bookingRepository.existsById(bookingModel.getId())).thenReturn(true); + when(caseRepository.findByIdAndDeletedAtIsNull(bookingModel.getCaseId())).thenReturn(Optional.empty()); + + assertThatExceptionOfType(BadRequestException.class) + .isThrownBy(() -> { + bookingService.upsert(bookingModel); + }) + .withMessage("Scheduled date must not be in the past"); + } + + @DisplayName("Update a booking when case not found") @Test void upsertUpdateBookingCaseNotFound() { @@ -285,6 +383,7 @@ void upsertUpdateBookingCaseNotFound() { bookingModel.setId(UUID.randomUUID()); bookingModel.setCaseId(caseId); bookingModel.setParticipants(Set.of()); + bookingModel.setScheduledFor(Timestamp.from(Instant.now().plus(Period.ofWeeks(1)))); var bookingEntity = new Booking(); when(caseRepository.findByIdAndDeletedAtIsNull(caseId)).thenReturn(Optional.empty()); @@ -312,6 +411,7 @@ void upsertBookingSuccessUpdated() { bookingModel.setId(UUID.randomUUID()); bookingModel.setCaseId(caseId); bookingModel.setParticipants(Set.of()); + bookingModel.setScheduledFor(Timestamp.from(Instant.now().plus(Period.ofWeeks(1)))); var bookingEntity = new Booking(); when(caseRepository.findByIdAndDeletedAtIsNull(caseId)).thenReturn(Optional.of(caseEntity)); @@ -331,6 +431,7 @@ void upsertBookingFailureCaseDoesntExist() { bookingModel.setId(UUID.randomUUID()); bookingModel.setCaseId(UUID.randomUUID()); bookingModel.setParticipants(Set.of()); + bookingModel.setScheduledFor(Timestamp.from(Instant.now().plus(Period.ofWeeks(1)))); var bookingEntity = new Booking(); @@ -355,13 +456,14 @@ void upsertBookingFailureAlreadyDeleted() { bookingModel.setId(UUID.randomUUID()); bookingModel.setCaseId(caseId); bookingModel.setParticipants(Set.of()); + bookingModel.setScheduledFor(Timestamp.from(Instant.now().plus(Period.ofWeeks(1)))); when(caseRepository.findByIdAndDeletedAtIsNull(caseId)).thenReturn(Optional.of(caseEntity)); when(bookingRepository.existsByIdAndDeletedAtIsNotNull(bookingModel.getId())).thenReturn(true); assertThatExceptionOfType(ResourceInDeletedStateException.class) - .isThrownBy(() -> { - bookingService.upsert(bookingModel); - }) + .isThrownBy(() -> { + bookingService.upsert(bookingModel); + }) .withMessage("Resource BookingDTO(" + bookingModel.getId().toString() + ") is in a deleted state and cannot be updated"); @@ -376,6 +478,7 @@ void upsertBookingFailureParticipantAlreadyDeleted() { caseEntity.setId(caseId); bookingModel.setId(UUID.randomUUID()); bookingModel.setCaseId(caseId); + bookingModel.setScheduledFor(Timestamp.from(Instant.now().plus(Period.ofWeeks(1)))); var participantModel = new CreateParticipantDTO(); participantModel.setId(UUID.randomUUID()); participantModel.setParticipantType(ParticipantType.WITNESS); @@ -424,6 +527,7 @@ void createBookingWithParticipantNotAssignedToTheCase() { participantModel.setLastName("Smith"); bookingModel.setParticipants(Set.of(participantModel)); + bookingModel.setScheduledFor(Timestamp.from(Instant.now().plus(Period.ofWeeks(1)))); when(caseRepository.findByIdAndDeletedAtIsNull(caseId)).thenReturn(Optional.of(caseEntity)); when(bookingRepository.findById(bookingModel.getId())).thenReturn(Optional.empty()); @@ -676,6 +780,7 @@ private void setAuthentication() { var mockAuth = mock(UserAuthentication.class); when(mockAuth.isAdmin()).thenReturn(true); when(mockAuth.isAppUser()).thenReturn(true); + when(mockAuth.hasRole("ROLE_SUPER_USER")).thenReturn(false); SecurityContextHolder.getContext().setAuthentication(mockAuth); } } diff --git a/src/test/java/uk/gov/hmcts/reform/preapi/services/CaseServiceTest.java b/src/test/java/uk/gov/hmcts/reform/preapi/services/CaseServiceTest.java index 0480988ab9..cc684f505b 100644 --- a/src/test/java/uk/gov/hmcts/reform/preapi/services/CaseServiceTest.java +++ b/src/test/java/uk/gov/hmcts/reform/preapi/services/CaseServiceTest.java @@ -1,7 +1,6 @@ package uk.gov.hmcts.reform.preapi.services; import feign.FeignException; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -67,9 +66,9 @@ @SuppressWarnings({"PMD.LawOfDemeter", "PMD.TooManyMethods"}) class CaseServiceTest { - private static Case caseEntity; + private Case caseEntity; - private static List allCaseEntities = new ArrayList<>(); + private List allCaseEntities = new ArrayList<>(); @MockitoBean private CaseRepository caseRepository; @@ -104,18 +103,10 @@ class CaseServiceTest { @Autowired private CaseService caseService; - @BeforeAll - static void setUp() { - caseEntity = new Case(); - caseEntity.setId(UUID.randomUUID()); - var court = new Court(); - court.setId(UUID.randomUUID()); - caseEntity.setCourt(court); + @BeforeEach + void setUp() { + caseEntity = createTestingCase(); caseEntity.setReference("1234567890"); - caseEntity.setTest(false); - caseEntity.setCreatedAt(Timestamp.from(Instant.now())); - caseEntity.setModifiedAt(Timestamp.from(Instant.now())); - allCaseEntities.add(caseEntity); } @@ -124,9 +115,6 @@ void reset() { when(emailServiceFactory.getEnabledEmailService()).thenReturn(govNotify); when(emailServiceFactory.getEnabledEmailService(eq("GovNotify"))).thenReturn(govNotify); when(emailServiceFactory.isEnabled()).thenReturn(false); - - caseEntity.setDeletedAt(null); - caseEntity.setState(CaseState.OPEN); } @DisplayName("Find a case by it's id and return a model") @@ -542,6 +530,11 @@ void updateBadRequest() { @Test @DisplayName("Should throw ResourceInWrongStateException when attempting to update a case in wrong state") void updateCaseNotOpenBadRequest() { + var mockAuth = mock(UserAuthentication.class); + when(mockAuth.isAdmin()).thenReturn(true); + when(mockAuth.hasRole("ROLE_SUPER_USER")).thenReturn(false); + SecurityContextHolder.getContext().setAuthentication(mockAuth); + caseEntity.setState(CaseState.CLOSED); var testingCase = createTestingCase(); var caseDTOModel = new CreateCaseDTO(testingCase); @@ -563,6 +556,27 @@ void updateCaseNotOpenBadRequest() { verify(caseRepository, never()).save(any()); } + @Test + @DisplayName("Should allow attempt to update a case in wrong state when Super Admin") + void updateCaseNotOpenAllowedAsSuperAdmin() { + var mockAuth = mock(UserAuthentication.class); + when(mockAuth.isAdmin()).thenReturn(true); + when(mockAuth.hasRole("ROLE_SUPER_USER")).thenReturn(true); + SecurityContextHolder.getContext().setAuthentication(mockAuth); + + caseEntity.setState(CaseState.CLOSED); + var testingCase = createTestingCase(); + var caseDTOModel = new CreateCaseDTO(testingCase); + caseDTOModel.setState(CaseState.CLOSED); + + when(caseRepository.findById(caseDTOModel.getId())).thenReturn(Optional.of(caseEntity)); + caseService.upsert(caseDTOModel); + + verify(courtRepository, times(1)).findById(caseDTOModel.getCourtId()); + verify(caseRepository, times(1)).findById(caseDTOModel.getId()); + verify(caseRepository, times(1)).saveAndFlush(any()); + } + @Test @DisplayName("Should throw ResourceInWrongStateException when setting state to PENDING_CLOSURE with open bookings") void updateCasePendingClosureBadRequest() {