Skip to content

Commit 5d72645

Browse files
committed
CCM-13278: Add component tests for MESH acknowledger
1 parent 2c856d1 commit 5d72645

6 files changed

Lines changed: 269 additions & 2 deletions

File tree

infrastructure/terraform/components/dl/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ No requirements.
5050
| <a name="module_pdm_uploader"></a> [pdm\_uploader](#module\_pdm\_uploader) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a |
5151
| <a name="module_s3bucket_cf_logs"></a> [s3bucket\_cf\_logs](#module\_s3bucket\_cf\_logs) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.24/terraform-s3bucket.zip | n/a |
5252
| <a name="module_s3bucket_letters"></a> [s3bucket\_letters](#module\_s3bucket\_letters) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.24/terraform-s3bucket.zip | n/a |
53+
| <a name="module_s3bucket_non_pii_data"></a> [s3bucket\_non\_pii\_data](#module\_s3bucket\_non\_pii\_data) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.24/terraform-s3bucket.zip | n/a |
5354
| <a name="module_s3bucket_static_assets"></a> [s3bucket\_static\_assets](#module\_s3bucket\_static\_assets) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.24/terraform-s3bucket.zip | n/a |
5455
| <a name="module_sqs_event_publisher_errors"></a> [sqs\_event\_publisher\_errors](#module\_sqs\_event\_publisher\_errors) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.24/terraform-sqs.zip | n/a |
5556
| <a name="module_sqs_mesh_acknowledge"></a> [sqs\_mesh\_acknowledge](#module\_sqs\_mesh\_acknowledge) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-sqs.zip | n/a |
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
module "s3bucket_non_pii_data" {
2+
source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.24/terraform-s3bucket.zip"
3+
4+
name = "non-pii-data"
5+
6+
aws_account_id = var.aws_account_id
7+
region = var.region
8+
project = var.project
9+
environment = var.environment
10+
component = local.component
11+
12+
kms_key_arn = module.kms.key_arn
13+
14+
policy_documents = [data.aws_iam_policy_document.s3bucket_non_pii_data.json]
15+
}
16+
17+
data "aws_iam_policy_document" "s3bucket_non_pii_data" {
18+
statement {
19+
sid = "AllowManagedAccountsToList"
20+
effect = "Allow"
21+
22+
actions = [
23+
"s3:ListBucket",
24+
]
25+
26+
resources = [
27+
module.s3bucket_non_pii_data.arn,
28+
]
29+
30+
principals {
31+
type = "AWS"
32+
identifiers = [
33+
"arn:aws:iam::${var.aws_account_id}:root"
34+
]
35+
}
36+
}
37+
38+
statement {
39+
sid = "AllowManagedAccountsToGet"
40+
effect = "Allow"
41+
42+
actions = [
43+
"s3:GetObject",
44+
"s3:PutObject",
45+
]
46+
47+
resources = [
48+
"${module.s3bucket_non_pii_data.arn}/*",
49+
]
50+
51+
principals {
52+
type = "AWS"
53+
identifiers = [
54+
"arn:aws:iam::${var.aws_account_id}:root"
55+
]
56+
}
57+
}
58+
}

tests/playwright/constants/backend-constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export const TTL_QUEUE_NAME = `${CSI}-ttl-queue`;
1717
export const TTL_DLQ_NAME = `${CSI}-ttl-dlq`;
1818
export const PDM_UPLOADER_DLQ_NAME = `${CSI}-pdm-uploader-dlq`;
1919
export const PDM_POLL_DLQ_NAME = `${CSI}-pdm-poll-dlq`;
20+
export const MESH_ACKNOWLEDGE_DLQ_NAME = `${CSI}-mesh-acknowledge-dlq`;
2021

2122
// Queue Url Prefix
2223
export const SQS_URL_PREFIX = `https://sqs.${REGION}.amazonaws.com/${AWS_ACCOUNT_ID}/`;
@@ -31,6 +32,7 @@ export const TTL_TABLE_NAME = `${CSI}-ttl`;
3132

3233
// S3
3334
export const LETTERS_S3_BUCKET_NAME = `nhs-${process.env.AWS_ACCOUNT_ID}-${REGION}-${ENV}-dl-letters`;
35+
export const NON_PII_S3_BUCKET_NAME = `nhs-${process.env.AWS_ACCOUNT_ID}-${REGION}-${ENV}-dl-non-pii-data`;
3436

3537
// Cloudwatch
3638
export const PDM_UPLOADER_LAMBDA_LOG_GROUP_NAME = `/aws/lambda/${CSI}-pdm-uploader`;
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import { expect, test } from '@playwright/test';
2+
import {
3+
ENV,
4+
MESH_ACKNOWLEDGE_DLQ_NAME,
5+
NON_PII_S3_BUCKET_NAME,
6+
} from 'constants/backend-constants';
7+
import { MESHInboxMessageDownloaded } from 'digital-letters-events';
8+
import messageDownloadedValidator from 'digital-letters-events/MESHInboxMessageDownloaded.js';
9+
import { getLogsFromCloudwatch } from 'helpers/cloudwatch-helpers';
10+
import eventPublisher from 'helpers/event-bus-helpers';
11+
import expectToPassEventually from 'helpers/expectations';
12+
import { downloadFromS3 } from 'helpers/s3-helpers';
13+
import { expectMessageContainingString } from 'helpers/sqs-helpers';
14+
import { v4 as uuidv4 } from 'uuid';
15+
16+
test.describe('Digital Letters - Mesh Acknowledger', () => {
17+
// These values match the ones configured in senders.setup.ts
18+
const senderId = 'test-sender-1';
19+
const sendersMeshMailboxId = 'test-mesh-sender-1';
20+
21+
const validMessageDownloadedEvent: MESHInboxMessageDownloaded = {
22+
id: uuidv4(),
23+
specversion: '1.0',
24+
source:
25+
'/nhs/england/notify/production/primary/data-plane/digitalletters/mesh',
26+
subject:
27+
'customer/920fca11-596a-4eca-9c47-99f624614658/recipient/769acdd4-6a47-496f-999f-76a6fd2c3959',
28+
type: 'uk.nhs.notify.digital.letters.mesh.inbox.message.downloaded.v1',
29+
time: '2023-06-20T12:00:00Z',
30+
recordedtime: '2023-06-20T12:00:00.250Z',
31+
severitynumber: 2,
32+
traceparent: '00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01',
33+
datacontenttype: 'application/json',
34+
dataschema:
35+
'https://notify.nhs.uk/cloudevents/schemas/digital-letters/2025-10-draft/data/digital-letters-mesh-inbox-message-downloaded-data.schema.json',
36+
severitytext: 'INFO',
37+
datacategory: 'non-sensitive',
38+
dataclassification: 'public',
39+
dataregulation: 'GDPR',
40+
tracestate: 'rojo=00f067aa0ba902b7,congo=t61rcWkgMzE',
41+
partitionkey: 'customer-920fca11',
42+
sampledrate: 5,
43+
sequence: '00000000000000000042',
44+
data: {
45+
meshMessageId: '12345',
46+
messageUri: `https://example.com/ttl/resource/${uuidv4()}`,
47+
messageReference: 'ref1',
48+
senderId,
49+
},
50+
};
51+
52+
test('should send MESH acknowledgement and publish message acknowledged event following message downloaded event', async () => {
53+
const letterId = uuidv4();
54+
const messageReference = uuidv4();
55+
const meshMessageId = '20200601122152994285_D59900';
56+
57+
await eventPublisher.sendEvents<MESHInboxMessageDownloaded>(
58+
[
59+
{
60+
...validMessageDownloadedEvent,
61+
id: letterId,
62+
data: {
63+
...validMessageDownloadedEvent.data,
64+
messageUri: `https://example.com/ttl/resource/${letterId}`,
65+
messageReference,
66+
meshMessageId,
67+
},
68+
},
69+
],
70+
messageDownloadedValidator,
71+
);
72+
73+
// The mailbox ID matches the Mock MESH config in SSM.
74+
const meshMailboxId = 'mock-mailbox';
75+
76+
// Verify MESH acknowledgement message was published.
77+
await expectToPassEventually(async () => {
78+
const messageContent = await downloadFromS3(
79+
NON_PII_S3_BUCKET_NAME,
80+
`mock-mesh/${meshMailboxId}/out/${sendersMeshMailboxId}/${messageReference}_`,
81+
);
82+
83+
const messageHeaders = messageContent.metadata ?? {};
84+
expect(messageHeaders.subject).toEqual('202');
85+
expect(messageHeaders.local_id).toEqual(messageReference);
86+
expect(messageHeaders.workflow_id).toEqual('NHS_NOTIFY_SEND_REQUEST_ACK');
87+
88+
const messageBody = JSON.parse(messageContent.body);
89+
expect(messageBody).toEqual({
90+
meshMessageId,
91+
requestId: `${senderId}_${messageReference}`,
92+
});
93+
});
94+
95+
// Verify message acknowledged event was published.
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.acknowledged.v1"',
102+
`$.details.event_detail = "*\\"messageReference\\":\\"${messageReference}\\"*"`,
103+
`$.details.event_detail = "*\\"senderId\\":\\"${senderId}\\"*"`,
104+
`$.details.event_detail = "*\\"meshMailboxId\\":\\"${sendersMeshMailboxId}\\"*"`,
105+
],
106+
);
107+
108+
expect(eventLogEntry.length).toEqual(1);
109+
});
110+
});
111+
112+
test('should send an event for an unknown sender to dlq', async () => {
113+
// We need to leave time to go through the 3 retries before it's sent to the DLQ.
114+
test.setTimeout(550_000);
115+
116+
const letterId = uuidv4();
117+
118+
await eventPublisher.sendEvents<MESHInboxMessageDownloaded>(
119+
[
120+
{
121+
...validMessageDownloadedEvent,
122+
id: letterId,
123+
data: {
124+
...validMessageDownloadedEvent.data,
125+
senderId: 'unknown-sender-id',
126+
},
127+
},
128+
],
129+
messageDownloadedValidator,
130+
);
131+
132+
await expectMessageContainingString(
133+
MESH_ACKNOWLEDGE_DLQ_NAME,
134+
letterId,
135+
420,
136+
);
137+
});
138+
139+
test('should send invalid event to dlq', async () => {
140+
// We need to leave time to go through the 3 retries before it's sent to the DLQ.
141+
test.setTimeout(550_000);
142+
143+
const letterId = uuidv4();
144+
145+
await eventPublisher.sendEvents<
146+
MESHInboxMessageDownloaded & { data: { unexpectedField: string } }
147+
>(
148+
[
149+
{
150+
...validMessageDownloadedEvent,
151+
id: letterId,
152+
data: {
153+
...validMessageDownloadedEvent.data,
154+
unexpectedField: 'I should not be here',
155+
},
156+
},
157+
],
158+
// We don't actually want to validate this event on the way out, as we intend it to be invalid.
159+
() => true,
160+
);
161+
162+
await expectMessageContainingString(
163+
MESH_ACKNOWLEDGE_DLQ_NAME,
164+
letterId,
165+
420,
166+
);
167+
});
168+
});

tests/playwright/digital-letters-component-tests/pdm-uploader.component.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ test.describe('Digital Letters - Upload to PDM', () => {
194194
},
195195
},
196196
],
197-
messageDownloadedValidator,
197+
() => true,
198198
);
199199

200200
await expectToPassEventually(async () => {

tests/playwright/helpers/s3-helpers.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import {
2+
GetObjectCommand,
23
ListBucketsCommand,
4+
ListObjectsV2Command,
35
PutObjectCommand,
46
S3Client,
57
} from '@aws-sdk/client-s3';
@@ -36,4 +38,40 @@ async function uploadToS3(
3638
);
3739
}
3840

39-
export { listBuckets, uploadToS3 };
41+
async function downloadFromS3(
42+
bucket: string,
43+
keyPrefix: string,
44+
): Promise<{ body: string; metadata?: Record<string, string> }> {
45+
const objects = await s3.send(
46+
new ListObjectsV2Command({ Bucket: bucket, Prefix: keyPrefix }),
47+
);
48+
49+
if ((objects.Contents?.length ?? 0) > 1) {
50+
throw new Error(
51+
`Multiple objects found for prefix s3://${bucket}/${keyPrefix}`,
52+
);
53+
}
54+
55+
if ((objects.Contents?.length ?? 0) === 0) {
56+
throw new Error(`No objects found for prefix s3://${bucket}/${keyPrefix}`);
57+
}
58+
59+
const key = objects.Contents?.[0]?.Key;
60+
const response = await s3.send(
61+
new GetObjectCommand({
62+
Bucket: bucket,
63+
Key: key,
64+
}),
65+
);
66+
67+
if (!response.Body) {
68+
throw new Error(`No content found for s3://${bucket}/${key}`);
69+
}
70+
71+
return {
72+
body: await response.Body.transformToString(),
73+
metadata: response.Metadata,
74+
};
75+
}
76+
77+
export { downloadFromS3, listBuckets, uploadToS3 };

0 commit comments

Comments
 (0)