Skip to content

Commit 156f93f

Browse files
committed
refactor: move login in controller to EOI service
1 parent a696ded commit 156f93f

File tree

4 files changed

+100
-86
lines changed

4 files changed

+100
-86
lines changed

src/main/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/controllers/v1/person/ExpressionInterestController.kt

+1-28
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,20 @@ import io.swagger.v3.oas.annotations.Operation
44
import io.swagger.v3.oas.annotations.Parameter
55
import io.swagger.v3.oas.annotations.responses.ApiResponse
66
import io.swagger.v3.oas.annotations.tags.Tag
7-
import jakarta.validation.ValidationException
8-
import org.slf4j.Logger
9-
import org.slf4j.LoggerFactory
107
import org.springframework.beans.factory.annotation.Autowired
118
import org.springframework.http.ResponseEntity
129
import org.springframework.web.bind.annotation.PathVariable
1310
import org.springframework.web.bind.annotation.PutMapping
1411
import org.springframework.web.bind.annotation.RequestMapping
1512
import org.springframework.web.bind.annotation.RestController
16-
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.exception.EntityNotFoundException
17-
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.ExpressionOfInterest
18-
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.NomisNumber
19-
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.Response
20-
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.UpstreamApiError
21-
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.services.GetPersonService
2213
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.services.PutExpressionInterestService
2314

