Skip to content

Commit 8a2881d

Browse files
CCM-12858: fixing tests and tidy up
1 parent 526ccc3 commit 8a2881d

File tree

6 files changed

+111
-180
lines changed

6 files changed

+111
-180
lines changed
Lines changed: 43 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
import type { SQSEvent, SQSRecord } from 'aws-lambda';
22
import { mock } from 'jest-mock-extended';
33
import { Logger, Sender } from 'utils';
4-
import { PDMResourceAvailable } from 'digital-letters-events';
54
import { NotifyMessageProcessor } from 'app/notify-message-processor';
6-
import { SenderManagement } from 'sender-management';
5+
import { ISenderManagement } from 'sender-management';
76
import { EventPublisherFacade } from 'infra/event-publisher-facade';
87
import { SqsHandlerDependencies, createHandler } from 'apis/sqs-handler';
98
import { parseSqsRecord } from 'app/parse-sqs-message';
109
import { InvalidPdmResourceAvailableEvent } from 'domain/invalid-pdm-resource-available-event';
1110
import { RequestNotifyError } from 'domain/request-notify-error';
11+
import { validPdmEvent, validSender } from '__tests__/constants';
1212

1313
jest.mock('app/parse-sqs-message');
1414

1515
const mockLogger = mock<Logger>();
1616
const mockNotifyMessageProcessor = mock<NotifyMessageProcessor>();
17-
const mockSenderManagement = mock<SenderManagement>();
17+
const mockSenderManagement = mock<ISenderManagement>();
1818
const mockEventPublisherFacade = mock<EventPublisherFacade>();
1919
const mockParseSqsRecord = jest.mocked(parseSqsRecord);
2020

