Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions .github/actions/acceptance-tests/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -34,11 +39,47 @@ 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
- 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
Expand All @@ -58,6 +99,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
Expand Down
7 changes: 7 additions & 0 deletions .github/scripts/dispatch_internal_repo_workflow.sh
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ while [[ $# -gt 0 ]]; do
overrideRoleName="$2"
shift 2
;;
--enableSharding) # Enable test sharding across 4 parallel runners (optional)
enableSharding="$2"
shift 2
;;
*)
echo "[ERROR] Unknown argument: $1"
exit 1
Expand Down Expand Up @@ -167,6 +171,7 @@ echo " overrides: $overrides"
echo " overrideProjectName: $overrideProjectName"
echo " overrideRoleName: $overrideRoleName"
echo " targetProject: $targetProject"
echo " enableSharding: ${enableSharding:-}"

DISPATCH_EVENT=$(jq -ncM \
--arg infraRepoName "$infraRepoName" \
Expand All @@ -180,6 +185,7 @@ DISPATCH_EVENT=$(jq -ncM \
--arg overrideProjectName "$overrideProjectName" \
--arg overrideRoleName "$overrideRoleName" \
--arg targetProject "$targetProject" \
--argjson enableSharding "${enableSharding:-false}" \
'{
"ref": "'"$internalRef"'",
"inputs": (
Expand All @@ -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 $enableSharding then { "enableSharding": $enableSharding } else {} end) +
{
"releaseVersion": $releaseVersion,
"targetEnvironment": $targetEnvironment,
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/cicd-1-pull-request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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 }}
Expand Down
8 changes: 7 additions & 1 deletion .github/workflows/stage-4-acceptance.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -84,7 +88,9 @@ jobs:
--targetWorkflow "dispatch-contextual-tests-dynamic-env.yaml" \
--targetEnvironment "$TARGET_ENVIRONMENT" \
--targetAccountGroup "$TARGET_ACCOUNT_GROUP" \
--targetComponent "dl"
--targetComponent "dl" \
--internalRef "${{ inputs.internal_ref || 'main' }}" \
--enableSharding "true"
test-accessibility:
name: "Accessibility test"
runs-on: ubuntu-latest
Expand Down
4 changes: 4 additions & 0 deletions infrastructure/terraform/components/dl/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ No requirements.
| <a name="input_event_anomaly_period"></a> [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 |
| <a name="input_eventpub_control_plane_bus_arn"></a> [eventpub\_control\_plane\_bus\_arn](#input\_eventpub\_control\_plane\_bus\_arn) | Event publisher control plane | `string` | n/a | yes |
| <a name="input_eventpub_data_plane_bus_arn"></a> [eventpub\_data\_plane\_bus\_arn](#input\_eventpub\_data\_plane\_bus\_arn) | Event publisher data plane | `string` | n/a | yes |
| <a name="input_firehose_destination_buffer_interval"></a> [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 |
| <a name="input_firehose_processor_buffer_interval"></a> [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 |
| <a name="input_force_destroy"></a> [force\_destroy](#input\_force\_destroy) | Flag to force deletion of S3 buckets | `bool` | `false` | no |
| <a name="input_force_lambda_code_deploy"></a> [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 |
| <a name="input_group"></a> [group](#input\_group) | The group variables are being inherited from (often synonmous with account short-name) | `string` | n/a | yes |
Expand Down Expand Up @@ -103,6 +105,8 @@ No requirements.
| <a name="module_sqs_report_generator"></a> [sqs\_report\_generator](#module\_sqs\_report\_generator) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-sqs.zip | n/a |
| <a name="module_sqs_report_sender"></a> [sqs\_report\_sender](#module\_sqs\_report\_sender) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-sqs.zip | n/a |
| <a name="module_sqs_scanner"></a> [sqs\_scanner](#module\_sqs\_scanner) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-sqs.zip | n/a |
| <a name="module_sqs_test_observer"></a> [sqs\_test\_observer](#module\_sqs\_test\_observer) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-sqs.zip | n/a |
| <a name="module_sqs_test_observer_queues"></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 |
| <a name="module_sqs_ttl"></a> [sqs\_ttl](#module\_sqs\_ttl) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-sqs.zip | n/a |
| <a name="module_sqs_ttl_handle_expiry_errors"></a> [sqs\_ttl\_handle\_expiry\_errors](#module\_sqs\_ttl\_handle\_expiry\_errors) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-sqs.zip | n/a |
| <a name="module_ttl_create"></a> [ttl\_create](#module\_ttl\_create) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-lambda.zip | n/a |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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]
}
}
}
Original file line number Diff line number Diff line change
@@ -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
}
12 changes: 12 additions & 0 deletions infrastructure/terraform/components/dl/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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. "
Expand Down
17 changes: 15 additions & 2 deletions scripts/tests/integration.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,21 @@ set -euo pipefail
cd "$(git rev-parse --show-toplevel)"

npm install
npx playwright install --with-deps > /dev/null

cd tests/playwright

npm run test:component
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
11 changes: 2 additions & 9 deletions tests/playwright/config/component/component.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,12 @@ export default defineConfig({
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'],
teardown: 'component:teardown',
},
{
name: 'component:teardown',
Expand Down
Loading
Loading