Skip to content

Commit 9e61efa

Browse files
separate allocate and update queues
1 parent e8a1aa5 commit 9e61efa

8 files changed

Lines changed: 67 additions & 290 deletions

File tree

infrastructure/terraform/components/api/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ No requirements.
4242

4343
| Name | Source | Version |
4444
|------|--------|---------|
45+
| <a name="module_allocate_letter"></a> [allocate\_letter](#module\_allocate\_letter) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a |
4546
| <a name="module_amendment_event_transformer"></a> [amendment\_event\_transformer](#module\_amendment\_event\_transformer) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a |
4647
| <a name="module_amendments_queue"></a> [amendments\_queue](#module\_amendments\_queue) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.24/terraform-sqs.zip | n/a |
47-
| <a name="module_allocate_letter"></a> [allocate\_letter](#module\_allocate\_letter) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a |
4848
| <a name="module_authorizer_lambda"></a> [authorizer\_lambda](#module\_authorizer\_lambda) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a |
4949
| <a name="module_domain_truststore"></a> [domain\_truststore](#module\_domain\_truststore) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.26/terraform-s3bucket.zip | n/a |
5050
| <a name="module_eventpub"></a> [eventpub](#module\_eventpub) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.26/terraform-eventpub.zip | n/a |

infrastructure/terraform/components/api/module_lambda_supplier_allocator.tf

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ module "allocate_letter" {
3535
log_subscription_role_arn = local.acct.log_subscription_role_arn
3636

3737
lambda_env_vars = merge(local.common_lambda_env_vars, {
38-
VARIANT_MAP = jsonencode(var.letter_variant_map)
39-
ALLOCATED_LETTERS_QUEUE_URL = module.sqs_allocated_letters.sqs_queue_url
38+
VARIANT_MAP = jsonencode(var.letter_variant_map)
39+
UPSERT_LETTERS_QUEUE_URL = module.sqs_letter_updates.sqs_queue_url
4040
})
4141
}
4242

infrastructure/terraform/components/api/sns_topic_subscription_eventsub_sqs_letter_updates.tf

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
resource "aws_sns_topic_subscription" "eventsub_sqs_letter_updates" {
2-
topic_arn = module.eventsub.sns_topic.arn
3-
protocol = "sqs"
4-
endpoint = module.sqs_letter_updates.sqs_queue_arn
2+
topic_arn = module.eventsub.sns_topic.arn
3+
protocol = "sqs"
4+
endpoint = module.sqs_letter_updates.sqs_queue_arn
5+
raw_message_delivery = true
56

67
filter_policy_scope = "MessageBody"
78
filter_policy = jsonencode({

infrastructure/terraform/components/api/sns_topic_subscription_eventsub_sqs_supplier_allocator.tf

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
resource "aws_sns_topic_subscription" "eventsub_sqs_supplier_allocator" {
22
# The supplier allocator queue will be introduced by another ticket. For now, route events directly to the letter updates queue.
3-
topic_arn = module.eventsub.sns_topic.arn
4-
protocol = "sqs"
5-
endpoint = module.sqs_letter_updates.sqs_queue_arn
3+
topic_arn = module.eventsub.sns_topic.arn
4+
protocol = "sqs"
5+
endpoint = module.sqs_supplier_allocator.sqs_queue_arn
6+
raw_message_delivery = true
67

78
filter_policy_scope = "MessageBody"
89
filter_policy = jsonencode({

lambdas/supplier-allocator/src/handler/__tests__/allocate-handler.test.ts

Lines changed: 28 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { SNSMessage, SQSEvent, SQSRecord } from "aws-lambda";
1+
import { SQSEvent, SQSRecord } from "aws-lambda";
22
import pino from "pino";
33
import { SQSClient, SendMessageCommand } from "@aws-sdk/client-sqs";
44
import { LetterRequestPreparedEventV2 } from "@nhsdigital/nhs-notify-event-schemas-letter-rendering";
@@ -36,11 +36,6 @@ function createSqsRecord(msgId: string, body: string): SQSRecord {
3636
};
3737
}
3838

39-
type SupportedEvent =
40-
| LetterRequestPreparedEventV2
41-
| LetterRequestPreparedEvent
42-
| LetterEvent;
43-
4439
function createPreparedV1Event(
4540
overrides: Partial<any> = {},
4641
): LetterRequestPreparedEvent {
@@ -134,58 +129,6 @@ function createSupplierStatusChangeEvent(
134129
});
135130
}
136131

137-
function createEventBridgeNotification(
138-
event: LetterRequestPreparedEventV2 | LetterRequestPreparedEvent,
139-
): Partial<SNSMessage> {
140-
return {
141-
SignatureVersion: "",
142-
Timestamp: "",
143-
Signature: "",
144-
SigningCertUrl: "",
145-
MessageId: "",
146-
Message: createEventBridgeEvent(event),
147-
MessageAttributes: {},
148-
Type: "Notification",
149-
UnsubscribeUrl: "",
150-
TopicArn: "",
151-
Subject: "",
152-
Token: "",
153-
};
154-
}
155-
156-
function createNotification(event: SupportedEvent): Partial<SNSMessage> {
157-
return {
158-
SignatureVersion: "",
159-
Timestamp: "",
160-
Signature: "",
161-
SigningCertUrl: "",
162-
MessageId: "",
163-
Message: JSON.stringify(event),
164-
MessageAttributes: {},
165-
Type: "Notification",
166-
UnsubscribeUrl: "",
167-
TopicArn: "",
168-
Subject: "",
169-
Token: "",
170-
};
171-
}
172-
173-
function createEventBridgeEvent(event: SupportedEvent): string {
174-
const now = new Date().toISOString();
175-
const eventBridgeEnvelope = {
176-
version: "0",
177-
id: "4f28e649-6832-18e8-7261-4b63e6dcd3b5",
178-
"detail-type": event.type,
179-
source: "custom.event",
180-
account: "815490582396",
181-
time: now,
182-
region: "eu-west-2",
183-
resources: [],
184-
detail: event,
185-
};
186-
return JSON.stringify(eventBridgeEnvelope);
187-
}
188-
189132
describe("createSupplierAllocatorHandler", () => {
190133
let mockSqsClient: jest.Mocked<SQSClient>;
191134
let mockedDeps: jest.Mocked<Deps>;
@@ -215,13 +158,11 @@ describe("createSupplierAllocatorHandler", () => {
215158

216159
test("parses SNS notification and sends message to SQS queue for v2 event", async () => {
217160
const preparedEvent = createPreparedV2Event();
218-
const snsNotification = createEventBridgeNotification(preparedEvent);
219-
220161
const evt: SQSEvent = createSQSEvent([
221-
createSqsRecord("msg1", JSON.stringify(snsNotification)),
162+
createSqsRecord("msg1", JSON.stringify(preparedEvent)),
222163
]);
223164

224-
process.env.ALLOCATED_LETTERS_QUEUE_URL = "https://sqs.test.queue";
165+
process.env.UPSERT_LETTERS_QUEUE_URL = "https://sqs.test.queue";
225166

226167
const handler = createSupplierAllocatorHandler(mockedDeps);
227168
const result = await handler(evt, {} as any, {} as any);
@@ -245,13 +186,12 @@ describe("createSupplierAllocatorHandler", () => {
245186

246187
test("parses SNS notification and sends message to SQS queue for v1 event", async () => {
247188
const preparedEvent = createPreparedV1Event();
248-
const snsNotification = createEventBridgeNotification(preparedEvent);
249189

250190
const evt: SQSEvent = createSQSEvent([
251-
createSqsRecord("msg1", JSON.stringify(snsNotification)),
191+
createSqsRecord("msg1", JSON.stringify(preparedEvent)),
252192
]);
253193

254-
process.env.ALLOCATED_LETTERS_QUEUE_URL = "https://sqs.test.queue";
194+
process.env.UPSERT_LETTERS_QUEUE_URL = "https://sqs.test.queue";
255195

256196
const handler = createSupplierAllocatorHandler(mockedDeps);
257197
const result = await handler(evt, {} as any, {} as any);
@@ -272,13 +212,12 @@ describe("createSupplierAllocatorHandler", () => {
272212

273213
test("returns batch failure for Update event", async () => {
274214
const preparedEvent = createSupplierStatusChangeEvent();
275-
const snsNotification = createNotification(preparedEvent);
276215

277216
const evt: SQSEvent = createSQSEvent([
278-
createSqsRecord("invalid-event", JSON.stringify(snsNotification)),
217+
createSqsRecord("invalid-event", JSON.stringify(preparedEvent)),
279218
]);
280219

281-
process.env.ALLOCATED_LETTERS_QUEUE_URL = "https://sqs.test.queue";
220+
process.env.UPSERT_LETTERS_QUEUE_URL = "https://sqs.test.queue";
282221

283222
const handler = createSupplierAllocatorHandler(mockedDeps);
284223
const result = await handler(evt, {} as any, {} as any);
@@ -293,13 +232,12 @@ describe("createSupplierAllocatorHandler", () => {
293232

294233
test("unwraps EventBridge envelope and extracts event details", async () => {
295234
const preparedEvent = createPreparedV2Event({ domainId: "letter-test" });
296-
const snsNotification = createEventBridgeNotification(preparedEvent);
297235

298236
const evt: SQSEvent = createSQSEvent([
299-
createSqsRecord("msg1", JSON.stringify(snsNotification)),
237+
createSqsRecord("msg1", JSON.stringify(preparedEvent)),
300238
]);
301239

302-
process.env.ALLOCATED_LETTERS_QUEUE_URL = "https://sqs.test.queue";
240+
process.env.UPSERT_LETTERS_QUEUE_URL = "https://sqs.test.queue";
303241

304242
const handler = createSupplierAllocatorHandler(mockedDeps);
305243
await handler(evt, {} as any, {} as any);
@@ -311,13 +249,12 @@ describe("createSupplierAllocatorHandler", () => {
311249

312250
test("resolves correct supplier spec from variant map", async () => {
313251
const preparedEvent = createPreparedV2Event();
314-
const snsNotification = createEventBridgeNotification(preparedEvent);
315252

316253
const evt: SQSEvent = createSQSEvent([
317-
createSqsRecord("msg1", JSON.stringify(snsNotification)),
254+
createSqsRecord("msg1", JSON.stringify(preparedEvent)),
318255
]);
319256

320-
process.env.ALLOCATED_LETTERS_QUEUE_URL = "https://sqs.test.queue";
257+
process.env.UPSERT_LETTERS_QUEUE_URL = "https://sqs.test.queue";
321258

322259
const handler = createSupplierAllocatorHandler(mockedDeps);
323260
await handler(evt, {} as any, {} as any);
@@ -332,23 +269,15 @@ describe("createSupplierAllocatorHandler", () => {
332269
const evt: SQSEvent = createSQSEvent([
333270
createSqsRecord(
334271
"msg1",
335-
JSON.stringify(
336-
createEventBridgeNotification(
337-
createPreparedV2Event({ domainId: "letter1" }),
338-
),
339-
),
272+
JSON.stringify(createPreparedV2Event({ domainId: "letter1" })),
340273
),
341274
createSqsRecord(
342275
"msg2",
343-
JSON.stringify(
344-
createEventBridgeNotification(
345-
createPreparedV2Event({ domainId: "letter2" }),
346-
),
347-
),
276+
JSON.stringify(createPreparedV2Event({ domainId: "letter2" })),
348277
),
349278
]);
350279

351-
process.env.ALLOCATED_LETTERS_QUEUE_URL = "https://sqs.test.queue";
280+
process.env.UPSERT_LETTERS_QUEUE_URL = "https://sqs.test.queue";
352281

353282
const handler = createSupplierAllocatorHandler(mockedDeps);
354283
const result = await handler(evt, {} as any, {} as any);
@@ -365,7 +294,7 @@ describe("createSupplierAllocatorHandler", () => {
365294
createSqsRecord("bad-json", "this-is-not-json"),
366295
]);
367296

368-
process.env.ALLOCATED_LETTERS_QUEUE_URL = "https://sqs.test.queue";
297+
process.env.UPSERT_LETTERS_QUEUE_URL = "https://sqs.test.queue";
369298

370299
const handler = createSupplierAllocatorHandler(mockedDeps);
371300
const result = await handler(evt, {} as any, {} as any);
@@ -378,59 +307,14 @@ describe("createSupplierAllocatorHandler", () => {
378307
expect((mockedDeps.logger.error as jest.Mock).mock.calls).toHaveLength(1);
379308
});
380309

381-
test("returns batch failure for invalid SNS notification schema", async () => {
382-
const evt: SQSEvent = createSQSEvent([
383-
createSqsRecord(
384-
"bad-notification",
385-
JSON.stringify({ not: "a valid notification" }),
386-
),
387-
]);
388-
389-
process.env.ALLOCATED_LETTERS_QUEUE_URL = "https://sqs.test.queue";
390-
391-
const handler = createSupplierAllocatorHandler(mockedDeps);
392-
const result = await handler(evt, {} as any, {} as any);
393-
394-
expect(result).toBeDefined();
395-
if (!result) throw new Error("expected BatchResponse, got void");
396-
397-
expect(result.batchItemFailures).toHaveLength(1);
398-
expect(result.batchItemFailures[0].itemIdentifier).toBe("bad-notification");
399-
});
400-
401-
test("returns batch failure when SNS Message is missing", async () => {
402-
const snsMessage: Partial<SNSMessage> = {
403-
Type: "Notification",
404-
MessageId: "test-id",
405-
};
406-
407-
const evt: SQSEvent = createSQSEvent([
408-
createSqsRecord("no-message", JSON.stringify(snsMessage)),
409-
]);
410-
411-
process.env.ALLOCATED_LETTERS_QUEUE_URL = "https://sqs.test.queue";
412-
413-
const handler = createSupplierAllocatorHandler(mockedDeps);
414-
const result = await handler(evt, {} as any, {} as any);
415-
if (!result) throw new Error("expected BatchResponse, got void");
416-
417-
expect(result.batchItemFailures).toHaveLength(1);
418-
expect(result.batchItemFailures[0].itemIdentifier).toBe("no-message");
419-
});
420-
421310
test("returns batch failure when event type is missing", async () => {
422311
const event = { no: "type" };
423-
const snsNotification: Partial<SNSMessage> = {
424-
Type: "Notification",
425-
Message: JSON.stringify(event),
426-
MessageId: "test-id",
427-
};
428312

429313
const evt: SQSEvent = createSQSEvent([
430-
createSqsRecord("no-type", JSON.stringify(snsNotification)),
314+
createSqsRecord("no-type", JSON.stringify(event)),
431315
]);
432316

433-
process.env.ALLOCATED_LETTERS_QUEUE_URL = "https://sqs.test.queue";
317+
process.env.UPSERT_LETTERS_QUEUE_URL = "https://sqs.test.queue";
434318

435319
const handler = createSupplierAllocatorHandler(mockedDeps);
436320
const result = await handler(evt, {} as any, {} as any);
@@ -440,15 +324,14 @@ describe("createSupplierAllocatorHandler", () => {
440324
expect(result.batchItemFailures[0].itemIdentifier).toBe("no-type");
441325
});
442326

443-
test("returns batch failure when ALLOCATED_LETTERS_QUEUE_URL is not set", async () => {
327+
test("returns batch failure when UPSERT_LETTERS_QUEUE_URL is not set", async () => {
444328
const preparedEvent = createPreparedV2Event();
445-
const snsNotification = createEventBridgeNotification(preparedEvent);
446329

447330
const evt: SQSEvent = createSQSEvent([
448-
createSqsRecord("msg1", JSON.stringify(snsNotification)),
331+
createSqsRecord("msg1", JSON.stringify(preparedEvent)),
449332
]);
450333

451-
delete process.env.ALLOCATED_LETTERS_QUEUE_URL;
334+
delete process.env.UPSERT_LETTERS_QUEUE_URL;
452335

453336
const handler = createSupplierAllocatorHandler(mockedDeps);
454337
const result = await handler(evt, {} as any, {} as any);
@@ -458,20 +341,19 @@ describe("createSupplierAllocatorHandler", () => {
458341
expect(result.batchItemFailures[0].itemIdentifier).toBe("msg1");
459342
expect((mockedDeps.logger.error as jest.Mock).mock.calls[0][0].err).toEqual(
460343
expect.objectContaining({
461-
message: "ALLOCATED_LETTERS_QUEUE_URL not configured",
344+
message: "UPSERT_LETTERS_QUEUE_URL not configured",
462345
}),
463346
);
464347
});
465348

466349
test("handles SQS send errors and returns batch failure", async () => {
467350
const preparedEvent = createPreparedV2Event();
468-
const snsNotification = createEventBridgeNotification(preparedEvent);
469351

470352
const evt: SQSEvent = createSQSEvent([
471-
createSqsRecord("msg1", JSON.stringify(snsNotification)),
353+
createSqsRecord("msg1", JSON.stringify(preparedEvent)),
472354
]);
473355

474-
process.env.ALLOCATED_LETTERS_QUEUE_URL = "https://sqs.test.queue";
356+
process.env.UPSERT_LETTERS_QUEUE_URL = "https://sqs.test.queue";
475357

476358
const sqsError = new Error("SQS send failed");
477359
(mockSqsClient.send as jest.Mock).mockRejectedValueOnce(sqsError);
@@ -489,24 +371,16 @@ describe("createSupplierAllocatorHandler", () => {
489371
const evt: SQSEvent = createSQSEvent([
490372
createSqsRecord(
491373
"ok-msg",
492-
JSON.stringify(
493-
createEventBridgeNotification(
494-
createPreparedV2Event({ domainId: "letter1" }),
495-
),
496-
),
374+
JSON.stringify(createPreparedV2Event({ domainId: "letter1" })),
497375
),
498376
createSqsRecord("fail-msg", "invalid-json"),
499377
createSqsRecord(
500378
"ok-msg-2",
501-
JSON.stringify(
502-
createEventBridgeNotification(
503-
createPreparedV2Event({ domainId: "letter2" }),
504-
),
505-
),
379+
JSON.stringify(createPreparedV2Event({ domainId: "letter2" })),
506380
),
507381
]);
508382

509-
process.env.ALLOCATED_LETTERS_QUEUE_URL = "https://sqs.test.queue";
383+
process.env.UPSERT_LETTERS_QUEUE_URL = "https://sqs.test.queue";
510384

511385
const handler = createSupplierAllocatorHandler(mockedDeps);
512386
const result = await handler(evt, {} as any, {} as any);
@@ -520,14 +394,13 @@ describe("createSupplierAllocatorHandler", () => {
520394

521395
test("sends correct queue URL in SQS message command", async () => {
522396
const preparedEvent = createPreparedV2Event();
523-
const snsNotification = createEventBridgeNotification(preparedEvent);
524397

525398
const evt: SQSEvent = createSQSEvent([
526-
createSqsRecord("msg1", JSON.stringify(snsNotification)),
399+
createSqsRecord("msg1", JSON.stringify(preparedEvent)),
527400
]);
528401

529402
const queueUrl = "https://sqs.eu-west-2.amazonaws.com/123456789/test-queue";
530-
process.env.ALLOCATED_LETTERS_QUEUE_URL = queueUrl;
403+
process.env.UPSERT_LETTERS_QUEUE_URL = queueUrl;
531404

532405
const handler = createSupplierAllocatorHandler(mockedDeps);
533406
await handler(evt, {} as any, {} as any);

0 commit comments

Comments
 (0)