From 537a3030cc4fc68351297c732581554303c4ac05 Mon Sep 17 00:00:00 2001 From: lapenna-bjss Date: Wed, 22 Apr 2026 15:42:24 +0100 Subject: [PATCH 01/16] component test sqs draft --- .../terraform/components/dl/README.md | 1 + .../dl/cloudwatch_event_rule_all_events.tf | 7 + .../components/dl/module_sqs_test_observer.tf | 41 ++++++ .../playwright/constants/backend-constants.ts | 1 + .../mesh-poll-download.component.spec.ts | 131 ++++++++---------- .../helpers/test-observer-helpers.ts | 56 ++++++++ 6 files changed, 167 insertions(+), 70 deletions(-) create mode 100644 infrastructure/terraform/components/dl/module_sqs_test_observer.tf create mode 100644 tests/playwright/helpers/test-observer-helpers.ts diff --git a/infrastructure/terraform/components/dl/README.md b/infrastructure/terraform/components/dl/README.md index 43435531..e2b3be4c 100644 --- a/infrastructure/terraform/components/dl/README.md +++ b/infrastructure/terraform/components/dl/README.md @@ -103,6 +103,7 @@ No requirements. | [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 | | [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 | | [sqs\_scanner](#module\_sqs\_scanner) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-sqs.zip | n/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 | | [sqs\_ttl](#module\_sqs\_ttl) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-sqs.zip | n/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 | | [ttl\_create](#module\_ttl\_create) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-lambda.zip | n/a | diff --git a/infrastructure/terraform/components/dl/cloudwatch_event_rule_all_events.tf b/infrastructure/terraform/components/dl/cloudwatch_event_rule_all_events.tf index f27e48de..136e6881 100644 --- a/infrastructure/terraform/components/dl/cloudwatch_event_rule_all_events.tf +++ b/infrastructure/terraform/components/dl/cloudwatch_event_rule_all_events.tf @@ -18,3 +18,10 @@ resource "aws_cloudwatch_event_target" "reporting_firehose" { role_arn = aws_iam_role.eventbridge_firehose.arn event_bus_name = aws_cloudwatch_event_bus.main.name } + +resource "aws_cloudwatch_event_target" "test_observer_sqs" { + rule = aws_cloudwatch_event_rule.all_events.name + target_id = "test-observer-sqs-target" + arn = module.sqs_test_observer.sqs_queue_arn + event_bus_name = aws_cloudwatch_event_bus.main.name +} diff --git a/infrastructure/terraform/components/dl/module_sqs_test_observer.tf b/infrastructure/terraform/components/dl/module_sqs_test_observer.tf new file mode 100644 index 00000000..f2be9f4a --- /dev/null +++ b/infrastructure/terraform/components/dl/module_sqs_test_observer.tf @@ -0,0 +1,41 @@ +module "sqs_test_observer" { + source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-sqs.zip" + + aws_account_id = var.aws_account_id + component = local.component + environment = var.environment + project = var.project + region = var.region + name = "test-observer" + sqs_kms_key_arn = module.kms.key_arn + visibility_timeout_seconds = var.sqs_visibility_timeout_seconds + create_dlq = false + max_receive_count = var.sqs_max_receive_count + sqs_policy_overload = data.aws_iam_policy_document.sqs_test_observer.json +} + +data "aws_iam_policy_document" "sqs_test_observer" { + statement { + sid = "AllowEventBridgeToSendMessage" + effect = "Allow" + + principals { + type = "Service" + identifiers = ["events.amazonaws.com"] + } + + actions = [ + "sqs:SendMessage" + ] + + resources = [ + "arn:aws:sqs:${var.region}:${var.aws_account_id}:${local.csi}-test-observer-queue" + ] + + condition { + test = "ArnLike" + variable = "aws:SourceArn" + values = [aws_cloudwatch_event_rule.all_events.arn] + } + } +} diff --git a/tests/playwright/constants/backend-constants.ts b/tests/playwright/constants/backend-constants.ts index 0ca126e3..625e25d2 100644 --- a/tests/playwright/constants/backend-constants.ts +++ b/tests/playwright/constants/backend-constants.ts @@ -32,6 +32,7 @@ export const PRINT_SENDER_DLQ_NAME = `${CSI}-print-sender-dlq`; export const MOVE_SCANNED_FILES_NAME = `${CSI}-move-scanned-files-queue`; export const MOVE_SCANNED_FILES_DLQ_NAME = `${CSI}-move-scanned-files-dlq`; export const REPORT_SENDER_DLQ_NAME = `${CSI}-report-sender-dlq`; +export const TEST_OBSERVER_QUEUE_NAME = `${CSI}-test-observer-queue`; // Queue Url Prefix export const SQS_URL_PREFIX = `https://sqs.${REGION}.amazonaws.com/${AWS_ACCOUNT_ID}/`; diff --git a/tests/playwright/digital-letters-component-tests/mesh-poll-download.component.spec.ts b/tests/playwright/digital-letters-component-tests/mesh-poll-download.component.spec.ts index 0ff3ce0e..a267858b 100644 --- a/tests/playwright/digital-letters-component-tests/mesh-poll-download.component.spec.ts +++ b/tests/playwright/digital-letters-component-tests/mesh-poll-download.component.spec.ts @@ -1,18 +1,19 @@ import { expect, test } from '@playwright/test'; import { - ENV, MESH_DOWNLOAD_DLQ_NAME, MESH_DOWNLOAD_LAMBDA_LOG_GROUP_NAME, MESH_POLL_LAMBDA_NAME, NON_PII_S3_BUCKET_NAME, PII_S3_BUCKET_NAME, + EVENT_BUS_LOG_GROUP_NAME, } from 'constants/backend-constants'; import { getLogsFromCloudwatch } from 'helpers/cloudwatch-helpers'; import eventPublisher from 'helpers/event-bus-helpers'; import expectToPassEventually from 'helpers/expectations'; import { invokeLambda } from 'helpers/lambda-helpers'; import { downloadFromS3, uploadToS3 } from 'helpers/s3-helpers'; -import { expectMessageContainingString } from 'helpers/sqs-helpers'; +import { expectMessageContainingString, purgeQueue } from 'helpers/sqs-helpers'; +import { expectEventOnTestObserverQueue } from 'helpers/test-observer-helpers'; import { v4 as uuidv4 } from 'uuid'; import { SENDER_ID_SKIPS_NOTIFY } from 'constants/tests-constants'; import { validateMESHInboxMessageReceived } from 'digital-letters-events'; @@ -72,6 +73,10 @@ test.describe('Digital Letters - MESH Poll and Download', () => { const sendersMeshMailboxId = 'test-mesh-sender-1'; const meshMailboxId = 'mock-mailbox'; + test.beforeAll(async () => { + await purgeQueue(MESH_DOWNLOAD_DLQ_NAME); + }); + async function uploadMeshMessage( meshMessageId: string, messageReference: string, @@ -93,58 +98,67 @@ test.describe('Digital Letters - MESH Poll and Download', () => { async function expectMeshInboxMessageReceivedEvent( meshMessageId: string, ): Promise { - await expectToPassEventually(async () => { - const eventLogEntry = await getLogsFromCloudwatch( - `/aws/vendedlogs/events/event-bus/nhs-${ENV}-dl`, - [ - '$.message_type = "EVENT_RECEIPT"', - '$.details.detail_type = "uk.nhs.notify.digital.letters.mesh.inbox.message.received.v1"', - `$.details.event_detail = "*\\"meshMessageId\\":\\"${meshMessageId}\\"*"`, - `$.details.event_detail = "*\\"senderId\\":\\"${senderId}\\"*"`, - ], - ); - - expect(eventLogEntry.length).toBeGreaterThanOrEqual(1); - }, 120_000); + await expectEventOnTestObserverQueue( + 'uk.nhs.notify.digital.letters.mesh.inbox.message.received.v1', + (detail) => { + const data = ( + detail as { data?: { meshMessageId?: string; senderId?: string } } + ).data; + return ( + data?.meshMessageId === meshMessageId && data?.senderId === senderId + ); + }, + 60_000, + ); } async function expectMeshInboxMessageDownloadedEvent( messageReference: string, ): Promise { - await expectToPassEventually(async () => { - const eventLogEntry = await getLogsFromCloudwatch( - `/aws/vendedlogs/events/event-bus/nhs-${ENV}-dl`, - [ - '$.message_type = "EVENT_RECEIPT"', - '$.details.detail_type = "uk.nhs.notify.digital.letters.mesh.inbox.message.downloaded.v1"', - `$.details.event_detail = "*\\"messageReference\\":\\"${messageReference}\\"*"`, - `$.details.event_detail = "*\\"senderId\\":\\"${senderId}\\"*"`, - ], - ); - - expect(eventLogEntry.length).toBeGreaterThanOrEqual(1); - }, 180_000); + await expectEventOnTestObserverQueue( + 'uk.nhs.notify.digital.letters.mesh.inbox.message.downloaded.v1', + (detail) => { + const data = ( + detail as { + data?: { messageReference?: string; senderId?: string }; + } + ).data; + return ( + data?.messageReference === messageReference && + data?.senderId === senderId + ); + }, + 60_000, + ); } async function expectMeshInboxMessageInvalidEvent( meshMessageId: string, messageReference: string, + failureCode = 'DL_CLIV_005', ): Promise { - await expectToPassEventually(async () => { - const eventLogEntry = await getLogsFromCloudwatch( - `/aws/vendedlogs/events/event-bus/nhs-${ENV}-dl`, - [ - '$.message_type = "EVENT_RECEIPT"', - '$.details.detail_type = "uk.nhs.notify.digital.letters.mesh.inbox.message.invalid.v1"', - `$.details.event_detail = "*\\"messageReference\\":\\"${messageReference}\\"*"`, - `$.details.event_detail = "*\\"senderId\\":\\"${senderId}\\"*"`, - `$.details.event_detail = "*\\"meshMessageId\\":\\"${meshMessageId}\\"*"`, - `$.details.event_detail = "*\\"failureCode\\":\\"DL_CLIV_005\\"*"`, - ], - ); - - expect(eventLogEntry.length).toBeGreaterThanOrEqual(1); - }, 180_000); + await expectEventOnTestObserverQueue( + 'uk.nhs.notify.digital.letters.mesh.inbox.message.invalid.v1', + (detail) => { + const data = ( + detail as { + data?: { + meshMessageId?: string; + messageReference?: string; + senderId?: string; + failureCode?: string; + }; + } + ).data; + return ( + data?.meshMessageId === meshMessageId && + data?.messageReference === messageReference && + data?.senderId === senderId && + data?.failureCode === failureCode + ); + }, + 60_000, + ); } 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', () => { }); test('should publish MESHInboxMessageInvalid event when local_id is missing', async () => { + test.setTimeout(200_000); + const meshMessageId = `${Date.now()}_INVALID_${uuidv4().slice(0, 8)}`; const messageContent = JSON.stringify({ senderId, @@ -300,20 +316,7 @@ test.describe('Digital Letters - MESH Poll and Download', () => { await invokeLambda(MESH_POLL_LAMBDA_NAME); - await expectToPassEventually(async () => { - const eventLogEntry = await getLogsFromCloudwatch( - `/aws/vendedlogs/events/event-bus/nhs-${ENV}-dl`, - [ - '$.message_type = "EVENT_RECEIPT"', - '$.details.detail_type = "uk.nhs.notify.digital.letters.mesh.inbox.message.invalid.v1"', - String.raw`$.details.event_detail = "*\"meshMessageId\":\"${meshMessageId}\"*"`, - String.raw`$.details.event_detail = "*\"senderId\":\"${senderId}\"*"`, - String.raw`$.details.event_detail = "*\"failureCode\":\"DL_CLIV_006\"*"`, - ], - ); - - expect(eventLogEntry.length).toBeGreaterThanOrEqual(1); - }, 120_000); + await expectMeshInboxMessageInvalidEvent(meshMessageId, '', 'DL_CLIV_006'); await expectToPassEventually(async () => { await expect(async () => { @@ -323,18 +326,6 @@ test.describe('Digital Letters - MESH Poll and Download', () => { ); }).rejects.toThrow('No objects found'); }, 60_000); - - await expectToPassEventually(async () => { - const receivedEvents = await getLogsFromCloudwatch( - `/aws/vendedlogs/events/event-bus/nhs-${ENV}-dl`, - [ - '$.message_type = "EVENT_RECEIPT"', - '$.details.detail_type = "uk.nhs.notify.digital.letters.mesh.inbox.message.received.v1"', - `$.details.event_detail = "*\\"meshMessageId\\":\\"${meshMessageId}\\"*"`, - ], - ); - expect(receivedEvents.length).toBe(0); - }, 15_000); }); 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', () => { // Assert that no MESHInboxMessageDownloaded event was published await expectToPassEventually(async () => { const downloadedEvents = await getLogsFromCloudwatch( - `/aws/vendedlogs/events/event-bus/nhs-${ENV}-dl`, + EVENT_BUS_LOG_GROUP_NAME, [ '$.message_type = "EVENT_RECEIPT"', '$.details.detail_type = "uk.nhs.notify.digital.letters.mesh.inbox.message.downloaded.v1"', diff --git a/tests/playwright/helpers/test-observer-helpers.ts b/tests/playwright/helpers/test-observer-helpers.ts new file mode 100644 index 00000000..5fea3e59 --- /dev/null +++ b/tests/playwright/helpers/test-observer-helpers.ts @@ -0,0 +1,56 @@ +import { + DeleteMessageCommand, + ReceiveMessageCommand, +} from '@aws-sdk/client-sqs'; +import { TEST_OBSERVER_QUEUE_NAME, SQS_URL_PREFIX } from 'constants/backend-constants'; +import { sqsClient } from 'utils'; + +const queueUrl = `${SQS_URL_PREFIX}${TEST_OBSERVER_QUEUE_NAME}`; + +/** + * Polls the test observer SQS queue for an event matching the given type and predicate. + * Deletes the matched message from the queue and returns the event detail. + * + * The test observer queue is subscribed to the EventBridge bus and receives all + * uk.nhs.notify.digital.letters.* events. + */ +export async function expectEventOnTestObserverQueue( + eventType: string, + matchFn: (detail: Record) => boolean, + timeoutMs = 60_000, +): Promise> { + const start = Date.now(); + + while (Date.now() - start < timeoutMs) { + const { Messages = [] } = await sqsClient.send( + new ReceiveMessageCommand({ + QueueUrl: queueUrl, + MaxNumberOfMessages: 10, + WaitTimeSeconds: 2, + VisibilityTimeout: 5, + }), + ); + + for (const msg of Messages) { + if (!msg.Body) continue; + + const envelope = JSON.parse(msg.Body) as Record; + const detailType = envelope['detail-type'] as string | undefined; + const detail = envelope['detail'] as Record | undefined; + + if (detailType === eventType && detail && matchFn(detail)) { + await sqsClient.send( + new DeleteMessageCommand({ + QueueUrl: queueUrl, + ReceiptHandle: msg.ReceiptHandle!, + }), + ); + return detail; + } + } + } + + throw new Error( + `Event of type "${eventType}" not found on test observer queue within ${timeoutMs}ms`, + ); +} From 34413d14adf6006396159e277c1a3138a7c5a644 Mon Sep 17 00:00:00 2001 From: lapenna-bjss Date: Wed, 22 Apr 2026 15:51:40 +0100 Subject: [PATCH 02/16] Fix linting issues --- .../mesh-poll-download.component.spec.ts | 34 ++++++++----------- .../helpers/test-observer-helpers.ts | 31 +++++++++-------- 2 files changed, 32 insertions(+), 33 deletions(-) diff --git a/tests/playwright/digital-letters-component-tests/mesh-poll-download.component.spec.ts b/tests/playwright/digital-letters-component-tests/mesh-poll-download.component.spec.ts index a267858b..3f02fcbb 100644 --- a/tests/playwright/digital-letters-component-tests/mesh-poll-download.component.spec.ts +++ b/tests/playwright/digital-letters-component-tests/mesh-poll-download.component.spec.ts @@ -1,11 +1,11 @@ import { expect, test } from '@playwright/test'; import { + EVENT_BUS_LOG_GROUP_NAME, MESH_DOWNLOAD_DLQ_NAME, MESH_DOWNLOAD_LAMBDA_LOG_GROUP_NAME, MESH_POLL_LAMBDA_NAME, NON_PII_S3_BUCKET_NAME, PII_S3_BUCKET_NAME, - EVENT_BUS_LOG_GROUP_NAME, } from 'constants/backend-constants'; import { getLogsFromCloudwatch } from 'helpers/cloudwatch-helpers'; import eventPublisher from 'helpers/event-bus-helpers'; @@ -101,9 +101,9 @@ test.describe('Digital Letters - MESH Poll and Download', () => { await expectEventOnTestObserverQueue( 'uk.nhs.notify.digital.letters.mesh.inbox.message.received.v1', (detail) => { - const data = ( - detail as { data?: { meshMessageId?: string; senderId?: string } } - ).data; + const { data } = detail as { + data?: { meshMessageId?: string; senderId?: string }; + }; return ( data?.meshMessageId === meshMessageId && data?.senderId === senderId ); @@ -118,11 +118,9 @@ test.describe('Digital Letters - MESH Poll and Download', () => { await expectEventOnTestObserverQueue( 'uk.nhs.notify.digital.letters.mesh.inbox.message.downloaded.v1', (detail) => { - const data = ( - detail as { - data?: { messageReference?: string; senderId?: string }; - } - ).data; + const { data } = detail as { + data?: { messageReference?: string; senderId?: string }; + }; return ( data?.messageReference === messageReference && data?.senderId === senderId @@ -140,16 +138,14 @@ test.describe('Digital Letters - MESH Poll and Download', () => { await expectEventOnTestObserverQueue( 'uk.nhs.notify.digital.letters.mesh.inbox.message.invalid.v1', (detail) => { - const data = ( - detail as { - data?: { - meshMessageId?: string; - messageReference?: string; - senderId?: string; - failureCode?: string; - }; - } - ).data; + const { data } = detail as { + data?: { + meshMessageId?: string; + messageReference?: string; + senderId?: string; + failureCode?: string; + }; + }; return ( data?.meshMessageId === meshMessageId && data?.messageReference === messageReference && diff --git a/tests/playwright/helpers/test-observer-helpers.ts b/tests/playwright/helpers/test-observer-helpers.ts index 5fea3e59..2740b7de 100644 --- a/tests/playwright/helpers/test-observer-helpers.ts +++ b/tests/playwright/helpers/test-observer-helpers.ts @@ -2,7 +2,10 @@ import { DeleteMessageCommand, ReceiveMessageCommand, } from '@aws-sdk/client-sqs'; -import { TEST_OBSERVER_QUEUE_NAME, SQS_URL_PREFIX } from 'constants/backend-constants'; +import { + SQS_URL_PREFIX, + TEST_OBSERVER_QUEUE_NAME, +} from 'constants/backend-constants'; import { sqsClient } from 'utils'; const queueUrl = `${SQS_URL_PREFIX}${TEST_OBSERVER_QUEUE_NAME}`; @@ -32,20 +35,20 @@ export async function expectEventOnTestObserverQueue( ); for (const msg of Messages) { - if (!msg.Body) continue; - - const envelope = JSON.parse(msg.Body) as Record; - const detailType = envelope['detail-type'] as string | undefined; - const detail = envelope['detail'] as Record | undefined; + if (msg.Body) { + const envelope = JSON.parse(msg.Body) as Record; + const detailType = envelope['detail-type'] as string | undefined; + const detail = envelope.detail as Record | undefined; - if (detailType === eventType && detail && matchFn(detail)) { - await sqsClient.send( - new DeleteMessageCommand({ - QueueUrl: queueUrl, - ReceiptHandle: msg.ReceiptHandle!, - }), - ); - return detail; + if (detailType === eventType && detail && matchFn(detail)) { + await sqsClient.send( + new DeleteMessageCommand({ + QueueUrl: queueUrl, + ReceiptHandle: msg.ReceiptHandle!, + }), + ); + return detail; + } } } } From f140993eeac3ac610d256e931e737702387d1a14 Mon Sep 17 00:00:00 2001 From: lapenna-bjss Date: Thu, 23 Apr 2026 09:31:52 +0100 Subject: [PATCH 03/16] Test --- .../playwright/constants/backend-constants.ts | 1 + .../core-notify.component.spec.ts | 103 +++++++-------- .../mesh-poll-download.component.spec.ts | 35 +++--- .../move-scanned-files.component.spec.ts | 64 +++++----- .../pdm-poll.component.spec.ts | 119 ++++++------------ .../send-reports-trust.component.spec.ts | 48 +++---- .../ttl-create.component.spec.ts | 44 +++---- .../ttl-handle.component.spec.ts | 30 ++--- .../helpers/test-observer-helpers.ts | 8 ++ 9 files changed, 194 insertions(+), 258 deletions(-) diff --git a/tests/playwright/constants/backend-constants.ts b/tests/playwright/constants/backend-constants.ts index 625e25d2..8522935f 100644 --- a/tests/playwright/constants/backend-constants.ts +++ b/tests/playwright/constants/backend-constants.ts @@ -82,3 +82,4 @@ export const MINIMUM_PROCESSOR_BUFFER_INTERVAL = 0; // Athena export const ATHENA_WORKGROUP_NAME = CSI; export const CREATE_TTL_LAMBDA_LOG_GROUP_NAME = `/aws/lambda/${CSI}-ttl-create`; +export const TTL_HANDLE_EXPIRY_LAMBDA_LOG_GROUP_NAME = `/aws/lambda/${CSI}-ttl-handle-expiry`; diff --git a/tests/playwright/digital-letters-component-tests/core-notify.component.spec.ts b/tests/playwright/digital-letters-component-tests/core-notify.component.spec.ts index 96ad8736..cf25ea57 100644 --- a/tests/playwright/digital-letters-component-tests/core-notify.component.spec.ts +++ b/tests/playwright/digital-letters-component-tests/core-notify.component.spec.ts @@ -2,7 +2,6 @@ import { expect, test } from '@playwright/test'; import { CORE_NOTIFIER_DLQ_NAME, CORE_NOTIFIER_LAMBDA_LOG_GROUP_NAME, - EVENT_BUS_LOG_GROUP_NAME, } from 'constants/backend-constants'; import { SENDER_ID_SKIPS_NOTIFY, @@ -17,6 +16,7 @@ import { getLogsFromCloudwatch } from 'helpers/cloudwatch-helpers'; import eventPublisher from 'helpers/event-bus-helpers'; import expectToPassEventually from 'helpers/expectations'; import { expectMessageContainingString, purgeQueue } from 'helpers/sqs-helpers'; +import { expectEventOnTestObserverQueue } from 'helpers/test-observer-helpers'; import { v4 as uuidv4 } from 'uuid'; const baseEvent: Omit = { @@ -83,21 +83,22 @@ test.describe('Digital Letters - Core Notify', () => { }, 240); // Verify the event is published in the event bus - await expectToPassEventually(async () => { - const eventLogEntry = await getLogsFromCloudwatch( - EVENT_BUS_LOG_GROUP_NAME, - [ - '$.message_type = "EVENT_RECEIPT"', - '$.details.detail_type = "uk.nhs.notify.digital.letters.messages.request.submitted.v1"', - `$.details.event_detail = "*\\"notifyId\\":\\"*\\"*"`, - `$.details.event_detail = "*\\"messageUri\\":\\"https://www.nhsapp.service.nhs.uk/digital-letters?letterid=${resourceId}\\"*"`, - `$.details.event_detail = "*\\"messageReference\\":\\"${messageReference}\\"*"`, - `$.details.event_detail = "*\\"senderId\\":\\"${SENDER_ID_VALID_FOR_NOTIFY_SANDBOX}\\"*"`, - ], - ); - - expect(eventLogEntry.length).toEqual(1); - }, 240); + const submittedDetail = await expectEventOnTestObserverQueue( + 'uk.nhs.notify.digital.letters.messages.request.submitted.v1', + (d) => { + const data = (d as any).data; + return ( + data.messageReference === messageReference && + data.senderId === SENDER_ID_VALID_FOR_NOTIFY_SANDBOX + ); + }, + 60_000, + ); + const submittedData = (submittedDetail as any).data; + expect(submittedData.notifyId).toBeTruthy(); + expect(submittedData.messageUri).toBe( + `https://www.nhsapp.service.nhs.uk/digital-letters?letterid=${resourceId}`, + ); }); test('given PDMResourceAvailable event with a client configured with a Routing plan not recognized by the Core Notify sandbox, when the sandbox receives the event then it replies with an error', async () => { @@ -136,21 +137,22 @@ test.describe('Digital Letters - Core Notify', () => { }, 240); // Verify the event is published in the event bus - await expectToPassEventually(async () => { - const eventLogEntry = await getLogsFromCloudwatch( - EVENT_BUS_LOG_GROUP_NAME, - [ - '$.message_type = "EVENT_RECEIPT"', - '$.details.detail_type = "uk.nhs.notify.digital.letters.messages.request.rejected.v1"', - `$.details.event_detail = "*\\"failureCode\\":\\"CM_INVALID_VALUE\\"*"`, - `$.details.event_detail = "*\\"messageUri\\":\\"https://www.nhsapp.service.nhs.uk/digital-letters?letterid=${resourceId}\\"*"`, - `$.details.event_detail = "*\\"messageReference\\":\\"${messageReference}\\"*"`, - `$.details.event_detail = "*\\"senderId\\":\\"${SENDER_ID_THAT_TRIGGERS_ERROR_IN_NOTIFY_SANDBOX}\\"*"`, - ], - ); - - expect(eventLogEntry.length).toEqual(1); - }, 240); + const rejectedDetail = await expectEventOnTestObserverQueue( + 'uk.nhs.notify.digital.letters.messages.request.rejected.v1', + (d) => { + const data = (d as any).data; + return ( + data.messageReference === messageReference && + data.senderId === SENDER_ID_THAT_TRIGGERS_ERROR_IN_NOTIFY_SANDBOX + ); + }, + 60_000, + ); + const rejectedData = (rejectedDetail as any).data; + expect(rejectedData.failureCode).toBe('CM_INVALID_VALUE'); + expect(rejectedData.messageUri).toBe( + `https://www.nhsapp.service.nhs.uk/digital-letters?letterid=${resourceId}`, + ); }); test('given PDMResourceAvailable event, when client does NOT have routingConfigId then a message is NOT sent to core Notify', async () => { @@ -175,19 +177,17 @@ test.describe('Digital Letters - Core Notify', () => { ); // Verify the event is published in the event bus - await expectToPassEventually(async () => { - const eventLogEntry = await getLogsFromCloudwatch( - EVENT_BUS_LOG_GROUP_NAME, - [ - '$.message_type = "EVENT_RECEIPT"', - '$.details.detail_type = "uk.nhs.notify.digital.letters.messages.request.skipped.v1"', - `$.details.event_detail = "*\\"messageReference\\":\\"${messageReference}\\"*"`, - `$.details.event_detail = "*\\"senderId\\":\\"${SENDER_ID_SKIPS_NOTIFY}\\"*"`, - ], - ); - - expect(eventLogEntry.length).toEqual(1); - }, 240); + await expectEventOnTestObserverQueue( + 'uk.nhs.notify.digital.letters.messages.request.skipped.v1', + (d) => { + const data = (d as any).data; + return ( + data.messageReference === messageReference && + data.senderId === SENDER_ID_SKIPS_NOTIFY + ); + }, + 60_000, + ); }); test('given PDMResourceAvailable event, when client does NOT exist then it goes to DLQ', async () => { @@ -212,18 +212,7 @@ test.describe('Digital Letters - Core Notify', () => { validatePDMResourceAvailable, ); - await Promise.all([ - // Verify the event is processed and a message appears in the Lambda logs - expectToPassEventually(async () => { - const filteredLogs = await getLogsFromCloudwatch( - CORE_NOTIFIER_LAMBDA_LOG_GROUP_NAME, - ['$.message.description = "0 of 1 records processed successfully"'], - ); - - expect(filteredLogs.length).toBeGreaterThanOrEqual(1); - }, 240), - // Verify there is a message in the DLQ - expectMessageContainingString(CORE_NOTIFIER_DLQ_NAME, eventId, 240), - ]); + // Verify there is a message in the DLQ + expectMessageContainingString(CORE_NOTIFIER_DLQ_NAME, eventId, 240); }); }); diff --git a/tests/playwright/digital-letters-component-tests/mesh-poll-download.component.spec.ts b/tests/playwright/digital-letters-component-tests/mesh-poll-download.component.spec.ts index 3f02fcbb..53f39466 100644 --- a/tests/playwright/digital-letters-component-tests/mesh-poll-download.component.spec.ts +++ b/tests/playwright/digital-letters-component-tests/mesh-poll-download.component.spec.ts @@ -102,10 +102,10 @@ test.describe('Digital Letters - MESH Poll and Download', () => { 'uk.nhs.notify.digital.letters.mesh.inbox.message.received.v1', (detail) => { const { data } = detail as { - data?: { meshMessageId?: string; senderId?: string }; + data: { meshMessageId?: string; senderId?: string }; }; return ( - data?.meshMessageId === meshMessageId && data?.senderId === senderId + data.meshMessageId === meshMessageId && data.senderId === senderId ); }, 60_000, @@ -119,11 +119,11 @@ test.describe('Digital Letters - MESH Poll and Download', () => { 'uk.nhs.notify.digital.letters.mesh.inbox.message.downloaded.v1', (detail) => { const { data } = detail as { - data?: { messageReference?: string; senderId?: string }; + data: { messageReference?: string; senderId?: string }; }; return ( - data?.messageReference === messageReference && - data?.senderId === senderId + data.messageReference === messageReference && + data.senderId === senderId ); }, 60_000, @@ -132,28 +132,32 @@ test.describe('Digital Letters - MESH Poll and Download', () => { async function expectMeshInboxMessageInvalidEvent( meshMessageId: string, - messageReference: string, + messageReference: string | undefined, failureCode = 'DL_CLIV_005', ): Promise { await expectEventOnTestObserverQueue( 'uk.nhs.notify.digital.letters.mesh.inbox.message.invalid.v1', (detail) => { const { data } = detail as { - data?: { - meshMessageId?: string; + data: { + meshMessageId: string; messageReference?: string; - senderId?: string; - failureCode?: string; + senderId: string; + failureCode: string; }; }; + const messageReferenceMatches = + messageReference === undefined || + messageReference === '' || + data?.messageReference === messageReference; return ( - data?.meshMessageId === meshMessageId && - data?.messageReference === messageReference && - data?.senderId === senderId && - data?.failureCode === failureCode + data.meshMessageId === meshMessageId && + messageReferenceMatches && + data.senderId === senderId && + data.failureCode === failureCode ); }, - 60_000, + 120_000, ); } @@ -189,6 +193,7 @@ test.describe('Digital Letters - MESH Poll and Download', () => { }); test('given invalid PDM request should publish invalid event, log an error, acknowledge message', async () => { + test.setTimeout(340_000); const meshMessageId = `${Date.now()}_TEST_${uuidv4().slice(0, 8)}`; const messageReference = uuidv4(); const invalidPdmRequest = { ...validPdmRequest, id: undefined }; diff --git a/tests/playwright/digital-letters-component-tests/move-scanned-files.component.spec.ts b/tests/playwright/digital-letters-component-tests/move-scanned-files.component.spec.ts index c04ac2bf..30648786 100644 --- a/tests/playwright/digital-letters-component-tests/move-scanned-files.component.spec.ts +++ b/tests/playwright/digital-letters-component-tests/move-scanned-files.component.spec.ts @@ -1,6 +1,5 @@ import { expect, test } from '@playwright/test'; import { - EVENT_BUS_LOG_GROUP_NAME, FILE_QUARANTINE_S3_BUCKET_NAME, FILE_SAFE_S3_BUCKET_NAME, MOVE_SCANNED_FILES_DLQ_NAME, @@ -12,6 +11,7 @@ import { SENDER_ID_SKIPS_NOTIFY } from 'constants/tests-constants'; import { getLogsFromCloudwatch } from 'helpers/cloudwatch-helpers'; import expectToPassEventually from 'helpers/expectations'; import { expectMessageContainingString } from 'helpers/sqs-helpers'; +import { expectEventOnTestObserverQueue } from 'helpers/test-observer-helpers'; import { v4 as uuidv4 } from 'uuid'; import { PutObjectCommand } from '@aws-sdk/client-s3'; import { getS3ObjectMetadata, s3Client } from 'utils'; @@ -55,22 +55,21 @@ test.describe('Digital Letters - Move Scanned Files', () => { }, 240); // Verify the event is published in the event bus - await expectToPassEventually(async () => { - const expectedLetterUri = `s3://${FILE_SAFE_S3_BUCKET_NAME}/${objectKey}`; - const eventLogEntry = await getLogsFromCloudwatch( - EVENT_BUS_LOG_GROUP_NAME, - [ - '$.message_type = "EVENT_RECEIPT"', - '$.details.detail_type = "uk.nhs.notify.digital.letters.print.file.safe.v1"', - `$.details.event_detail = "*\\"messageReference\\":\\"${messageReference}\\"*"`, - `$.details.event_detail = "*\\"senderId\\":\\"${SENDER_ID_SKIPS_NOTIFY}\\"*"`, - `$.details.event_detail = "*\\"createdAt\\":\\"${createdAt}\\"*"`, - `$.details.event_detail = "*\\"letterUri\\":\\"${expectedLetterUri}\\"*"`, - ], - ); - - expect(eventLogEntry.length).toEqual(1); - }, 240); + await expectEventOnTestObserverQueue( + 'uk.nhs.notify.digital.letters.print.file.safe.v1', + (detail) => { + const data = ( + detail as { + data: { messageReference?: string; senderId?: string }; + } + ).data; + return ( + data.messageReference === messageReference && + data.senderId === SENDER_ID_SKIPS_NOTIFY + ); + }, + 60_000, + ); await expectToPassEventually(async () => { const metadata = await getS3ObjectMetadata({ @@ -137,22 +136,21 @@ test.describe('Digital Letters - Move Scanned Files', () => { }, 240); // Verify the event is published in the event bus - await expectToPassEventually(async () => { - const expectedLetterUri = `s3://${FILE_QUARANTINE_S3_BUCKET_NAME}/${objectKey}`; - const eventLogEntry = await getLogsFromCloudwatch( - EVENT_BUS_LOG_GROUP_NAME, - [ - '$.message_type = "EVENT_RECEIPT"', - '$.details.detail_type = "uk.nhs.notify.digital.letters.print.file.quarantined.v1"', - `$.details.event_detail = "*\\"messageReference\\":\\"${messageReference}\\"*"`, - `$.details.event_detail = "*\\"senderId\\":\\"${SENDER_ID_SKIPS_NOTIFY}\\"*"`, - `$.details.event_detail = "*\\"createdAt\\":\\"${createdAt}\\"*"`, - `$.details.event_detail = "*\\"letterUri\\":\\"${expectedLetterUri}\\"*"`, - ], - ); - - expect(eventLogEntry.length).toEqual(1); - }, 240); + await expectEventOnTestObserverQueue( + 'uk.nhs.notify.digital.letters.print.file.quarantined.v1', + (detail) => { + const data = ( + detail as { + data: { messageReference?: string; senderId?: string }; + } + ).data; + return ( + data.messageReference === messageReference && + data.senderId === SENDER_ID_SKIPS_NOTIFY + ); + }, + 60_000, + ); await expectToPassEventually(async () => { const metadata = await getS3ObjectMetadata({ diff --git a/tests/playwright/digital-letters-component-tests/pdm-poll.component.spec.ts b/tests/playwright/digital-letters-component-tests/pdm-poll.component.spec.ts index fca7aff5..f0e78dd2 100644 --- a/tests/playwright/digital-letters-component-tests/pdm-poll.component.spec.ts +++ b/tests/playwright/digital-letters-component-tests/pdm-poll.component.spec.ts @@ -1,6 +1,5 @@ import { expect, test } from '@playwright/test'; import { - EVENT_BUS_LOG_GROUP_NAME, PDM_POLL_DLQ_NAME, PDM_POLL_LAMBDA_LOG_GROUP_NAME, } from 'constants/backend-constants'; @@ -12,6 +11,7 @@ import { getLogsFromCloudwatch } from 'helpers/cloudwatch-helpers'; import eventPublisher from 'helpers/event-bus-helpers'; import expectToPassEventually from 'helpers/expectations'; import { expectMessageContainingString, purgeQueue } from 'helpers/sqs-helpers'; +import { expectEventOnTestObserverQueue } from 'helpers/test-observer-helpers'; import { v4 as uuidv4 } from 'uuid'; const baseEvent = { @@ -71,20 +71,13 @@ test.describe('PDM Poll', () => { validatePDMResourceSubmitted, ); - await expectToPassEventually(async () => { - const eventLogEntry = await getLogsFromCloudwatch( - EVENT_BUS_LOG_GROUP_NAME, - [ - '$.message_type = "EVENT_RECEIPT"', - '$.details.detail_type = "uk.nhs.notify.digital.letters.pdm.resource.available.v1"', - `$.details.event_detail = "*\\"messageReference\\":\\"${messageReference}\\"*"`, - `$.details.event_detail = "*\\"odsCode\\":\\"Y05868\\"*"`, - `$.details.event_detail = "*\\"nhsNumber\\":\\"9912003071\\"*"`, - ], - ); - - expect(eventLogEntry.length).toEqual(1); - }, 120); + const availableDetail = await expectEventOnTestObserverQueue( + 'uk.nhs.notify.digital.letters.pdm.resource.available.v1', + (d) => (d as any).data.messageReference === messageReference, + 60_000, + ); + expect((availableDetail as any).data.odsCode).toBe('Y05868'); + expect((availableDetail as any).data.nhsNumber).toBe('9912003071'); }); test('should send a pdm.resource.unavailable event when unavailable in PDM', async () => { @@ -108,19 +101,12 @@ test.describe('PDM Poll', () => { validatePDMResourceSubmitted, ); - await expectToPassEventually(async () => { - const eventLogEntry = await getLogsFromCloudwatch( - EVENT_BUS_LOG_GROUP_NAME, - [ - '$.message_type = "EVENT_RECEIPT"', - '$.details.detail_type = "uk.nhs.notify.digital.letters.pdm.resource.unavailable.v1"', - `$.details.event_detail = "*\\"messageReference\\":\\"${messageReference}\\"*"`, - `$.details.event_detail = "*\\"retryCount\\":0*"`, - ], - ); - - expect(eventLogEntry.length).toEqual(1); - }, 120); + const unavailableDetail = await expectEventOnTestObserverQueue( + 'uk.nhs.notify.digital.letters.pdm.resource.unavailable.v1', + (d) => (d as any).data.messageReference === messageReference, + 60_000, + ); + expect((unavailableDetail as any).data.retryCount).toBe(0); }); }); @@ -147,20 +133,13 @@ test.describe('PDM Poll', () => { validatePDMResourceUnavailable, ); - await expectToPassEventually(async () => { - const eventLogEntry = await getLogsFromCloudwatch( - EVENT_BUS_LOG_GROUP_NAME, - [ - '$.message_type = "EVENT_RECEIPT"', - '$.details.detail_type = "uk.nhs.notify.digital.letters.pdm.resource.available.v1"', - `$.details.event_detail = "*\\"messageReference\\":\\"${messageReference}\\"*"`, - `$.details.event_detail = "*\\"odsCode\\":\\"Y05868\\"*"`, - `$.details.event_detail = "*\\"nhsNumber\\":\\"9912003071\\"*"`, - ], - ); - - expect(eventLogEntry.length).toEqual(1); - }, 120); + const availableDetail2 = await expectEventOnTestObserverQueue( + 'uk.nhs.notify.digital.letters.pdm.resource.available.v1', + (d) => (d as any).data.messageReference === messageReference, + 60_000, + ); + expect((availableDetail2 as any).data.odsCode).toBe('Y05868'); + expect((availableDetail2 as any).data.nhsNumber).toBe('9912003071'); }); test('should send a pdm.resource.unavailable event when still unavailable in PDM', async () => { @@ -185,19 +164,15 @@ test.describe('PDM Poll', () => { validatePDMResourceUnavailable, ); - await expectToPassEventually(async () => { - const eventLogEntry = await getLogsFromCloudwatch( - EVENT_BUS_LOG_GROUP_NAME, - [ - '$.message_type = "EVENT_RECEIPT"', - '$.details.detail_type = "uk.nhs.notify.digital.letters.pdm.resource.unavailable.v1"', - `$.details.event_detail = "*\\"messageReference\\":\\"${messageReference}\\"*"`, - `$.details.event_detail = "*\\"retryCount\\":1*"`, - ], - ); - - expect(eventLogEntry.length).toEqual(1); - }, 120); + const unavailableDetail2 = await expectEventOnTestObserverQueue( + 'uk.nhs.notify.digital.letters.pdm.resource.unavailable.v1', + (d) => { + const data = (d as any).data; + return data.messageReference === messageReference && data.retryCount === 1; + }, + 60_000, + ); + expect((unavailableDetail2 as any).data.retryCount).toBe(1); }); test('should send a pdm.resource.retries.exceeded event when unavailable in PDM after 10 retries', async () => { @@ -222,19 +197,12 @@ test.describe('PDM Poll', () => { validatePDMResourceUnavailable, ); - await expectToPassEventually(async () => { - const eventLogEntry = await getLogsFromCloudwatch( - EVENT_BUS_LOG_GROUP_NAME, - [ - '$.message_type = "EVENT_RECEIPT"', - '$.details.detail_type = "uk.nhs.notify.digital.letters.pdm.resource.retries.exceeded.v1"', - `$.details.event_detail = "*\\"messageReference\\":\\"${messageReference}\\"*"`, - `$.details.event_detail = "*\\"retryCount\\":10*"`, - ], - ); - - expect(eventLogEntry.length).toEqual(1); - }, 120); + const exceededDetail = await expectEventOnTestObserverQueue( + 'uk.nhs.notify.digital.letters.pdm.resource.retries.exceeded.v1', + (d) => (d as any).data.messageReference === messageReference, + 60_000, + ); + expect((exceededDetail as any).data.retryCount).toBe(10); }); }); @@ -265,19 +233,6 @@ test.describe('PDM Poll', () => { () => true, ); - await Promise.all([ - expectToPassEventually(async () => { - const eventLogEntry = await getLogsFromCloudwatch( - PDM_POLL_LAMBDA_LOG_GROUP_NAME, - [ - `$.message.err[0].message = "must have required property 'retryCount'"`, - ], - ); - - expect(eventLogEntry.length).toEqual(1); - }, 150), - - expectMessageContainingString(PDM_POLL_DLQ_NAME, eventId, 150), - ]); + expectMessageContainingString(PDM_POLL_DLQ_NAME, eventId, 150); }); }); diff --git a/tests/playwright/digital-letters-component-tests/send-reports-trust.component.spec.ts b/tests/playwright/digital-letters-component-tests/send-reports-trust.component.spec.ts index eb14754d..1eaf834d 100644 --- a/tests/playwright/digital-letters-component-tests/send-reports-trust.component.spec.ts +++ b/tests/playwright/digital-letters-component-tests/send-reports-trust.component.spec.ts @@ -5,8 +5,8 @@ import { REPORTING_S3_BUCKET_NAME, REPORT_SENDER_DLQ_NAME, } from 'constants/backend-constants'; -import { getLogsFromCloudwatch } from 'helpers/cloudwatch-helpers'; import eventPublisher from 'helpers/event-bus-helpers'; +import { expectEventOnTestObserverQueue } from 'helpers/test-observer-helpers'; import expectToPassEventually from 'helpers/expectations'; import { downloadFromS3, uploadToS3 } from 'helpers/s3-helpers'; import { expectMessageContainingString } from 'helpers/sqs-helpers'; @@ -53,35 +53,23 @@ test.describe('Digital Letters - Send reports to Trust', () => { async function expectReportSentEventAndMeshMessageSent( meshMailboxReportsId: string, ): Promise { - await expectToPassEventually(async () => { - const eventLogEntry = await getLogsFromCloudwatch( - `/aws/vendedlogs/events/event-bus/nhs-${ENV}-dl`, - [ - '$.message_type = "EVENT_RECEIPT"', - '$.details.detail_type = "uk.nhs.notify.digital.letters.reporting.report.sent.v1"', - `$.details.event_detail = "*\\"meshMailboxReportsId\\":\\"${meshMailboxReportsId}\\"*"`, - `$.details.event_detail = "*\\"senderId\\":\\"${senderId}\\"*"`, - ], - ); - - expect(eventLogEntry.length).toBeGreaterThanOrEqual(1); - - const parsedEvents = eventLogEntry.map((entry: any) => - JSON.parse(entry.details.event_detail), - ); + const detail = await expectEventOnTestObserverQueue( + 'uk.nhs.notify.digital.letters.reporting.report.sent.v1', + (d) => + (d.data as any)?.meshMailboxReportsId === meshMailboxReportsId && + (d.data as any)?.senderId === senderId, + 120_000, + ); - for (const event of parsedEvents) { - const { sentMeshMessageId } = event.data; - expect(sentMeshMessageId).toBeTruthy(); - // Mock MESH uses NON_PII_S3_BUCKET_NAME bucket, the object key is the sentMeshMessageId. - const storedMessage = await downloadFromS3( - NON_PII_S3_BUCKET_NAME, - `mock-mesh/mock-mailbox/out/${trustMeshMailboxReportsId}/${sentMeshMessageId}`, - ); + const { sentMeshMessageId } = detail.data as any; + expect(sentMeshMessageId).toBeTruthy(); + // Mock MESH uses NON_PII_S3_BUCKET_NAME bucket, the object key is the sentMeshMessageId. + const storedMessage = await downloadFromS3( + NON_PII_S3_BUCKET_NAME, + `mock-mesh/mock-mailbox/out/${trustMeshMailboxReportsId}/${sentMeshMessageId}`, + ); - expect(storedMessage.body).toContain(messageContent); - } - }, 120_000); + expect(storedMessage.body).toContain(messageContent); } test('should send a ReportSent event following a successful reportGenerated event', async () => { @@ -95,9 +83,7 @@ test.describe('Digital Letters - Send reports to Trust', () => { await uploadToS3(messageContent, REPORTING_S3_BUCKET_NAME, reportKey); await publishReportGeneratedEvent(reportKey); - await expectToPassEventually(async () => { - await expectReportSentEventAndMeshMessageSent(trustMeshMailboxReportsId); - }, 120_000); + await expectReportSentEventAndMeshMessageSent(trustMeshMailboxReportsId); }); test('should send message to report-sender DLQ when file does not exists', async () => { diff --git a/tests/playwright/digital-letters-component-tests/ttl-create.component.spec.ts b/tests/playwright/digital-letters-component-tests/ttl-create.component.spec.ts index b1a30c10..65260091 100644 --- a/tests/playwright/digital-letters-component-tests/ttl-create.component.spec.ts +++ b/tests/playwright/digital-letters-component-tests/ttl-create.component.spec.ts @@ -2,7 +2,6 @@ import { expect, test } from '@playwright/test'; import { CREATE_TTL_DLQ_NAME, CREATE_TTL_LAMBDA_LOG_GROUP_NAME, - ENV, } from 'constants/backend-constants'; import { SENDER_ID_SKIPS_NOTIFY, @@ -17,6 +16,7 @@ import { getTtl } from 'helpers/dynamodb-helpers'; import eventPublisher from 'helpers/event-bus-helpers'; import expectToPassEventually from 'helpers/expectations'; import { expectMessageContainingString, purgeQueue } from 'helpers/sqs-helpers'; +import { expectEventOnTestObserverQueue } from 'helpers/test-observer-helpers'; import { v4 as uuidv4 } from 'uuid'; test.describe('Digital Letters - Create TTL', () => { @@ -50,6 +50,7 @@ test.describe('Digital Letters - Create TTL', () => { }; test('should create TTL and publish item enqueued event following message downloaded event', async () => { + test.setTimeout(110_000); // 30s TTL check + 60s event + 20s buffer const letterId = uuidv4(); const messageUri = `https://example.com/ttl/resource/${letterId}`; const messageReference = letterId; @@ -80,21 +81,18 @@ test.describe('Digital Letters - Create TTL', () => { }); // Verify item enqueued event published - await expectToPassEventually(async () => { - const eventLogEntry = await getLogsFromCloudwatch( - `/aws/vendedlogs/events/event-bus/nhs-${ENV}-dl`, - [ - '$.message_type = "EVENT_RECEIPT"', - '$.details.detail_type = "uk.nhs.notify.digital.letters.queue.item.enqueued.v1"', - `$.details.event_detail = "*\\"messageUri\\":\\"${messageUri}\\"*"`, - ], - ); - - expect(eventLogEntry.length).toEqual(1); - }); + await expectEventOnTestObserverQueue( + 'uk.nhs.notify.digital.letters.queue.item.enqueued.v1', + (detail) => { + const data = (detail as { data: { messageUri: string } }).data; + return data.messageUri === messageUri; + }, + 60_000, + ); }); test('should create TTL and publish item enqueued event following message downloaded event - direct to print', async () => { + test.setTimeout(110_000); // 30s TTL check + 60s event + 20s buffer const letterId = uuidv4(); const messageUri = `https://example.com/ttl/resource/${letterId}`; const messageReference = letterId; @@ -123,18 +121,14 @@ test.describe('Digital Letters - Create TTL', () => { }); // Verify item enqueued event published - await expectToPassEventually(async () => { - const eventLogEntry = await getLogsFromCloudwatch( - `/aws/vendedlogs/events/event-bus/nhs-${ENV}-dl`, - [ - '$.message_type = "EVENT_RECEIPT"', - '$.details.detail_type = "uk.nhs.notify.digital.letters.queue.item.enqueued.v1"', - `$.details.event_detail = "*\\"messageUri\\":\\"${messageUri}\\"*"`, - ], - ); - - expect(eventLogEntry.length).toEqual(1); - }); + await expectEventOnTestObserverQueue( + 'uk.nhs.notify.digital.letters.queue.item.enqueued.v1', + (detail) => { + const data = (detail as { data: { messageUri: string } }).data; + return data.messageUri === messageUri; + }, + 60_000, + ); }); test('should send invalid event to dlq', async () => { diff --git a/tests/playwright/digital-letters-component-tests/ttl-handle.component.spec.ts b/tests/playwright/digital-letters-component-tests/ttl-handle.component.spec.ts index d57d71f8..3def2bda 100644 --- a/tests/playwright/digital-letters-component-tests/ttl-handle.component.spec.ts +++ b/tests/playwright/digital-letters-component-tests/ttl-handle.component.spec.ts @@ -1,10 +1,14 @@ import { expect, test } from '@playwright/test'; -import { ENV, HANDLE_TTL_DLQ_NAME } from 'constants/backend-constants'; +import { + HANDLE_TTL_DLQ_NAME, + TTL_HANDLE_EXPIRY_LAMBDA_LOG_GROUP_NAME, +} from 'constants/backend-constants'; import { MESHInboxMessageDownloaded } from 'digital-letters-events'; import { getLogsFromCloudwatch } from 'helpers/cloudwatch-helpers'; import { deleteTtl, putTtl } from 'helpers/dynamodb-helpers'; import expectToPassEventually from 'helpers/expectations'; import { expectMessageContainingString, purgeQueue } from 'helpers/sqs-helpers'; +import { expectEventOnTestObserverQueue } from 'helpers/test-observer-helpers'; import { v4 as uuidv4 } from 'uuid'; test.describe('Digital Letters - Handle TTL', () => { @@ -70,7 +74,7 @@ test.describe('Digital Letters - Handle TTL', () => { await expectToPassEventually(async () => { const eventLogEntry = await getLogsFromCloudwatch( - `/aws/lambda/nhs-${ENV}-dl-ttl-handle-expiry`, + TTL_HANDLE_EXPIRY_LAMBDA_LOG_GROUP_NAME, [ `$.message.messageUri = "${messageUri}"`, '$.message.description = "ItemDequeued event not sent as item withdrawn"', @@ -78,7 +82,7 @@ test.describe('Digital Letters - Handle TTL', () => { ); expect(eventLogEntry.length).toEqual(1); - }); + }, 120_000); }); test('should handle expired item', async () => { @@ -111,18 +115,14 @@ test.describe('Digital Letters - Handle TTL', () => { const deleteResponseCode = await deleteTtl(senderId, messageReference); expect(deleteResponseCode).toBe(200); - await expectToPassEventually(async () => { - const eventLogEntry = await getLogsFromCloudwatch( - `/aws/vendedlogs/events/event-bus/nhs-${ENV}-dl`, - [ - '$.message_type = "EVENT_RECEIPT"', - '$.details.detail_type = "uk.nhs.notify.digital.letters.queue.item.dequeued.v1"', - `$.details.event_detail = "*\\"messageUri\\":\\"${messageUri}\\"*"`, - ], - ); - - expect(eventLogEntry.length).toEqual(1); - }); + await expectEventOnTestObserverQueue( + 'uk.nhs.notify.digital.letters.queue.item.dequeued.v1', + (detail) => { + const data = (detail as { data: { messageUri: string } }).data; + return data.messageUri === messageUri; + }, + 120_000, + ); }); test('should send invalid item to dlq', async () => { diff --git a/tests/playwright/helpers/test-observer-helpers.ts b/tests/playwright/helpers/test-observer-helpers.ts index 2740b7de..158edf7e 100644 --- a/tests/playwright/helpers/test-observer-helpers.ts +++ b/tests/playwright/helpers/test-observer-helpers.ts @@ -1,4 +1,5 @@ import { + ChangeMessageVisibilityCommand, DeleteMessageCommand, ReceiveMessageCommand, } from '@aws-sdk/client-sqs'; @@ -49,6 +50,13 @@ export async function expectEventOnTestObserverQueue( ); return detail; } + await sqsClient.send( + new ChangeMessageVisibilityCommand({ + QueueUrl: queueUrl, + ReceiptHandle: msg.ReceiptHandle!, + VisibilityTimeout: 0, + }), + ); } } } From 3fa1dc3a5d3a863a277f752216445f2e819f5127 Mon Sep 17 00:00:00 2001 From: lapenna-bjss Date: Thu, 23 Apr 2026 09:55:33 +0100 Subject: [PATCH 04/16] Fix linting issues --- .../core-notify.component.spec.ts | 6 +++--- .../move-scanned-files.component.spec.ts | 16 ++++++---------- .../pdm-poll.component.spec.ts | 13 +++++-------- .../send-reports-trust.component.spec.ts | 1 - .../ttl-create.component.spec.ts | 4 ++-- .../ttl-handle.component.spec.ts | 2 +- 6 files changed, 17 insertions(+), 25 deletions(-) diff --git a/tests/playwright/digital-letters-component-tests/core-notify.component.spec.ts b/tests/playwright/digital-letters-component-tests/core-notify.component.spec.ts index cf25ea57..531261b1 100644 --- a/tests/playwright/digital-letters-component-tests/core-notify.component.spec.ts +++ b/tests/playwright/digital-letters-component-tests/core-notify.component.spec.ts @@ -86,7 +86,7 @@ test.describe('Digital Letters - Core Notify', () => { const submittedDetail = await expectEventOnTestObserverQueue( 'uk.nhs.notify.digital.letters.messages.request.submitted.v1', (d) => { - const data = (d as any).data; + const { data } = d as any; return ( data.messageReference === messageReference && data.senderId === SENDER_ID_VALID_FOR_NOTIFY_SANDBOX @@ -140,7 +140,7 @@ test.describe('Digital Letters - Core Notify', () => { const rejectedDetail = await expectEventOnTestObserverQueue( 'uk.nhs.notify.digital.letters.messages.request.rejected.v1', (d) => { - const data = (d as any).data; + const { data } = d as any; return ( data.messageReference === messageReference && data.senderId === SENDER_ID_THAT_TRIGGERS_ERROR_IN_NOTIFY_SANDBOX @@ -180,7 +180,7 @@ test.describe('Digital Letters - Core Notify', () => { await expectEventOnTestObserverQueue( 'uk.nhs.notify.digital.letters.messages.request.skipped.v1', (d) => { - const data = (d as any).data; + const { data } = d as any; return ( data.messageReference === messageReference && data.senderId === SENDER_ID_SKIPS_NOTIFY diff --git a/tests/playwright/digital-letters-component-tests/move-scanned-files.component.spec.ts b/tests/playwright/digital-letters-component-tests/move-scanned-files.component.spec.ts index 30648786..f72366ff 100644 --- a/tests/playwright/digital-letters-component-tests/move-scanned-files.component.spec.ts +++ b/tests/playwright/digital-letters-component-tests/move-scanned-files.component.spec.ts @@ -58,11 +58,9 @@ test.describe('Digital Letters - Move Scanned Files', () => { await expectEventOnTestObserverQueue( 'uk.nhs.notify.digital.letters.print.file.safe.v1', (detail) => { - const data = ( - detail as { - data: { messageReference?: string; senderId?: string }; - } - ).data; + const { data } = detail as { + data: { messageReference?: string; senderId?: string }; + }; return ( data.messageReference === messageReference && data.senderId === SENDER_ID_SKIPS_NOTIFY @@ -139,11 +137,9 @@ test.describe('Digital Letters - Move Scanned Files', () => { await expectEventOnTestObserverQueue( 'uk.nhs.notify.digital.letters.print.file.quarantined.v1', (detail) => { - const data = ( - detail as { - data: { messageReference?: string; senderId?: string }; - } - ).data; + const { data } = detail as { + data: { messageReference?: string; senderId?: string }; + }; return ( data.messageReference === messageReference && data.senderId === SENDER_ID_SKIPS_NOTIFY diff --git a/tests/playwright/digital-letters-component-tests/pdm-poll.component.spec.ts b/tests/playwright/digital-letters-component-tests/pdm-poll.component.spec.ts index f0e78dd2..4d4d77e2 100644 --- a/tests/playwright/digital-letters-component-tests/pdm-poll.component.spec.ts +++ b/tests/playwright/digital-letters-component-tests/pdm-poll.component.spec.ts @@ -1,15 +1,10 @@ import { expect, test } from '@playwright/test'; -import { - PDM_POLL_DLQ_NAME, - PDM_POLL_LAMBDA_LOG_GROUP_NAME, -} from 'constants/backend-constants'; +import { PDM_POLL_DLQ_NAME } from 'constants/backend-constants'; import { validatePDMResourceSubmitted, validatePDMResourceUnavailable, } from 'digital-letters-events'; -import { getLogsFromCloudwatch } from 'helpers/cloudwatch-helpers'; import eventPublisher from 'helpers/event-bus-helpers'; -import expectToPassEventually from 'helpers/expectations'; import { expectMessageContainingString, purgeQueue } from 'helpers/sqs-helpers'; import { expectEventOnTestObserverQueue } from 'helpers/test-observer-helpers'; import { v4 as uuidv4 } from 'uuid'; @@ -167,8 +162,10 @@ test.describe('PDM Poll', () => { const unavailableDetail2 = await expectEventOnTestObserverQueue( 'uk.nhs.notify.digital.letters.pdm.resource.unavailable.v1', (d) => { - const data = (d as any).data; - return data.messageReference === messageReference && data.retryCount === 1; + const { data } = d as any; + return ( + data.messageReference === messageReference && data.retryCount === 1 + ); }, 60_000, ); diff --git a/tests/playwright/digital-letters-component-tests/send-reports-trust.component.spec.ts b/tests/playwright/digital-letters-component-tests/send-reports-trust.component.spec.ts index 1eaf834d..b43ce3c7 100644 --- a/tests/playwright/digital-letters-component-tests/send-reports-trust.component.spec.ts +++ b/tests/playwright/digital-letters-component-tests/send-reports-trust.component.spec.ts @@ -7,7 +7,6 @@ import { } from 'constants/backend-constants'; import eventPublisher from 'helpers/event-bus-helpers'; import { expectEventOnTestObserverQueue } from 'helpers/test-observer-helpers'; -import expectToPassEventually from 'helpers/expectations'; import { downloadFromS3, uploadToS3 } from 'helpers/s3-helpers'; import { expectMessageContainingString } from 'helpers/sqs-helpers'; import { v4 as uuidv4 } from 'uuid'; diff --git a/tests/playwright/digital-letters-component-tests/ttl-create.component.spec.ts b/tests/playwright/digital-letters-component-tests/ttl-create.component.spec.ts index 65260091..6d309a06 100644 --- a/tests/playwright/digital-letters-component-tests/ttl-create.component.spec.ts +++ b/tests/playwright/digital-letters-component-tests/ttl-create.component.spec.ts @@ -84,7 +84,7 @@ test.describe('Digital Letters - Create TTL', () => { await expectEventOnTestObserverQueue( 'uk.nhs.notify.digital.letters.queue.item.enqueued.v1', (detail) => { - const data = (detail as { data: { messageUri: string } }).data; + const { data } = detail as { data: { messageUri: string } }; return data.messageUri === messageUri; }, 60_000, @@ -124,7 +124,7 @@ test.describe('Digital Letters - Create TTL', () => { await expectEventOnTestObserverQueue( 'uk.nhs.notify.digital.letters.queue.item.enqueued.v1', (detail) => { - const data = (detail as { data: { messageUri: string } }).data; + const { data } = detail as { data: { messageUri: string } }; return data.messageUri === messageUri; }, 60_000, diff --git a/tests/playwright/digital-letters-component-tests/ttl-handle.component.spec.ts b/tests/playwright/digital-letters-component-tests/ttl-handle.component.spec.ts index 3def2bda..7d20f7ef 100644 --- a/tests/playwright/digital-letters-component-tests/ttl-handle.component.spec.ts +++ b/tests/playwright/digital-letters-component-tests/ttl-handle.component.spec.ts @@ -118,7 +118,7 @@ test.describe('Digital Letters - Handle TTL', () => { await expectEventOnTestObserverQueue( 'uk.nhs.notify.digital.letters.queue.item.dequeued.v1', (detail) => { - const data = (detail as { data: { messageUri: string } }).data; + const { data } = detail as { data: { messageUri: string } }; return data.messageUri === messageUri; }, 120_000, From 2a98cf20e7d75aeabf1b6ea4d88b27e524eea0b9 Mon Sep 17 00:00:00 2001 From: lapenna-bjss Date: Thu, 23 Apr 2026 16:39:09 +0100 Subject: [PATCH 05/16] Test --- .../config/component/component.config.ts | 6 +- .../config/component/component.setup.ts | 15 ++++ .../core-notify.component.spec.ts | 41 +--------- .../mesh-acknowledge.component.spec.ts | 80 ++++++++----------- .../mesh-poll-download.component.spec.ts | 28 ++----- .../move-scanned-files.component.spec.ts | 36 +-------- .../pdm-poll.component.spec.ts | 10 +-- .../pdm-uploader.component.spec.ts | 53 +++--------- .../print-analyser.component.spec.ts | 65 ++++++--------- .../print-status-handler.component.spec.ts | 58 +++++--------- .../report-scheduler.component.spec.ts | 59 +++++--------- .../send-reports-trust.component.spec.ts | 4 +- .../ttl-create.component.spec.ts | 20 +---- .../helpers/test-observer-helpers.ts | 28 ++++--- 14 files changed, 165 insertions(+), 338 deletions(-) create mode 100644 tests/playwright/config/component/component.setup.ts diff --git a/tests/playwright/config/component/component.config.ts b/tests/playwright/config/component/component.config.ts index 87ca74fa..4a784936 100644 --- a/tests/playwright/config/component/component.config.ts +++ b/tests/playwright/config/component/component.config.ts @@ -22,10 +22,14 @@ export default defineConfig({ name: 'firehose:teardown', testMatch: 'firehose.teardown.ts', }, + { + name: 'component:setup', + testMatch: 'component.setup.ts', + }, { name: 'component', testMatch: '*.component.spec.ts', - dependencies: ['senders:setup', 'firehose:setup'], + dependencies: ['senders:setup', 'firehose:setup', 'component:setup'], teardown: 'component:teardown', }, { diff --git a/tests/playwright/config/component/component.setup.ts b/tests/playwright/config/component/component.setup.ts new file mode 100644 index 00000000..710ce52b --- /dev/null +++ b/tests/playwright/config/component/component.setup.ts @@ -0,0 +1,15 @@ +import { PurgeQueueCommand } from '@aws-sdk/client-sqs'; +import { test as setup } from '@playwright/test'; +import { + SQS_URL_PREFIX, + TEST_OBSERVER_QUEUE_NAME, +} from 'constants/backend-constants'; +import { sqsClient } from 'utils'; + +setup('Purge test observer queue', async () => { + await sqsClient.send( + new PurgeQueueCommand({ + QueueUrl: `${SQS_URL_PREFIX}${TEST_OBSERVER_QUEUE_NAME}`, + }), + ); +}); diff --git a/tests/playwright/digital-letters-component-tests/core-notify.component.spec.ts b/tests/playwright/digital-letters-component-tests/core-notify.component.spec.ts index 531261b1..67498090 100644 --- a/tests/playwright/digital-letters-component-tests/core-notify.component.spec.ts +++ b/tests/playwright/digital-letters-component-tests/core-notify.component.spec.ts @@ -1,8 +1,5 @@ import { expect, test } from '@playwright/test'; -import { - CORE_NOTIFIER_DLQ_NAME, - CORE_NOTIFIER_LAMBDA_LOG_GROUP_NAME, -} from 'constants/backend-constants'; +import { CORE_NOTIFIER_DLQ_NAME } from 'constants/backend-constants'; import { SENDER_ID_SKIPS_NOTIFY, SENDER_ID_THAT_TRIGGERS_ERROR_IN_NOTIFY_SANDBOX, @@ -12,9 +9,7 @@ import { PDMResourceAvailable, validatePDMResourceAvailable, } from 'digital-letters-events'; -import { getLogsFromCloudwatch } from 'helpers/cloudwatch-helpers'; import eventPublisher from 'helpers/event-bus-helpers'; -import expectToPassEventually from 'helpers/expectations'; import { expectMessageContainingString, purgeQueue } from 'helpers/sqs-helpers'; import { expectEventOnTestObserverQueue } from 'helpers/test-observer-helpers'; import { v4 as uuidv4 } from 'uuid'; @@ -69,20 +64,6 @@ test.describe('Digital Letters - Core Notify', () => { validatePDMResourceAvailable, ); - // Verify the event is processed and a message appears in the Lambda logs - await expectToPassEventually(async () => { - const filteredLogs = await getLogsFromCloudwatch( - CORE_NOTIFIER_LAMBDA_LOG_GROUP_NAME, - [ - '$.message.description = "Successfully processed request and sent to Notify"', - `$.message.messageReference = "${messageReference}"`, - ], - ); - - expect(filteredLogs.length).toEqual(1); - }, 240); - - // Verify the event is published in the event bus const submittedDetail = await expectEventOnTestObserverQueue( 'uk.nhs.notify.digital.letters.messages.request.submitted.v1', (d) => { @@ -92,7 +73,7 @@ test.describe('Digital Letters - Core Notify', () => { data.senderId === SENDER_ID_VALID_FOR_NOTIFY_SANDBOX ); }, - 60_000, + 80_000, ); const submittedData = (submittedDetail as any).data; expect(submittedData.notifyId).toBeTruthy(); @@ -123,20 +104,6 @@ test.describe('Digital Letters - Core Notify', () => { validatePDMResourceAvailable, ); - // Verify the event is processed and a message appears in the Lambda logs - await expectToPassEventually(async () => { - const filteredLogs = await getLogsFromCloudwatch( - CORE_NOTIFIER_LAMBDA_LOG_GROUP_NAME, - [ - '$.message.description = "Failed sending request to Notify API"', - `$.message.messageReference = "${messageReference}"`, - ], - ); - - expect(filteredLogs.length).toEqual(1); - }, 240); - - // Verify the event is published in the event bus const rejectedDetail = await expectEventOnTestObserverQueue( 'uk.nhs.notify.digital.letters.messages.request.rejected.v1', (d) => { @@ -146,7 +113,7 @@ test.describe('Digital Letters - Core Notify', () => { data.senderId === SENDER_ID_THAT_TRIGGERS_ERROR_IN_NOTIFY_SANDBOX ); }, - 60_000, + 80_000, ); const rejectedData = (rejectedDetail as any).data; expect(rejectedData.failureCode).toBe('CM_INVALID_VALUE'); @@ -186,7 +153,7 @@ test.describe('Digital Letters - Core Notify', () => { data.senderId === SENDER_ID_SKIPS_NOTIFY ); }, - 60_000, + 80_000, ); }); diff --git a/tests/playwright/digital-letters-component-tests/mesh-acknowledge.component.spec.ts b/tests/playwright/digital-letters-component-tests/mesh-acknowledge.component.spec.ts index c65a1ed3..35526064 100644 --- a/tests/playwright/digital-letters-component-tests/mesh-acknowledge.component.spec.ts +++ b/tests/playwright/digital-letters-component-tests/mesh-acknowledge.component.spec.ts @@ -1,6 +1,5 @@ import { expect, test } from '@playwright/test'; import { - ENV, MESH_ACKNOWLEDGE_DLQ_NAME, NON_PII_S3_BUCKET_NAME, } from 'constants/backend-constants'; @@ -11,11 +10,11 @@ import { validateMESHInboxMessageDownloaded, validateMESHInboxMessageInvalid, } from 'digital-letters-events'; -import { getLogsFromCloudwatch } from 'helpers/cloudwatch-helpers'; import eventPublisher from 'helpers/event-bus-helpers'; import expectToPassEventually from 'helpers/expectations'; import { downloadFromS3 } from 'helpers/s3-helpers'; import { expectMessageContainingString } from 'helpers/sqs-helpers'; +import { expectEventOnTestObserverQueue } from 'helpers/test-observer-helpers'; import { v4 as uuidv4 } from 'uuid'; test.describe('Digital Letters - Mesh Acknowledger', () => { @@ -76,33 +75,25 @@ test.describe('Digital Letters - Mesh Acknowledger', () => { validateMESHInboxMessageDownloaded, ); - // The mailbox ID matches the Mock MESH config in SSM. const meshMailboxId = 'mock-mailbox'; // Verify message acknowledged event was published, // and extract sentMeshMessageId to use for the S3 lookup. - let sentMeshMessageId: string; - await expectToPassEventually(async () => { - const eventLogEntry = await getLogsFromCloudwatch( - `/aws/vendedlogs/events/event-bus/nhs-${ENV}-dl`, - [ - '$.message_type = "EVENT_RECEIPT"', - '$.details.detail_type = "uk.nhs.notify.digital.letters.mesh.inbox.message.acknowledged.v1"', - `$.details.event_detail = "*\\"messageReference\\":\\"${messageReference}\\"*"`, - `$.details.event_detail = "*\\"senderId\\":\\"${senderId}\\"*"`, - `$.details.event_detail = "*\\"meshMailboxId\\":\\"${sendersMeshMailboxId}\\"*"`, - `$.details.event_detail = "*\\"receivedMeshMessageId\\":\\"${meshMessageId}\\"*"`, - ], - ); - - expect(eventLogEntry.length).toEqual(1); - - const eventDetail = JSON.parse( - (eventLogEntry[0] as any).details.event_detail, - ); - sentMeshMessageId = eventDetail.data.sentMeshMessageId; - expect(sentMeshMessageId).toBeTruthy(); - }); + const acknowledgedDetail = await expectEventOnTestObserverQueue( + 'uk.nhs.notify.digital.letters.mesh.inbox.message.acknowledged.v1', + (d) => { + const { data } = d as any; + return ( + data.messageReference === messageReference && + data.senderId === senderId && + data.meshMailboxId === sendersMeshMailboxId && + data.receivedMeshMessageId === meshMessageId + ); + }, + 80_000, + ); + const { sentMeshMessageId } = (acknowledgedDetail as any).data; + expect(sentMeshMessageId).toBeTruthy(); // Verify MESH acknowledgement message was sent. await expectToPassEventually(async () => { @@ -208,29 +199,22 @@ test.describe('Digital Letters - Mesh Acknowledger', () => { // Verify message acknowledged event was published with statusCode 400, // and extract sentMeshMessageId to use for the S3 lookup. - let sentMeshMessageId: string; - await expectToPassEventually(async () => { - const eventLogEntry = await getLogsFromCloudwatch( - `/aws/vendedlogs/events/event-bus/nhs-${ENV}-dl`, - [ - '$.message_type = "EVENT_RECEIPT"', - '$.details.detail_type = "uk.nhs.notify.digital.letters.mesh.inbox.message.acknowledged.v1"', - `$.details.event_detail = "*\\"senderId\\":\\"${senderId}\\"*"`, - `$.details.event_detail = "*\\"meshMailboxId\\":\\"${sendersMeshMailboxId}\\"*"`, - `$.details.event_detail = "*\\"statusCode\\":400*"`, - `$.details.event_detail = "*\\"failureCode\\":\\"${failureCode}\\"*"`, - `$.details.event_detail = "*\\"receivedMeshMessageId\\":\\"${meshMessageId}\\"*"`, - ], - ); - - expect(eventLogEntry.length).toEqual(1); - - const eventDetail = JSON.parse( - (eventLogEntry[0] as any).details.event_detail, - ); - sentMeshMessageId = eventDetail.data.sentMeshMessageId; - expect(sentMeshMessageId).toBeTruthy(); - }, 120_000); + const acknowledgedDetail2 = await expectEventOnTestObserverQueue( + 'uk.nhs.notify.digital.letters.mesh.inbox.message.acknowledged.v1', + (d) => { + const { data } = d as any; + return ( + data.senderId === senderId && + data.meshMailboxId === sendersMeshMailboxId && + data.statusCode === 400 && + data.failureCode === failureCode && + data.receivedMeshMessageId === meshMessageId + ); + }, + 80_000, + ); + const { sentMeshMessageId } = (acknowledgedDetail2 as any).data; + expect(sentMeshMessageId).toBeTruthy(); // Verify MESH negative acknowledgement message was sent. await expectToPassEventually(async () => { diff --git a/tests/playwright/digital-letters-component-tests/mesh-poll-download.component.spec.ts b/tests/playwright/digital-letters-component-tests/mesh-poll-download.component.spec.ts index 53f39466..2ed6ac4e 100644 --- a/tests/playwright/digital-letters-component-tests/mesh-poll-download.component.spec.ts +++ b/tests/playwright/digital-letters-component-tests/mesh-poll-download.component.spec.ts @@ -1,6 +1,5 @@ import { expect, test } from '@playwright/test'; import { - EVENT_BUS_LOG_GROUP_NAME, MESH_DOWNLOAD_DLQ_NAME, MESH_DOWNLOAD_LAMBDA_LOG_GROUP_NAME, MESH_POLL_LAMBDA_NAME, @@ -108,7 +107,7 @@ test.describe('Digital Letters - MESH Poll and Download', () => { data.meshMessageId === meshMessageId && data.senderId === senderId ); }, - 60_000, + 80_000, ); } @@ -126,7 +125,7 @@ test.describe('Digital Letters - MESH Poll and Download', () => { data.senderId === senderId ); }, - 60_000, + 80_000, ); } @@ -180,7 +179,7 @@ test.describe('Digital Letters - MESH Poll and Download', () => { ); expect(storedMessage.body).toContain(messageContent); - }, 60_000); + }, 80_000); await expectToPassEventually(async () => { await expect(async () => { @@ -189,7 +188,7 @@ test.describe('Digital Letters - MESH Poll and Download', () => { `mock-mesh/${meshMailboxId}/in/${meshMessageId}`, ); }).rejects.toThrow('No objects found'); - }, 60_000); + }, 80_000); }); test('given invalid PDM request should publish invalid event, log an error, acknowledge message', async () => { @@ -227,7 +226,7 @@ test.describe('Digital Letters - MESH Poll and Download', () => { `mock-mesh/${meshMailboxId}/in/${meshMessageId}`, ); }).rejects.toThrow('No objects found'); - }, 60_000); + }, 80_000); }); test('should send message to mesh-download DLQ when download fails', async () => { @@ -326,7 +325,7 @@ test.describe('Digital Letters - MESH Poll and Download', () => { `mock-mesh/${meshMailboxId}/in/${meshMessageId}`, ); }).rejects.toThrow('No objects found'); - }, 60_000); + }, 80_000); }); test('should skip publishing downloaded event and acknowledge message when document already exists in S3', async () => { @@ -384,19 +383,6 @@ test.describe('Digital Letters - MESH Poll and Download', () => { expect(warnLogEntry.length).toBeGreaterThanOrEqual(1); }, 120_000); - // Assert that no MESHInboxMessageDownloaded event was published - await expectToPassEventually(async () => { - const downloadedEvents = await getLogsFromCloudwatch( - EVENT_BUS_LOG_GROUP_NAME, - [ - '$.message_type = "EVENT_RECEIPT"', - '$.details.detail_type = "uk.nhs.notify.digital.letters.mesh.inbox.message.downloaded.v1"', - `$.details.event_detail = "*\\"messageReference\\":\\"${messageReference}\\"*"`, - ], - ); - expect(downloadedEvents.length).toBe(0); - }, 15_000); - // Assert the MESH message was still acknowledged (deleted from mock inbox) await expectToPassEventually(async () => { await expect(async () => { @@ -405,6 +391,6 @@ test.describe('Digital Letters - MESH Poll and Download', () => { `mock-mesh/${meshMailboxId}/in/${meshMessageId}`, ); }).rejects.toThrow('No objects found'); - }, 60_000); + }, 80_000); }); }); diff --git a/tests/playwright/digital-letters-component-tests/move-scanned-files.component.spec.ts b/tests/playwright/digital-letters-component-tests/move-scanned-files.component.spec.ts index f72366ff..d1759c81 100644 --- a/tests/playwright/digital-letters-component-tests/move-scanned-files.component.spec.ts +++ b/tests/playwright/digital-letters-component-tests/move-scanned-files.component.spec.ts @@ -3,12 +3,10 @@ import { FILE_QUARANTINE_S3_BUCKET_NAME, FILE_SAFE_S3_BUCKET_NAME, MOVE_SCANNED_FILES_DLQ_NAME, - MOVE_SCANNED_FILES_LAMBDA_LOG_GROUP_NAME, PREFIX_DL_FILES, UNSCANNED_FILES_S3_BUCKET_NAME, } from 'constants/backend-constants'; import { SENDER_ID_SKIPS_NOTIFY } from 'constants/tests-constants'; -import { getLogsFromCloudwatch } from 'helpers/cloudwatch-helpers'; import expectToPassEventually from 'helpers/expectations'; import { expectMessageContainingString } from 'helpers/sqs-helpers'; import { expectEventOnTestObserverQueue } from 'helpers/test-observer-helpers'; @@ -39,21 +37,6 @@ test.describe('Digital Letters - Move Scanned Files', () => { await s3Client.send(new PutObjectCommand(params)); - // Verify the event is processed and a message appears in the Lambda logs - await expectToPassEventually(async () => { - const filteredLogs = await getLogsFromCloudwatch( - MOVE_SCANNED_FILES_LAMBDA_LOG_GROUP_NAME, - [ - '$.message.description = "Moved file to destination bucket"', - '$.message.scanStatus = "COMPLETED"', - `$.message.messageReference = "${messageReference}"`, - `$.message.senderId = "${SENDER_ID_SKIPS_NOTIFY}"`, - ], - ); - - expect(filteredLogs.length).toEqual(1); - }, 240); - // Verify the event is published in the event bus await expectEventOnTestObserverQueue( 'uk.nhs.notify.digital.letters.print.file.safe.v1', @@ -66,7 +49,7 @@ test.describe('Digital Letters - Move Scanned Files', () => { data.senderId === SENDER_ID_SKIPS_NOTIFY ); }, - 60_000, + 80_000, ); await expectToPassEventually(async () => { @@ -118,21 +101,6 @@ test.describe('Digital Letters - Move Scanned Files', () => { await s3Client.send(new PutObjectCommand(params)); - // Verify the event is processed and a message appears in the Lambda logs - await expectToPassEventually(async () => { - const filteredLogs = await getLogsFromCloudwatch( - MOVE_SCANNED_FILES_LAMBDA_LOG_GROUP_NAME, - [ - '$.message.description = "Moved file to destination bucket"', - '$.message.scanStatus = "COMPLETED"', - `$.message.messageReference = "${messageReference}"`, - `$.message.senderId = "${SENDER_ID_SKIPS_NOTIFY}"`, - ], - ); - - expect(filteredLogs.length).toEqual(1); - }, 240); - // Verify the event is published in the event bus await expectEventOnTestObserverQueue( 'uk.nhs.notify.digital.letters.print.file.quarantined.v1', @@ -145,7 +113,7 @@ test.describe('Digital Letters - Move Scanned Files', () => { data.senderId === SENDER_ID_SKIPS_NOTIFY ); }, - 60_000, + 80_000, ); await expectToPassEventually(async () => { diff --git a/tests/playwright/digital-letters-component-tests/pdm-poll.component.spec.ts b/tests/playwright/digital-letters-component-tests/pdm-poll.component.spec.ts index 4d4d77e2..44a4cc27 100644 --- a/tests/playwright/digital-letters-component-tests/pdm-poll.component.spec.ts +++ b/tests/playwright/digital-letters-component-tests/pdm-poll.component.spec.ts @@ -69,7 +69,7 @@ test.describe('PDM Poll', () => { const availableDetail = await expectEventOnTestObserverQueue( 'uk.nhs.notify.digital.letters.pdm.resource.available.v1', (d) => (d as any).data.messageReference === messageReference, - 60_000, + 80_000, ); expect((availableDetail as any).data.odsCode).toBe('Y05868'); expect((availableDetail as any).data.nhsNumber).toBe('9912003071'); @@ -99,7 +99,7 @@ test.describe('PDM Poll', () => { const unavailableDetail = await expectEventOnTestObserverQueue( 'uk.nhs.notify.digital.letters.pdm.resource.unavailable.v1', (d) => (d as any).data.messageReference === messageReference, - 60_000, + 80_000, ); expect((unavailableDetail as any).data.retryCount).toBe(0); }); @@ -131,7 +131,7 @@ test.describe('PDM Poll', () => { const availableDetail2 = await expectEventOnTestObserverQueue( 'uk.nhs.notify.digital.letters.pdm.resource.available.v1', (d) => (d as any).data.messageReference === messageReference, - 60_000, + 80_000, ); expect((availableDetail2 as any).data.odsCode).toBe('Y05868'); expect((availableDetail2 as any).data.nhsNumber).toBe('9912003071'); @@ -167,7 +167,7 @@ test.describe('PDM Poll', () => { data.messageReference === messageReference && data.retryCount === 1 ); }, - 60_000, + 80_000, ); expect((unavailableDetail2 as any).data.retryCount).toBe(1); }); @@ -197,7 +197,7 @@ test.describe('PDM Poll', () => { const exceededDetail = await expectEventOnTestObserverQueue( 'uk.nhs.notify.digital.letters.pdm.resource.retries.exceeded.v1', (d) => (d as any).data.messageReference === messageReference, - 60_000, + 80_000, ); expect((exceededDetail as any).data.retryCount).toBe(10); }); diff --git a/tests/playwright/digital-letters-component-tests/pdm-uploader.component.spec.ts b/tests/playwright/digital-letters-component-tests/pdm-uploader.component.spec.ts index 6dfebc61..19fd3681 100644 --- a/tests/playwright/digital-letters-component-tests/pdm-uploader.component.spec.ts +++ b/tests/playwright/digital-letters-component-tests/pdm-uploader.component.spec.ts @@ -1,6 +1,5 @@ import { expect, test } from '@playwright/test'; import { - EVENT_BUS_LOG_GROUP_NAME, LETTERS_S3_BUCKET_NAME, PDM_UPLOADER_DLQ_NAME, PDM_UPLOADER_LAMBDA_LOG_GROUP_NAME, @@ -9,6 +8,7 @@ import { getLogsFromCloudwatch } from 'helpers/cloudwatch-helpers'; import eventPublisher from 'helpers/event-bus-helpers'; import expectToPassEventually from 'helpers/expectations'; import { expectMessageContainingString, purgeQueue } from 'helpers/sqs-helpers'; +import { expectEventOnTestObserverQueue } from 'helpers/test-observer-helpers'; import { v4 as uuidv4 } from 'uuid'; import { SENDER_ID_SKIPS_NOTIFY } from 'constants/tests-constants'; import { putDataS3 } from 'utils'; @@ -100,18 +100,11 @@ test.describe('Digital Letters - Upload to PDM', () => { expect(filteredLogs.length).toEqual(1); }, 120); - await expectToPassEventually(async () => { - const eventLogEntry = await getLogsFromCloudwatch( - EVENT_BUS_LOG_GROUP_NAME, - [ - '$.message_type = "EVENT_RECEIPT"', - '$.details.detail_type = "uk.nhs.notify.digital.letters.pdm.resource.submitted.v1"', - `$.details.event_detail = "*\\"messageReference\\":\\"${messageReference}\\"*"`, - ], - ); - - expect(eventLogEntry.length).toEqual(1); - }, 120); + await expectEventOnTestObserverQueue( + 'uk.nhs.notify.digital.letters.pdm.resource.submitted.v1', + (d) => (d as any).data.messageReference === messageReference, + 80_000, + ); }); test('should send a pdm.resource.submission.rejected event following an error from PDM', async () => { @@ -166,18 +159,11 @@ test.describe('Digital Letters - Upload to PDM', () => { expect(filteredLogs.length).toEqual(1); }, 120); - await expectToPassEventually(async () => { - const eventLogEntry = await getLogsFromCloudwatch( - EVENT_BUS_LOG_GROUP_NAME, - [ - '$.message_type = "EVENT_RECEIPT"', - '$.details.detail_type = "uk.nhs.notify.digital.letters.pdm.resource.submission.rejected.v1"', - `$.details.event_detail = "*\\"messageReference\\":\\"${messageReference}\\"*"`, - ], - ); - - expect(eventLogEntry.length).toEqual(1); - }, 120); + await expectEventOnTestObserverQueue( + 'uk.nhs.notify.digital.letters.pdm.resource.submission.rejected.v1', + (d) => (d as any).data.messageReference === messageReference, + 80_000, + ); }); test('should send invalid event to uploader dlq', async () => { @@ -205,21 +191,6 @@ test.describe('Digital Letters - Upload to PDM', () => { () => true, ); - await Promise.all([ - expectToPassEventually(async () => { - const eventLogEntry = await getLogsFromCloudwatch( - EVENT_BUS_LOG_GROUP_NAME, - [ - '$.message_type = "EVENT_RECEIPT"', - '$.details.detail_type = "uk.nhs.notify.digital.letters.pdm.resource.submission.rejected.v1"', - `$.details.event_detail = "*\\"messageReference\\":\\"${messageReference}\\"*"`, - ], - ); - - expect(eventLogEntry.length).toEqual(1); - }, 150), - - expectMessageContainingString(PDM_UPLOADER_DLQ_NAME, eventId, 150), - ]); + await expectMessageContainingString(PDM_UPLOADER_DLQ_NAME, eventId, 150); }); }); diff --git a/tests/playwright/digital-letters-component-tests/print-analyser.component.spec.ts b/tests/playwright/digital-letters-component-tests/print-analyser.component.spec.ts index befb25d1..5900ecc8 100644 --- a/tests/playwright/digital-letters-component-tests/print-analyser.component.spec.ts +++ b/tests/playwright/digital-letters-component-tests/print-analyser.component.spec.ts @@ -1,17 +1,14 @@ import { expect, test } from '@playwright/test'; import { - ENV, FILE_SAFE_S3_BUCKET_NAME, PRINT_ANALYSER_DLQ_NAME, - PRINT_ANALYSER_LAMBDA_LOG_GROUP_NAME, } from 'constants/backend-constants'; -import { getLogsFromCloudwatch } from 'helpers/cloudwatch-helpers'; import eventPublisher from 'helpers/event-bus-helpers'; -import expectToPassEventually from 'helpers/expectations'; import { fivePagePdf } from 'helpers/pdf-helpers'; import { v4 as uuidv4 } from 'uuid'; import { FileSafe, validateFileSafe } from 'digital-letters-events'; import { expectMessageContainingString, purgeQueue } from 'helpers/sqs-helpers'; +import { expectEventOnTestObserverQueue } from 'helpers/test-observer-helpers'; import { putFileS3 } from 'utils'; export const fileSafeEvent: FileSafe = { @@ -66,23 +63,24 @@ test.describe('Print analyser', () => { await eventPublisher.sendEvents([event], validateFileSafe); - await expectToPassEventually(async () => { - const eventLogEntry = await getLogsFromCloudwatch( - `/aws/vendedlogs/events/event-bus/nhs-${ENV}-dl`, - [ - '$.message_type = "EVENT_RECEIPT"', - '$.details.detail_type = "uk.nhs.notify.digital.letters.print.pdf.analysed.v1"', - `$.details.event_detail = "*\\"messageReference\\":\\"${messageReference}\\"*"`, - `$.details.event_detail = "*\\"senderId\\":\\"${event.data.senderId}\\"*"`, - `$.details.event_detail = "*\\"letterUri\\":\\"${event.data.letterUri}\\"*"`, - `$.details.event_detail = "*\\"pageCount\\":5*"`, - `$.details.event_detail = "*\\"sha256Hash\\":\\"631b6ef1a936e62277d55a80deb850babdde861152d476489d75b0c9161bd326\\"*"`, - `$.details.event_detail = "*\\"createdAt\\":\\"${event.data.createdAt}\\"*"`, - ], - ); - - expect(eventLogEntry.length).toEqual(1); - }, 120); + const analysedDetail = await expectEventOnTestObserverQueue( + 'uk.nhs.notify.digital.letters.print.pdf.analysed.v1', + (d) => { + const { data } = d as any; + return ( + data.messageReference === messageReference && + data.senderId === event.data.senderId + ); + }, + 80_000, + ); + const analysedData = (analysedDetail as any).data; + expect(analysedData.letterUri).toBe(event.data.letterUri); + expect(analysedData.pageCount).toBe(5); + expect(analysedData.sha256Hash).toBe( + '631b6ef1a936e62277d55a80deb850babdde861152d476489d75b0c9161bd326', + ); + expect(analysedData.createdAt).toBe(event.data.createdAt); }); test('should send invalid event to print analyser dlq', async () => { @@ -99,25 +97,10 @@ test.describe('Print analyser', () => { await eventPublisher.sendEvents([event], () => true); - await Promise.all([ - expectToPassEventually(async () => { - const eventLogEntry = await getLogsFromCloudwatch( - PRINT_ANALYSER_LAMBDA_LOG_GROUP_NAME, - [ - '$.message.description = "Error parsing FileSafe event"', - `$.message.err[0].message = "must have required property 'senderId'"`, - `$.messageReference = "${messageReference}"`, - ], - ); - - expect(eventLogEntry.length).toEqual(1); - }, 150), - - expectMessageContainingString( - PRINT_ANALYSER_DLQ_NAME, - messageReference, - 150, - ), - ]); + await expectMessageContainingString( + PRINT_ANALYSER_DLQ_NAME, + messageReference, + 150, + ); }); }); diff --git a/tests/playwright/digital-letters-component-tests/print-status-handler.component.spec.ts b/tests/playwright/digital-letters-component-tests/print-status-handler.component.spec.ts index d6b01338..7b813ec1 100644 --- a/tests/playwright/digital-letters-component-tests/print-status-handler.component.spec.ts +++ b/tests/playwright/digital-letters-component-tests/print-status-handler.component.spec.ts @@ -1,15 +1,10 @@ -import { expect, test } from '@playwright/test'; +import { test } from '@playwright/test'; import { LetterEvent } from '@nhsdigital/nhs-notify-event-schemas-supplier-api/src/events/letter-events'; -import { - ENV, - PRINT_STATUS_HANDLER_DLQ_NAME, - PRINT_STATUS_HANDLER_LAMBDA_LOG_GROUP_NAME, -} from 'constants/backend-constants'; -import { getLogsFromCloudwatch } from 'helpers/cloudwatch-helpers'; +import { PRINT_STATUS_HANDLER_DLQ_NAME } from 'constants/backend-constants'; import eventPublisher from 'helpers/event-bus-helpers'; -import expectToPassEventually from 'helpers/expectations'; import { v4 as uuidv4 } from 'uuid'; import { expectMessageContainingString, purgeQueue } from 'helpers/sqs-helpers'; +import { expectEventOnTestObserverQueue } from 'helpers/test-observer-helpers'; const baseLetterEvent = { id: '550e8400-e29b-41d4-a716-446655440001', @@ -78,19 +73,16 @@ test.describe('Print status handler', () => { await eventPublisher.sendEvents([letterEvent], () => true); - await expectToPassEventually(async () => { - const eventLogEntry = await getLogsFromCloudwatch( - `/aws/vendedlogs/events/event-bus/nhs-${ENV}-dl`, - [ - '$.message_type = "EVENT_RECEIPT"', - '$.details.detail_type = "uk.nhs.notify.digital.letters.print.letter.transitioned.v1"', - `$.details.event_detail = "*\\"messageReference\\":\\"${messageReference}\\"*"`, - `$.details.event_detail = "*\\"status\\":\\"${status}\\"*"`, - ], - ); - - expect(eventLogEntry.length).toEqual(1); - }, 120); + await expectEventOnTestObserverQueue( + 'uk.nhs.notify.digital.letters.print.letter.transitioned.v1', + (d) => { + const { data } = d as any; + return ( + data.messageReference === messageReference && data.status === status + ); + }, + 120_000, + ); }); } @@ -119,24 +111,10 @@ test.describe('Print status handler', () => { () => true, ); - await Promise.all([ - expectToPassEventually(async () => { - const eventLogEntry = await getLogsFromCloudwatch( - PRINT_STATUS_HANDLER_LAMBDA_LOG_GROUP_NAME, - [ - String.raw`$.message.err.message = "*Invalid option: expected one of \\\"PENDING\\\"*"`, - '$.message.description = "Error parsing queue item"', - ], - ); - - expect(eventLogEntry.length).toEqual(1); - }, 150), - - expectMessageContainingString( - PRINT_STATUS_HANDLER_DLQ_NAME, - messageReference, - 150, - ), - ]); + await expectMessageContainingString( + PRINT_STATUS_HANDLER_DLQ_NAME, + messageReference, + 150, + ); }); }); diff --git a/tests/playwright/digital-letters-component-tests/report-scheduler.component.spec.ts b/tests/playwright/digital-letters-component-tests/report-scheduler.component.spec.ts index 945c5e5c..1251bc77 100644 --- a/tests/playwright/digital-letters-component-tests/report-scheduler.component.spec.ts +++ b/tests/playwright/digital-letters-component-tests/report-scheduler.component.spec.ts @@ -1,57 +1,34 @@ import { expect, test } from '@playwright/test'; +import { REPORT_SCHEDULER_LAMBDA_NAME } from 'constants/backend-constants'; import { - EVENT_BUS_LOG_GROUP_NAME, - REPORT_SCHEDULER_LAMBDA_NAME, -} from 'constants/backend-constants'; -import { - EXISTING_SENDER_IDS, SENDER_ID_SKIPS_NOTIFY, SENDER_ID_THAT_TRIGGERS_ERROR_IN_NOTIFY_SANDBOX, SENDER_ID_VALID_FOR_NOTIFY_SANDBOX, } from 'constants/tests-constants'; -import { getLogsFromCloudwatch } from 'helpers/cloudwatch-helpers'; -import expectToPassEventually from 'helpers/expectations'; import { invokeLambda } from 'helpers/lambda-helpers'; +import { expectEventOnTestObserverQueue } from 'helpers/test-observer-helpers'; test.describe('Digital Letters - Report Scheduler', () => { test('should send reporting.generate.report for all senders', async () => { - invokeLambda(REPORT_SCHEDULER_LAMBDA_NAME); + test.setTimeout(120_000); - await expectToPassEventually(async () => { - const eventLogEntries = await getLogsFromCloudwatch( - EVENT_BUS_LOG_GROUP_NAME, - [ - '$.message_type = "EVENT_RECEIPT"', - '$.details.detail_type = "uk.nhs.notify.digital.letters.reporting.generate.report.v1"', - ], - ); - // to avoid conflicts with other tests, we filter the events to only include those with senderIds we know exist in the system, which will produce generation day of yesterday. - const parsedEvents = eventLogEntries - .map((entry: any) => JSON.parse(entry.details.event_detail)) - .filter( - (event: any) => - event.data && EXISTING_SENDER_IDS.includes(event.data.senderId), - ); + const yesterday = new Date(); + yesterday.setDate(yesterday.getDate() - 1); + const yesterdayString = yesterday.toISOString().split('T')[0]; - const yesterday = new Date(); - yesterday.setDate(yesterday.getDate() - 1); - const yesterdayString = yesterday.toISOString().split('T')[0]; - - for (const event of parsedEvents) { - expect(event.type).toBe( - 'uk.nhs.notify.digital.letters.reporting.generate.report.v1', - ); - expect(event.data).toBeDefined(); - expect(event.data.senderId).toBeDefined(); - expect(event.data.reportDate).toBe(yesterdayString); - } + invokeLambda(REPORT_SCHEDULER_LAMBDA_NAME); - const senderIds = parsedEvents.map((event) => event.data.senderId); - expect(senderIds).toContain(SENDER_ID_VALID_FOR_NOTIFY_SANDBOX); - expect(senderIds).toContain( - SENDER_ID_THAT_TRIGGERS_ERROR_IN_NOTIFY_SANDBOX, + for (const senderId of [ + SENDER_ID_VALID_FOR_NOTIFY_SANDBOX, + SENDER_ID_THAT_TRIGGERS_ERROR_IN_NOTIFY_SANDBOX, + SENDER_ID_SKIPS_NOTIFY, + ]) { + const detail = await expectEventOnTestObserverQueue( + 'uk.nhs.notify.digital.letters.reporting.generate.report.v1', + (d) => (d as any).data.senderId === senderId, + 80_000, ); - expect(senderIds).toContain(SENDER_ID_SKIPS_NOTIFY); - }, 120); + expect((detail as any).data.reportDate).toBe(yesterdayString); + } }); }); diff --git a/tests/playwright/digital-letters-component-tests/send-reports-trust.component.spec.ts b/tests/playwright/digital-letters-component-tests/send-reports-trust.component.spec.ts index b43ce3c7..f08e0f61 100644 --- a/tests/playwright/digital-letters-component-tests/send-reports-trust.component.spec.ts +++ b/tests/playwright/digital-letters-component-tests/send-reports-trust.component.spec.ts @@ -55,8 +55,8 @@ test.describe('Digital Letters - Send reports to Trust', () => { const detail = await expectEventOnTestObserverQueue( 'uk.nhs.notify.digital.letters.reporting.report.sent.v1', (d) => - (d.data as any)?.meshMailboxReportsId === meshMailboxReportsId && - (d.data as any)?.senderId === senderId, + (d.data as any).meshMailboxReportsId === meshMailboxReportsId && + (d.data as any).senderId === senderId, 120_000, ); diff --git a/tests/playwright/digital-letters-component-tests/ttl-create.component.spec.ts b/tests/playwright/digital-letters-component-tests/ttl-create.component.spec.ts index 6d309a06..f436010c 100644 --- a/tests/playwright/digital-letters-component-tests/ttl-create.component.spec.ts +++ b/tests/playwright/digital-letters-component-tests/ttl-create.component.spec.ts @@ -87,7 +87,7 @@ test.describe('Digital Letters - Create TTL', () => { const { data } = detail as { data: { messageUri: string } }; return data.messageUri === messageUri; }, - 60_000, + 80_000, ); }); @@ -127,7 +127,7 @@ test.describe('Digital Letters - Create TTL', () => { const { data } = detail as { data: { messageUri: string } }; return data.messageUri === messageUri; }, - 60_000, + 80_000, ); }); @@ -153,21 +153,7 @@ test.describe('Digital Letters - Create TTL', () => { () => true, ); - await Promise.all([ - expectToPassEventually(async () => { - const eventLogEntry = await getLogsFromCloudwatch( - CREATE_TTL_LAMBDA_LOG_GROUP_NAME, - [ - '$.message.description = "Error parsing MESHInboxMessageDownloaded event"', - `$.message.err[0].params.additionalProperty = "${unexpectedField}"`, - ], - ); - - expect(eventLogEntry.length).toEqual(1); - }, 150), - - expectMessageContainingString(CREATE_TTL_DLQ_NAME, letterId, 150), - ]); + expectMessageContainingString(CREATE_TTL_DLQ_NAME, letterId, 150); }); test('should send events from unknown sender to dlq', async () => { diff --git a/tests/playwright/helpers/test-observer-helpers.ts b/tests/playwright/helpers/test-observer-helpers.ts index 158edf7e..58e80897 100644 --- a/tests/playwright/helpers/test-observer-helpers.ts +++ b/tests/playwright/helpers/test-observer-helpers.ts @@ -17,11 +17,14 @@ const queueUrl = `${SQS_URL_PREFIX}${TEST_OBSERVER_QUEUE_NAME}`; * * The test observer queue is subscribed to the EventBridge bus and receives all * uk.nhs.notify.digital.letters.* events. + * + * Unmatched messages are immediately returned to the queue (VisibilityTimeout: 0) + * so concurrent tests are not starved. */ export async function expectEventOnTestObserverQueue( eventType: string, matchFn: (detail: Record) => boolean, - timeoutMs = 60_000, + timeoutMs = 80_000, ): Promise> { const start = Date.now(); @@ -30,8 +33,8 @@ export async function expectEventOnTestObserverQueue( new ReceiveMessageCommand({ QueueUrl: queueUrl, MaxNumberOfMessages: 10, - WaitTimeSeconds: 2, - VisibilityTimeout: 5, + WaitTimeSeconds: 10, + VisibilityTimeout: 30, }), ); @@ -50,13 +53,18 @@ export async function expectEventOnTestObserverQueue( ); return detail; } - await sqsClient.send( - new ChangeMessageVisibilityCommand({ - QueueUrl: queueUrl, - ReceiptHandle: msg.ReceiptHandle!, - VisibilityTimeout: 0, - }), - ); + + // Immediately return unmatched messages so concurrent tests are not starved + try { + await sqsClient.send( + new ChangeMessageVisibilityCommand({ + QueueUrl: queueUrl, + ReceiptHandle: msg.ReceiptHandle!, + VisibilityTimeout: 5, + }), + ); + } catch { + } } } } From 88e1c911fec6f47840e46aa475a331fd360b8763 Mon Sep 17 00:00:00 2001 From: lapenna-bjss Date: Thu, 23 Apr 2026 17:15:24 +0100 Subject: [PATCH 06/16] Fix linting errors --- tests/playwright/helpers/test-observer-helpers.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/playwright/helpers/test-observer-helpers.ts b/tests/playwright/helpers/test-observer-helpers.ts index 58e80897..5111b6ab 100644 --- a/tests/playwright/helpers/test-observer-helpers.ts +++ b/tests/playwright/helpers/test-observer-helpers.ts @@ -64,6 +64,7 @@ export async function expectEventOnTestObserverQueue( }), ); } catch { + // Receipt handle already expired. SQS returns the message automatically } } } From 11bcb68d92921d2fa8a1abdb717f13a1c5bcacb2 Mon Sep 17 00:00:00 2001 From: lapenna-bjss Date: Fri, 24 Apr 2026 10:08:25 +0100 Subject: [PATCH 07/16] Test --- .../terraform/components/dl/README.md | 1 + .../dl/module_sqs_test_observer_queues.tf | 84 +++++++++++++++++++ .../config/component/component.setup.ts | 28 +++++-- .../playwright/constants/backend-constants.ts | 8 ++ .../core-notify.component.spec.ts | 8 +- .../mesh-acknowledge.component.spec.ts | 7 +- .../mesh-poll-download.component.spec.ts | 8 +- .../move-scanned-files.component.spec.ts | 7 +- .../pdm-poll.component.spec.ts | 10 ++- .../pdm-uploader.component.spec.ts | 7 +- .../print-analyser.component.spec.ts | 6 +- .../print-status-handler.component.spec.ts | 6 +- .../report-scheduler.component.spec.ts | 6 +- .../send-reports-trust.component.spec.ts | 6 +- .../ttl-create.component.spec.ts | 7 +- .../ttl-handle.component.spec.ts | 6 +- .../helpers/test-observer-helpers.ts | 27 ++++-- 17 files changed, 205 insertions(+), 27 deletions(-) create mode 100644 infrastructure/terraform/components/dl/module_sqs_test_observer_queues.tf diff --git a/infrastructure/terraform/components/dl/README.md b/infrastructure/terraform/components/dl/README.md index e2b3be4c..5b800ed2 100644 --- a/infrastructure/terraform/components/dl/README.md +++ b/infrastructure/terraform/components/dl/README.md @@ -104,6 +104,7 @@ No requirements. | [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 | | [sqs\_scanner](#module\_sqs\_scanner) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-sqs.zip | n/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 | +| [sqs\_test\_observer\_queues](#module\_sqs\_test\_observer\_queues) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-sqs.zip | n/a | | [sqs\_ttl](#module\_sqs\_ttl) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-sqs.zip | n/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 | | [ttl\_create](#module\_ttl\_create) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-lambda.zip | n/a | diff --git a/infrastructure/terraform/components/dl/module_sqs_test_observer_queues.tf b/infrastructure/terraform/components/dl/module_sqs_test_observer_queues.tf new file mode 100644 index 00000000..34ff9f77 --- /dev/null +++ b/infrastructure/terraform/components/dl/module_sqs_test_observer_queues.tf @@ -0,0 +1,84 @@ + +# Per-component test observer queues +# Each queue subscribes to a filtered subset of Digital Letters events, +# reducing queue depth per test and eliminating contention between spec files. + +locals { + test_observer_queues = { + mesh = "uk.nhs.notify.digital.letters.mesh.inbox.message." + pdm = "uk.nhs.notify.digital.letters.pdm.resource." + messages = "uk.nhs.notify.digital.letters.messages.request." + print = "uk.nhs.notify.digital.letters.print." + queue-items = "uk.nhs.notify.digital.letters.queue.item." + reporting = "uk.nhs.notify.digital.letters.reporting." + } +} + +module "sqs_test_observer_queues" { + for_each = local.test_observer_queues + + source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-sqs.zip" + + aws_account_id = var.aws_account_id + component = local.component + environment = var.environment + project = var.project + region = var.region + name = "test-observer-${each.key}" + sqs_kms_key_arn = module.kms.key_arn + visibility_timeout_seconds = var.sqs_visibility_timeout_seconds + create_dlq = false + max_receive_count = var.sqs_max_receive_count + sqs_policy_overload = data.aws_iam_policy_document.sqs_test_observer_queues[each.key].json +} + +data "aws_iam_policy_document" "sqs_test_observer_queues" { + for_each = local.test_observer_queues + + statement { + sid = "AllowEventBridgeToSendMessage" + effect = "Allow" + + principals { + type = "Service" + identifiers = ["events.amazonaws.com"] + } + + actions = ["sqs:SendMessage"] + + resources = [ + "arn:aws:sqs:${var.region}:${var.aws_account_id}:${local.csi}-test-observer-${each.key}-queue" + ] + + condition { + test = "ArnLike" + variable = "aws:SourceArn" + values = [aws_cloudwatch_event_rule.test_observer[each.key].arn] + } + } +} + +resource "aws_cloudwatch_event_rule" "test_observer" { + for_each = local.test_observer_queues + + name = "${local.csi}-test-observer-${each.key}" + description = "Event rule for test observer queue: ${each.key}" + event_bus_name = aws_cloudwatch_event_bus.main.name + + event_pattern = jsonencode({ + "detail" : { + "type" : [{ + "prefix" : each.value + }] + } + }) +} + +resource "aws_cloudwatch_event_target" "test_observer_sqs_queues" { + for_each = local.test_observer_queues + + rule = aws_cloudwatch_event_rule.test_observer[each.key].name + target_id = "test-observer-${each.key}-sqs-target" + arn = module.sqs_test_observer_queues[each.key].sqs_queue_arn + event_bus_name = aws_cloudwatch_event_bus.main.name +} diff --git a/tests/playwright/config/component/component.setup.ts b/tests/playwright/config/component/component.setup.ts index 710ce52b..39a48816 100644 --- a/tests/playwright/config/component/component.setup.ts +++ b/tests/playwright/config/component/component.setup.ts @@ -2,14 +2,30 @@ import { PurgeQueueCommand } from '@aws-sdk/client-sqs'; import { test as setup } from '@playwright/test'; import { SQS_URL_PREFIX, - TEST_OBSERVER_QUEUE_NAME, + TEST_OBSERVER_MESH_QUEUE_NAME, + TEST_OBSERVER_MESSAGES_QUEUE_NAME, + TEST_OBSERVER_PDM_QUEUE_NAME, + TEST_OBSERVER_PRINT_QUEUE_NAME, + TEST_OBSERVER_QUEUE_ITEMS_QUEUE_NAME, + TEST_OBSERVER_REPORTING_QUEUE_NAME, } from 'constants/backend-constants'; import { sqsClient } from 'utils'; -setup('Purge test observer queue', async () => { - await sqsClient.send( - new PurgeQueueCommand({ - QueueUrl: `${SQS_URL_PREFIX}${TEST_OBSERVER_QUEUE_NAME}`, - }), +const queues = [ + TEST_OBSERVER_MESH_QUEUE_NAME, + TEST_OBSERVER_MESSAGES_QUEUE_NAME, + TEST_OBSERVER_PDM_QUEUE_NAME, + TEST_OBSERVER_PRINT_QUEUE_NAME, + TEST_OBSERVER_QUEUE_ITEMS_QUEUE_NAME, + TEST_OBSERVER_REPORTING_QUEUE_NAME, +]; + +setup('Purge test observer queues', async () => { + await Promise.all( + queues.map((name) => + sqsClient.send( + new PurgeQueueCommand({ QueueUrl: `${SQS_URL_PREFIX}${name}` }), + ), + ), ); }); diff --git a/tests/playwright/constants/backend-constants.ts b/tests/playwright/constants/backend-constants.ts index 8522935f..8122b774 100644 --- a/tests/playwright/constants/backend-constants.ts +++ b/tests/playwright/constants/backend-constants.ts @@ -34,6 +34,14 @@ export const MOVE_SCANNED_FILES_DLQ_NAME = `${CSI}-move-scanned-files-dlq`; export const REPORT_SENDER_DLQ_NAME = `${CSI}-report-sender-dlq`; export const TEST_OBSERVER_QUEUE_NAME = `${CSI}-test-observer-queue`; +// Per-component test observer queues +export const TEST_OBSERVER_MESH_QUEUE_NAME = `${CSI}-test-observer-mesh-queue`; +export const TEST_OBSERVER_PDM_QUEUE_NAME = `${CSI}-test-observer-pdm-queue`; +export const TEST_OBSERVER_MESSAGES_QUEUE_NAME = `${CSI}-test-observer-messages-queue`; +export const TEST_OBSERVER_PRINT_QUEUE_NAME = `${CSI}-test-observer-print-queue`; +export const TEST_OBSERVER_QUEUE_ITEMS_QUEUE_NAME = `${CSI}-test-observer-queue-items-queue`; +export const TEST_OBSERVER_REPORTING_QUEUE_NAME = `${CSI}-test-observer-reporting-queue`; + // Queue Url Prefix export const SQS_URL_PREFIX = `https://sqs.${REGION}.amazonaws.com/${AWS_ACCOUNT_ID}/`; diff --git a/tests/playwright/digital-letters-component-tests/core-notify.component.spec.ts b/tests/playwright/digital-letters-component-tests/core-notify.component.spec.ts index 67498090..c41b94ff 100644 --- a/tests/playwright/digital-letters-component-tests/core-notify.component.spec.ts +++ b/tests/playwright/digital-letters-component-tests/core-notify.component.spec.ts @@ -11,7 +11,10 @@ import { } from 'digital-letters-events'; import eventPublisher from 'helpers/event-bus-helpers'; import { expectMessageContainingString, purgeQueue } from 'helpers/sqs-helpers'; -import { expectEventOnTestObserverQueue } from 'helpers/test-observer-helpers'; +import { + MESSAGES_OBSERVER_QUEUE_URL, + expectEventOnTestObserverQueue, +} from 'helpers/test-observer-helpers'; import { v4 as uuidv4 } from 'uuid'; const baseEvent: Omit = { @@ -65,6 +68,7 @@ test.describe('Digital Letters - Core Notify', () => { ); const submittedDetail = await expectEventOnTestObserverQueue( + MESSAGES_OBSERVER_QUEUE_URL, 'uk.nhs.notify.digital.letters.messages.request.submitted.v1', (d) => { const { data } = d as any; @@ -105,6 +109,7 @@ test.describe('Digital Letters - Core Notify', () => { ); const rejectedDetail = await expectEventOnTestObserverQueue( + MESSAGES_OBSERVER_QUEUE_URL, 'uk.nhs.notify.digital.letters.messages.request.rejected.v1', (d) => { const { data } = d as any; @@ -145,6 +150,7 @@ test.describe('Digital Letters - Core Notify', () => { // Verify the event is published in the event bus await expectEventOnTestObserverQueue( + MESSAGES_OBSERVER_QUEUE_URL, 'uk.nhs.notify.digital.letters.messages.request.skipped.v1', (d) => { const { data } = d as any; diff --git a/tests/playwright/digital-letters-component-tests/mesh-acknowledge.component.spec.ts b/tests/playwright/digital-letters-component-tests/mesh-acknowledge.component.spec.ts index 35526064..7adacb1f 100644 --- a/tests/playwright/digital-letters-component-tests/mesh-acknowledge.component.spec.ts +++ b/tests/playwright/digital-letters-component-tests/mesh-acknowledge.component.spec.ts @@ -14,7 +14,10 @@ import eventPublisher from 'helpers/event-bus-helpers'; import expectToPassEventually from 'helpers/expectations'; import { downloadFromS3 } from 'helpers/s3-helpers'; import { expectMessageContainingString } from 'helpers/sqs-helpers'; -import { expectEventOnTestObserverQueue } from 'helpers/test-observer-helpers'; +import { + MESH_OBSERVER_QUEUE_URL, + expectEventOnTestObserverQueue, +} from 'helpers/test-observer-helpers'; import { v4 as uuidv4 } from 'uuid'; test.describe('Digital Letters - Mesh Acknowledger', () => { @@ -80,6 +83,7 @@ test.describe('Digital Letters - Mesh Acknowledger', () => { // Verify message acknowledged event was published, // and extract sentMeshMessageId to use for the S3 lookup. const acknowledgedDetail = await expectEventOnTestObserverQueue( + MESH_OBSERVER_QUEUE_URL, 'uk.nhs.notify.digital.letters.mesh.inbox.message.acknowledged.v1', (d) => { const { data } = d as any; @@ -200,6 +204,7 @@ test.describe('Digital Letters - Mesh Acknowledger', () => { // Verify message acknowledged event was published with statusCode 400, // and extract sentMeshMessageId to use for the S3 lookup. const acknowledgedDetail2 = await expectEventOnTestObserverQueue( + MESH_OBSERVER_QUEUE_URL, 'uk.nhs.notify.digital.letters.mesh.inbox.message.acknowledged.v1', (d) => { const { data } = d as any; diff --git a/tests/playwright/digital-letters-component-tests/mesh-poll-download.component.spec.ts b/tests/playwright/digital-letters-component-tests/mesh-poll-download.component.spec.ts index 2ed6ac4e..3c75ac07 100644 --- a/tests/playwright/digital-letters-component-tests/mesh-poll-download.component.spec.ts +++ b/tests/playwright/digital-letters-component-tests/mesh-poll-download.component.spec.ts @@ -12,7 +12,10 @@ import expectToPassEventually from 'helpers/expectations'; import { invokeLambda } from 'helpers/lambda-helpers'; import { downloadFromS3, uploadToS3 } from 'helpers/s3-helpers'; import { expectMessageContainingString, purgeQueue } from 'helpers/sqs-helpers'; -import { expectEventOnTestObserverQueue } from 'helpers/test-observer-helpers'; +import { + MESH_OBSERVER_QUEUE_URL, + expectEventOnTestObserverQueue, +} from 'helpers/test-observer-helpers'; import { v4 as uuidv4 } from 'uuid'; import { SENDER_ID_SKIPS_NOTIFY } from 'constants/tests-constants'; import { validateMESHInboxMessageReceived } from 'digital-letters-events'; @@ -98,6 +101,7 @@ test.describe('Digital Letters - MESH Poll and Download', () => { meshMessageId: string, ): Promise { await expectEventOnTestObserverQueue( + MESH_OBSERVER_QUEUE_URL, 'uk.nhs.notify.digital.letters.mesh.inbox.message.received.v1', (detail) => { const { data } = detail as { @@ -115,6 +119,7 @@ test.describe('Digital Letters - MESH Poll and Download', () => { messageReference: string, ): Promise { await expectEventOnTestObserverQueue( + MESH_OBSERVER_QUEUE_URL, 'uk.nhs.notify.digital.letters.mesh.inbox.message.downloaded.v1', (detail) => { const { data } = detail as { @@ -135,6 +140,7 @@ test.describe('Digital Letters - MESH Poll and Download', () => { failureCode = 'DL_CLIV_005', ): Promise { await expectEventOnTestObserverQueue( + MESH_OBSERVER_QUEUE_URL, 'uk.nhs.notify.digital.letters.mesh.inbox.message.invalid.v1', (detail) => { const { data } = detail as { diff --git a/tests/playwright/digital-letters-component-tests/move-scanned-files.component.spec.ts b/tests/playwright/digital-letters-component-tests/move-scanned-files.component.spec.ts index d1759c81..43459db3 100644 --- a/tests/playwright/digital-letters-component-tests/move-scanned-files.component.spec.ts +++ b/tests/playwright/digital-letters-component-tests/move-scanned-files.component.spec.ts @@ -9,7 +9,10 @@ import { import { SENDER_ID_SKIPS_NOTIFY } from 'constants/tests-constants'; import expectToPassEventually from 'helpers/expectations'; import { expectMessageContainingString } from 'helpers/sqs-helpers'; -import { expectEventOnTestObserverQueue } from 'helpers/test-observer-helpers'; +import { + PRINT_OBSERVER_QUEUE_URL, + expectEventOnTestObserverQueue, +} from 'helpers/test-observer-helpers'; import { v4 as uuidv4 } from 'uuid'; import { PutObjectCommand } from '@aws-sdk/client-s3'; import { getS3ObjectMetadata, s3Client } from 'utils'; @@ -39,6 +42,7 @@ test.describe('Digital Letters - Move Scanned Files', () => { // Verify the event is published in the event bus await expectEventOnTestObserverQueue( + PRINT_OBSERVER_QUEUE_URL, 'uk.nhs.notify.digital.letters.print.file.safe.v1', (detail) => { const { data } = detail as { @@ -103,6 +107,7 @@ test.describe('Digital Letters - Move Scanned Files', () => { // Verify the event is published in the event bus await expectEventOnTestObserverQueue( + PRINT_OBSERVER_QUEUE_URL, 'uk.nhs.notify.digital.letters.print.file.quarantined.v1', (detail) => { const { data } = detail as { diff --git a/tests/playwright/digital-letters-component-tests/pdm-poll.component.spec.ts b/tests/playwright/digital-letters-component-tests/pdm-poll.component.spec.ts index 44a4cc27..4f43f9c7 100644 --- a/tests/playwright/digital-letters-component-tests/pdm-poll.component.spec.ts +++ b/tests/playwright/digital-letters-component-tests/pdm-poll.component.spec.ts @@ -6,7 +6,10 @@ import { } from 'digital-letters-events'; import eventPublisher from 'helpers/event-bus-helpers'; import { expectMessageContainingString, purgeQueue } from 'helpers/sqs-helpers'; -import { expectEventOnTestObserverQueue } from 'helpers/test-observer-helpers'; +import { + PDM_OBSERVER_QUEUE_URL, + expectEventOnTestObserverQueue, +} from 'helpers/test-observer-helpers'; import { v4 as uuidv4 } from 'uuid'; const baseEvent = { @@ -67,6 +70,7 @@ test.describe('PDM Poll', () => { ); const availableDetail = await expectEventOnTestObserverQueue( + PDM_OBSERVER_QUEUE_URL, 'uk.nhs.notify.digital.letters.pdm.resource.available.v1', (d) => (d as any).data.messageReference === messageReference, 80_000, @@ -97,6 +101,7 @@ test.describe('PDM Poll', () => { ); const unavailableDetail = await expectEventOnTestObserverQueue( + PDM_OBSERVER_QUEUE_URL, 'uk.nhs.notify.digital.letters.pdm.resource.unavailable.v1', (d) => (d as any).data.messageReference === messageReference, 80_000, @@ -129,6 +134,7 @@ test.describe('PDM Poll', () => { ); const availableDetail2 = await expectEventOnTestObserverQueue( + PDM_OBSERVER_QUEUE_URL, 'uk.nhs.notify.digital.letters.pdm.resource.available.v1', (d) => (d as any).data.messageReference === messageReference, 80_000, @@ -160,6 +166,7 @@ test.describe('PDM Poll', () => { ); const unavailableDetail2 = await expectEventOnTestObserverQueue( + PDM_OBSERVER_QUEUE_URL, 'uk.nhs.notify.digital.letters.pdm.resource.unavailable.v1', (d) => { const { data } = d as any; @@ -195,6 +202,7 @@ test.describe('PDM Poll', () => { ); const exceededDetail = await expectEventOnTestObserverQueue( + PDM_OBSERVER_QUEUE_URL, 'uk.nhs.notify.digital.letters.pdm.resource.retries.exceeded.v1', (d) => (d as any).data.messageReference === messageReference, 80_000, diff --git a/tests/playwright/digital-letters-component-tests/pdm-uploader.component.spec.ts b/tests/playwright/digital-letters-component-tests/pdm-uploader.component.spec.ts index 19fd3681..8eb72486 100644 --- a/tests/playwright/digital-letters-component-tests/pdm-uploader.component.spec.ts +++ b/tests/playwright/digital-letters-component-tests/pdm-uploader.component.spec.ts @@ -8,7 +8,10 @@ import { getLogsFromCloudwatch } from 'helpers/cloudwatch-helpers'; import eventPublisher from 'helpers/event-bus-helpers'; import expectToPassEventually from 'helpers/expectations'; import { expectMessageContainingString, purgeQueue } from 'helpers/sqs-helpers'; -import { expectEventOnTestObserverQueue } from 'helpers/test-observer-helpers'; +import { + PDM_OBSERVER_QUEUE_URL, + expectEventOnTestObserverQueue, +} from 'helpers/test-observer-helpers'; import { v4 as uuidv4 } from 'uuid'; import { SENDER_ID_SKIPS_NOTIFY } from 'constants/tests-constants'; import { putDataS3 } from 'utils'; @@ -101,6 +104,7 @@ test.describe('Digital Letters - Upload to PDM', () => { }, 120); await expectEventOnTestObserverQueue( + PDM_OBSERVER_QUEUE_URL, 'uk.nhs.notify.digital.letters.pdm.resource.submitted.v1', (d) => (d as any).data.messageReference === messageReference, 80_000, @@ -160,6 +164,7 @@ test.describe('Digital Letters - Upload to PDM', () => { }, 120); await expectEventOnTestObserverQueue( + PDM_OBSERVER_QUEUE_URL, 'uk.nhs.notify.digital.letters.pdm.resource.submission.rejected.v1', (d) => (d as any).data.messageReference === messageReference, 80_000, diff --git a/tests/playwright/digital-letters-component-tests/print-analyser.component.spec.ts b/tests/playwright/digital-letters-component-tests/print-analyser.component.spec.ts index 5900ecc8..81de74f9 100644 --- a/tests/playwright/digital-letters-component-tests/print-analyser.component.spec.ts +++ b/tests/playwright/digital-letters-component-tests/print-analyser.component.spec.ts @@ -8,7 +8,10 @@ import { fivePagePdf } from 'helpers/pdf-helpers'; import { v4 as uuidv4 } from 'uuid'; import { FileSafe, validateFileSafe } from 'digital-letters-events'; import { expectMessageContainingString, purgeQueue } from 'helpers/sqs-helpers'; -import { expectEventOnTestObserverQueue } from 'helpers/test-observer-helpers'; +import { + PRINT_OBSERVER_QUEUE_URL, + expectEventOnTestObserverQueue, +} from 'helpers/test-observer-helpers'; import { putFileS3 } from 'utils'; export const fileSafeEvent: FileSafe = { @@ -64,6 +67,7 @@ test.describe('Print analyser', () => { await eventPublisher.sendEvents([event], validateFileSafe); const analysedDetail = await expectEventOnTestObserverQueue( + PRINT_OBSERVER_QUEUE_URL, 'uk.nhs.notify.digital.letters.print.pdf.analysed.v1', (d) => { const { data } = d as any; diff --git a/tests/playwright/digital-letters-component-tests/print-status-handler.component.spec.ts b/tests/playwright/digital-letters-component-tests/print-status-handler.component.spec.ts index 7b813ec1..10891226 100644 --- a/tests/playwright/digital-letters-component-tests/print-status-handler.component.spec.ts +++ b/tests/playwright/digital-letters-component-tests/print-status-handler.component.spec.ts @@ -4,7 +4,10 @@ import { PRINT_STATUS_HANDLER_DLQ_NAME } from 'constants/backend-constants'; import eventPublisher from 'helpers/event-bus-helpers'; import { v4 as uuidv4 } from 'uuid'; import { expectMessageContainingString, purgeQueue } from 'helpers/sqs-helpers'; -import { expectEventOnTestObserverQueue } from 'helpers/test-observer-helpers'; +import { + PRINT_OBSERVER_QUEUE_URL, + expectEventOnTestObserverQueue, +} from 'helpers/test-observer-helpers'; const baseLetterEvent = { id: '550e8400-e29b-41d4-a716-446655440001', @@ -74,6 +77,7 @@ test.describe('Print status handler', () => { await eventPublisher.sendEvents([letterEvent], () => true); await expectEventOnTestObserverQueue( + PRINT_OBSERVER_QUEUE_URL, 'uk.nhs.notify.digital.letters.print.letter.transitioned.v1', (d) => { const { data } = d as any; diff --git a/tests/playwright/digital-letters-component-tests/report-scheduler.component.spec.ts b/tests/playwright/digital-letters-component-tests/report-scheduler.component.spec.ts index 1251bc77..8f06df62 100644 --- a/tests/playwright/digital-letters-component-tests/report-scheduler.component.spec.ts +++ b/tests/playwright/digital-letters-component-tests/report-scheduler.component.spec.ts @@ -6,7 +6,10 @@ import { SENDER_ID_VALID_FOR_NOTIFY_SANDBOX, } from 'constants/tests-constants'; import { invokeLambda } from 'helpers/lambda-helpers'; -import { expectEventOnTestObserverQueue } from 'helpers/test-observer-helpers'; +import { + REPORTING_OBSERVER_QUEUE_URL, + expectEventOnTestObserverQueue, +} from 'helpers/test-observer-helpers'; test.describe('Digital Letters - Report Scheduler', () => { test('should send reporting.generate.report for all senders', async () => { @@ -24,6 +27,7 @@ test.describe('Digital Letters - Report Scheduler', () => { SENDER_ID_SKIPS_NOTIFY, ]) { const detail = await expectEventOnTestObserverQueue( + REPORTING_OBSERVER_QUEUE_URL, 'uk.nhs.notify.digital.letters.reporting.generate.report.v1', (d) => (d as any).data.senderId === senderId, 80_000, diff --git a/tests/playwright/digital-letters-component-tests/send-reports-trust.component.spec.ts b/tests/playwright/digital-letters-component-tests/send-reports-trust.component.spec.ts index f08e0f61..1b1b0f78 100644 --- a/tests/playwright/digital-letters-component-tests/send-reports-trust.component.spec.ts +++ b/tests/playwright/digital-letters-component-tests/send-reports-trust.component.spec.ts @@ -6,7 +6,10 @@ import { REPORT_SENDER_DLQ_NAME, } from 'constants/backend-constants'; import eventPublisher from 'helpers/event-bus-helpers'; -import { expectEventOnTestObserverQueue } from 'helpers/test-observer-helpers'; +import { + REPORTING_OBSERVER_QUEUE_URL, + expectEventOnTestObserverQueue, +} from 'helpers/test-observer-helpers'; import { downloadFromS3, uploadToS3 } from 'helpers/s3-helpers'; import { expectMessageContainingString } from 'helpers/sqs-helpers'; import { v4 as uuidv4 } from 'uuid'; @@ -53,6 +56,7 @@ test.describe('Digital Letters - Send reports to Trust', () => { meshMailboxReportsId: string, ): Promise { const detail = await expectEventOnTestObserverQueue( + REPORTING_OBSERVER_QUEUE_URL, 'uk.nhs.notify.digital.letters.reporting.report.sent.v1', (d) => (d.data as any).meshMailboxReportsId === meshMailboxReportsId && diff --git a/tests/playwright/digital-letters-component-tests/ttl-create.component.spec.ts b/tests/playwright/digital-letters-component-tests/ttl-create.component.spec.ts index f436010c..a39fefe4 100644 --- a/tests/playwright/digital-letters-component-tests/ttl-create.component.spec.ts +++ b/tests/playwright/digital-letters-component-tests/ttl-create.component.spec.ts @@ -16,7 +16,10 @@ import { getTtl } from 'helpers/dynamodb-helpers'; import eventPublisher from 'helpers/event-bus-helpers'; import expectToPassEventually from 'helpers/expectations'; import { expectMessageContainingString, purgeQueue } from 'helpers/sqs-helpers'; -import { expectEventOnTestObserverQueue } from 'helpers/test-observer-helpers'; +import { + QUEUE_ITEMS_OBSERVER_QUEUE_URL, + expectEventOnTestObserverQueue, +} from 'helpers/test-observer-helpers'; import { v4 as uuidv4 } from 'uuid'; test.describe('Digital Letters - Create TTL', () => { @@ -82,6 +85,7 @@ test.describe('Digital Letters - Create TTL', () => { // Verify item enqueued event published await expectEventOnTestObserverQueue( + QUEUE_ITEMS_OBSERVER_QUEUE_URL, 'uk.nhs.notify.digital.letters.queue.item.enqueued.v1', (detail) => { const { data } = detail as { data: { messageUri: string } }; @@ -122,6 +126,7 @@ test.describe('Digital Letters - Create TTL', () => { // Verify item enqueued event published await expectEventOnTestObserverQueue( + QUEUE_ITEMS_OBSERVER_QUEUE_URL, 'uk.nhs.notify.digital.letters.queue.item.enqueued.v1', (detail) => { const { data } = detail as { data: { messageUri: string } }; diff --git a/tests/playwright/digital-letters-component-tests/ttl-handle.component.spec.ts b/tests/playwright/digital-letters-component-tests/ttl-handle.component.spec.ts index 7d20f7ef..e3c6e4fa 100644 --- a/tests/playwright/digital-letters-component-tests/ttl-handle.component.spec.ts +++ b/tests/playwright/digital-letters-component-tests/ttl-handle.component.spec.ts @@ -8,7 +8,10 @@ import { getLogsFromCloudwatch } from 'helpers/cloudwatch-helpers'; import { deleteTtl, putTtl } from 'helpers/dynamodb-helpers'; import expectToPassEventually from 'helpers/expectations'; import { expectMessageContainingString, purgeQueue } from 'helpers/sqs-helpers'; -import { expectEventOnTestObserverQueue } from 'helpers/test-observer-helpers'; +import { + QUEUE_ITEMS_OBSERVER_QUEUE_URL, + expectEventOnTestObserverQueue, +} from 'helpers/test-observer-helpers'; import { v4 as uuidv4 } from 'uuid'; test.describe('Digital Letters - Handle TTL', () => { @@ -116,6 +119,7 @@ test.describe('Digital Letters - Handle TTL', () => { expect(deleteResponseCode).toBe(200); await expectEventOnTestObserverQueue( + QUEUE_ITEMS_OBSERVER_QUEUE_URL, 'uk.nhs.notify.digital.letters.queue.item.dequeued.v1', (detail) => { const { data } = detail as { data: { messageUri: string } }; diff --git a/tests/playwright/helpers/test-observer-helpers.ts b/tests/playwright/helpers/test-observer-helpers.ts index 5111b6ab..f000546b 100644 --- a/tests/playwright/helpers/test-observer-helpers.ts +++ b/tests/playwright/helpers/test-observer-helpers.ts @@ -5,23 +5,32 @@ import { } from '@aws-sdk/client-sqs'; import { SQS_URL_PREFIX, - TEST_OBSERVER_QUEUE_NAME, + TEST_OBSERVER_MESH_QUEUE_NAME, + TEST_OBSERVER_MESSAGES_QUEUE_NAME, + TEST_OBSERVER_PDM_QUEUE_NAME, + TEST_OBSERVER_PRINT_QUEUE_NAME, + TEST_OBSERVER_QUEUE_ITEMS_QUEUE_NAME, + TEST_OBSERVER_REPORTING_QUEUE_NAME, } from 'constants/backend-constants'; import { sqsClient } from 'utils'; -const queueUrl = `${SQS_URL_PREFIX}${TEST_OBSERVER_QUEUE_NAME}`; +export const MESH_OBSERVER_QUEUE_URL = `${SQS_URL_PREFIX}${TEST_OBSERVER_MESH_QUEUE_NAME}`; +export const PDM_OBSERVER_QUEUE_URL = `${SQS_URL_PREFIX}${TEST_OBSERVER_PDM_QUEUE_NAME}`; +export const MESSAGES_OBSERVER_QUEUE_URL = `${SQS_URL_PREFIX}${TEST_OBSERVER_MESSAGES_QUEUE_NAME}`; +export const PRINT_OBSERVER_QUEUE_URL = `${SQS_URL_PREFIX}${TEST_OBSERVER_PRINT_QUEUE_NAME}`; +export const QUEUE_ITEMS_OBSERVER_QUEUE_URL = `${SQS_URL_PREFIX}${TEST_OBSERVER_QUEUE_ITEMS_QUEUE_NAME}`; +export const REPORTING_OBSERVER_QUEUE_URL = `${SQS_URL_PREFIX}${TEST_OBSERVER_REPORTING_QUEUE_NAME}`; /** - * Polls the test observer SQS queue for an event matching the given type and predicate. + * Polls a test observer SQS queue for an event matching the given type and predicate. * Deletes the matched message from the queue and returns the event detail. * - * The test observer queue is subscribed to the EventBridge bus and receives all - * uk.nhs.notify.digital.letters.* events. - * - * Unmatched messages are immediately returned to the queue (VisibilityTimeout: 0) - * so concurrent tests are not starved. + * Each queue subscribes to a filtered subset of uk.nhs.notify.digital.letters.* events + * Unmatched messages are immediately returned (VisibilityTimeout: 0) so concurrent + * tests within the same spec are not starved. */ export async function expectEventOnTestObserverQueue( + queueUrl: string, eventType: string, matchFn: (detail: Record) => boolean, timeoutMs = 80_000, @@ -60,7 +69,7 @@ export async function expectEventOnTestObserverQueue( new ChangeMessageVisibilityCommand({ QueueUrl: queueUrl, ReceiptHandle: msg.ReceiptHandle!, - VisibilityTimeout: 5, + VisibilityTimeout: 0, }), ); } catch { From 482d79ddc9f7cbc0ee4a965f1ffa4c6ebd24e869 Mon Sep 17 00:00:00 2001 From: lapenna-bjss Date: Fri, 24 Apr 2026 11:49:07 +0100 Subject: [PATCH 08/16] Add shard input to workflow call --- .github/actions/acceptance-tests/action.yaml | 6 ++++++ .github/scripts/dispatch_internal_repo_workflow.sh | 7 +++++++ .github/workflows/stage-4-acceptance.yaml | 8 ++++++-- scripts/tests/integration.sh | 2 +- 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/.github/actions/acceptance-tests/action.yaml b/.github/actions/acceptance-tests/action.yaml index 3b76f9bb..6592ef46 100644 --- a/.github/actions/acceptance-tests/action.yaml +++ b/.github/actions/acceptance-tests/action.yaml @@ -21,6 +21,11 @@ inputs: description: Name of the component under test default: dl + shard: + description: "Playwright shard index in the format N/total e.g. 1/4 (optional, omit to run all tests)" + required: false + default: "" + runs: using: "composite" @@ -58,6 +63,7 @@ runs: env: TEST_TYPE: ${{ inputs.testType }} ENVIRONMENT: ${{ inputs.targetEnvironment }} + PLAYWRIGHT_SHARD: ${{ inputs.shard }} - name: Archive integration test results if: ${{ inputs.testType == 'integration' }} uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 diff --git a/.github/scripts/dispatch_internal_repo_workflow.sh b/.github/scripts/dispatch_internal_repo_workflow.sh index a52c1bbe..19c43c9e 100755 --- a/.github/scripts/dispatch_internal_repo_workflow.sh +++ b/.github/scripts/dispatch_internal_repo_workflow.sh @@ -80,6 +80,10 @@ while [[ $# -gt 0 ]]; do overrideRoleName="$2" shift 2 ;; + --shard) # Playwright shard index in the format N/total e.g. 1/4 (optional) + shard="$2" + shift 2 + ;; *) echo "[ERROR] Unknown argument: $1" exit 1 @@ -167,6 +171,7 @@ echo " overrides: $overrides" echo " overrideProjectName: $overrideProjectName" echo " overrideRoleName: $overrideRoleName" echo " targetProject: $targetProject" +echo " shard: ${shard:-}" DISPATCH_EVENT=$(jq -ncM \ --arg infraRepoName "$infraRepoName" \ @@ -180,6 +185,7 @@ DISPATCH_EVENT=$(jq -ncM \ --arg overrideProjectName "$overrideProjectName" \ --arg overrideRoleName "$overrideRoleName" \ --arg targetProject "$targetProject" \ + --arg shard "${shard:-}" \ '{ "ref": "'"$internalRef"'", "inputs": ( @@ -188,6 +194,7 @@ DISPATCH_EVENT=$(jq -ncM \ (if $overrideProjectName != "" then { "overrideProjectName": $overrideProjectName } else {} end) + (if $overrideRoleName != "" then { "overrideRoleName": $overrideRoleName } else {} end) + (if $targetProject != "" then { "targetProject": $targetProject } else {} end) + + (if $shard != "" then { "shard": $shard } else {} end) + { "releaseVersion": $releaseVersion, "targetEnvironment": $targetEnvironment, diff --git a/.github/workflows/stage-4-acceptance.yaml b/.github/workflows/stage-4-acceptance.yaml index cbd3b155..052df794 100644 --- a/.github/workflows/stage-4-acceptance.yaml +++ b/.github/workflows/stage-4-acceptance.yaml @@ -62,8 +62,11 @@ jobs: run: | echo "Nothing to save" test-integration: - name: "Integration test" + name: "Integration test (shard ${{ matrix.shard }} of ${{ strategy.job-total }})" runs-on: ubuntu-latest + strategy: + matrix: + shard: [1, 2, 3, 4] steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 # Calls out to the nhs-notify-internal repo. @@ -84,7 +87,8 @@ jobs: --targetWorkflow "dispatch-contextual-tests-dynamic-env.yaml" \ --targetEnvironment "$TARGET_ENVIRONMENT" \ --targetAccountGroup "$TARGET_ACCOUNT_GROUP" \ - --targetComponent "dl" + --targetComponent "dl" \ + --shard "${{ matrix.shard }}/4" test-accessibility: name: "Accessibility test" runs-on: ubuntu-latest diff --git a/scripts/tests/integration.sh b/scripts/tests/integration.sh index 893c4efc..e8c22afe 100755 --- a/scripts/tests/integration.sh +++ b/scripts/tests/integration.sh @@ -9,4 +9,4 @@ npx playwright install --with-deps > /dev/null cd tests/playwright -npm run test:component +npm run test:component -- ${PLAYWRIGHT_SHARD:+--shard="$PLAYWRIGHT_SHARD"} From 7c0aef3cfaa1e4272624d676e9371d424672ffce Mon Sep 17 00:00:00 2001 From: lapenna-bjss Date: Fri, 24 Apr 2026 12:59:07 +0100 Subject: [PATCH 09/16] Test --- .../dispatch_internal_repo_workflow.sh | 10 ++--- .github/workflows/stage-4-acceptance.yaml | 7 +-- scripts/tests/integration.sh | 8 ++++ .../config/component/component.config.ts | 45 +++++++++++-------- 4 files changed, 42 insertions(+), 28 deletions(-) diff --git a/.github/scripts/dispatch_internal_repo_workflow.sh b/.github/scripts/dispatch_internal_repo_workflow.sh index 19c43c9e..1e0e221b 100755 --- a/.github/scripts/dispatch_internal_repo_workflow.sh +++ b/.github/scripts/dispatch_internal_repo_workflow.sh @@ -80,8 +80,8 @@ while [[ $# -gt 0 ]]; do overrideRoleName="$2" shift 2 ;; - --shard) # Playwright shard index in the format N/total e.g. 1/4 (optional) - shard="$2" + --enableSharding) # Enable test sharding across 4 parallel runners (optional) + enableSharding="$2" shift 2 ;; *) @@ -171,7 +171,7 @@ echo " overrides: $overrides" echo " overrideProjectName: $overrideProjectName" echo " overrideRoleName: $overrideRoleName" echo " targetProject: $targetProject" -echo " shard: ${shard:-}" +echo " enableSharding: ${enableSharding:-}" DISPATCH_EVENT=$(jq -ncM \ --arg infraRepoName "$infraRepoName" \ @@ -185,7 +185,7 @@ DISPATCH_EVENT=$(jq -ncM \ --arg overrideProjectName "$overrideProjectName" \ --arg overrideRoleName "$overrideRoleName" \ --arg targetProject "$targetProject" \ - --arg shard "${shard:-}" \ + --argjson enableSharding "${enableSharding:-false}" \ '{ "ref": "'"$internalRef"'", "inputs": ( @@ -194,7 +194,7 @@ DISPATCH_EVENT=$(jq -ncM \ (if $overrideProjectName != "" then { "overrideProjectName": $overrideProjectName } else {} end) + (if $overrideRoleName != "" then { "overrideRoleName": $overrideRoleName } else {} end) + (if $targetProject != "" then { "targetProject": $targetProject } else {} end) + - (if $shard != "" then { "shard": $shard } else {} end) + + (if $enableSharding then { "enableSharding": $enableSharding } else {} end) + { "releaseVersion": $releaseVersion, "targetEnvironment": $targetEnvironment, diff --git a/.github/workflows/stage-4-acceptance.yaml b/.github/workflows/stage-4-acceptance.yaml index 052df794..08d6a1fb 100644 --- a/.github/workflows/stage-4-acceptance.yaml +++ b/.github/workflows/stage-4-acceptance.yaml @@ -62,11 +62,8 @@ jobs: run: | echo "Nothing to save" test-integration: - name: "Integration test (shard ${{ matrix.shard }} of ${{ strategy.job-total }})" + name: "Integration test" runs-on: ubuntu-latest - strategy: - matrix: - shard: [1, 2, 3, 4] steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 # Calls out to the nhs-notify-internal repo. @@ -88,7 +85,7 @@ jobs: --targetEnvironment "$TARGET_ENVIRONMENT" \ --targetAccountGroup "$TARGET_ACCOUNT_GROUP" \ --targetComponent "dl" \ - --shard "${{ matrix.shard }}/4" + --enableSharding "true" test-accessibility: name: "Accessibility test" runs-on: ubuntu-latest diff --git a/scripts/tests/integration.sh b/scripts/tests/integration.sh index e8c22afe..3fc69266 100755 --- a/scripts/tests/integration.sh +++ b/scripts/tests/integration.sh @@ -9,4 +9,12 @@ npx playwright install --with-deps > /dev/null cd tests/playwright +# When sharding, shards 2+ must wait for shard 1 to complete setup +# (purge queues, alter firehose buffers) before running tests. +# Shard 1 setup typically takes ~30s. +if [[ -n "${PLAYWRIGHT_SHARD:-}" && ! "${PLAYWRIGHT_SHARD}" == 1/* ]]; then + echo "Shard ${PLAYWRIGHT_SHARD}: waiting 30s for shard 1 setup to complete..." + sleep 30 +fi + npm run test:component -- ${PLAYWRIGHT_SHARD:+--shard="$PLAYWRIGHT_SHARD"} diff --git a/tests/playwright/config/component/component.config.ts b/tests/playwright/config/component/component.config.ts index 4a784936..a414af80 100644 --- a/tests/playwright/config/component/component.config.ts +++ b/tests/playwright/config/component/component.config.ts @@ -1,6 +1,9 @@ import { defineConfig } from '@playwright/test'; import baseConfig from 'config/playwright.config'; +const shard = process.env.PLAYWRIGHT_SHARD; +const isFirstShard = !shard || shard.startsWith('1/'); + export default defineConfig({ ...baseConfig, @@ -9,27 +12,33 @@ export default defineConfig({ timeout: 10_000, // default is 5 seconds. After creating and previewing sometimes the load is slow on a cold start }, projects: [ - { - name: 'senders:setup', - testMatch: 'senders.setup.ts', - }, - { - name: 'firehose:setup', - testMatch: 'firehose.setup.ts', - teardown: 'firehose:teardown', - }, - { - name: 'firehose:teardown', - testMatch: 'firehose.teardown.ts', - }, - { - name: 'component:setup', - testMatch: 'component.setup.ts', - }, + ...(isFirstShard + ? [ + { + name: 'senders:setup', + testMatch: 'senders.setup.ts', + }, + { + name: 'firehose:setup', + testMatch: 'firehose.setup.ts', + teardown: 'firehose:teardown', + }, + { + name: 'firehose:teardown', + testMatch: 'firehose.teardown.ts', + }, + { + name: 'component:setup', + testMatch: 'component.setup.ts', + }, + ] + : []), { name: 'component', testMatch: '*.component.spec.ts', - dependencies: ['senders:setup', 'firehose:setup', 'component:setup'], + dependencies: isFirstShard + ? ['senders:setup', 'firehose:setup', 'component:setup'] + : [], teardown: 'component:teardown', }, { From 1989386b02d7d7f7a22b7f2a245a92f7b77743d0 Mon Sep 17 00:00:00 2001 From: lapenna-bjss Date: Fri, 24 Apr 2026 13:22:52 +0100 Subject: [PATCH 10/16] Extract setup and teardown from component tests --- scripts/tests/integration.sh | 24 ++++++---- .../config/component/component.config.ts | 45 ++++++++----------- tests/playwright/package.json | 4 +- 3 files changed, 36 insertions(+), 37 deletions(-) diff --git a/scripts/tests/integration.sh b/scripts/tests/integration.sh index 3fc69266..50445829 100755 --- a/scripts/tests/integration.sh +++ b/scripts/tests/integration.sh @@ -9,12 +9,18 @@ npx playwright install --with-deps > /dev/null cd tests/playwright -# When sharding, shards 2+ must wait for shard 1 to complete setup -# (purge queues, alter firehose buffers) before running tests. -# Shard 1 setup typically takes ~30s. -if [[ -n "${PLAYWRIGHT_SHARD:-}" && ! "${PLAYWRIGHT_SHARD}" == 1/* ]]; then - echo "Shard ${PLAYWRIGHT_SHARD}: waiting 30s for shard 1 setup to complete..." - sleep 30 -fi - -npm run test:component -- ${PLAYWRIGHT_SHARD:+--shard="$PLAYWRIGHT_SHARD"} +case "${INTEGRATION_MODE:-run}" in + setup) + npm run test:component:setup + ;; + teardown) + npm run test:component:teardown + ;; + run) + npm run test:component -- ${PLAYWRIGHT_SHARD:+--shard="$PLAYWRIGHT_SHARD"} + ;; + *) + echo "[ERROR] Unknown INTEGRATION_MODE: ${INTEGRATION_MODE}" >&2 + exit 1 + ;; +esac diff --git a/tests/playwright/config/component/component.config.ts b/tests/playwright/config/component/component.config.ts index a414af80..4a784936 100644 --- a/tests/playwright/config/component/component.config.ts +++ b/tests/playwright/config/component/component.config.ts @@ -1,9 +1,6 @@ import { defineConfig } from '@playwright/test'; import baseConfig from 'config/playwright.config'; -const shard = process.env.PLAYWRIGHT_SHARD; -const isFirstShard = !shard || shard.startsWith('1/'); - export default defineConfig({ ...baseConfig, @@ -12,33 +9,27 @@ export default defineConfig({ timeout: 10_000, // default is 5 seconds. After creating and previewing sometimes the load is slow on a cold start }, projects: [ - ...(isFirstShard - ? [ - { - name: 'senders:setup', - testMatch: 'senders.setup.ts', - }, - { - name: 'firehose:setup', - testMatch: 'firehose.setup.ts', - teardown: 'firehose:teardown', - }, - { - name: 'firehose:teardown', - testMatch: 'firehose.teardown.ts', - }, - { - name: 'component:setup', - testMatch: 'component.setup.ts', - }, - ] - : []), + { + name: 'senders:setup', + testMatch: 'senders.setup.ts', + }, + { + name: 'firehose:setup', + testMatch: 'firehose.setup.ts', + teardown: 'firehose:teardown', + }, + { + name: 'firehose:teardown', + testMatch: 'firehose.teardown.ts', + }, + { + name: 'component:setup', + testMatch: 'component.setup.ts', + }, { name: 'component', testMatch: '*.component.spec.ts', - dependencies: isFirstShard - ? ['senders:setup', 'firehose:setup', 'component:setup'] - : [], + dependencies: ['senders:setup', 'firehose:setup', 'component:setup'], teardown: 'component:teardown', }, { diff --git a/tests/playwright/package.json b/tests/playwright/package.json index 7ebede52..2bae29e6 100644 --- a/tests/playwright/package.json +++ b/tests/playwright/package.json @@ -26,7 +26,9 @@ "scripts": { "lint": "eslint .", "lint:fix": "npm run lint -- --fix", - "test:component": "playwright test -c config/component/component.config.ts", + "test:component": "playwright test -c config/component/component.config.ts --project component --project component:teardown", + "test:component:setup": "playwright test -c config/component/component.config.ts --project senders:setup --project firehose:setup --project component:setup", + "test:component:teardown": "playwright test -c config/component/component.config.ts --project firehose:teardown --project component:teardown", "test:unit": "echo \"Unit tests not required\"", "typecheck": "tsc --noEmit" }, From 4bb6a4ef0890a6d32bc956c239f2ddd1a4560b2e Mon Sep 17 00:00:00 2001 From: lapenna-bjss Date: Fri, 24 Apr 2026 13:58:15 +0100 Subject: [PATCH 11/16] Add npm ci cache --- .github/actions/acceptance-tests/action.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/actions/acceptance-tests/action.yaml b/.github/actions/acceptance-tests/action.yaml index 6592ef46..cc8d1f23 100644 --- a/.github/actions/acceptance-tests/action.yaml +++ b/.github/actions/acceptance-tests/action.yaml @@ -39,7 +39,18 @@ runs: with: node-version: ${{ steps.nodejs_version.outputs.nodejs_version }} GITHUB_TOKEN: ${{ env.GITHUB_TOKEN }} + - name: Cache node modules + id: cache-node-modules + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + with: + path: | + node_modules + tests/playwright/node_modules + key: node-modules-${{ runner.os }}-${{ hashFiles('package-lock.json', 'tests/playwright/package-lock.json') }} + restore-keys: | + node-modules-${{ runner.os }}- - name: "Install dependencies" + if: steps.cache-node-modules.outputs.cache-hit != 'true' shell: bash run: | npm ci From e73ba4978157599e1318ce17b50c575e8e479245 Mon Sep 17 00:00:00 2001 From: lapenna-bjss Date: Fri, 24 Apr 2026 16:03:16 +0100 Subject: [PATCH 12/16] Add schema/playwright cache --- .github/actions/acceptance-tests/action.yaml | 25 +++++++++++++++++++ scripts/tests/integration.sh | 1 - .../config/component/component.config.ts | 1 - 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/.github/actions/acceptance-tests/action.yaml b/.github/actions/acceptance-tests/action.yaml index cc8d1f23..f1d5d9c1 100644 --- a/.github/actions/acceptance-tests/action.yaml +++ b/.github/actions/acceptance-tests/action.yaml @@ -54,7 +54,32 @@ runs: shell: bash run: | npm ci + - name: "Cache Playwright browsers" + id: cache-playwright + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + with: + path: ~/.cache/ms-playwright + key: playwright-${{ runner.os }}-${{ hashFiles('tests/playwright/package-lock.json') }} + restore-keys: | + playwright-${{ runner.os }}- + - name: "Install Playwright browsers" + if: steps.cache-playwright.outputs.cache-hit != 'true' + shell: bash + run: npx playwright install --with-deps + - name: "Cache generated dependencies" + id: cache-generated-deps + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + with: + path: | + schemas/digital-letters/ + output/digital-letters/ + src/digital-letters-events/types/ + src/digital-letters-events/validators/ + src/digital-letters-events/digital_letters_events/models/ + src/digital-letters-events/guard-functions/ + key: generated-deps-${{ runner.os }}-${{ hashFiles('src/cloudevents/**', 'src/typescript-schema-generator/**', 'src/python-schema-generator/**') }} - name: "Generate dependencies" + if: steps.cache-generated-deps.outputs.cache-hit != 'true' shell: bash run: | npm run generate-dependencies diff --git a/scripts/tests/integration.sh b/scripts/tests/integration.sh index 50445829..22a824b1 100755 --- a/scripts/tests/integration.sh +++ b/scripts/tests/integration.sh @@ -5,7 +5,6 @@ set -euo pipefail cd "$(git rev-parse --show-toplevel)" npm install -npx playwright install --with-deps > /dev/null cd tests/playwright diff --git a/tests/playwright/config/component/component.config.ts b/tests/playwright/config/component/component.config.ts index 4a784936..ff5fb815 100644 --- a/tests/playwright/config/component/component.config.ts +++ b/tests/playwright/config/component/component.config.ts @@ -29,7 +29,6 @@ export default defineConfig({ { name: 'component', testMatch: '*.component.spec.ts', - dependencies: ['senders:setup', 'firehose:setup', 'component:setup'], teardown: 'component:teardown', }, { From 340281d52e8cf6a44957419ef04cb0c5e1df7aee Mon Sep 17 00:00:00 2001 From: lapenna-bjss Date: Fri, 24 Apr 2026 16:34:00 +0100 Subject: [PATCH 13/16] Remove teardown from the script --- tests/playwright/config/component/component.config.ts | 1 - tests/playwright/package.json | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/playwright/config/component/component.config.ts b/tests/playwright/config/component/component.config.ts index ff5fb815..1dbda7ae 100644 --- a/tests/playwright/config/component/component.config.ts +++ b/tests/playwright/config/component/component.config.ts @@ -29,7 +29,6 @@ export default defineConfig({ { name: 'component', testMatch: '*.component.spec.ts', - teardown: 'component:teardown', }, { name: 'component:teardown', diff --git a/tests/playwright/package.json b/tests/playwright/package.json index 2bae29e6..6fae9a76 100644 --- a/tests/playwright/package.json +++ b/tests/playwright/package.json @@ -26,7 +26,7 @@ "scripts": { "lint": "eslint .", "lint:fix": "npm run lint -- --fix", - "test:component": "playwright test -c config/component/component.config.ts --project component --project component:teardown", + "test:component": "playwright test -c config/component/component.config.ts --project component", "test:component:setup": "playwright test -c config/component/component.config.ts --project senders:setup --project firehose:setup --project component:setup", "test:component:teardown": "playwright test -c config/component/component.config.ts --project firehose:teardown --project component:teardown", "test:unit": "echo \"Unit tests not required\"", From f2e0a9d1abe2a7669527be8ee21bd3d9a505ec30 Mon Sep 17 00:00:00 2001 From: lapenna-bjss Date: Fri, 24 Apr 2026 16:46:59 +0100 Subject: [PATCH 14/16] Make firehose buffer configurable per env --- .../terraform/components/dl/README.md | 2 ++ ...irehose_delivery_stream_to_s3_reporting.tf | 4 ++-- .../terraform/components/dl/variables.tf | 12 +++++++++++ .../config/component/component.config.ts | 9 -------- .../config/component/firehose.setup.ts | 21 ------------------- .../config/component/firehose.teardown.ts | 21 ------------------- .../playwright/constants/backend-constants.ts | 4 ++-- tests/playwright/package.json | 4 ++-- 8 files changed, 20 insertions(+), 57 deletions(-) delete mode 100644 tests/playwright/config/component/firehose.setup.ts delete mode 100644 tests/playwright/config/component/firehose.teardown.ts diff --git a/infrastructure/terraform/components/dl/README.md b/infrastructure/terraform/components/dl/README.md index 5b800ed2..1149a210 100644 --- a/infrastructure/terraform/components/dl/README.md +++ b/infrastructure/terraform/components/dl/README.md @@ -34,6 +34,8 @@ No requirements. | [event\_anomaly\_period](#input\_event\_anomaly\_period) | The period in seconds over which the specified statistic is applied for anomaly detection. Minimum 300 seconds (5 minutes). Recommended: 300-600. | `number` | `300` | no | | [eventpub\_control\_plane\_bus\_arn](#input\_eventpub\_control\_plane\_bus\_arn) | Event publisher control plane | `string` | n/a | yes | | [eventpub\_data\_plane\_bus\_arn](#input\_eventpub\_data\_plane\_bus\_arn) | Event publisher data plane | `string` | n/a | yes | +| [firehose\_destination\_buffer\_interval](#input\_firehose\_destination\_buffer\_interval) | The Firehose destination buffer interval in seconds. Lower values reduce latency for tests but increase costs. Minimum is 60, default (Terraform) is 300. | `number` | `300` | no | +| [firehose\_processor\_buffer\_interval](#input\_firehose\_processor\_buffer\_interval) | The Firehose Lambda processor buffer interval in seconds. Should be 1 more than firehose\_destination\_buffer\_interval to ensure destination flushes first. Minimum is 0. | `number` | `301` | no | | [force\_destroy](#input\_force\_destroy) | Flag to force deletion of S3 buckets | `bool` | `false` | no | | [force\_lambda\_code\_deploy](#input\_force\_lambda\_code\_deploy) | If the lambda package in s3 has the same commit id tag as the terraform build branch, the lambda will not update automatically. Set to True if making changes to Lambda code from on the same commit for example during development | `bool` | `false` | no | | [group](#input\_group) | The group variables are being inherited from (often synonmous with account short-name) | `string` | n/a | yes | diff --git a/infrastructure/terraform/components/dl/kinesis_firehose_delivery_stream_to_s3_reporting.tf b/infrastructure/terraform/components/dl/kinesis_firehose_delivery_stream_to_s3_reporting.tf index 86efee7f..0be07006 100644 --- a/infrastructure/terraform/components/dl/kinesis_firehose_delivery_stream_to_s3_reporting.tf +++ b/infrastructure/terraform/components/dl/kinesis_firehose_delivery_stream_to_s3_reporting.tf @@ -11,7 +11,7 @@ resource "aws_kinesis_firehose_delivery_stream" "to_s3_reporting" { error_output_prefix = "${local.firehose_output_path_prefix}/errors/!{timestamp:yyyy}-!{timestamp:MM}-!{timestamp:dd}-!{timestamp:HH}/!{firehose:error-output-type}/" buffering_size = 128 - buffering_interval = 300 + buffering_interval = var.firehose_destination_buffer_interval dynamic_partitioning_configuration { enabled = true @@ -37,7 +37,7 @@ resource "aws_kinesis_firehose_delivery_stream" "to_s3_reporting" { } parameters { parameter_name = "BufferIntervalInSeconds" - parameter_value = 301 + parameter_value = var.firehose_processor_buffer_interval } } } diff --git a/infrastructure/terraform/components/dl/variables.tf b/infrastructure/terraform/components/dl/variables.tf index 96a18682..10760f56 100644 --- a/infrastructure/terraform/components/dl/variables.tf +++ b/infrastructure/terraform/components/dl/variables.tf @@ -261,6 +261,18 @@ variable "sqs_visibility_timeout_seconds" { default = "270" } +variable "firehose_destination_buffer_interval" { + type = number + description = "The Firehose destination buffer interval in seconds. Lower values reduce latency for tests but increase costs. Minimum is 60, default (Terraform) is 300." + default = 300 +} + +variable "firehose_processor_buffer_interval" { + type = number + description = "The Firehose Lambda processor buffer interval in seconds. Should be 1 more than firehose_destination_buffer_interval to ensure destination flushes first. Minimum is 0." + default = 301 +} + variable "lambda_timeout_seconds" { type = string description = "The timeout of the lambdas that are triggered by SQS. " diff --git a/tests/playwright/config/component/component.config.ts b/tests/playwright/config/component/component.config.ts index 1dbda7ae..2b39dacb 100644 --- a/tests/playwright/config/component/component.config.ts +++ b/tests/playwright/config/component/component.config.ts @@ -13,15 +13,6 @@ export default defineConfig({ name: 'senders:setup', testMatch: 'senders.setup.ts', }, - { - name: 'firehose:setup', - testMatch: 'firehose.setup.ts', - teardown: 'firehose:teardown', - }, - { - name: 'firehose:teardown', - testMatch: 'firehose.teardown.ts', - }, { name: 'component:setup', testMatch: 'component.setup.ts', diff --git a/tests/playwright/config/component/firehose.setup.ts b/tests/playwright/config/component/firehose.setup.ts deleted file mode 100644 index f7ecf537..00000000 --- a/tests/playwright/config/component/firehose.setup.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { test as setup } from '@playwright/test'; -import { - MINIMUM_DESTINATION_BUFFER_INTERVAL, - MINIMUM_PROCESSOR_BUFFER_INTERVAL, - TERRAFORM_DESTINATION_BUFFER_INTERVAL, - TERRAFORM_PROCESSOR_BUFFER_INTERVAL, -} from 'constants/backend-constants'; -import { alterFirehoseBufferIntervals } from 'helpers/data-firehose-helpers'; - -setup('Reduce Firehose buffer intervals', async () => { - await alterFirehoseBufferIntervals({ - expected: { - destination: TERRAFORM_DESTINATION_BUFFER_INTERVAL, - processor: TERRAFORM_PROCESSOR_BUFFER_INTERVAL, - }, - update: { - destination: MINIMUM_DESTINATION_BUFFER_INTERVAL, - processor: MINIMUM_PROCESSOR_BUFFER_INTERVAL, - }, - }); -}); diff --git a/tests/playwright/config/component/firehose.teardown.ts b/tests/playwright/config/component/firehose.teardown.ts deleted file mode 100644 index 11844458..00000000 --- a/tests/playwright/config/component/firehose.teardown.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { test as teardown } from '@playwright/test'; -import { - MINIMUM_DESTINATION_BUFFER_INTERVAL, - MINIMUM_PROCESSOR_BUFFER_INTERVAL, - TERRAFORM_DESTINATION_BUFFER_INTERVAL, - TERRAFORM_PROCESSOR_BUFFER_INTERVAL, -} from 'constants/backend-constants'; -import { alterFirehoseBufferIntervals } from 'helpers/data-firehose-helpers'; - -teardown('Restore Firehose buffer intervals', async () => { - await alterFirehoseBufferIntervals({ - expected: { - destination: MINIMUM_DESTINATION_BUFFER_INTERVAL, - processor: MINIMUM_PROCESSOR_BUFFER_INTERVAL, - }, - update: { - destination: TERRAFORM_DESTINATION_BUFFER_INTERVAL, - processor: TERRAFORM_PROCESSOR_BUFFER_INTERVAL, - }, - }); -}); diff --git a/tests/playwright/constants/backend-constants.ts b/tests/playwright/constants/backend-constants.ts index 8122b774..3adacdc2 100644 --- a/tests/playwright/constants/backend-constants.ts +++ b/tests/playwright/constants/backend-constants.ts @@ -82,8 +82,8 @@ export const MESH_DOWNLOAD_LAMBDA_LOG_GROUP_NAME = `/aws/lambda/${CSI}-mesh-down // Data Firehose export const FIREHOSE_STREAM_NAME = `${CSI}-to-s3-reporting`; -export const TERRAFORM_DESTINATION_BUFFER_INTERVAL = 300; -export const TERRAFORM_PROCESSOR_BUFFER_INTERVAL = 301; +export const TERRAFORM_DESTINATION_BUFFER_INTERVAL = 60; +export const TERRAFORM_PROCESSOR_BUFFER_INTERVAL = 61; export const MINIMUM_DESTINATION_BUFFER_INTERVAL = 60; export const MINIMUM_PROCESSOR_BUFFER_INTERVAL = 0; diff --git a/tests/playwright/package.json b/tests/playwright/package.json index 6fae9a76..a8367ec7 100644 --- a/tests/playwright/package.json +++ b/tests/playwright/package.json @@ -27,8 +27,8 @@ "lint": "eslint .", "lint:fix": "npm run lint -- --fix", "test:component": "playwright test -c config/component/component.config.ts --project component", - "test:component:setup": "playwright test -c config/component/component.config.ts --project senders:setup --project firehose:setup --project component:setup", - "test:component:teardown": "playwright test -c config/component/component.config.ts --project firehose:teardown --project component:teardown", + "test:component:setup": "playwright test -c config/component/component.config.ts --project senders:setup --project component:setup", + "test:component:teardown": "playwright test -c config/component/component.config.ts --project component:teardown", "test:unit": "echo \"Unit tests not required\"", "typecheck": "tsc --noEmit" }, From 2aa82d34bc84e4e71ff565d01f592c972777f16c Mon Sep 17 00:00:00 2001 From: lapenna-bjss Date: Fri, 24 Apr 2026 18:09:43 +0100 Subject: [PATCH 15/16] test --- .github/workflows/cicd-1-pull-request.yaml | 2 ++ .github/workflows/stage-4-acceptance.yaml | 5 +++++ tests/playwright/helpers/sqs-helpers.ts | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cicd-1-pull-request.yaml b/.github/workflows/cicd-1-pull-request.yaml index 9309aec5..063c800b 100644 --- a/.github/workflows/cicd-1-pull-request.yaml +++ b/.github/workflows/cicd-1-pull-request.yaml @@ -180,6 +180,7 @@ jobs: --terraformAction "apply" \ --overrideProjectName "nhs" \ --overrideRoleName "nhs-main-acct-digital-letters-github-deploy" \ + --internalRef "feature/add-shard-input-to-dynamic-env-tests" \ --overrides "branch_name=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" acceptance-stage: # Recommended maximum execution time is 10 minutes name: "Acceptance stage" @@ -189,6 +190,7 @@ jobs: with: target_environment: "pr${{ needs.metadata.outputs.pr_number }}" target_account_group: nhs-notify-digital-letters-dev + internal_ref: "feature/add-shard-input-to-dynamic-env-tests" secrets: APP_PEM_FILE: ${{ secrets.APP_PEM_FILE }} APP_CLIENT_ID: ${{ secrets.APP_CLIENT_ID }} diff --git a/.github/workflows/stage-4-acceptance.yaml b/.github/workflows/stage-4-acceptance.yaml index 08d6a1fb..c68c3d60 100644 --- a/.github/workflows/stage-4-acceptance.yaml +++ b/.github/workflows/stage-4-acceptance.yaml @@ -20,6 +20,10 @@ on: description: "Account for the environment being tested" required: true type: string + internal_ref: + description: "Branch or ref to use (defaults to main)" + required: false + type: string jobs: test-security: @@ -85,6 +89,7 @@ jobs: --targetEnvironment "$TARGET_ENVIRONMENT" \ --targetAccountGroup "$TARGET_ACCOUNT_GROUP" \ --targetComponent "dl" \ + --internalRef "${{ inputs.internal_ref || 'main' }}" \ --enableSharding "true" test-accessibility: name: "Accessibility test" diff --git a/tests/playwright/helpers/sqs-helpers.ts b/tests/playwright/helpers/sqs-helpers.ts index ec9a7a4d..962f90c4 100644 --- a/tests/playwright/helpers/sqs-helpers.ts +++ b/tests/playwright/helpers/sqs-helpers.ts @@ -21,7 +21,7 @@ export async function expectMessageContainingString( QueueUrl: getQueueUrl(queueName), MaxNumberOfMessages: 10, WaitTimeSeconds: 1, - VisibilityTimeout: 2, + VisibilityTimeout: 5, }; await expectToPassEventually(async () => { From 3539a06fe35817c3aa05898ffde3264ad021443c Mon Sep 17 00:00:00 2001 From: lapenna-bjss Date: Fri, 24 Apr 2026 18:38:27 +0100 Subject: [PATCH 16/16] Update timeouts --- tests/playwright/helpers/expectations.ts | 2 +- tests/playwright/helpers/sqs-helpers.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/playwright/helpers/expectations.ts b/tests/playwright/helpers/expectations.ts index 2f171fa7..fbbda638 100644 --- a/tests/playwright/helpers/expectations.ts +++ b/tests/playwright/helpers/expectations.ts @@ -36,7 +36,7 @@ test.afterEach(async () => { */ async function expectToPassEventually( expectationFunction: () => Promise, - timeout = 30, + timeout = 60, delay = 1, ): Promise { const invocationToken = Symbol('invocationToken'); diff --git a/tests/playwright/helpers/sqs-helpers.ts b/tests/playwright/helpers/sqs-helpers.ts index 962f90c4..9e0103bb 100644 --- a/tests/playwright/helpers/sqs-helpers.ts +++ b/tests/playwright/helpers/sqs-helpers.ts @@ -15,7 +15,7 @@ function getQueueUrl(queueName: string) { export async function expectMessageContainingString( queueName: string, searchTerm: string, - timeout = 30, + timeout = 60, ) { const input: ReceiveMessageCommandInput = { QueueUrl: getQueueUrl(queueName),