Skip to content

Commit e773b5c

Browse files
committed
Wrote nomis gateway tests for new post transaction functionality
1 parent 7d1d0db commit e773b5c

File tree

9 files changed

+219
-16
lines changed

9 files changed

+219
-16
lines changed

src/main/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/config/RequestLogger.kt

+3-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import jakarta.servlet.http.HttpServletResponse
55
import org.slf4j.LoggerFactory
66
import org.springframework.stereotype.Component
77
import org.springframework.web.servlet.HandlerInterceptor
8-
import java.util.stream.Collectors
98

109
// Intercepts incoming requests and logs them
1110
@Component
@@ -32,8 +31,9 @@ class RequestLogger : HandlerInterceptor {
3231
val method: String = "Method: " + request.method
3332
val endpoint: String = "Request URI: " + request.requestURI // Could this expose authentication credentials?
3433
val requestURL: String = "Full Request URL: " + request.requestURL
35-
val body: String = ("Body: " + request.reader.lines().collect(Collectors.joining()))
34+
// Unable to use reader here and read the request body into a controller method
35+
// val body: String = ("Body: " + request.reader.lines().collect(Collectors.joining()))
3636

37-
return "$requestIp \n $method \n $endpoint \n $body \n $requestURL"
37+
return "$requestIp \n $method \n $endpoint \n $requestURL"
3838
}
3939
}

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ class TransactionsController(
104104
}
105105

106106
if (response.hasError(UpstreamApiError.Type.BAD_REQUEST)) {
107-
throw ValidationException("Either invalid HMPPS ID: $hmppsId at or incorrect prison: $prisonId")
107+
throw ValidationException("Either invalid HMPPS ID: $hmppsId or incorrect prison: $prisonId")
108108
}
109109

110110
auditService.createEvent("GET_TRANSACTIONS_FOR_PERSON", mapOf("hmppsId" to hmppsId, "prisonId" to prisonId, "fromDate" to fromDate, "toDate" to toDate))
@@ -163,7 +163,7 @@ class TransactionsController(
163163
}
164164

165165
if (response.hasError(UpstreamApiError.Type.BAD_REQUEST)) {
166-
throw ValidationException("Either invalid HMPPS ID: $hmppsId at or incorrect prison: $prisonId")
166+
throw ValidationException("Either invalid HMPPS ID: $hmppsId or incorrect prison: $prisonId")
167167
}
168168

169169
auditService.createEvent("GET_TRANSACTION_FOR_PERSON", mapOf("hmppsId" to hmppsId, "prisonId" to prisonId, "clientUniqueRef" to clientUniqueRef))
@@ -218,7 +218,7 @@ class TransactionsController(
218218
val response = postTransactionsForPersonService.execute(prisonId, hmppsId, transactionRequest, filters)
219219

220220
if (response.hasError(UpstreamApiError.Type.BAD_REQUEST)) {
221-
throw ValidationException("TODO")
221+
throw ValidationException("Either invalid HMPPS ID: $hmppsId or incorrect prison: $prisonId or invalid request body: ${transactionRequest.toMap()}")
222222
}
223223

224224
auditService.createEvent("CREATE_TRANSACTION", mapOf("hmppsId" to hmppsId, "prisonId" to prisonId)) // add req obj

src/main/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/models/hmpps/TransactionRequest.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps
22

33
data class TransactionRequest(
4-
val type: String, // reject if not in enum
4+
var type: String, // reject if not in enum
55
val description: String?,
66
val amount: Int,
77
val clientTransactionId: String,
@@ -14,5 +14,5 @@ data class TransactionRequest(
1414
"amount" to amount,
1515
"clientTransactionId" to clientTransactionId,
1616
"clientUniqueRef" to clientUniqueRef,
17-
)
17+
).filterValues { it != null }
1818
}

src/main/resources/application-test.yml

+1
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ authorisation:
101101
include:
102102
- "/v1/prison/.*/prisoners/[^/]*/balances$"
103103
- "/v1/prison/.*/prisoners/.*/accounts/.*/balances"
104+
- "/v1/prison/.*/prisoners/.*/transactions"
104105
filters:
105106
prisons:
106107
- XYZ

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

