Skip to content

Commit edec71b

Browse files
HMAI - Added validation to transaction transfer POST (#731)
* Added validation to data class * Add test for validation
1 parent 4f00e6b commit edec71b

File tree

3 files changed

+113
-93
lines changed

3 files changed

+113
-93
lines changed

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ class TransactionsController(
302302
@Parameter(description = "The ID of the prison that holds the account") @PathVariable prisonId: String,
303303
@Parameter(description = "The HMPPS ID of the person") @PathVariable hmppsId: String,
304304
@RequestAttribute filters: ConsumerFilters?,
305-
@RequestBody transactionTransferRequest: TransactionTransferRequest,
305+
@Valid @RequestBody transactionTransferRequest: TransactionTransferRequest,
306306
): DataResponse<TransactionTransferCreateResponse?> {
307307
val response = postTransactionTransferForPersonService.execute(prisonId, hmppsId, transactionTransferRequest, filters)
308308

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

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

33
import io.swagger.v3.oas.annotations.media.Schema
4+
import jakarta.validation.constraints.NotBlank
45

56
data class TransactionTransferRequest(
7+
@field:NotBlank(message = "Description must not be blank")
68
@Schema(description = "Description of the transaction.")
79
val description: String,
810
@Schema(
911
description = "Amount of money in pence, must be positive.",
1012
example = "1234",
1113
)
1214
val amount: Int,
15+
@field:NotBlank(message = "Client transaction ID must not be blank")
1316
@Schema(description = "Client Transaction Id.")
1417
val clientTransactionId: String,
18+
@field:NotBlank(message = "Client unique ref must not be blank")
1519
@Schema(description = "A reference unique to the client making the post. Maximum size 64 characters, only alphabetic, numeric, '-' and '_' are allowed.")
1620
val clientUniqueRef: String,
21+
@field:NotBlank(message = "From account must not be blank")
1722
@Schema(description = "The account to move money from. Must be 'spends'.", example = "spends")
1823
val fromAccount: String,
24+
@field:NotBlank(message = "To account must not be blank")
1925
@Schema(description = "The account to move money to. Must be 'savings'.", example = "savings")
2026
val toAccount: String,
2127
) {

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

+106-92
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,6 @@ class TransactionsControllerTest(
5353
val basePath = "/v1/prison/$prisonId/prisoners/$hmppsId"
5454
val transactionsPath = "$basePath/accounts/$accountCode/transactions"
5555
val transactionPath = "$basePath/transactions/$clientUniqueRef"
56-
val postTransactionPath = "$basePath/transactions"
57-
val postTransactionTransferPath = "$postTransactionPath/transfer"
5856
val mockMvc = IntegrationAPIMockMvc(springMockMvc)
5957

6058
val type = "CANT"
@@ -64,7 +62,6 @@ class TransactionsControllerTest(
6462
val postClientUniqueRef = "CLIENT121131-0_11"
6563
val fromAccount = "spends"
6664
val toAccount = "savings"
67-
val exampleTransactionTransfer = TransactionTransferRequest(description, amount, clientTransactionId, postClientUniqueRef, fromAccount, toAccount)
6865

6966
val transactions =
7067
Transactions(
@@ -225,6 +222,7 @@ class TransactionsControllerTest(
225222
}
226223

227224
describe("POST transaction") {
225+
val postTransactionPath = "$basePath/transactions"
228226
val exampleTransaction = TransactionRequest(type, description, amount, clientTransactionId, postClientUniqueRef)
229227

230228
it("returns a response with a transaction ID") {
@@ -327,117 +325,133 @@ class TransactionsControllerTest(
327325
}
328326
}
329327

330-
// Post transaction transfer
328+
describe("POST transaction/transfer") {
329+
val postTransactionTransferPath = "$basePath/transactions/transfer"
330+
val exampleTransactionTransfer = TransactionTransferRequest(description, amount, clientTransactionId, postClientUniqueRef, fromAccount, toAccount)
331331

332-
it("returns a response with a transaction ID, debit and credit transaction IDs") {
333-
whenever(
334-
postTransactionTransferForPersonService.execute(prisonId, hmppsId, exampleTransactionTransfer, null),
335-
).thenReturn(
336-
Response(transactionTransferCreateResponse),
337-
)
332+
it("returns a response with a transaction ID, debit and credit transaction IDs") {
333+
whenever(
334+
postTransactionTransferForPersonService.execute(prisonId, hmppsId, exampleTransactionTransfer, null),
335+
).thenReturn(
336+
Response(transactionTransferCreateResponse),
337+
)
338338

339-
val result = mockMvc.performAuthorisedPost(postTransactionTransferPath, exampleTransactionTransfer)
339+
val result = mockMvc.performAuthorisedPost(postTransactionTransferPath, exampleTransactionTransfer)
340340

341-
result.response.contentAsString.shouldContain(
342-
"""
343-
{
344-
"data": {
345-
"debitTransactionId": "6179604-1",
346-
"creditTransactionId": "6179604-1",
347-
"transactionId": "6179604"
341+
result.response.contentAsString.shouldContain(
342+
"""
343+
{
344+
"data": {
345+
"debitTransactionId": "6179604-1",
346+
"creditTransactionId": "6179604-1",
347+
"transactionId": "6179604"
348+
}
348349
}
349-
}
350-
""".removeWhitespaceAndNewlines(),
351-
)
352-
}
350+
""".removeWhitespaceAndNewlines(),
351+
)
352+
}
353353

354-
it("POST transfer - returns a 200 status code when successful") {
355-
whenever(
356-
postTransactionTransferForPersonService.execute(prisonId, hmppsId, exampleTransactionTransfer, null),
357-
).thenReturn(
358-
Response(transactionTransferCreateResponse),
359-
)
354+
it("returns a 200 status code when successful") {
355+
whenever(
356+
postTransactionTransferForPersonService.execute(prisonId, hmppsId, exampleTransactionTransfer, null),
357+
).thenReturn(
358+
Response(transactionTransferCreateResponse),
359+
)
360360

361-
val result = mockMvc.performAuthorisedPost(postTransactionTransferPath, exampleTransactionTransfer)
361+
val result = mockMvc.performAuthorisedPost(postTransactionTransferPath, exampleTransactionTransfer)
362362

363-
result.response.status.shouldBe(HttpStatus.OK.value())
364-
}
363+
result.response.status.shouldBe(HttpStatus.OK.value())
364+
}
365365

366-
// Prison filter rejection test
367-
it("POST transfer - returns 400 if prison filter is not matched") {
368-
whenever(
369-
postTransactionTransferForPersonService.execute(prisonId, hmppsId, exampleTransactionTransfer, ConsumerFilters(prisons = listOf("XYZ"))),
370-
).thenReturn(
371-
Response(
372-
data = null,
373-
errors =
374-
listOf(
375-
UpstreamApiError(
376-
causedBy = UpstreamApi.NOMIS,
377-
type = UpstreamApiError.Type.BAD_REQUEST,
366+
it("returns 400 if prison filter is not matched") {
367+
whenever(
368+
postTransactionTransferForPersonService.execute(prisonId, hmppsId, exampleTransactionTransfer, ConsumerFilters(prisons = listOf("XYZ"))),
369+
).thenReturn(
370+
Response(
371+
data = null,
372+
errors =
373+
listOf(
374+
UpstreamApiError(
375+
causedBy = UpstreamApi.NOMIS,
376+
type = UpstreamApiError.Type.BAD_REQUEST,
377+
),
378378
),
379-
),
380-
),
381-
)
379+
),
380+
)
382381

383-
val result = mockMvc.performAuthorisedPostWithCN(postTransactionTransferPath, "limited-prisons", exampleTransactionTransfer)
382+
val result = mockMvc.performAuthorisedPostWithCN(postTransactionTransferPath, "limited-prisons", exampleTransactionTransfer)
384383

385-
result.response.status.shouldBe(HttpStatus.BAD_REQUEST.value())
386-
}
384+
result.response.status.shouldBe(HttpStatus.BAD_REQUEST.value())
385+
}
387386

388-
it("POST transfers - calls the API with the correct filters") {
389-
whenever(
390-
postTransactionTransferForPersonService.execute(prisonId, hmppsId, exampleTransactionTransfer, ConsumerFilters(prisons = listOf("XYZ"))),
391-
).thenReturn(
392-
Response(transactionTransferCreateResponse),
393-
)
387+
it("returns 400 if request body validation does not succeed") {
388+
val invalidTransactionTransfer = TransactionTransferRequest(description = "", amount, clientTransactionId = "", clientUniqueRef = "", fromAccount = "", toAccount = "")
389+
whenever(
390+
postTransactionTransferForPersonService.execute(prisonId, hmppsId, exampleTransactionTransfer, null),
391+
).thenReturn(
392+
Response(transactionTransferCreateResponse),
393+
)
394+
395+
val result = mockMvc.performAuthorisedPost(postTransactionTransferPath, invalidTransactionTransfer)
396+
result.response.run {
397+
status.shouldBe(HttpStatus.BAD_REQUEST.value())
398+
contentAsJson<ValidationErrorResponse>().validationErrors.shouldContainAll("Description must not be blank", "Client transaction ID must not be blank", "Client unique ref must not be blank", "From account must not be blank", "To account must not be blank")
399+
}
400+
}
394401

395-
val result = mockMvc.performAuthorisedPostWithCN(postTransactionTransferPath, "limited-prisons", exampleTransactionTransfer)
402+
it("calls the API with the correct filters") {
403+
whenever(
404+
postTransactionTransferForPersonService.execute(prisonId, hmppsId, exampleTransactionTransfer, ConsumerFilters(prisons = listOf("XYZ"))),
405+
).thenReturn(
406+
Response(transactionTransferCreateResponse),
407+
)
396408

397-
result.response.status.shouldBe(HttpStatus.OK.value())
398-
}
409+
val result = mockMvc.performAuthorisedPostWithCN(postTransactionTransferPath, "limited-prisons", exampleTransactionTransfer)
399410

400-
// bad request
401-
it("POST transfer - returns a 400 BAD REQUEST status code when there is an invalid HMPPS ID or incorrect prison, or an invalid request body") {
402-
whenever(
403-
postTransactionTransferForPersonService.execute(prisonId, hmppsId, exampleTransactionTransfer, null),
404-
).thenReturn(
405-
Response(
406-
data = null,
407-
errors =
408-
listOf(
409-
UpstreamApiError(
410-
causedBy = UpstreamApi.NOMIS,
411-
type = UpstreamApiError.Type.BAD_REQUEST,
411+
result.response.status.shouldBe(HttpStatus.OK.value())
412+
}
413+
414+
it("returns a 400 BAD REQUEST status code when there is an invalid HMPPS ID or incorrect prison, or an invalid request body") {
415+
whenever(
416+
postTransactionTransferForPersonService.execute(prisonId, hmppsId, exampleTransactionTransfer, null),
417+
).thenReturn(
418+
Response(
419+
data = null,
420+
errors =
421+
listOf(
422+
UpstreamApiError(
423+
causedBy = UpstreamApi.NOMIS,
424+
type = UpstreamApiError.Type.BAD_REQUEST,
425+
),
412426
),
413-
),
414-
),
415-
)
427+
),
428+
)
416429

417-
val result = mockMvc.performAuthorisedPost(postTransactionTransferPath, exampleTransactionTransfer)
430+
val result = mockMvc.performAuthorisedPost(postTransactionTransferPath, exampleTransactionTransfer)
418431

419-
result.response.status.shouldBe(HttpStatus.BAD_REQUEST.value())
420-
}
432+
result.response.status.shouldBe(HttpStatus.BAD_REQUEST.value())
433+
}
421434

422-
it("POST transfer - returns a 409 CONFLICT status code when there is a duplicate transaction requested") {
423-
whenever(
424-
postTransactionTransferForPersonService.execute(prisonId, hmppsId, exampleTransactionTransfer, null),
425-
).thenReturn(
426-
Response(
427-
data = null,
428-
errors =
429-
listOf(
430-
UpstreamApiError(
431-
causedBy = UpstreamApi.NOMIS,
432-
type = UpstreamApiError.Type.CONFLICT,
435+
it("returns a 409 CONFLICT status code when there is a duplicate transaction requested") {
436+
whenever(
437+
postTransactionTransferForPersonService.execute(prisonId, hmppsId, exampleTransactionTransfer, null),
438+
).thenReturn(
439+
Response(
440+
data = null,
441+
errors =
442+
listOf(
443+
UpstreamApiError(
444+
causedBy = UpstreamApi.NOMIS,
445+
type = UpstreamApiError.Type.CONFLICT,
446+
),
433447
),
434-
),
435-
),
436-
)
448+
),
449+
)
437450

438-
val result = mockMvc.performAuthorisedPost(postTransactionTransferPath, exampleTransactionTransfer)
451+
val result = mockMvc.performAuthorisedPost(postTransactionTransferPath, exampleTransactionTransfer)
439452

440-
result.response.status.shouldBe(HttpStatus.CONFLICT.value())
453+
result.response.status.shouldBe(HttpStatus.CONFLICT.value())
454+
}
441455
}
442456
},
443457
)

0 commit comments

Comments
 (0)