Skip to content

Commit 4679a07

Browse files
CCM-15256: PDF Component
1 parent 4ef4213 commit 4679a07

2 files changed

Lines changed: 150 additions & 7 deletions

File tree

lambdas/print-analyser/src/__tests__/apis/sqs-handler.test.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,15 @@ describe('SQS Handler', () => {
4242
const testPdf = fivePagePdf();
4343
mockGetS3ObjectBufferFromUri.mockResolvedValue(testPdf);
4444

45+
eventPublisher.sendEvents.mockImplementation(
46+
async (events, validateFn) => {
47+
for (const event of events) {
48+
validateFn(event, logger);
49+
}
50+
return [];
51+
},
52+
);
53+
4554
const response = await handler(recordEvent([fileSafeEvent]));
4655

4756
expect(mockGetS3ObjectBufferFromUri).toHaveBeenCalledWith(
@@ -206,5 +215,79 @@ describe('SQS Handler', () => {
206215
batchItemFailures: [{ itemIdentifier: '1' }],
207216
});
208217
});
218+
219+
it('should publish InvalidAttachmentReceived event when PDF parsing fails (non-PDF attachment)', async () => {
220+
const testPdfBuffer = Buffer.from('not a valid PDF file');
221+
mockGetS3ObjectBufferFromUri.mockResolvedValue(testPdfBuffer);
222+
223+
eventPublisher.sendEvents.mockImplementation(
224+
async (events, validateFn) => {
225+
for (const event of events) {
226+
validateFn(event, logger);
227+
}
228+
return [];
229+
},
230+
);
231+
232+
const event = recordEvent([fileSafeEvent]);
233+
234+
const result = await handler(event);
235+
236+
expect(eventPublisher.sendEvents).toHaveBeenCalledWith(
237+
[
238+
{
239+
...fileSafeEvent,
240+
id: '550e8400-e29b-41d4-a716-446655440001',
241+
time: '2023-06-20T12:00:00.250Z',
242+
recordedtime: '2023-06-20T12:00:00.250Z',
243+
dataschema:
244+
'https://notify.nhs.uk/cloudevents/schemas/digital-letters/2025-10-draft/data/digital-letters-print-invalid-attachment-received-data.schema.json',
245+
type: 'uk.nhs.notify.digital.letters.print.invalid.attachment.received.v1',
246+
source:
247+
'/nhs/england/notify/production/primary/digitalletters/print',
248+
data: {
249+
senderId: fileSafeEvent.data.senderId,
250+
messageReference: fileSafeEvent.data.messageReference,
251+
reasonCode: 'DL_CLIV_002',
252+
},
253+
},
254+
],
255+
expect.any(Function),
256+
);
257+
258+
expect(logger.warn).toHaveBeenCalledWith({
259+
messageReference: fileSafeEvent.data.messageReference,
260+
reasonCode: 'DL_CLIV_002',
261+
description: 'Failed to analyze PDF - invalid attachment format',
262+
});
263+
264+
expect(logger.info).toHaveBeenCalledWith(
265+
'1 of 1 records processed successfully',
266+
);
267+
268+
expect(result).toEqual({ batchItemFailures: [] });
269+
});
270+
271+
it('should throw error for unknown event type during validation', async () => {
272+
const testPdf = fivePagePdf();
273+
mockGetS3ObjectBufferFromUri.mockResolvedValue(testPdf);
274+
275+
eventPublisher.sendEvents.mockImplementation(
276+
async (events, validateFn) => {
277+
const modifiedEvent = {
278+
...events[0],
279+
type: 'uk.nhs.notify.unknown.event.type',
280+
};
281+
validateFn(modifiedEvent, logger);
282+
return [];
283+
},
284+
);
285+
286+
const event = recordEvent([fileSafeEvent]);
287+
288+
await expect(handler(event)).rejects.toThrow(
289+
'Unknown event type: uk.nhs.notify.unknown.event.type',
290+
);
291+
});
209292
});
210293
});

lambdas/print-analyser/src/apis/sqs-handler.ts

