diff --git a/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/CourtOpeningHoursControllerFunctionalTest.java b/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/CourtOpeningHoursControllerFunctionalTest.java index 2f824014..1b0d4798 100644 --- a/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/CourtOpeningHoursControllerFunctionalTest.java +++ b/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/CourtOpeningHoursControllerFunctionalTest.java @@ -162,11 +162,14 @@ void shouldGetCounterServiceOpeningHours() throws Exception { final Response getResponse = http.doGet("/courts/" + courtId + "/v1/opening-hours/counter-service"); assertThat(getResponse.statusCode()).isEqualTo(OK.value()); - final CourtCounterServiceOpeningHours retrievedHours = mapper.readValue( + final List retrievedHoursList = mapper.readValue( getResponse.asString(), - CourtCounterServiceOpeningHours.class + mapper.getTypeFactory().constructCollectionType(List.class, CourtCounterServiceOpeningHours.class) ); + assertThat((retrievedHoursList)).hasSize(1); + final CourtCounterServiceOpeningHours retrievedHours = retrievedHoursList.getFirst(); + assertThat(retrievedHours.getOpeningTimesDetails()).hasSize(5); assertThat(retrievedHours.getOpeningTimesDetails().getFirst().getOpeningTime()) .isEqualTo(LocalTime.of(9, 0)); diff --git a/src/integrationTest/java/uk/gov/hmcts/reform/fact/data/api/controllers/CourtOpeningHoursControllerTest.java b/src/integrationTest/java/uk/gov/hmcts/reform/fact/data/api/controllers/CourtOpeningHoursControllerTest.java index 3a187f95..e4c9c259 100644 --- a/src/integrationTest/java/uk/gov/hmcts/reform/fact/data/api/controllers/CourtOpeningHoursControllerTest.java +++ b/src/integrationTest/java/uk/gov/hmcts/reform/fact/data/api/controllers/CourtOpeningHoursControllerTest.java @@ -225,12 +225,17 @@ void getOpeningHoursByIdInvalidUUID() throws Exception { } @Test - @DisplayName("GET /courts/{courtId}/v1/opening-hours/counter-service returns opening hours successfully") + @DisplayName("GET /courts/{courtId}/v1/opening-hours/counter-service/{counterServiceOpeningHoursId}" + + "returns counter service opening hours successfully") void getCounterServiceOpeningHoursReturnsSuccessfully() throws Exception { - when(courtOpeningHoursService.getCounterServiceOpeningHoursByCourtId(courtId)) + when(courtOpeningHoursService.getCounterServiceOpeningHoursById(courtId, counterServiceOpeningHours.getId())) .thenReturn(counterServiceOpeningHours); - mockMvc.perform(get("/courts/{courtId}/v1/opening-hours/counter-service", courtId)) + mockMvc.perform( + get( + "/courts/{courtId}/v1/opening-hours/counter-service/{counterServiceOpeningHoursId}", + courtId, + counterServiceOpeningHours.getId())) .andExpect(status().isOk()) .andExpect(jsonPath("$.openingTimesDetails[0].dayOfWeek") .value(counterServiceOpeningHours @@ -562,8 +567,9 @@ void deleteOpeningHoursInvalidUUID() throws Exception { @DisplayName("DELETE /courts/{courtId}/v1/opening-hours/counter-service deletes opening hours successfully") void deleteCounterServiceOpeningHoursDeletesSuccessfully() throws Exception { mockMvc.perform(delete( - "/courts/{courtId}/v1/opening-hours/counter-service", - courtId + "/courts/{courtId}/v1/opening-hours/counter-service/{counterServiceId}", + courtId, + counterServiceOpeningHours.getId() )) .andExpect(status().isOk()); } @@ -573,11 +579,12 @@ void deleteCounterServiceOpeningHoursDeletesSuccessfully() throws Exception { void deleteCounterServiceOpeningHoursNonExistentCourtReturnsNotFound() throws Exception { doThrow(new NotFoundException("Court not found")) .when(courtOpeningHoursService) - .deleteCourtCounterServiceOpeningHours(nonExistentCourtId); + .deleteCourtCounterServiceOpeningHours(nonExistentCourtId, counterServiceOpeningHours.getId()); mockMvc.perform(delete( - "/courts/{courtId}/v1/opening-hours/counter-service", - nonExistentCourtId + "/courts/{courtId}/v1/opening-hours/counter-service/{counterServiceId}", + nonExistentCourtId, + counterServiceOpeningHours.getId() )) .andExpect(status().isNotFound()); } @@ -586,8 +593,9 @@ void deleteCounterServiceOpeningHoursNonExistentCourtReturnsNotFound() throws Ex @DisplayName("DELETE /courts/{courtId}/v1/opening-hours/counter-service returns 400 for invalid UUID") void deleteCounterServiceOpeningHoursInvalidUUID() throws Exception { mockMvc.perform(delete( - "/courts/{courtId}/v1/opening-hours/counter-service", - "invalid-uuid" + "/courts/{courtId}/v1/opening-hours/counter-service/{counterServiceId}", + "invalid-uuid", + counterServiceOpeningHours.getId() )) .andExpect(status().isBadRequest()); } diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/controllers/CourtOpeningHoursController.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/controllers/CourtOpeningHoursController.java index 72450007..17996092 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/controllers/CourtOpeningHoursController.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/controllers/CourtOpeningHoursController.java @@ -87,10 +87,32 @@ public ResponseEntity getOpeningHoursById( @ApiResponse(responseCode = "400", description = "Invalid court ID supplied"), @ApiResponse(responseCode = "404", description = "Court not found") }) - public ResponseEntity getCounterServiceOpeningHoursByCourtId( + public ResponseEntity> getCounterServiceOpeningHoursByCourtId( @Parameter(description = "UUID of the court", required = true) @ValidUUID @PathVariable String courtId) { + return ResponseEntity.ok(courtOpeningHoursService.getCounterServiceOpeningHoursByCourtId( + UUID.fromString(courtId)) + ); + } + + @GetMapping("/v1/opening-hours/counter-service/{counterServiceId}") + @Operation( + summary = "Get court counter service opening hours by court ID and counter service ID", + description = "Fetch counter service opening hours for a given court." + + "Returns 204 if no counter service opening hours exist for the court with this ID." + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Successfully retrieved court counter service opening hours"), + @ApiResponse(responseCode = "204", description = "No counter service found for the court with this ID"), + @ApiResponse(responseCode = "400", description = "Invalid court ID or court counter service ID supplied"), + @ApiResponse(responseCode = "404", description = "Court or counter service opening hours not found") + }) + public ResponseEntity getCounterServiceOpeningHoursById( + @Parameter(description = "UUID of the court", required = true) @ValidUUID @PathVariable String courtId, + @Parameter(description = "UUID of the counter service opening hours", required = true) + @ValidUUID @PathVariable String counterServiceId) { return ResponseEntity.ok( - courtOpeningHoursService.getCounterServiceOpeningHoursByCourtId(UUID.fromString(courtId))); + courtOpeningHoursService + .getCounterServiceOpeningHoursById(UUID.fromString(courtId), UUID.fromString(counterServiceId))); } @PutMapping("/v1/opening-hours") @@ -120,7 +142,7 @@ public ResponseEntity setOpeningHours( description = "Creates a opening hours for a court or updates the existing one." ) @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Successfully created/updated opening hours"), + @ApiResponse(responseCode = "200", description = "Successfully created/updated counter service opening hours"), @ApiResponse(responseCode = "204", description = "No Content"), @ApiResponse(responseCode = "400", description = "Invalid court ID or request body"), @ApiResponse(responseCode = "404", description = "Court or court type not found") @@ -154,7 +176,7 @@ public ResponseEntity deleteOpeningHours( return ResponseEntity.ok().body(null); } - @DeleteMapping("/v1/opening-hours/counter-service") + @DeleteMapping("/v1/opening-hours/counter-service/{counterServiceId}") @Operation( summary = "Delete counter service opening hours for a court", description = "Deletes counter service opening hours for a court." @@ -166,8 +188,12 @@ public ResponseEntity deleteOpeningHours( }) @PreAuthorize("@authService.isAdmin()") public ResponseEntity deleteCounterServiceOpeningHours( - @Parameter(description = "UUID of the court", required = true) @ValidUUID @PathVariable String courtId) { - courtOpeningHoursService.deleteCourtCounterServiceOpeningHours(UUID.fromString(courtId)); + @Parameter(description = "UUID of the court", required = true) @ValidUUID @PathVariable String courtId, + @Parameter(description = "UUID of the counter service", required = true) + @ValidUUID @PathVariable String counterServiceId) { + courtOpeningHoursService.deleteCourtCounterServiceOpeningHours( + UUID.fromString(courtId), UUID.fromString(counterServiceId) + ); return ResponseEntity.ok().body(null); } } diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/repositories/CourtCounterServiceOpeningHoursRepository.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/repositories/CourtCounterServiceOpeningHoursRepository.java index 49066c8f..f058487f 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/repositories/CourtCounterServiceOpeningHoursRepository.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/repositories/CourtCounterServiceOpeningHoursRepository.java @@ -2,6 +2,7 @@ import uk.gov.hmcts.reform.fact.data.api.entities.CourtCounterServiceOpeningHours; +import java.util.List; import java.util.Optional; import java.util.UUID; @@ -12,7 +13,7 @@ public interface CourtCounterServiceOpeningHoursRepository extends JpaRepository { - Optional findByCourtId(UUID courtId); + List findByCourtId(UUID courtId); - void deleteByCourtId(UUID courtId); + Optional findByCourtIdAndId(UUID courtId, UUID counterServiceOpeningHourId); } diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/CourtOpeningHoursService.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/CourtOpeningHoursService.java index f292ae2a..4f7573dd 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/CourtOpeningHoursService.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/CourtOpeningHoursService.java @@ -71,21 +71,39 @@ public CourtOpeningHours getOpeningHoursById(UUID courtId, UUID openingHoursId) "No opening hour found for court ID: " + courtId + " with ID: " + openingHoursId)); } + /** + * Get a list of counter service opening hours by court ID. + * + * @param courtId The court ID to find the counter service opening hours for. + * @return The list of counter service opening hours. + * @throws CourtResourceNotFoundException if no counter service record exists for the court or the list is empty. + */ + public List getCounterServiceOpeningHoursByCourtId(UUID courtId) { + List result = + courtCounterServiceOpeningHoursRepository.findByCourtId(courtService.getCourtById(courtId).getId()); + if (result.isEmpty()) { + throw new CourtResourceNotFoundException( + "No counter service opening hours found for court ID: " + courtId); + } + return result; + } /** - * Get counter-service opening hours by court ID. - * A court will only ever have zero or one counter-service opening hours record. + * Get counter-service opening hours by court ID and counter service ID. * * @param courtId The court ID to find the counter-service opening hours for. + * @param counterServiceId The ID for the counter service opening hours to find. * @return The counter-service opening hours record. * @throws CourtResourceNotFoundException if no counter-service opening hours record exists for the court. */ - public CourtCounterServiceOpeningHours getCounterServiceOpeningHoursByCourtId(UUID courtId) { + public CourtCounterServiceOpeningHours getCounterServiceOpeningHoursById( + final UUID courtId, final UUID counterServiceId) { return courtCounterServiceOpeningHoursRepository - .findByCourtId(courtService.getCourtById(courtId).getId()) + .findByCourtIdAndId(courtService.getCourtById(courtId).getId(), counterServiceId) .orElseThrow( () -> new CourtResourceNotFoundException( - "No counter service opening hours found for court ID: " + courtId)); + "No counter service opening hours found for court ID: " + courtId + " with ID: " + + counterServiceId)); } /** @@ -147,7 +165,7 @@ public CourtCounterServiceOpeningHours setCounterServiceOpeningHours( .map(this::validateCourtTypeIds) .ifPresent(courtCounterServiceOpeningHours::setCourtTypes); - courtCounterServiceOpeningHoursRepository.findByCourtId(courtId) + courtCounterServiceOpeningHoursRepository.findByCourtIdAndId(courtId, courtCounterServiceOpeningHours.getId()) .ifPresent(existing -> courtCounterServiceOpeningHours.setId(existing.getId())); return courtCounterServiceOpeningHoursRepository.save(courtCounterServiceOpeningHours); @@ -165,11 +183,13 @@ public void deleteCourtOpeningHours(UUID courtId, UUID openingHoursId) { /** * Delete court counter service opening hours. - * @param courtId The ID of the court to delete counter service opening hours for. + * @param counterServiceId The ID of the counter service to delete counter service opening hours for court courtId. */ @Transactional - public void deleteCourtCounterServiceOpeningHours(UUID courtId) { - courtCounterServiceOpeningHoursRepository.deleteByCourtId(courtService.getCourtById(courtId).getId()); + public void deleteCourtCounterServiceOpeningHours(final UUID courtId, final UUID counterServiceId) { + log.info("Deleting court counter service opening hours for court ID: {} and counter service ID: {}", + courtId, counterServiceId); + courtCounterServiceOpeningHoursRepository.deleteById(counterServiceId); } /** diff --git a/src/test/java/uk/gov/hmcts/reform/fact/data/api/controllers/CourtOpeningHoursControllerTest.java b/src/test/java/uk/gov/hmcts/reform/fact/data/api/controllers/CourtOpeningHoursControllerTest.java index 0160810b..5da61eb5 100644 --- a/src/test/java/uk/gov/hmcts/reform/fact/data/api/controllers/CourtOpeningHoursControllerTest.java +++ b/src/test/java/uk/gov/hmcts/reform/fact/data/api/controllers/CourtOpeningHoursControllerTest.java @@ -203,13 +203,13 @@ void getOpeningHoursByIdThrowsIllegalArgumentExceptionForInvalidUUID() { @Test void getCounterServiceOpeningHoursByCourtIdReturns200() { when(courtOpeningHoursService.getCounterServiceOpeningHoursByCourtId(COURT_ID)) - .thenReturn(counterServiceOpeningHours); + .thenReturn(List.of(counterServiceOpeningHours)); - ResponseEntity response + ResponseEntity> response = courtOpeningHoursController.getCounterServiceOpeningHoursByCourtId(COURT_ID.toString()); assertThat(response.getStatusCode()).as(RESPONSE_STATUS_MESSAGE).isEqualTo(HttpStatus.OK); - assertThat(response.getBody()).as(RESPONSE_BODY_MESSAGE).isEqualTo(counterServiceOpeningHours); + assertThat(response.getBody()).as(RESPONSE_BODY_MESSAGE).isEqualTo(List.of(counterServiceOpeningHours)); } @Test @@ -238,7 +238,7 @@ void getCounterServiceOpeningHoursThrowsCourtNotFoundException() { void getCounterServiceOpeningHoursThrowsIllegalArgumentExceptionForInvalidUUID() { assertThrows( IllegalArgumentException.class, () -> - courtOpeningHoursController.getCounterServiceOpeningHoursByCourtId(INVALID_UUID) + courtOpeningHoursController.getCounterServiceOpeningHoursById(INVALID_UUID, INVALID_UUID) ); } @@ -351,7 +351,8 @@ void deleteOpeningHoursByTypeIdThrowsIllegalArgumentException() { @Test void deleteCourtCounterServiceOpeningHoursReturns200() { ResponseEntity response = courtOpeningHoursController.deleteCounterServiceOpeningHours( - COURT_ID.toString() + COURT_ID.toString(), + counterServiceOpeningHours.getId().toString() ); assertThat(response.getStatusCode()).as(RESPONSE_STATUS_MESSAGE).isEqualTo(HttpStatus.OK); } @@ -360,12 +361,13 @@ void deleteCourtCounterServiceOpeningHoursReturns200() { void deleteCourtCounterServiceOpeningHoursThrowsNotFoundException() { doThrow(new NotFoundException(COURT_NOT_FOUND_MESSAGE)) .when(courtOpeningHoursService) - .deleteCourtCounterServiceOpeningHours(UNKNOWN_COURT_ID); + .deleteCourtCounterServiceOpeningHours(UNKNOWN_COURT_ID, UNKNOWN_ID); assertThrows( NotFoundException.class, () -> courtOpeningHoursController.deleteCounterServiceOpeningHours( - UNKNOWN_COURT_ID.toString() + UNKNOWN_COURT_ID.toString(), + UNKNOWN_ID.toString() ) ); } @@ -375,7 +377,7 @@ void deleteCounterServiceOpeningHoursThrowsIllegalArgumentException() { assertThrows( IllegalArgumentException.class, () -> courtOpeningHoursController - .deleteCounterServiceOpeningHours(INVALID_UUID) + .deleteCounterServiceOpeningHours(INVALID_UUID, INVALID_UUID) ); } } diff --git a/src/test/java/uk/gov/hmcts/reform/fact/data/api/services/CourtOpeningHoursServiceTest.java b/src/test/java/uk/gov/hmcts/reform/fact/data/api/services/CourtOpeningHoursServiceTest.java index d1deb84a..81a755ef 100644 --- a/src/test/java/uk/gov/hmcts/reform/fact/data/api/services/CourtOpeningHoursServiceTest.java +++ b/src/test/java/uk/gov/hmcts/reform/fact/data/api/services/CourtOpeningHoursServiceTest.java @@ -202,18 +202,18 @@ void getOpeningHoursThrowsExceptionWhenOpeningHourTypeDoesNotExist() { void getCounterServiceOpeningHoursByCourtIdReturnsOpeningHoursWhenFound() { when(courtService.getCourtById(courtId)).thenReturn(court); when(courtCounterServiceOpeningHoursRepository.findByCourtId(courtId)) - .thenReturn(Optional.of(counterServiceOpeningHours)); + .thenReturn(List.of(counterServiceOpeningHours)); - CourtCounterServiceOpeningHours result = + List result = courtOpeningHoursService.getCounterServiceOpeningHoursByCourtId(courtId); - assertThat(result).isEqualTo(counterServiceOpeningHours); + assertThat(result).isEqualTo(List.of(counterServiceOpeningHours)); } @Test void getCounterServiceOpeningHoursByCourtIdThrowsExceptionWhenNotFound() { when(courtService.getCourtById(courtId)).thenReturn(court); - when(courtCounterServiceOpeningHoursRepository.findByCourtId(courtId)).thenReturn(Optional.empty()); + when(courtCounterServiceOpeningHoursRepository.findByCourtId(courtId)).thenReturn(List.of()); assertThrows( CourtResourceNotFoundException.class, @@ -230,6 +230,40 @@ void getCounterServiceOpeningHoursThrowsExceptionWhenCourtDoesNotExist() { ); } + @Test + void getCounterServiceOpeningHoursByIdReturnsCounterServiceOpeningHoursWhenFound() { + when(courtService.getCourtById(courtId)).thenReturn(court); + when(courtCounterServiceOpeningHoursRepository.findByCourtIdAndId(courtId, counterServiceOpeningHours.getId())) + .thenReturn(Optional.of(counterServiceOpeningHours)); + + CourtCounterServiceOpeningHours result = courtOpeningHoursService + .getCounterServiceOpeningHoursById(courtId, counterServiceOpeningHours.getId()); + + assertThat(result).isEqualTo(counterServiceOpeningHours); + } + + @Test + void getCounterServiceOpeningHoursByIdThrowsExceptionWhenNotFound() { + when(courtService.getCourtById(courtId)).thenReturn(court); + when(courtCounterServiceOpeningHoursRepository.findByCourtIdAndId(courtId, counterServiceOpeningHours.getId())) + .thenReturn(Optional.empty()); + + final UUID counterServiceId = counterServiceOpeningHours.getId(); + assertThrows( + CourtResourceNotFoundException.class, + () -> courtOpeningHoursService.getCounterServiceOpeningHoursById(courtId, counterServiceId)); + } + + @Test + void getCounterServiceOpeningHoursByIdThrowsExceptionWhenCourtDoesNotExist() { + when(courtService.getCourtById(courtId)).thenThrow(new NotFoundException(COURT_NOT_FOUND_MESSAGE)); + + final UUID counterServiceId = counterServiceOpeningHours.getId(); + assertThrows(NotFoundException.class, () -> + courtOpeningHoursService.getCounterServiceOpeningHoursById(courtId, counterServiceId) + ); + } + @Test void setOpeningHoursSuccessfullyCreatesNewOpeningHours() { when(courtService.getCourtById(courtId)).thenReturn(court); @@ -474,5 +508,12 @@ void deleteCourtOpeningHoursSuccessfullyDeletesHours() { verify(courtOpeningHoursRepository).deleteById(openingHours.getId()); } + + @Test + void deleteCounterServiceOpeningHoursSuccessfullyDeletesHours() { + courtOpeningHoursService.deleteCourtCounterServiceOpeningHours(courtId, counterServiceOpeningHours.getId()); + + verify(courtCounterServiceOpeningHoursRepository).deleteById(counterServiceOpeningHours.getId()); + } }