Skip to content

Commit 2536071

Browse files
Add duplicate validation
1 parent f05987d commit 2536071

File tree

8 files changed

+68
-14
lines changed

8 files changed

+68
-14
lines changed

internal/datastore/src/types.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,16 @@ erDiagram
3131
erDiagram
3232
MI {
3333
string id
34-
string supplierId
35-
string specificationId
36-
string groupId
3734
string lineItem
35+
string timestamp
3836
number quantity
37+
string specificationId
38+
string groupId
3939
number stockRemaining
40+
string supplierId
4041
string createdAt
4142
string updatedAt
43+
number ttl "min: -9007199254740991, max: 9007199254740991"
4244
}
4345
```
4446

lambdas/api-handler/src/contracts/errors.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ export enum ApiErrorDetail {
3636
InvalidRequestLimitOnly = "Only 'limit' query parameter is supported",
3737
InvalidRequestNoRequestId = 'The request does not contain a request id',
3838
InvalidRequestTimestamp = 'Timestamps should be UTC date/times in ISO8601 format, with a Z suffix',
39-
InvalidRequestLettersToUpdate = 'The request exceeds the maximum of %s items allowed for update'
39+
InvalidRequestLettersToUpdate = 'The request exceeds the maximum of %s items allowed for update',
40+
InvalidRequestDuplicateLetterId = 'The request cannot include multiple letter objects with the same id'
4041
}
4142

4243
export function buildApiError(params: {

lambdas/api-handler/src/handlers/__tests__/post-letters.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,44 @@ describe('postLetters API Handler', () => {
209209
expect(result).toEqual(expectedErrorResponse);
210210
});
211211

212+
it('returns error when the request has duplicate letter ids', async () => {
213+
214+
const event = makeApiGwEvent({
215+
path: '/letters',
216+
body: JSON.stringify({
217+
data: [
218+
{
219+
id: 'id1',
220+
type: 'Letter',
221+
attributes: {
222+
status: 'ACCEPTED'
223+
}
224+
},
225+
{
226+
id: 'id1',
227+
type: 'Letter',
228+
attributes: {
229+
status: 'ACCEPTED'
230+
}
231+
}
232+
]
233+
}),
234+
headers: {
235+
'nhsd-supplier-id': 'supplier1',
236+
'nhsd-correlation-id': 'correlationId',
237+
'x-request-id': 'requestId'
238+
}
239+
});
240+
const context = mockDeep<Context>();
241+
const callback = jest.fn();
242+
243+
const postLettersHandler = createPostLettersHandler(mockedDeps);
244+
const result = await postLettersHandler(event, context, callback);
245+
246+
expect(mockedProcessError).toHaveBeenCalledWith(new ValidationError(errors.ApiErrorDetail.InvalidRequestDuplicateLetterId), 'correlationId', mockedDeps.logger);
247+
expect(result).toEqual(expectedErrorResponse);
248+
});
249+
212250
it('returns error when request body is not json', async () => {
213251
const event = makeApiGwEvent({
214252
path: '/letters',

lambdas/api-handler/src/handlers/post-letters.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ export function createPostLettersHandler(deps: Deps): APIGatewayProxyHandler {
3838
throw new ValidationError(ApiErrorDetail.InvalidRequestLettersToUpdate, { args: [maxUpdateItems]});
3939
}
4040

41+
if( duplicateIdsExist(postLettersRequest) ) {
42+
throw new ValidationError(ApiErrorDetail.InvalidRequestDuplicateLetterId);
43+
}
44+
4145
await enqueueLetterUpdateRequests(postLettersRequest, commonHeadersResult.value.supplierId, commonHeadersResult.value.correlationId, deps);
4246

4347
return {
@@ -50,3 +54,8 @@ export function createPostLettersHandler(deps: Deps): APIGatewayProxyHandler {
5054
}
5155
};
5256
};
57+
58+
function duplicateIdsExist(postLettersRequest: PostLettersRequest) {
59+
const ids = postLettersRequest.data.map(item => item.id);
60+
return new Set(ids).size !== ids.length;
61+
}

sandbox/api/openapi.yaml

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,8 @@ paths:
459459
460460
It is not possible to update a letter to status of `PENDING`.
461461
462+
The request should not contain multiple letter objects with the same ID.
463+
462464
Optionally a `reasonCode` and `reasonText` explaining the status (for example, validation failures) can be included in the request body for each update.
463465
operationId: postLetters
464466
parameters:
@@ -561,23 +563,23 @@ paths:
561563
example: 11C46F5F-CDEF-4865-94B2-0EE0EDCC26DA
562564
type: string
563565
style: simple
564-
"404":
566+
"400":
565567
content:
566568
application/vnd.api+json:
567569
examples:
568-
error-not-found:
570+
error-bad-request-invalid-request-body:
569571
value:
570572
errors:
571-
- code: NOTIFY_RESOURCE_NOT_FOUND
572-
detail: No resource found with that ID
573+
- code: NOTIFY_INVALID_REQUEST
574+
detail: The request body is invalid
573575
id: rrt-1931948104716186917-c-geu2-10664-3111479-3.0
574576
links:
575577
about: https://digital.nhs.uk/developer/api-catalogue/nhs-notify-supplier
576-
status: "404"
577-
title: Resource not found
578+
status: "400"
579+
title: Invalid request
578580
schema:
579581
$ref: "#/components/schemas/listLetters_400_response"
580-
description: Resource not found
582+
description: "Bad request, invalid input data"
581583
"429":
582584
content:
583585
application/vnd.api+json:

sandbox/services/LetterService.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ const patchLetter = ({ xRequestId, id, body, xCorrelationId }) => new Promise(
111111
* xRequestId String Unique request identifier, in the format of a GUID
112112
* postLettersRequest PostLettersRequest
113113
* xCorrelationId String An optional ID which you can use to track transactions across multiple systems. It can take any value, but we recommend avoiding `.` characters. If not provided in the request, NHS Notify will default to a system generated ID in its place. The ID will be returned in a response header. (optional)
114-
* returns listLetters_200_response
114+
* returns 202
115115
* */
116116
const postLetters = ({ xRequestId, body, xCorrelationId }) => new Promise(
117117
async (resolve, reject) => {

sandbox/utils/ResponseProvider.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,10 @@ async function patchLetterResponse(request) {
9393
}
9494

9595
async function postLettersResponse(request) {
96-
const patchLetterFileMap = {
96+
const postLettersFileMap = {
9797
'data/examples/postLetter/requests/postLetters.json': {responsePath: 'data/examples/postLetter/responses/postLetters.json', responseCode: 202},
9898
};
99-
return await mapExampleResponse(request, patchLetterFileMap);
99+
return await mapExampleResponse(request, postLettersFileMap);
100100
}
101101

102102
async function postMIResponse(request) {

specification/api/components/documentation/postLetters.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,6 @@ Allowed `status` values that can be used to are:
2525

2626
It is not possible to update a letter to status of `PENDING`.
2727

28+
The request should not contain multiple letter objects with the same ID.
29+
2830
Optionally a `reasonCode` and `reasonText` explaining the status (for example, validation failures) can be included in the request body for each update.

0 commit comments

Comments
 (0)