Lines changed: 67 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ import { createHash, randomUUID } from 'node:crypto';
77
import { PDFDocument } from 'pdf-lib';
88
import {
99
FileSafe,
10+
InvalidAttachmentReceived,
1011
PDFAnalysed,
1112
validateFileSafe,
13+
validateInvalidAttachmentReceived,
1214
validatePDFAnalysed,
1315
} from 'digital-letters-events';
1416
import { EventPublisher, Logger, getS3ObjectBufferFromUri } from 'utils';
@@ -81,6 +83,33 @@ function generateUpdatedEvent(event: FileSafe, pdfInfo: PdfInfo): PDFAnalysed {
8183
};
8284
}
8385

86+
function generateInvalidAttachmentEvent(
87+
event: FileSafe,
88+
): InvalidAttachmentReceived {
89+
const eventTime = new Date().toISOString();
90+
91+
const {
92+
data: { messageReference, senderId },
93+
} = event;
94+
95+
return {
96+
...event,
97+
id: randomUUID(),
98+
time: eventTime,
99+
recordedtime: eventTime,
100+
dataschema:
101+
'https://notify.nhs.uk/cloudevents/schemas/digital-letters/2025-10-draft/data/digital-letters-print-invalid-attachment-received-data.schema.json',
102+
type: 'uk.nhs.notify.digital.letters.print.invalid.attachment.received.v1',
103+
// NOTE: CCM-13892 Generate event digital letters source property from scratch
104+
source: '/nhs/england/notify/production/primary/digitalletters/print',
105+
data: {
106+
senderId,
107+
messageReference,
108+
reasonCode: 'DL_CLIV_002',
109+
},
110+
};
111+
}
112+
84113
async function analysePdf(pdf: Buffer): Promise<PdfInfo> {
85114
const doc = await PDFDocument.load(pdf);
86115
const pageCount = doc.getPageCount();
@@ -97,7 +126,7 @@ export const createHandler = ({
97126
const receivedItemCount = sqsEvent.Records.length;
98127
const batchItemFailures: SQSBatchItemFailure[] = [];
99128
const validatedRecords: ValidatedRecord[] = [];
100-
const validEvents: PDFAnalysed[] = [];
129+
const validEvents: (PDFAnalysed | InvalidAttachmentReceived)[] = [];
101130

102131
logger.info(`Received SQS Event of ${receivedItemCount} record(s)`);
103132

@@ -120,16 +149,47 @@ export const createHandler = ({
120149
const pdfInfo = await analysePdf(pdfBuffer);
121150
validEvents.push(generateUpdatedEvent(event, pdfInfo));
122151
} catch (error: any) {
123-
logger.warn({
124-
err: error.message,
125-
description: 'Failed processing message',
126-
});
127-
batchItemFailures.push({ itemIdentifier: validatedRecord.messageId });
152+
const isPdfParsingError =
153+
error.message?.includes('PDF') ||
154+
error.message?.includes('parse') ||
155+
error.message?.includes('Invalid') ||
156+
error.name === 'PDFParsingError';
157+
158+
if (isPdfParsingError) {
159+
const invalidAttachmentEvent = generateInvalidAttachmentEvent(
160+
validatedRecord.event,
161+
);
162+
validEvents.push(invalidAttachmentEvent);
163+
logger.warn({
164+
messageReference: validatedRecord.event.data.messageReference,
165+
reasonCode: 'DL_CLIV_002',
166+
description: 'Failed to analyze PDF - invalid attachment format',
167+
});
168+
} else {
169+
logger.warn({
170+
err: error.message,
171+
description: 'Failed processing message',
172+
});
173+
batchItemFailures.push({
174+
itemIdentifier: validatedRecord.messageId,
175+
});
176+
}
128177
}
129178
}),
130179
);
131180

132-
await eventPublisher.sendEvents(validEvents, validatePDFAnalysed);
181+
await eventPublisher.sendEvents(validEvents, (event) => {
182+
if (event.type.includes('pdf.analysed')) {
183+
return validatePDFAnalysed(event as PDFAnalysed, logger);
184+
}
185+
if (event.type.includes('invalid.attachment.received')) {
186+
return validateInvalidAttachmentReceived(
187+
event as InvalidAttachmentReceived,
188+
logger,
189+
);
190+
}
191+
throw new Error(`Unknown event type: ${event.type}`);
192+
});
133193

134194
const processedItemCount = receivedItemCount - batchItemFailures.length;
135195
logger.info(

0 commit comments

Comments
 (0)