+53-5
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.Transaction
2222
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.Type
2323
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.UpstreamApi
2424
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.UpstreamApiError
25+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.roleconfig.ConsumerFilters
2526
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.services.GetTransactionForPersonService
2627
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.services.GetTransactionsForPersonService
2728
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.services.PostTransactionForPersonService
@@ -53,7 +54,7 @@ class TransactionsControllerTest(
5354
val amount = 1634
5455
val clientTransactionId = "CL123212"
5556
val postClientUniqueRef = "CLIENT121131-0_11"
56-
val exampleTransaction = TransactionRequest(type, description, amount, clientTransactionId, postClientUniqueRef)
57+
var exampleTransaction = TransactionRequest(type, description, amount, clientTransactionId, postClientUniqueRef)
5758

5859
val transactions =
5960
Transactions(
@@ -217,9 +218,6 @@ class TransactionsControllerTest(
217218

218219
val result = mockMvc.performAuthorisedPost(postTransactionPath, exampleTransaction)
219220

220-
result.response.contentAsString.shouldContain("transactionId")
221-
222-
/*
223221
result.response.contentAsString.shouldContain(
224222
"""
225223
{
@@ -228,7 +226,7 @@ class TransactionsControllerTest(
228226
}
229227
}
230228
""".removeWhitespaceAndNewlines(),
231-
)*/
229+
)
232230
}
233231

234232
it("returns a 200 status code when successful") {
@@ -238,5 +236,55 @@ class TransactionsControllerTest(
238236

239237
result.response.status.shouldBe(HttpStatus.OK.value())
240238
}
239+
240+
// prison filter rejection test
241+
it("returns 400 if prison filter is not matched") {
242+
whenever(postTransactionForPersonService.execute(prisonId, hmppsId, exampleTransaction, ConsumerFilters(prisons = listOf("XYZ")))).thenReturn(
243+
Response(
244+
data = null,
245+
errors =
246+
listOf(
247+
UpstreamApiError(
248+
causedBy = UpstreamApi.NOMIS,
249+
type = UpstreamApiError.Type.BAD_REQUEST,
250+
),
251+
),
252+
),
253+
)
254+
255+
val result = mockMvc.performAuthorisedPostWithCN(postTransactionPath, "limited-prisons", exampleTransaction)
256+
257+
result.response.status.shouldBe(HttpStatus.BAD_REQUEST.value())
258+
}
259+
260+
it("calls the API with the correct filters") {
261+
whenever(postTransactionForPersonService.execute(prisonId, hmppsId, exampleTransaction, ConsumerFilters(prisons = listOf("XYZ")))).thenReturn(Response(transactionCreateResponse))
262+
263+
val result = mockMvc.performAuthorisedPostWithCN(postTransactionPath, "limited-prisons", exampleTransaction)
264+
265+
result.response.status.shouldBe(HttpStatus.OK.value())
266+
}
267+
268+
// bad request
269+
it("returns a 400 BAD REQUEST status code when there is an invalid HMPPS ID or incorrect prison, or an invalid request body") {
270+
whenever(postTransactionForPersonService.execute(prisonId, hmppsId, exampleTransaction, null)).thenReturn(
271+
Response(
272+
data = null,
273+
errors =
274+
listOf(
275+
UpstreamApiError(
276+
causedBy = UpstreamApi.NOMIS,
277+
type = UpstreamApiError.Type.BAD_REQUEST,
278+
),
279+
),
280+
),
281+
)
282+
283+
exampleTransaction.type = ""
284+
285+
val result = mockMvc.performAuthorisedPost(postTransactionPath, exampleTransaction)
286+
287+
result.response.status.shouldBe(HttpStatus.BAD_REQUEST.value())
288+
}
241289
},
242290
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package uk.gov.justice.digital.hmpps.hmppsintegrationapi.gateways.nomis
2+
3+
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
4+
import io.kotest.core.spec.style.DescribeSpec
5+
import io.kotest.matchers.collections.shouldBeEmpty
6+
import io.kotest.matchers.shouldBe
7+
import org.mockito.Mockito
8+
import org.mockito.internal.verification.VerificationModeFactory
9+
import org.mockito.kotlin.verify
10+
import org.mockito.kotlin.whenever
11+
import org.springframework.boot.test.context.ConfigDataApplicationContextInitializer
12+
import org.springframework.http.HttpStatus
13+
import org.springframework.test.context.ActiveProfiles
14+
import org.springframework.test.context.ContextConfiguration
15+
import org.springframework.test.context.bean.override.mockito.MockitoBean
16+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.extensions.removeWhitespaceAndNewlines
17+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.gateways.HmppsAuthGateway
18+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.gateways.NomisGateway
19+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.mockservers.HmppsAuthMockServer
20+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.mockservers.NomisApiMockServer
21+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.TransactionRequest
22+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.UpstreamApi
23+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.UpstreamApiError
24+
25+
@ActiveProfiles("test")
26+
@ContextConfiguration(
27+
initializers = [ConfigDataApplicationContextInitializer::class],
28+
classes = [NomisGateway::class],
29+
)
30+
class PostTransactionForPersonTest(
31+
@MockitoBean val hmppsAuthGateway: HmppsAuthGateway,
32+
val nomisGateway: NomisGateway,
33+
) : DescribeSpec({
34+
val nomisApiMockServer = NomisApiMockServer()
35+
val prisonId = "XYZ"
36+
val nomisNumber = "AA1234Z"
37+
val basePath = "/api/v1/prison/$prisonId/prisoners/$nomisNumber/transactions"
38+
val regexPath = "/api/v1/prison/.*/prisoners/.*/transactions"
39+
40+
val description = "Canteen Purchase of £16.34"
41+
val amount = 1634
42+
val clientTransactionId = "CL123212"
43+
val clientUniqueRef = "CLIENT121131-0_11"
44+
val type = "CANT"
45+
val exampleTransaction = TransactionRequest(type, description, amount, clientTransactionId, clientUniqueRef)
46+
47+
beforeEach {
48+
nomisApiMockServer.start()
49+
50+
nomisApiMockServer.stubNomisApiResponseForPost(
51+
prisonId,
52+
nomisNumber,
53+
asJsonString(exampleTransaction),
54+
"""
55+
{
56+
"id": "6179604-1",
57+
"description": "Transfer In Regular from caseload PVR"
58+
}
59+
""".removeWhitespaceAndNewlines(),
60+
)
61+
62+
Mockito.reset(hmppsAuthGateway)
63+
whenever(hmppsAuthGateway.getClientToken("NOMIS")).thenReturn(HmppsAuthMockServer.TOKEN)
64+
}
65+
66+
afterTest {
67+
nomisApiMockServer.stop()
68+
}
69+
70+
it("authenticates using HMPPS Auth with credentials") {
71+
nomisGateway.postTransactionForPerson(
72+
prisonId,
73+
nomisNumber,
74+
exampleTransaction,
75+
)
76+
77+
verify(hmppsAuthGateway, VerificationModeFactory.times(1)).getClientToken("NOMIS")
78+
}
79+
80+
it("returns expected response with transaction id and description when a valid request body is provided") {
81+
val response =
82+
nomisGateway.postTransactionForPerson(
83+
prisonId,
84+
nomisNumber,
85+
exampleTransaction,
86+
)
87+
88+
response.errors.shouldBeEmpty()
89+
response.data
90+
?.id
91+
.shouldBe("6179604-1")
92+
response.data
93+
?.description
94+
.shouldBe("Transfer In Regular from caseload PVR")
95+
}
96+
97+
it("return a 500 error response") {
98+
var invalidTransactionRequest = TransactionRequest("invalid", "", 0, "", "")
99+
nomisApiMockServer.stubNomisApiResponseForPost(prisonId, nomisNumber, asJsonString(invalidTransactionRequest), "", HttpStatus.NOT_FOUND)
100+
101+
val response = nomisGateway.postTransactionForPerson(prisonId, nomisNumber, invalidTransactionRequest)
102+
103+
response.errors.shouldBe(arrayOf(UpstreamApiError(causedBy = UpstreamApi.NOMIS, type = UpstreamApiError.Type.ENTITY_NOT_FOUND)))
104+
}
105+
})
106+
107+
private fun asJsonString(obj: Any): String = jacksonObjectMapper().writeValueAsString(obj)

src/test/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/helpers/IntegrationAPIMockMvc.kt

+15
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,21 @@ class IntegrationAPIMockMvc(
3939
return mockMvc.perform(requestBuilder).andReturn()
4040
}
4141

42+
fun <T : Any> performAuthorisedPostWithCN(
43+
path: String,
44+
cn: String,
45+
requestBody: T,
46+
): MvcResult {
47+
val subjectDistinguishedName = "C=GB,ST=London,L=London,O=Home Office,CN=$cn"
48+
val requestBuilder =
49+
MockMvcRequestBuilders
50+
.post(path)
51+
.header("subject-distinguished-name", subjectDistinguishedName)
52+
.content(asJsonString(requestBody))
53+
.contentType(MediaType.APPLICATION_JSON)
54+
return mockMvc.perform(requestBuilder).andReturn()
55+
}
56+
4257
fun performUnAuthorised(path: String): MvcResult = mockMvc.perform(MockMvcRequestBuilders.get(path)).andReturn()
4358

4459
private fun asJsonString(obj: Any): String = jacksonObjectMapper().writeValueAsString(obj)

src/test/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/integration/TransactionsIntegrationTest.kt

+8
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,12 @@ class TransactionsIntegrationTest : IntegrationTestBase() {
6161
get("/v1/prison/$wrongPrisonId/prisoners/$hmppsId/transactions/$clientUniqueRef").headers(headers),
6262
).andExpect(status().isNotFound)
6363
}
64+
65+
// POST transaction
66+
@Test
67+
fun `return a response with a transaction ID`() {
68+
callApi("/v1/prison/$prisonId/prisoners/$hmppsId/transactions")
69+
.andExpect(status().isOk)
70+
.andExpect(content().json(getExpectedResponse("transactions-response")))
71+
}
6472
}

