diff --git a/src/main/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/config/HmppsBoldLrsExceptionHandler.kt b/src/main/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/config/HmppsBoldLrsExceptionHandler.kt index 2aad166..b50635a 100644 --- a/src/main/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/config/HmppsBoldLrsExceptionHandler.kt +++ b/src/main/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/config/HmppsBoldLrsExceptionHandler.kt @@ -20,6 +20,7 @@ import uk.gov.justice.digital.hmpps.learnerrecordsapi.logging.LoggerUtil.errorLo import uk.gov.justice.digital.hmpps.learnerrecordsapi.models.lrsapi.response.exceptions.DFEApiDownException import uk.gov.justice.digital.hmpps.learnerrecordsapi.models.lrsapi.response.exceptions.LRSException import uk.gov.justice.digital.hmpps.learnerrecordsapi.models.lrsapi.response.exceptions.MatchNotFoundException +import uk.gov.justice.digital.hmpps.learnerrecordsapi.models.lrsapi.response.exceptions.MatchNotPossibleException import java.net.SocketTimeoutException @RestControllerAdvice @@ -33,6 +34,7 @@ class HmppsBoldLrsExceptionHandler { val dFEApiFailedToRespond = "DfE API failed to Respond" val dfeApiDependencyFailed = "LRS API Dependency Failed - DfE API is under maintenance" val individualNotMatched = "Individual with this NomisId has not been matched to a ULN yet" + val noMatchForIndividual = "Individual with this NomisId does not have a ULN" data class ErrorResponse( val status: HttpStatus, @@ -252,11 +254,27 @@ class HmppsBoldLrsExceptionHandler { val errorResponse = ErrorResponse( status = HttpStatus.NOT_FOUND, errorCode = "Match not found", - userMessage = "No Match found for given NomisId ${ex.message}", + userMessage = "No Match found for given NomisId ${ex.nomisId}", developerMessage = individualNotMatched, moreInfo = individualNotMatched, ) logger.errorLog(individualNotMatched) return ResponseEntity(errorResponse, HttpStatus.NOT_FOUND) } + + @ExceptionHandler(MatchNotPossibleException::class) + fun handleMatchNotPossibleException( + ex: MatchNotPossibleException, + request: WebRequest, + ): ResponseEntity { + val errorResponse = ErrorResponse( + status = HttpStatus.BAD_REQUEST, + errorCode = "Match not possible", + userMessage = "Not possible to match given NomisId ${ex.nomisId}", + developerMessage = noMatchForIndividual, + moreInfo = noMatchForIndividual, + ) + logger.errorLog(individualNotMatched) + return ResponseEntity(errorResponse, HttpStatus.BAD_REQUEST) + } } diff --git a/src/main/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/models/lrsapi/response/exceptions/MatchExceptions.kt b/src/main/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/models/lrsapi/response/exceptions/MatchExceptions.kt new file mode 100644 index 0000000..37bd63a --- /dev/null +++ b/src/main/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/models/lrsapi/response/exceptions/MatchExceptions.kt @@ -0,0 +1,23 @@ +package uk.gov.justice.digital.hmpps.learnerrecordsapi.models.lrsapi.response.exceptions + +import uk.gov.justice.digital.hmpps.learnerrecordsapi.models.response.CheckMatchStatus + +abstract class MatchException( + val nomisId: String, + val status: CheckMatchStatus, + message: String, +) : RuntimeException("$message: $nomisId!") + +class MatchNotFoundException(nomisId: String) : + MatchException( + nomisId, + CheckMatchStatus.NotFound, + "Cannot find a match for", + ) + +class MatchNotPossibleException(nomisId: String) : + MatchException( + nomisId, + CheckMatchStatus.NoMatch, + "Not possible to match", + ) diff --git a/src/main/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/models/lrsapi/response/exceptions/MatchNotFoundException.kt b/src/main/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/models/lrsapi/response/exceptions/MatchNotFoundException.kt deleted file mode 100644 index af4ba7b..0000000 --- a/src/main/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/models/lrsapi/response/exceptions/MatchNotFoundException.kt +++ /dev/null @@ -1,5 +0,0 @@ -package uk.gov.justice.digital.hmpps.learnerrecordsapi.models.lrsapi.response.exceptions - -class MatchNotFoundException(nomisId: String) : RuntimeException() { - override val message: String = nomisId -} diff --git a/src/main/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/resource/MatchResource.kt b/src/main/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/resource/MatchResource.kt index bef52d1..4010a96 100644 --- a/src/main/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/resource/MatchResource.kt +++ b/src/main/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/resource/MatchResource.kt @@ -18,6 +18,7 @@ import uk.gov.justice.digital.hmpps.learnerrecordsapi.config.Roles.ROLE_LEARNERS import uk.gov.justice.digital.hmpps.learnerrecordsapi.logging.LoggerUtil import uk.gov.justice.digital.hmpps.learnerrecordsapi.logging.LoggerUtil.log import uk.gov.justice.digital.hmpps.learnerrecordsapi.models.lrsapi.response.exceptions.MatchNotFoundException +import uk.gov.justice.digital.hmpps.learnerrecordsapi.models.lrsapi.response.exceptions.MatchNotPossibleException import uk.gov.justice.digital.hmpps.learnerrecordsapi.models.request.ConfirmMatchRequest import uk.gov.justice.digital.hmpps.learnerrecordsapi.models.response.CheckMatchResponse import uk.gov.justice.digital.hmpps.learnerrecordsapi.models.response.CheckMatchStatus @@ -91,9 +92,11 @@ class MatchResource( ): ResponseEntity { auditHelper.publishLearnerEventsAuditEvent(userName, nomisId) logger.log("Received a post request to learner events by Nomis ID endpoint", nomisId) - val checkMatchResponse: CheckMatchResponse? = learnerEventsService.getMatchEntityForNomisId(nomisId) + val checkMatchResponse = matchService.findMatch(nomisId) if (checkMatchResponse == null) { throw MatchNotFoundException(nomisId) + } else if (checkMatchResponse.setStatus().status == CheckMatchStatus.NoMatch) { + throw MatchNotPossibleException(nomisId) } else { val learnerEventsRequest = learnerEventsService.formLearningEventRequestFromMatchEntity(checkMatchResponse) val learnerEventsResponse = learnerEventsService.getLearningEvents(learnerEventsRequest, userName) diff --git a/src/main/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/service/LearnerEventsService.kt b/src/main/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/service/LearnerEventsService.kt index 703652c..04c362d 100644 --- a/src/main/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/service/LearnerEventsService.kt +++ b/src/main/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/service/LearnerEventsService.kt @@ -21,8 +21,6 @@ class LearnerEventsService( private val httpClientConfiguration: HttpClientConfiguration, @Autowired private val lrsConfiguration: LRSConfiguration, - @Autowired - private val matchService: MatchService, ) : BaseService() { private val logger: Logger = LoggerUtil.getLogger() @@ -57,8 +55,6 @@ class LearnerEventsService( ) } - fun getMatchEntityForNomisId(nomisId: String): CheckMatchResponse? = matchService.findMatch(nomisId = nomisId) - fun formLearningEventRequestFromMatchEntity(checkMatchResponse: CheckMatchResponse): LearnerEventsRequest = LearnerEventsRequest( checkMatchResponse.givenName.orEmpty(), checkMatchResponse.familyName.orEmpty(), diff --git a/src/test/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/integration/MatchResourceIntTest.kt b/src/test/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/integration/MatchResourceIntTest.kt index 1a6c462..c2a1ff2 100644 --- a/src/test/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/integration/MatchResourceIntTest.kt +++ b/src/test/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/integration/MatchResourceIntTest.kt @@ -342,7 +342,7 @@ class MatchResourceIntTest : IntegrationTestBase() { } @Test - fun `should return Not Found if the Given Nomis ID does not match or exists`() { + fun `should return Not Found if the Given Nomis ID does not exist`() { val expectedResponse = HmppsBoldLrsExceptionHandler.ErrorResponse( status = HttpStatus.NOT_FOUND, errorCode = "Match not found", @@ -367,4 +367,43 @@ class MatchResourceIntTest : IntegrationTestBase() { ) assertThat(actualResponse).isEqualTo(expectedResponse) } + + @Test + fun `should return No Content if the Given Nomis ID can't be matched`() { + val expectedResponse = HmppsBoldLrsExceptionHandler.ErrorResponse( + status = HttpStatus.BAD_REQUEST, + errorCode = "Match not possible", + userMessage = "Not possible to match given NomisId 456789", + developerMessage = "Individual with this NomisId does not have a ULN", + moreInfo = "Individual with this NomisId does not have a ULN", + ) + + matchRepository.save( + MatchEntity( + null, + "456789", + "", + "", + "", + null, + Gender.MALE.toString(), + ), + ) + + val actualResponse = objectMapper.readValue( + webTestClient.get() + .uri("/match/{nomisId}/learner-events", "456789") + .headers(setAuthorisation(roles = listOf(ROLE_LEARNERS_RO))) + .header("X-Username", "TestUser") + .accept(MediaType.parseMediaType("application/json")) + .exchange() + .expectStatus() + .isBadRequest + .expectBody() + .returnResult() + .responseBody, + HmppsBoldLrsExceptionHandler.ErrorResponse::class.java, + ) + assertThat(actualResponse).isEqualTo(expectedResponse) + } } diff --git a/src/test/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/resource/MatchResourceTest.kt b/src/test/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/resource/MatchResourceTest.kt index e651d65..dddb240 100644 --- a/src/test/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/resource/MatchResourceTest.kt +++ b/src/test/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/resource/MatchResourceTest.kt @@ -17,6 +17,7 @@ import org.springframework.security.core.GrantedAuthority import uk.gov.justice.digital.hmpps.learnerrecordsapi.config.Roles.ROLE_LEARNERS_RO import uk.gov.justice.digital.hmpps.learnerrecordsapi.config.Roles.ROLE_LEARNERS_UI import uk.gov.justice.digital.hmpps.learnerrecordsapi.models.lrsapi.response.exceptions.MatchNotFoundException +import uk.gov.justice.digital.hmpps.learnerrecordsapi.models.lrsapi.response.exceptions.MatchNotPossibleException import uk.gov.justice.digital.hmpps.learnerrecordsapi.models.response.CheckMatchResponse import uk.gov.justice.digital.hmpps.learnerrecordsapi.models.response.CheckMatchStatus import uk.gov.justice.digital.hmpps.learnerrecordsapi.service.LearnerEventsService @@ -97,17 +98,33 @@ class MatchResourceTest { @Test fun `should throw MatchNotFound Exception if no match found`(): Unit = runTest { - `when`(mockLearnerEventsService.getMatchEntityForNomisId(any())).thenReturn(null) + `when`(mockMatchService.findMatch(any())).thenReturn(null) val exception = assertThrows { matchResource.findLearnerEventsByNomisId(nomisId, "") } - assertThat(exception.message).isEqualTo(nomisId) + assertThat(exception.nomisId).isEqualTo(nomisId) + assertThat(exception.status).isEqualTo(CheckMatchStatus.NotFound) + verify(mockAuditService, times(1)).publishEvent(any()) + } + + @Test + fun `should throw MatchNotPossible Exception if match not possible`(): Unit = runTest { + `when`(mockMatchService.findMatch(any())).thenReturn( + CheckMatchResponse( + status = CheckMatchStatus.NoMatch, + ), + ) + val exception = assertThrows { + matchResource.findLearnerEventsByNomisId(nomisId, "") + } + assertThat(exception.nomisId).isEqualTo(nomisId) + assertThat(exception.status).isEqualTo(CheckMatchStatus.NoMatch) verify(mockAuditService, times(1)).publishEvent(any()) } @Test fun `should return Match entity if record found`(): Unit = runTest { - `when`(mockLearnerEventsService.getMatchEntityForNomisId(any())).thenReturn( + `when`(mockMatchService.findMatch(any())).thenReturn( CheckMatchResponse( matchedUln = matchedUln, familyName = familyName, diff --git a/src/test/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/service/LearnerEventsServiceTest.kt b/src/test/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/service/LearnerEventsServiceTest.kt index a1a0f20..8d8c2b5 100644 --- a/src/test/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/service/LearnerEventsServiceTest.kt +++ b/src/test/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/service/LearnerEventsServiceTest.kt @@ -36,7 +36,6 @@ class LearnerEventsServiceTest { private lateinit var lrsApiInterfaceMock: LRSApiInterface private lateinit var lrsConfiguration: LRSConfiguration private lateinit var learnerEventsService: LearnerEventsService - private lateinit var mockMatchService: MatchService @BeforeEach fun setup() { @@ -46,8 +45,7 @@ class LearnerEventsServiceTest { mock(LRSApiInterface::class.java) `when`(httpClientConfigurationMock.lrsClient()).thenReturn(lrsApiInterfaceMock) lrsConfiguration = mock(LRSConfiguration::class.java) - mockMatchService = mock(MatchService::class.java) - learnerEventsService = LearnerEventsService(httpClientConfigurationMock, lrsConfiguration, mockMatchService) + learnerEventsService = LearnerEventsService(httpClientConfigurationMock, lrsConfiguration) `when`(lrsConfiguration.ukprn).thenReturn("test") `when`(lrsConfiguration.orgPassword).thenReturn("pass") `when`(lrsConfiguration.vendorId).thenReturn("01")