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 43435531b..e2b3be4cc 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 f27e48de9..136e6881a 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 000000000..f2be9f4ac --- /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 0ca126e33..625e25d2f 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 0ff3ce0ee..a267858bd 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 000000000..5fea3e598 --- /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 a267858bd..3f02fcbb3 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 5fea3e598..2740b7de4 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 625e25d2f..8522935ff 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 96ad87362..cf25ea579 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 3f02fcbb3..53f39466a 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 c04ac2bf6..30648786b 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 fca7aff57..f0e78dd23 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 eb14754da..1eaf834d6 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 b1a30c109..65260091e 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 d57d71f8a..3def2bda8 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 2740b7de4..158edf7e3 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 cf25ea579..531261b17 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 30648786b..f72366ff4 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 f0e78dd23..4d4d77e23 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 1eaf834d6..b43ce3c7a 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 65260091e..6d309a062 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 3def2bda8..7d20f7ef3 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 87ca74faa..4a7849368 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 000000000..710ce52be --- /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 531261b17..67498090d 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 c65a1ed37..355260646 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 53f39466a..2ed6ac4e4 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 f72366ff4..d1759c81e 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 4d4d77e23..44a4cc275 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 6dfebc614..19fd3681c 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 befb25d11..5900ecc86 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 d6b013389..7b813ec11 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 945c5e5cd..1251bc778 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 b43ce3c7a..f08e0f61f 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 6d309a062..f436010c1 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 158edf7e3..58e80897f 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 58e80897f..5111b6ab5 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 e2b3be4cc..5b800ed22 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 000000000..34ff9f77f --- /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 710ce52be..39a48816d 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 8522935ff..8122b774e 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 67498090d..c41b94ffa 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 355260646..7adacb1f3 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 2ed6ac4e4..3c75ac075 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 d1759c81e..43459db31 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 44a4cc275..4f43f9c78 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 19fd3681c..8eb724861 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 5900ecc86..81de74f91 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 7b813ec11..108912264 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 1251bc778..8f06df627 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 f08e0f61f..1b1b0f78c 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 f436010c1..a39fefe47 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 7d20f7ef3..e3c6e4fa4 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 5111b6ab5..f000546ba 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 3b76f9bb8..6592ef46d 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 a52c1bbee..19c43c9e2 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 cbd3b1551..052df794d 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 893c4efc2..e8c22afe8 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 19c43c9e2..1e0e221b5 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 052df794d..08d6a1fb9 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 e8c22afe8..3fc69266a 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 4a7849368..a414af80e 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 3fc69266a..50445829b 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 a414af80e..4a7849368 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 7ebede52e..2bae29e63 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 6592ef46d..cc8d1f235 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 cc8d1f235..f1d5d9c1b 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 50445829b..22a824b1d 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 4a7849368..ff5fb8156 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 ff5fb8156..1dbda7ae0 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 2bae29e63..6fae9a76d 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 5b800ed22..1149a210c 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 86efee7f4..0be07006e 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 96a186827..10760f568 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 1dbda7ae0..2b39dacb2 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 f7ecf537e..000000000 --- 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 11844458c..000000000 --- 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 8122b774e..3adacdc27 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 6fae9a76d..a8367ec76 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 9309aec55..063c800b4 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 08d6a1fb9..c68c3d60f 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 ec9a7a4dc..962f90c44 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 2f171fa72..fbbda6384 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 962f90c44..9e0103bb5 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),