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

[BTD-589] Added error handling #118

Merged
merged 2 commits into from
Mar 5, 2025
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 @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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<ErrorResponse> {
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)
}
}
Original file line number Diff line number Diff line change
@@ -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",
)

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -91,9 +92,11 @@ class MatchResource(
): ResponseEntity<LearnerEventsResponse> {
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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<LearnerEventsService>()

Expand Down Expand Up @@ -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(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<MatchNotFoundException> {
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<MatchNotPossibleException> {
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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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")
Expand Down