2415
@RestController
2516
@RequestMapping("/v1/persons")
2617
@Tag(name = "persons")
2718
class ExpressionInterestController(
28-
@Autowired val getPersonService: GetPersonService,
2919
@Autowired val putExpressionInterestService: PutExpressionInterestService,
3020
) {
31-
val logger: Logger = LoggerFactory.getLogger(this::class.java)
32-
3321
@PutMapping("{hmppsId}/expression-of-interest/jobs/{jobid}")
3422
@Operation(
3523
summary = "Returns completed response",
@@ -44,23 +32,8 @@ class ExpressionInterestController(
4432
@Parameter(description = "A URL-encoded HMPPS identifier", example = "2008%2F0545166T") @PathVariable hmppsId: String,
4533
@Parameter(description = "A job identifier") @PathVariable jobid: String,
4634
): ResponseEntity<Void> {
47-
val hmppsIdCheck = getPersonService.getNomisNumber(hmppsId)
48-
49-
if (hmppsIdCheck.hasError(UpstreamApiError.Type.ENTITY_NOT_FOUND)) {
50-
logger.info("ExpressionInterestController: Could not find nomis number for hmppsId: $hmppsId")
51-
throw EntityNotFoundException("Could not find person with id: $hmppsId")
52-
}
53-
54-
if (hmppsIdCheck.hasError(UpstreamApiError.Type.BAD_REQUEST)) {
55-
logger.info("ExpressionInterestController: Invalid hmppsId: $hmppsId")
56-
throw ValidationException("Invalid HMPPS ID: $hmppsId")
57-
}
58-
59-
val verifiedNomisNumber = getVerifiedNomisNumber(hmppsIdCheck) ?: return ResponseEntity.badRequest().build()
60-
putExpressionInterestService.sendExpressionOfInterest(ExpressionOfInterest(jobid, verifiedNomisNumber))
35+
putExpressionInterestService.sendExpressionOfInterest(hmppsId, jobid)
6136

6237
return ResponseEntity.ok().build()
6338
}
64-
65-
fun getVerifiedNomisNumber(nomisNumberResponse: Response<NomisNumber?>) = nomisNumberResponse.data?.nomisNumber
6639
}

src/main/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/services/PutExpressionInterestService.kt

+30-6
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,63 @@
11
package uk.gov.justice.digital.hmpps.hmppsintegrationapi.services
22

33
import com.fasterxml.jackson.databind.ObjectMapper
4+
import jakarta.validation.ValidationException
5+
import org.slf4j.Logger
6+
import org.slf4j.LoggerFactory
47
import org.springframework.stereotype.Component
58
import software.amazon.awssdk.services.sqs.model.SendMessageRequest
9+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.exception.EntityNotFoundException
610
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.exception.MessageFailedException
711
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.ExpressionOfInterest
812
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.HmppsMessage
913
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.HmppsMessageEventType
14+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.UpstreamApiError
1015
import uk.gov.justice.hmpps.sqs.HmppsQueue
1116
import uk.gov.justice.hmpps.sqs.HmppsQueueService
1217
import uk.gov.justice.hmpps.sqs.eventTypeMessageAttributes
1318
import java.util.UUID
1419

1520
@Component
1621
class PutExpressionInterestService(
22+
private val getPersonService: GetPersonService,
1723
private val hmppsQueueService: HmppsQueueService,
1824
private val objectMapper: ObjectMapper,
1925
) {
2026
private val eoiQueue by lazy { hmppsQueueService.findByQueueId("jobsboardintegration") as HmppsQueue }
2127
private val eoiQueueSqsClient by lazy { eoiQueue.sqsClient }
2228
private val eoiQueueUrl by lazy { eoiQueue.queueUrl }
2329

24-
fun sendExpressionOfInterest(expressionOfInterest: ExpressionOfInterest) {
30+
companion object {
31+
private val logger: Logger = LoggerFactory.getLogger(this::class.java)
32+
}
33+
34+
fun sendExpressionOfInterest(
35+
hmppsId: String,
36+
jobid: String,
37+
) {
38+
val personResponse = getPersonService.getNomisNumber(hmppsId = hmppsId)
39+
40+
if (personResponse.hasError(UpstreamApiError.Type.ENTITY_NOT_FOUND)) {
41+
logger.debug("ExpressionOfInterest: Could not find nomis number for hmppsId: $hmppsId")
42+
throw EntityNotFoundException("Could not find person with id: $hmppsId")
43+
}
44+
45+
if (personResponse.hasError(UpstreamApiError.Type.BAD_REQUEST)) {
46+
logger.debug("ExpressionOfInterest: Invalid hmppsId: $hmppsId")
47+
throw ValidationException("Invalid HMPPS ID: $hmppsId")
48+
}
49+
50+
val nomisNumber = personResponse.data?.nomisNumber ?: run { throw ValidationException("Invalid HMPPS ID: $hmppsId") }
51+
val expressionOfInterest = ExpressionOfInterest(jobid, nomisNumber)
52+
2553
val eventType = HmppsMessageEventType.EXPRESSION_OF_INTEREST_CREATED
2654
try {
2755
val hmppsMessage =
2856
objectMapper.writeValueAsString(
2957
HmppsMessage(
3058
messageId = UUID.randomUUID().toString(),
3159
eventType = eventType,
32-
messageAttributes =
33-
mapOf(
34-
"jobId" to expressionOfInterest.jobId,
35-
"prisonNumber" to expressionOfInterest.prisonNumber,
36-
),
60+
messageAttributes = with(expressionOfInterest) { mapOf("jobId" to jobId, "prisonNumber" to prisonNumber) },
3761
),
3862
)
3963

src/test/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/controllers/v1/person/ExpressionInterestControllerTest.kt

+14-44
Original file line numberDiff line numberDiff line change
@@ -2,92 +2,62 @@ package uk.gov.justice.digital.hmpps.hmppsintegrationapi.controllers.v1.person
22

33
import io.kotest.core.spec.style.DescribeSpec
44
import io.kotest.matchers.shouldBe
5-
import org.mockito.kotlin.verify
5+
import jakarta.validation.ValidationException
6+
import org.mockito.kotlin.any
7+
import org.mockito.kotlin.doNothing
68
import org.mockito.kotlin.whenever
79
import org.springframework.beans.factory.annotation.Autowired
810
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
911
import org.springframework.http.HttpStatus
1012
import org.springframework.test.context.ActiveProfiles
1113
import org.springframework.test.context.bean.override.mockito.MockitoBean
1214
import org.springframework.test.web.servlet.MockMvc
15+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.exception.EntityNotFoundException
1316
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.helpers.IntegrationAPIMockMvc
14-
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.ExpressionOfInterest
15-
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.NomisNumber
16-
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.Response
17-
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.UpstreamApi
18-
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.UpstreamApiError
19-
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.services.GetPersonService
2017
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.services.PutExpressionInterestService
2118

2219
@WebMvcTest(controllers = [ExpressionInterestController::class])
2320
@ActiveProfiles("test")
2421
class ExpressionInterestControllerTest(
2522
@Autowired var springMockMvc: MockMvc,
26-
@MockitoBean val getPersonService: GetPersonService,
2723
@MockitoBean val expressionOfInterestService: PutExpressionInterestService,
2824
) : DescribeSpec({
2925
val mockMvc = IntegrationAPIMockMvc(springMockMvc)
3026
val basePath = "/v1/persons"
3127
val validHmppsId = "AABCD1ABC"
32-
val nomisId = "AABCD1ABC"
3328
val invalidHmppsId = "INVALID_ID"
3429
val jobId = "5678"
3530

3631
describe("PUT $basePath/{hmppsId}/expression-of-interest/jobs/{jobId}") {
3732
it("should return 404 Not Found if ENTITY_NOT_FOUND error occurs") {
38-
val notFoundResponse =
39-
Response<NomisNumber?>(
40-
data = null,
41-
errors =
42-
listOf(
43-
UpstreamApiError(
44-
type = UpstreamApiError.Type.ENTITY_NOT_FOUND,
45-
description = "Entity not found",
46-
causedBy = UpstreamApi.PRISONER_OFFENDER_SEARCH,
47-
),
48-
),
49-
)
50-
whenever(getPersonService.getNomisNumber(validHmppsId)).thenReturn(notFoundResponse)
33+
validHmppsId.let { id ->
34+
whenever(expressionOfInterestService.sendExpressionOfInterest(id, jobId)).thenThrow(EntityNotFoundException("Could not find person with id: $id"))
35+
}
5136

5237
val result = mockMvc.performAuthorisedPut("$basePath/$validHmppsId/expression-of-interest/jobs/$jobId")
5338
result.response.status.shouldBe(HttpStatus.NOT_FOUND.value())
5439
}
5540

5641
it("should throw ValidationException if an invalid hmppsId is provided") {
57-
val invalidResponse =
58-
Response<NomisNumber?>(
59-
data = null,
60-
errors =
61-
listOf(
62-
UpstreamApiError(
63-
type = UpstreamApiError.Type.BAD_REQUEST,
64-
description = "Invalid HmppsId",
65-
causedBy = UpstreamApi.NOMIS,
66-
),
67-
),
68-
)
69-
whenever(getPersonService.getNomisNumber(invalidHmppsId)).thenReturn(invalidResponse)
42+
invalidHmppsId.let { id ->
43+
whenever(expressionOfInterestService.sendExpressionOfInterest(id, jobId)).thenThrow(ValidationException("Invalid HMPPS ID: $id"))
44+
}
7045

7146
val result = mockMvc.performAuthorisedPut("$basePath/$invalidHmppsId/expression-of-interest/jobs/$jobId")
7247
result.response.status.shouldBe(HttpStatus.BAD_REQUEST.value())
7348
}
7449

7550
it("should return 200 OK on successful expression of interest submission") {
76-
val validNomisResponse =
77-
Response<NomisNumber?>(
78-
data = NomisNumber(nomisId),
79-
errors = emptyList(),
80-
)
81-
whenever(getPersonService.getNomisNumber(validHmppsId)).thenReturn(validNomisResponse)
51+
validHmppsId.let { id ->
52+
doNothing().whenever(expressionOfInterestService).sendExpressionOfInterest(id, jobId)
53+
}
8254

8355
val result = mockMvc.performAuthorisedPut("$basePath/$validHmppsId/expression-of-interest/jobs/$jobId")
84-
85-
verify(expressionOfInterestService).sendExpressionOfInterest(ExpressionOfInterest(jobId, nomisId))
8656
result.response.status.shouldBe(HttpStatus.OK.value())
8757
}
8858

8959
it("should return 500 Server Error if an exception occurs") {
90-
whenever(getPersonService.getNomisNumber(validHmppsId)).thenThrow(RuntimeException("Unexpected error"))
60+
whenever(expressionOfInterestService.sendExpressionOfInterest(any(), any())).thenThrow(RuntimeException("Unexpected error"))
9161

9262
val result = mockMvc.performAuthorisedPut("$basePath/$validHmppsId/expression-of-interest/jobs/$jobId")
9363
result.response.status.shouldBe(HttpStatus.INTERNAL_SERVER_ERROR.value())

src/test/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/services/PutExpressionInterestServiceTest.kt

+55-8
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import com.fasterxml.jackson.module.kotlin.readValue
55
import io.kotest.assertions.throwables.shouldThrow
66
import io.kotest.core.spec.style.DescribeSpec
77
import io.kotest.matchers.shouldBe
8+
import jakarta.validation.ValidationException
89
import org.mockito.kotlin.any
910
import org.mockito.kotlin.argThat
1011
import org.mockito.kotlin.doReturn
@@ -14,17 +15,22 @@ import org.mockito.kotlin.verify
1415
import org.mockito.kotlin.whenever
1516
import software.amazon.awssdk.services.sqs.SqsAsyncClient
1617
import software.amazon.awssdk.services.sqs.model.SendMessageRequest
18+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.exception.EntityNotFoundException
1719
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.exception.MessageFailedException
1820
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.extensions.MockMvcExtensions.objectMapper
19-
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.ExpressionOfInterest
2021
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.HmppsMessage
2122
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.HmppsMessageEventType
23+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.NomisNumber
24+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.Response
25+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.UpstreamApi
26+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.UpstreamApiError
2227
import uk.gov.justice.hmpps.sqs.HmppsQueue
2328
import uk.gov.justice.hmpps.sqs.HmppsQueueService
2429
import kotlin.test.assertEquals
2530

2631
class PutExpressionInterestServiceTest :
2732
DescribeSpec({
33+
val mockGetPersonService = mock<GetPersonService>()
2834
val mockQueueService = mock<HmppsQueueService>()
2935
val mockObjectMapper = mock<ObjectMapper>()
3036
val mockSqsClient = mock<SqsAsyncClient>()
@@ -36,22 +42,27 @@ class PutExpressionInterestServiceTest :
3642
}
3743

3844
val queId = "jobsboardintegration"
39-
val service = PutExpressionInterestService(mockQueueService, mockObjectMapper)
45+
val service = PutExpressionInterestService(mockGetPersonService, mockQueueService, mockObjectMapper)
4046

4147
beforeTest {
4248
reset(mockQueueService, mockSqsClient, mockObjectMapper)
4349
whenever(mockQueueService.findByQueueId(queId)).thenReturn(eoiQueue)
4450
}
4551

4652
describe("sendExpressionOfInterest") {
53+
beforeTest {
54+
"H1234".let { whenever(mockGetPersonService.getNomisNumber(it)).thenReturn(Response(NomisNumber(it))) }
55+
}
56+
4757
it("should send a valid message successfully to SQS") {
48-
val expressionOfInterest = ExpressionOfInterest(jobId = "12345", prisonNumber = "H1234")
58+
val jobId = "12345"
59+
val hmppsId = "H1234"
4960
val messageBody = """{"messageId":"1","eventType":"ExpressionOfInterestCreated","messageAttributes":{"jobId":"12345","prisonNumber":"H1234"}}"""
5061

5162
whenever(mockObjectMapper.writeValueAsString(any<HmppsMessage>()))
5263
.thenReturn(messageBody)
5364

54-
service.sendExpressionOfInterest(expressionOfInterest)
65+
service.sendExpressionOfInterest(hmppsId, jobId)
5566

5667
verify(mockSqsClient).sendMessage(
5768
argThat<SendMessageRequest> { request: SendMessageRequest? ->
@@ -62,14 +73,15 @@ class PutExpressionInterestServiceTest :
6273
}
6374

6475
it("should throw MessageFailedException when SQS fails") {
65-
val expressionInterest = ExpressionOfInterest(jobId = "12345", prisonNumber = "H1234")
76+
val jobId = "12345"
77+
val hmppsId = "H1234"
6678

6779
whenever(mockSqsClient.sendMessage(any<SendMessageRequest>()))
6880
.thenThrow(RuntimeException("Failed to send message to SQS"))
6981

7082
val exception =
7183
shouldThrow<MessageFailedException> {
72-
service.sendExpressionOfInterest(expressionInterest)
84+
service.sendExpressionOfInterest(hmppsId, jobId)
7385
}
7486

7587
exception.message shouldBe "Failed to send message to SQS"
@@ -105,7 +117,8 @@ class PutExpressionInterestServiceTest :
105117
}
106118

107119
it("should serialize ExpressionOfInterestMessage with ExpressionOfInterestCreated type") {
108-
val expressionOfInterest = ExpressionOfInterest(jobId = "12345", prisonNumber = "H1234")
120+
val jobId = "12345"
121+
val hmppsId = "H1234"
109122
val expectedMessage =
110123
HmppsMessage(
111124
messageId = "1",
@@ -129,7 +142,7 @@ class PutExpressionInterestServiceTest :
129142
whenever(mockObjectMapper.writeValueAsString(any<HmppsMessage>()))
130143
.thenReturn(expectedMessageBody)
131144

132-
service.sendExpressionOfInterest(expressionOfInterest)
145+
service.sendExpressionOfInterest(hmppsId, jobId)
133146

134147
verify(mockSqsClient).sendMessage(
135148
argThat<SendMessageRequest> { request ->
@@ -139,4 +152,38 @@ class PutExpressionInterestServiceTest :
139152
)
140153
}
141154
}
155+
156+
describe("sendExpressionOfInterest, with errors at HMPPS ID translation") {
157+
val validHmppsId = "AABCD1ABC"
158+
val invalidHmppsId = "INVALID_ID"
159+
val jobId = "5678"
160+
161+
it("should throw EntityNotFoundException. if ENTITY_NOT_FOUND error occurs") {
162+
val hmppsId = validHmppsId
163+
val notFoundResponse = errorResponseNomisNumber(UpstreamApiError.Type.ENTITY_NOT_FOUND, "Entity not found")
164+
whenever(mockGetPersonService.getNomisNumber(hmppsId)).thenReturn(notFoundResponse)
165+
166+
val exception = shouldThrow<EntityNotFoundException> { service.sendExpressionOfInterest(hmppsId, jobId) }
167+
168+
assertEquals("Could not find person with id: $hmppsId", exception.message)
169+
}
170+
171+
it("should throw ValidationException if an invalid hmppsId is provided") {
172+
val hmppsId = invalidHmppsId
173+
val invalidIdBadRequestResponse = errorResponseNomisNumber(UpstreamApiError.Type.BAD_REQUEST, "Invalid HMPPS ID")
174+
whenever(mockGetPersonService.getNomisNumber(hmppsId)).thenReturn(invalidIdBadRequestResponse)
175+
176+
val exception = shouldThrow<ValidationException> { service.sendExpressionOfInterest(hmppsId, jobId) }
177+
178+
assertEquals("Invalid HMPPS ID: $hmppsId", exception.message)
179+
}
180+
}
142181
})
182+
183+
private fun errorResponseNomisNumber(
184+
errorType: UpstreamApiError.Type,
185+
errorDescription: String,
186+
) = Response<NomisNumber?>(
187+
data = null,
188+
errors = listOf(UpstreamApiError(type = errorType, description = errorDescription, causedBy = UpstreamApi.PRISONER_OFFENDER_SEARCH)),
189+
)

0 commit comments

Comments
 (0)