src/test/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/mockservers/NomisApiMockServer.kt

+27-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1+
@file:Suppress("ktlint:standard:no-wildcard-imports")
2+
13
package uk.gov.justice.digital.hmpps.hmppsintegrationapi.mockservers
24

35
import com.github.tomakehurst.wiremock.WireMockServer
4-
import com.github.tomakehurst.wiremock.client.WireMock.aResponse
5-
import com.github.tomakehurst.wiremock.client.WireMock.get
6-
import com.github.tomakehurst.wiremock.client.WireMock.matching
6+
import com.github.tomakehurst.wiremock.client.WireMock.*
77
import org.springframework.http.HttpStatus
88

99
class NomisApiMockServer : WireMockServer(WIREMOCK_PORT) {
@@ -30,6 +30,30 @@ class NomisApiMockServer : WireMockServer(WIREMOCK_PORT) {
3030
)
3131
}
3232

33+
fun stubNomisApiResponseForPost(
34+
prisonId: String,
35+
nomisNumber: String,
36+
reqBody: String,
37+
resBody: String,
38+
status: HttpStatus = HttpStatus.OK,
39+
) {
40+
val externalUrl = urlPathTemplate("/api/v1/prison/$prisonId/offenders/$nomisNumber/transactions")
41+
42+
stubFor(
43+
post(externalUrl)
44+
.withHeader(
45+
"Authorization",
46+
matching("Bearer ${HmppsAuthMockServer.TOKEN}"),
47+
).withRequestBody(equalToJson(reqBody))
48+
.willReturn(
49+
aResponse()
50+
.withHeader("Content-Type", "application/json")
51+
.withStatus(status.value())
52+
.withBody(resBody.trimIndent()),
53+
),
54+
)
55+
}
56+
3357
fun stubGetImageData(
3458
imageId: Int,
3559
status: HttpStatus = HttpStatus.OK,

0 commit comments

Comments
 (0)