Skip to content

Commit 537a303

Browse files
committed
component test sqs draft
1 parent 453b908 commit 537a303

6 files changed

Lines changed: 167 additions & 70 deletions

File tree

infrastructure/terraform/components/dl/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ No requirements.
103103
| <a name="module_sqs_report_generator"></a> [sqs\_report\_generator](#module\_sqs\_report\_generator) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-sqs.zip | n/a |
104104
| <a name="module_sqs_report_sender"></a> [sqs\_report\_sender](#module\_sqs\_report\_sender) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-sqs.zip | n/a |
105105
| <a name="module_sqs_scanner"></a> [sqs\_scanner](#module\_sqs\_scanner) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-sqs.zip | n/a |
106+
| <a name="module_sqs_test_observer"></a> [sqs\_test\_observer](#module\_sqs\_test\_observer) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-sqs.zip | n/a |
106107
| <a name="module_sqs_ttl"></a> [sqs\_ttl](#module\_sqs\_ttl) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-sqs.zip | n/a |
107108
| <a name="module_sqs_ttl_handle_expiry_errors"></a> [sqs\_ttl\_handle\_expiry\_errors](#module\_sqs\_ttl\_handle\_expiry\_errors) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-sqs.zip | n/a |
108109
| <a name="module_ttl_create"></a> [ttl\_create](#module\_ttl\_create) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-lambda.zip | n/a |

infrastructure/terraform/components/dl/cloudwatch_event_rule_all_events.tf

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,10 @@ resource "aws_cloudwatch_event_target" "reporting_firehose" {
1818
role_arn = aws_iam_role.eventbridge_firehose.arn
1919
event_bus_name = aws_cloudwatch_event_bus.main.name
2020
}
21+
22+
resource "aws_cloudwatch_event_target" "test_observer_sqs" {
23+
rule = aws_cloudwatch_event_rule.all_events.name
24+
target_id = "test-observer-sqs-target"
25+
arn = module.sqs_test_observer.sqs_queue_arn
26+
event_bus_name = aws_cloudwatch_event_bus.main.name
27+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
module "sqs_test_observer" {
2+
source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-sqs.zip"
3+
4+
aws_account_id = var.aws_account_id
5+
component = local.component
6+
environment = var.environment
7+
project = var.project
8+
region = var.region
9+
name = "test-observer"
10+
sqs_kms_key_arn = module.kms.key_arn
11+
visibility_timeout_seconds = var.sqs_visibility_timeout_seconds
12+
create_dlq = false
13+
max_receive_count = var.sqs_max_receive_count
14+
sqs_policy_overload = data.aws_iam_policy_document.sqs_test_observer.json
15+
}
16+
17+
data "aws_iam_policy_document" "sqs_test_observer" {
18+
statement {
19+
sid = "AllowEventBridgeToSendMessage"
20+
effect = "Allow"
21+
22+
principals {
23+
type = "Service"
24+
identifiers = ["events.amazonaws.com"]
25+
}
26+
27+
actions = [
28+
"sqs:SendMessage"
29+
]
30+
31+
resources = [
32+
"arn:aws:sqs:${var.region}:${var.aws_account_id}:${local.csi}-test-observer-queue"
33+
]
34+
35+
condition {
36+
test = "ArnLike"
37+
variable = "aws:SourceArn"
38+
values = [aws_cloudwatch_event_rule.all_events.arn]
39+
}
40+
}
41+
}

tests/playwright/constants/backend-constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export const PRINT_SENDER_DLQ_NAME = `${CSI}-print-sender-dlq`;
3232
export const MOVE_SCANNED_FILES_NAME = `${CSI}-move-scanned-files-queue`;
3333
export const MOVE_SCANNED_FILES_DLQ_NAME = `${CSI}-move-scanned-files-dlq`;
3434
export const REPORT_SENDER_DLQ_NAME = `${CSI}-report-sender-dlq`;
35+
export const TEST_OBSERVER_QUEUE_NAME = `${CSI}-test-observer-queue`;
3536

3637
// Queue Url Prefix
3738
export const SQS_URL_PREFIX = `https://sqs.${REGION}.amazonaws.com/${AWS_ACCOUNT_ID}/`;

tests/playwright/digital-letters-component-tests/mesh-poll-download.component.spec.ts

Lines changed: 61 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
import { expect, test } from '@playwright/test';
22
import {
3-
ENV,
43
MESH_DOWNLOAD_DLQ_NAME,
54
MESH_DOWNLOAD_LAMBDA_LOG_GROUP_NAME,
65
MESH_POLL_LAMBDA_NAME,
76
NON_PII_S3_BUCKET_NAME,
87
PII_S3_BUCKET_NAME,
8+
EVENT_BUS_LOG_GROUP_NAME,
99
} from 'constants/backend-constants';
1010
import { getLogsFromCloudwatch } from 'helpers/cloudwatch-helpers';
1111
import eventPublisher from 'helpers/event-bus-helpers';
1212
import expectToPassEventually from 'helpers/expectations';
1313
import { invokeLambda } from 'helpers/lambda-helpers';
1414
import { downloadFromS3, uploadToS3 } from 'helpers/s3-helpers';
15-
import { expectMessageContainingString } from 'helpers/sqs-helpers';
15+
import { expectMessageContainingString, purgeQueue } from 'helpers/sqs-helpers';
16+
import { expectEventOnTestObserverQueue } from 'helpers/test-observer-helpers';
1617
import { v4 as uuidv4 } from 'uuid';
1718
import { SENDER_ID_SKIPS_NOTIFY } from 'constants/tests-constants';
1819
import { validateMESHInboxMessageReceived } from 'digital-letters-events';
@@ -72,6 +73,10 @@ test.describe('Digital Letters - MESH Poll and Download', () => {
7273
const sendersMeshMailboxId = 'test-mesh-sender-1';
7374
const meshMailboxId = 'mock-mailbox';
7475

76+
test.beforeAll(async () => {
77+
await purgeQueue(MESH_DOWNLOAD_DLQ_NAME);
78+
});
79+
7580
async function uploadMeshMessage(
7681
meshMessageId: string,
7782
messageReference: string,
@@ -93,58 +98,67 @@ test.describe('Digital Letters - MESH Poll and Download', () => {
9398
async function expectMeshInboxMessageReceivedEvent(
9499
meshMessageId: string,
95100
): Promise<void> {
96-
await expectToPassEventually(async () => {
97-
const eventLogEntry = await getLogsFromCloudwatch(
98-
`/aws/vendedlogs/events/event-bus/nhs-${ENV}-dl`,
99-
[
100-
'$.message_type = "EVENT_RECEIPT"',
101-
'$.details.detail_type = "uk.nhs.notify.digital.letters.mesh.inbox.message.received.v1"',
102-
`$.details.event_detail = "*\\"meshMessageId\\":\\"${meshMessageId}\\"*"`,
103-
`$.details.event_detail = "*\\"senderId\\":\\"${senderId}\\"*"`,
104-
],
105-
);
106-
107-
expect(eventLogEntry.length).toBeGreaterThanOrEqual(1);
108-
}, 120_000);
101+
await expectEventOnTestObserverQueue(
102+
'uk.nhs.notify.digital.letters.mesh.inbox.message.received.v1',
103+
(detail) => {
104+
const data = (
105+
detail as { data?: { meshMessageId?: string; senderId?: string } }
106+
).data;
107+
return (
108+
data?.meshMessageId === meshMessageId && data?.senderId === senderId
109+
);
110+
},
111+
60_000,
112+
);
109113
}
110114

111115
async function expectMeshInboxMessageDownloadedEvent(
112116
messageReference: string,
113117
): Promise<void> {
114-
await expectToPassEventually(async () => {
115-
const eventLogEntry = await getLogsFromCloudwatch(
116-
`/aws/vendedlogs/events/event-bus/nhs-${ENV}-dl`,
117-
[
118-
'$.message_type = "EVENT_RECEIPT"',
119-
'$.details.detail_type = "uk.nhs.notify.digital.letters.mesh.inbox.message.downloaded.v1"',
120-
`$.details.event_detail = "*\\"messageReference\\":\\"${messageReference}\\"*"`,
121-
`$.details.event_detail = "*\\"senderId\\":\\"${senderId}\\"*"`,
122-
],
123-
);
124-
125-
expect(eventLogEntry.length).toBeGreaterThanOrEqual(1);
126-
}, 180_000);
118+
await expectEventOnTestObserverQueue(
119+
'uk.nhs.notify.digital.letters.mesh.inbox.message.downloaded.v1',
120+
(detail) => {
121+
const data = (
122+
detail as {
123+
data?: { messageReference?: string; senderId?: string };
124+
}
125+
).data;
126+
return (
127+
data?.messageReference === messageReference &&
128+
data?.senderId === senderId
129+
);
130+
},
131+
60_000,
132+
);
127133
}
128134

129135
async function expectMeshInboxMessageInvalidEvent(
130136
meshMessageId: string,
131137
messageReference: string,
138+
failureCode = 'DL_CLIV_005',
132139
): Promise<void> {
133-
await expectToPassEventually(async () => {
134-
const eventLogEntry = await getLogsFromCloudwatch(
135-
`/aws/vendedlogs/events/event-bus/nhs-${ENV}-dl`,
136-
[
137-
'$.message_type = "EVENT_RECEIPT"',
138-
'$.details.detail_type = "uk.nhs.notify.digital.letters.mesh.inbox.message.invalid.v1"',
139-
`$.details.event_detail = "*\\"messageReference\\":\\"${messageReference}\\"*"`,
140-
`$.details.event_detail = "*\\"senderId\\":\\"${senderId}\\"*"`,
141-
`$.details.event_detail = "*\\"meshMessageId\\":\\"${meshMessageId}\\"*"`,
142-
`$.details.event_detail = "*\\"failureCode\\":\\"DL_CLIV_005\\"*"`,
143-
],
144-
);
145-
146-
expect(eventLogEntry.length).toBeGreaterThanOrEqual(1);
147-
}, 180_000);
140+
await expectEventOnTestObserverQueue(
141+
'uk.nhs.notify.digital.letters.mesh.inbox.message.invalid.v1',
142+
(detail) => {
143+
const data = (
144+
detail as {
145+
data?: {
146+
meshMessageId?: string;
147+
messageReference?: string;
148+
senderId?: string;
149+
failureCode?: string;
150+
};
151+
}
152+
).data;
153+
return (
154+
data?.meshMessageId === meshMessageId &&
155+
data?.messageReference === messageReference &&
156+
data?.senderId === senderId &&
157+
data?.failureCode === failureCode
158+
);
159+
},
160+
60_000,
161+
);
148162
}
149163

150164
test('should poll message from MESH inbox, publish received event, download message, and publish downloaded event', async () => {
@@ -287,6 +301,8 @@ test.describe('Digital Letters - MESH Poll and Download', () => {
287301
});
288302

289303
test('should publish MESHInboxMessageInvalid event when local_id is missing', async () => {
304+
test.setTimeout(200_000);
305+
290306
const meshMessageId = `${Date.now()}_INVALID_${uuidv4().slice(0, 8)}`;
291307
const messageContent = JSON.stringify({
292308
senderId,
@@ -300,20 +316,7 @@ test.describe('Digital Letters - MESH Poll and Download', () => {
300316

301317
await invokeLambda(MESH_POLL_LAMBDA_NAME);
302318

303-
await expectToPassEventually(async () => {
304-
const eventLogEntry = await getLogsFromCloudwatch(
305-
`/aws/vendedlogs/events/event-bus/nhs-${ENV}-dl`,
306-
[
307-
'$.message_type = "EVENT_RECEIPT"',
308-
'$.details.detail_type = "uk.nhs.notify.digital.letters.mesh.inbox.message.invalid.v1"',
309-
String.raw`$.details.event_detail = "*\"meshMessageId\":\"${meshMessageId}\"*"`,
310-
String.raw`$.details.event_detail = "*\"senderId\":\"${senderId}\"*"`,
311-
String.raw`$.details.event_detail = "*\"failureCode\":\"DL_CLIV_006\"*"`,
312-
],
313-
);
314-
315-
expect(eventLogEntry.length).toBeGreaterThanOrEqual(1);
316-
}, 120_000);
319+
await expectMeshInboxMessageInvalidEvent(meshMessageId, '', 'DL_CLIV_006');
317320

318321
await expectToPassEventually(async () => {
319322
await expect(async () => {
@@ -323,18 +326,6 @@ test.describe('Digital Letters - MESH Poll and Download', () => {
323326
);
324327
}).rejects.toThrow('No objects found');
325328
}, 60_000);
326-
327-
await expectToPassEventually(async () => {
328-
const receivedEvents = await getLogsFromCloudwatch(
329-
`/aws/vendedlogs/events/event-bus/nhs-${ENV}-dl`,
330-
[
331-
'$.message_type = "EVENT_RECEIPT"',
332-
'$.details.detail_type = "uk.nhs.notify.digital.letters.mesh.inbox.message.received.v1"',
333-
`$.details.event_detail = "*\\"meshMessageId\\":\\"${meshMessageId}\\"*"`,
334-
],
335-
);
336-
expect(receivedEvents.length).toBe(0);
337-
}, 15_000);
338329
});
339330

340331
test('should skip publishing downloaded event and acknowledge message when document already exists in S3', async () => {
@@ -395,7 +386,7 @@ test.describe('Digital Letters - MESH Poll and Download', () => {
395386
// Assert that no MESHInboxMessageDownloaded event was published
396387
await expectToPassEventually(async () => {
397388
const downloadedEvents = await getLogsFromCloudwatch(
398-
`/aws/vendedlogs/events/event-bus/nhs-${ENV}-dl`,
389+
EVENT_BUS_LOG_GROUP_NAME,
399390
[
400391
'$.message_type = "EVENT_RECEIPT"',
401392
'$.details.detail_type = "uk.nhs.notify.digital.letters.mesh.inbox.message.downloaded.v1"',
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import {
2+
DeleteMessageCommand,
3+
ReceiveMessageCommand,
4+
} from '@aws-sdk/client-sqs';
5+
import { TEST_OBSERVER_QUEUE_NAME, SQS_URL_PREFIX } from 'constants/backend-constants';
6+
import { sqsClient } from 'utils';
7+
8+
const queueUrl = `${SQS_URL_PREFIX}${TEST_OBSERVER_QUEUE_NAME}`;
9+
10+
/**
11+
* Polls the test observer SQS queue for an event matching the given type and predicate.
12+
* Deletes the matched message from the queue and returns the event detail.
13+
*
14+
* The test observer queue is subscribed to the EventBridge bus and receives all
15+
* uk.nhs.notify.digital.letters.* events.
16+
*/
17+
export async function expectEventOnTestObserverQueue(
18+
eventType: string,
19+
matchFn: (detail: Record<string, unknown>) => boolean,
20+
timeoutMs = 60_000,
21+
): Promise<Record<string, unknown>> {
22+
const start = Date.now();
23+
24+
while (Date.now() - start < timeoutMs) {
25+
const { Messages = [] } = await sqsClient.send(
26+
new ReceiveMessageCommand({
27+
QueueUrl: queueUrl,
28+
MaxNumberOfMessages: 10,
29+
WaitTimeSeconds: 2,
30+
VisibilityTimeout: 5,
31+
}),
32+
);
33+
34+
for (const msg of Messages) {
35+
if (!msg.Body) continue;
36+
37+
const envelope = JSON.parse(msg.Body) as Record<string, unknown>;
38+
const detailType = envelope['detail-type'] as string | undefined;
39+
const detail = envelope['detail'] as Record<string, unknown> | undefined;
40+
41+
if (detailType === eventType && detail && matchFn(detail)) {
42+
await sqsClient.send(
43+
new DeleteMessageCommand({
44+
QueueUrl: queueUrl,
45+
ReceiptHandle: msg.ReceiptHandle!,
46+
}),
47+
);
48+
return detail;
49+
}
50+
}
51+
}
52+
53+
throw new Error(
54+
`Event of type "${eventType}" not found on test observer queue within ${timeoutMs}ms`,
55+
);
56+
}

0 commit comments

Comments
 (0)