@@ -31,36 +31,11 @@ describe('createHandler', () => {
3131
const messageReference = 'msg-ref-123';
3232
const notifyId = 'notify-id-123';
3333

34-
const mockSender: Sender = {
35-
senderId,
36-
routingConfigId,
37-
senderName: 'Test Sender',
38-
meshMailboxSenderId: 'meshMailBoxSender-123',
39-
meshMailboxReportsId: 'meshMailBoxReports-123',
40-
fallbackWaitTimeSeconds: 100,
41-
};
42-
43-
const mockPdmEvent: PDMResourceAvailable = {
44-
id: 'event-id-123',
45-
source: 'urn:nhs:names:services:notify:pdm',
46-
specversion: '1.0',
47-
type: 'uk.nhs.notify.digital.letters.pdm.resource.available.v1',
48-
time: '2025-12-15T10:00:00Z',
49-
datacontenttype: 'application/json',
50-
data: {
51-
senderId,
52-
messageReference,
53-
resourceId: 'ResourceId-123',
54-
nhsNumber: '9991234566',
55-
odsCode: 'A12345',
56-
},
57-
};
58-
5934
const createSqsRecord = (messageId: string): SQSRecord => ({
6035
messageId,
6136
receiptHandle: 'receipt-handle',
6237
body: JSON.stringify({
63-
detail: mockPdmEvent,
38+
detail: validPdmEvent,
6439
}),
6540
attributes: {
6641
ApproximateReceiveCount: '1',
@@ -90,8 +65,8 @@ describe('createHandler', () => {
9065
const sqsEvent = createSqsEvent(1);
9166
const handler = createHandler(dependencies);
9267

93-
mockParseSqsRecord.mockReturnValueOnce(mockPdmEvent);
94-
mockSenderManagement.getSender.mockReturnValueOnce(mockSender);
68+
mockParseSqsRecord.mockReturnValueOnce(validPdmEvent);
69+
mockSenderManagement.getSender.mockResolvedValue(validSender);
9570
mockNotifyMessageProcessor.process.mockResolvedValueOnce(notifyId);
9671

9772
const result = await handler(sqsEvent);
@@ -107,32 +82,27 @@ describe('createHandler', () => {
10782
sqsEvent.Records[0],
10883
mockLogger,
10984
);
110-
expect(mockSenderManagement.getSender).toHaveBeenCalledWith(senderId);
85+
expect(mockSenderManagement.getSender).toHaveBeenCalledWith({ senderId: senderId });
11186
expect(mockNotifyMessageProcessor.process).toHaveBeenCalledTimes(1);
11287
expect(
11388
mockEventPublisherFacade.publishMessageRequestSubmitted,
11489
).toHaveBeenCalledTimes(1);
11590
});
116-
});
11791

118-
describe('when sender has no routing config', () => {
11992
it('skips the message and publishes a skipped event', async () => {
12093
const sqsEvent = createSqsEvent(1);
12194
const handler = createHandler(dependencies);
12295
const senderWithoutRouting: Sender = {
123-
...mockSender,
96+
...validSender,
12497
routingConfigId: undefined,
12598
};
12699

127-
mockParseSqsRecord.mockReturnValueOnce(mockPdmEvent);
128-
mockSenderManagement.getSender.mockReturnValueOnce(senderWithoutRouting);
100+
mockParseSqsRecord.mockReturnValueOnce(validPdmEvent);
101+
mockSenderManagement.getSender.mockResolvedValue(senderWithoutRouting);
129102

130103
const result = await handler(sqsEvent);
131104

132105
expect(result).toEqual({ batchItemFailures: [] });
133-
expect(mockLogger.debug).toHaveBeenCalledWith(
134-
`No routing config for sender ${senderId}, skipping message`,
135-
);
136106
expect(mockNotifyMessageProcessor.process).not.toHaveBeenCalled();
137107
expect(
138108
mockEventPublisherFacade.publishMessageRequestSkipped,
@@ -145,8 +115,8 @@ describe('createHandler', () => {
145115
const sqsEvent = createSqsEvent(3);
146116
const handler = createHandler(dependencies);
147117

148-
mockParseSqsRecord.mockReturnValue(mockPdmEvent);
149-
mockSenderManagement.getSender.mockReturnValue(mockSender);
118+
mockParseSqsRecord.mockReturnValue(validPdmEvent);
119+
mockSenderManagement.getSender.mockResolvedValue(validSender);
150120
mockNotifyMessageProcessor.process.mockResolvedValue(notifyId);
151121

152122
const result = await handler(sqsEvent);
@@ -161,6 +131,30 @@ describe('createHandler', () => {
161131
expect(mockParseSqsRecord).toHaveBeenCalledTimes(3);
162132
expect(mockNotifyMessageProcessor.process).toHaveBeenCalledTimes(3);
163133
});
134+
135+
it('returns only failed message IDs', async () => {
136+
const sqsEvent = createSqsEvent(3);
137+
const handler = createHandler(dependencies);
138+
139+
mockParseSqsRecord
140+
.mockReturnValueOnce(validPdmEvent)
141+
.mockImplementationOnce(() => {
142+
throw new Error('Parse error');
143+
})
144+
.mockReturnValueOnce(validPdmEvent);
145+
146+
mockSenderManagement.getSender.mockResolvedValue(validSender);
147+
mockNotifyMessageProcessor.process.mockResolvedValue(notifyId);
148+
149+
const result = await handler(sqsEvent);
150+
151+
expect(result).toEqual({
152+
batchItemFailures: [{ itemIdentifier: 'message-id-2' }],
153+
});
154+
expect(mockLogger.info).toHaveBeenCalledWith(
155+
'2 of 3 records processed successfully',
156+
);
157+
});
164158
});
165159

166160
describe('when parseSqsRecord throws InvalidPdmResourceAvailableEvent', () => {
@@ -189,21 +183,15 @@ describe('createHandler', () => {
189183
});
190184
});
191185

192-
describe('when processing throws RequestNotifyError', () => {
193-
it('marks the message as failed for retry since error lacks messageReference', async () => {
186+
describe('when processing throws error', () => {
187+
it('marks the message as failed as is not a RequestNotifyError', async () => {
194188
const sqsEvent = createSqsEvent(1);
195189
const handler = createHandler(dependencies);
196190
const { messageId } = sqsEvent.Records[0];
197-
const errorCode = 'VALIDATION_ERROR';
198-
const correlationId = 'corr-123';
199-
const error = new RequestNotifyError(
200-
new Error('Validation failed'),
201-
correlationId,
202-
errorCode,
203-
);
191+
const error = new Error('Validation failed');
204192

205-
mockParseSqsRecord.mockReturnValueOnce(mockPdmEvent);
206-
mockSenderManagement.getSender.mockReturnValueOnce(mockSender);
193+
mockParseSqsRecord.mockReturnValueOnce(validPdmEvent);
194+
mockSenderManagement.getSender.mockResolvedValue(validSender);
207195
mockNotifyMessageProcessor.process.mockRejectedValueOnce(error);
208196

209197
const result = await handler(sqsEvent);
@@ -237,8 +225,8 @@ describe('createHandler', () => {
237225
// Add messageReference property dynamically to trigger the terminal error path
238226
(error as any).messageReference = messageReference;
239227

240-
mockParseSqsRecord.mockReturnValueOnce(mockPdmEvent);
241-
mockSenderManagement.getSender.mockReturnValueOnce(mockSender);
228+
mockParseSqsRecord.mockReturnValueOnce(validPdmEvent);
229+
mockSenderManagement.getSender.mockResolvedValue(validSender);
242230
mockNotifyMessageProcessor.process.mockRejectedValueOnce(error);
243231

244232
const result = await handler(sqsEvent);
@@ -266,75 +254,4 @@ describe('createHandler', () => {
266254
);
267255
});
268256
});
269-
270-
describe('when processing throws a generic error', () => {
271-
it('marks the message as failed for retry', async () => {
272-
const sqsEvent = createSqsEvent(1);
273-
const handler = createHandler(dependencies);
274-
const { messageId } = sqsEvent.Records[0];
275-
const error = new Error('Unexpected error');
276-
277-
mockParseSqsRecord.mockReturnValueOnce(mockPdmEvent);
278-
mockSenderManagement.getSender.mockReturnValueOnce(mockSender);
279-
mockNotifyMessageProcessor.process.mockRejectedValueOnce(error);
280-
281-
const result = await handler(sqsEvent);
282-
283-
expect(result).toEqual({
284-
batchItemFailures: [{ itemIdentifier: messageId }],
285-
});
286-
expect(mockLogger.warn).toHaveBeenCalledWith({
287-
error: error.message,
288-
description: 'Failed processing message',
289-
messageId,
290-
});
291-
expect(
292-
mockEventPublisherFacade.publishMessageRequestRejected,
293-
).not.toHaveBeenCalled();
294-
});
295-
});
296-
297-
describe('when processing mixed success and failure records', () => {
298-
it('returns only failed message IDs', async () => {
299-
const sqsEvent = createSqsEvent(3);
300-
const handler = createHandler(dependencies);
301-
302-
mockParseSqsRecord
303-
.mockReturnValueOnce(mockPdmEvent)
304-
.mockImplementationOnce(() => {
305-
throw new Error('Parse error');
306-
})
307-
.mockReturnValueOnce(mockPdmEvent);
308-
309-
mockSenderManagement.getSender.mockReturnValue(mockSender);
310-
mockNotifyMessageProcessor.process.mockResolvedValue(notifyId);
311-
312-
const result = await handler(sqsEvent);
313-
314-
expect(result).toEqual({
315-
batchItemFailures: [{ itemIdentifier: 'message-id-2' }],
316-
});
317-
expect(mockLogger.info).toHaveBeenCalledWith(
318-
'2 of 3 records processed successfully',
319-
);
320-
});
321-
});
322-
323-
describe('when notifyMessageProcessor returns undefined', () => {
324-
it('does not publish submitted event', async () => {
325-
const sqsEvent = createSqsEvent(1);
326-
const handler = createHandler(dependencies);
327-
328-
mockParseSqsRecord.mockReturnValueOnce(mockPdmEvent);
329-
mockSenderManagement.getSender.mockReturnValueOnce(mockSender);
330-
mockNotifyMessageProcessor.process.mockResolvedValueOnce();
331-
332-
const result = await handler(sqsEvent);
333-
334-
expect(result).toEqual({ batchItemFailures: [] });
335-
expect(
336-
mockEventPublisherFacade.publishMessageRequestSubmitted,
337-
).not.toHaveBeenCalled();
338-
});
339-
});
340257
});

lambdas/core-notifier-lambda/src/__tests__/app/notify-message-processor.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,17 @@ describe('NotifyMessageProcessor', () => {
6262
});
6363
});
6464

65-
it('does not re-throw when a RequestAlreadyReceivedError is thrown by the API client', async () => {
65+
it('re-throw when a RequestAlreadyReceivedError is thrown by the API client', async () => {
6666
const { messageReference } = mockRequest1.data.attributes;
6767
const err = new RequestAlreadyReceivedError(
6868
new Error('Request was already received!'),
6969
messageReference,
7070
);
7171
mockClient.sendRequest.mockRejectedValue(err);
7272

73-
expect(await notifyMessageProcessor.process(mockRequest1)).toBeUndefined();
73+
await expect( notifyMessageProcessor.process(mockRequest1)).rejects.toThrow(
74+
err,
75+
);
7476

7577
expect(mockLogger.info).toHaveBeenCalledWith(
7678
'Request has already been received by Notify',

lambdas/core-notifier-lambda/src/__tests__/app/parse-sqs-message.test.ts

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { mock } from 'jest-mock-extended';
33
import { Logger } from 'utils';
44
import { parseSqsRecord } from 'app/parse-sqs-message';
55
import { InvalidPdmResourceAvailableEvent } from 'domain/invalid-pdm-resource-available-event';
6-
import { PDMResourceAvailable } from 'digital-letters-events';
6+
import { validPdmEvent } from '__tests__/constants';
77

88
// Import the mocked validator after the mock setup
99
import { messageDownloadedValidator } from 'digital-letters-events/PDMResourceAvailable.js';
@@ -16,21 +16,7 @@ const mockLogger = mock<Logger>();
1616

1717
describe('parseSqsRecord', () => {
1818
const messageId = 'test-message-id-123';
19-
const validPdmEvent: PDMResourceAvailable = {
20-
id: 'event-id-123',
21-
source: 'urn:nhs:names:services:notify:pdm',
22-
specversion: '1.0',
23-
type: 'uk.nhs.notify.digital.letters.pdm.resource.available.v1',
24-
time: '2025-12-15T10:00:00Z',
25-
datacontenttype: 'application/json',
26-
data: {
27-
senderId: 'sender-123',
28-
messageReference: 'ref-123',
29-
resourceId: 'ResourceId-123',
30-
nhsNumber: '9991234566',
31-
odsCode: 'A12345',
32-
},
33-
};
19+
3420

3521
const createSqsRecord = (detail: any): SQSRecord => ({
3622
messageId,

lambdas/core-notifier-lambda/src/__tests__/constants.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import type {
22
SingleMessageRequest,
33
SingleMessageResponse,
44
} from 'domain/request';
5+
import { Sender } from 'utils';
6+
7+
import { PDMResourceAvailable } from 'digital-letters-events';
58

69
export const mockRoutingPlanId = 'routing-plan-id';
710

@@ -49,3 +52,32 @@ export const mockResponse: SingleMessageResponse = {
4952
},
5053
},
5154
};
55+
56+
export const validPdmEvent: PDMResourceAvailable = {
57+
id: 'event-id-123',
58+
source: 'urn:nhs:names:services:notify:pdm',
59+
specversion: '1.0',
60+
type: 'uk.nhs.notify.digital.letters.pdm.resource.available.v1',
61+
time: '2025-12-15T10:00:00Z',
62+
datacontenttype: 'application/json',
63+
subject: 'message-subject-123',
64+
traceparent: '00-trace-parent',
65+
recordedtime: '2025-12-15T10:00:00Z',
66+
severitynumber: 2,
67+
data: {
68+
senderId : 'sender-123',
69+
messageReference: 'msg-ref-123',
70+
resourceId: 'ResourceId-123',
71+
nhsNumber: '9991234566',
72+
odsCode: 'A12345',
73+
},
74+
};
75+
76+
export const validSender: Sender = {
77+
senderId: 'sender-123',
78+
routingConfigId: 'routing-config-123',
79+
senderName: 'Test Sender',
80+
meshMailboxSenderId: 'meshMailBoxSender-123',
81+
meshMailboxReportsId: 'meshMailBoxReports-123',
82+
fallbackWaitTimeSeconds: 100,
83+
};

0 commit comments

Comments
 (0)