diff --git a/.github/workflows/stage-1-commit.yaml b/.github/workflows/stage-1-commit.yaml index 4d9d5b14..80ad8c8f 100644 --- a/.github/workflows/stage-1-commit.yaml +++ b/.github/workflows/stage-1-commit.yaml @@ -1,5 +1,8 @@ name: "Commit stage" +permissions: + contents: read + on: workflow_call: inputs: diff --git a/docs/package.json b/docs/package.json index 2f2d699b..c694856e 100644 --- a/docs/package.json +++ b/docs/package.json @@ -14,6 +14,7 @@ "build": "JEKYLL_ENV=production bundle exec jekyll build --trace --config _config.yml,_config.version.yml", "debug": "JEKYLL_ENV=development BUNDLE_GEMFILE=Gemfile bundle exec jekyll serve --config _config.yml,_config.dev.yml,_config.version.yml --limit_posts 100 --trace", "generate-includes": "./generate-includes.sh", + "lint": "echo \"Documentation module has no code to lint\"", "test:unit": "echo \"Documentation module has no unit tests\"", "typecheck": "echo \"Documentation module has no typescript to typecheck\"" }, diff --git a/internal/datastore/.eslintignore b/internal/datastore/.eslintignore deleted file mode 100644 index 1521c8b7..00000000 --- a/internal/datastore/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -dist diff --git a/internal/events/package.json b/internal/events/package.json index 2d799646..8005d13e 100644 --- a/internal/events/package.json +++ b/internal/events/package.json @@ -50,5 +50,5 @@ "typecheck": "tsc --noEmit" }, "types": "dist/index.d.ts", - "version": "1.0.5" + "version": "1.0.6" } diff --git a/internal/events/tsconfig.json b/internal/events/tsconfig.json index fa5c2949..a9b1c7ab 100644 --- a/internal/events/tsconfig.json +++ b/internal/events/tsconfig.json @@ -14,6 +14,7 @@ "extends": "../../tsconfig.base.json", "include": [ "src/**/*", - "package.json" + "package.json", + "jest.config.ts" ] } diff --git a/lambdas/api-handler/.eslintignore b/lambdas/api-handler/.eslintignore deleted file mode 100644 index 1521c8b7..00000000 --- a/lambdas/api-handler/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -dist diff --git a/lambdas/authorizer/.eslintignore b/lambdas/authorizer/.eslintignore deleted file mode 100644 index 1521c8b7..00000000 --- a/lambdas/authorizer/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -dist diff --git a/lambdas/authorizer/package.json b/lambdas/authorizer/package.json index b359c393..d89f0315 100644 --- a/lambdas/authorizer/package.json +++ b/lambdas/authorizer/package.json @@ -4,6 +4,7 @@ "@aws-sdk/lib-dynamodb": "^3.858.0", "@internal/datastore": "*", "@types/aws-lambda": "^8.10.148", + "aws-lambda": "^1.0.7", "esbuild": "^0.25.11", "pino": "^9.7.0", "zod": "^4.1.11" diff --git a/lambdas/letter-updates-transformer/.eslintignore b/lambdas/letter-updates-transformer/.eslintignore deleted file mode 100644 index 1521c8b7..00000000 --- a/lambdas/letter-updates-transformer/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -dist diff --git a/lambdas/letter-updates-transformer/src/mappers/__tests__/letter-mapper.test.ts b/lambdas/letter-updates-transformer/src/mappers/__tests__/letter-mapper.test.ts index fe5b5a79..9499ef95 100644 --- a/lambdas/letter-updates-transformer/src/mappers/__tests__/letter-mapper.test.ts +++ b/lambdas/letter-updates-transformer/src/mappers/__tests__/letter-mapper.test.ts @@ -22,7 +22,7 @@ describe("letter-mapper", () => { expect(event.dataschema).toBe( `https://notify.nhs.uk/cloudevents/schemas/supplier-api/letter.PRINTED.${event.dataschemaversion}.schema.json`, ); - expect(event.dataschemaversion).toBe("1.0.5"); + expect(event.dataschemaversion).toBe("1.0.6"); expect(event.subject).toBe("letter-origin/supplier-api/letter/id1"); expect(event.time).toBe("2025-11-24T15:55:18.000Z"); expect(event.recordedtime).toBe("2025-11-24T15:55:18.000Z"); diff --git a/lambdas/upsert-letter/.eslintignore b/lambdas/upsert-letter/.eslintignore deleted file mode 100644 index 1521c8b7..00000000 --- a/lambdas/upsert-letter/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -dist diff --git a/lambdas/upsert-letter/package.json b/lambdas/upsert-letter/package.json index af6f91bf..61d29e01 100644 --- a/lambdas/upsert-letter/package.json +++ b/lambdas/upsert-letter/package.json @@ -1,6 +1,7 @@ { "dependencies": { "@types/aws-lambda": "^8.10.148", + "aws-lambda": "^1.0.7", "esbuild": "^0.24.0" }, "devDependencies": { diff --git a/package-lock.json b/package-lock.json index 45ac4684..583d1726 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1630,6 +1630,7 @@ "@aws-sdk/lib-dynamodb": "^3.858.0", "@internal/datastore": "*", "@types/aws-lambda": "^8.10.148", + "aws-lambda": "^1.0.7", "esbuild": "^0.25.11", "pino": "^9.7.0", "zod": "^4.1.11" @@ -2501,6 +2502,7 @@ "version": "0.0.1", "dependencies": { "@types/aws-lambda": "^8.10.148", + "aws-lambda": "^1.0.7", "esbuild": "^0.24.0" }, "devDependencies": { @@ -8502,12 +8504,12 @@ } }, "node_modules/@smithy/abort-controller": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.5.tgz", - "integrity": "sha512-j7HwVkBw68YW8UmFRcjZOmssE77Rvk0GWAIN1oFBhsaovQmZWYCIcGa9/pwRB0ExI8Sk9MWNALTjftjHZea7VA==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.6.tgz", + "integrity": "sha512-P7JD4J+wxHMpGxqIg6SHno2tPkZbBUBLbPpR5/T1DEUvw/mEaINBMaPFZNM7lA+ToSCZ36j6nMHa+5kej+fhGg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.10.0", "tslib": "^2.6.2" }, "engines": { @@ -8540,16 +8542,16 @@ } }, "node_modules/@smithy/config-resolver": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.3.tgz", - "integrity": "sha512-ezHLe1tKLUxDJo2LHtDuEDyWXolw8WGOR92qb4bQdWq/zKenO5BvctZGrVJBK08zjezSk7bmbKFOXIVyChvDLw==", + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.4.tgz", + "integrity": "sha512-s3U5ChS21DwU54kMmZ0UJumoS5cg0+rGVZvN6f5Lp6EbAVi0ZyP+qDSHdewfmXKUgNK1j3z45JyzulkDukrjAA==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.5", - "@smithy/types": "^4.9.0", + "@smithy/node-config-provider": "^4.3.6", + "@smithy/types": "^4.10.0", "@smithy/util-config-provider": "^4.2.0", - "@smithy/util-endpoints": "^3.2.5", - "@smithy/util-middleware": "^4.2.5", + "@smithy/util-endpoints": "^3.2.6", + "@smithy/util-middleware": "^4.2.6", "tslib": "^2.6.2" }, "engines": { @@ -8557,18 +8559,18 @@ } }, "node_modules/@smithy/core": { - "version": "3.18.7", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.18.7.tgz", - "integrity": "sha512-axG9MvKhMWOhFbvf5y2DuyTxQueO0dkedY9QC3mAfndLosRI/9LJv8WaL0mw7ubNhsO4IuXX9/9dYGPFvHrqlw==", + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.19.0.tgz", + "integrity": "sha512-Y9oHXpBcXQgYHOcAEmxjkDilUbSTkgKjoHYed3WaYUH8jngq8lPWDBSpjHblJ9uOgBdy5mh3pzebrScDdYr29w==", "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-serde": "^4.2.6", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", + "@smithy/middleware-serde": "^4.2.7", + "@smithy/protocol-http": "^5.3.6", + "@smithy/types": "^4.10.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-stream": "^4.5.6", + "@smithy/util-middleware": "^4.2.6", + "@smithy/util-stream": "^4.5.7", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" @@ -8578,15 +8580,15 @@ } }, "node_modules/@smithy/credential-provider-imds": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.5.tgz", - "integrity": "sha512-BZwotjoZWn9+36nimwm/OLIcVe+KYRwzMjfhd4QT7QxPm9WY0HiOV8t/Wlh+HVUif0SBVV7ksq8//hPaBC/okQ==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.6.tgz", + "integrity": "sha512-xBmawExyTzOjbhzkZwg+vVm/khg28kG+rj2sbGlULjFd1jI70sv/cbpaR0Ev4Yfd6CpDUDRMe64cTqR//wAOyA==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", + "@smithy/node-config-provider": "^4.3.6", + "@smithy/property-provider": "^4.2.6", + "@smithy/types": "^4.10.0", + "@smithy/url-parser": "^4.2.6", "tslib": "^2.6.2" }, "engines": { @@ -8664,14 +8666,14 @@ } }, "node_modules/@smithy/fetch-http-handler": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.6.tgz", - "integrity": "sha512-3+RG3EA6BBJ/ofZUeTFJA7mHfSYrZtQIrDP9dI8Lf7X6Jbos2jptuLrAAteDiFVrmbEmLSuRG/bUKzfAXk7dhg==", + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.7.tgz", + "integrity": "sha512-fcVap4QwqmzQwQK9QU3keeEpCzTjnP9NJ171vI7GnD7nbkAIcP9biZhDUx88uRH9BabSsQDS0unUps88uZvFIQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.5", - "@smithy/querystring-builder": "^4.2.5", - "@smithy/types": "^4.9.0", + "@smithy/protocol-http": "^5.3.6", + "@smithy/querystring-builder": "^4.2.6", + "@smithy/types": "^4.10.0", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" }, @@ -8695,12 +8697,12 @@ } }, "node_modules/@smithy/hash-node": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.5.tgz", - "integrity": "sha512-DpYX914YOfA3UDT9CN1BM787PcHfWRBB43fFGCYrZFUH0Jv+5t8yYl+Pd5PW4+QzoGEDvn5d5QIO4j2HyYZQSA==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.6.tgz", + "integrity": "sha512-k3Dy9VNR37wfMh2/1RHkFf/e0rMyN0pjY0FdyY6ItJRjENYyVPRMwad6ZR1S9HFm6tTuIOd9pqKBmtJ4VHxvxg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.10.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" @@ -8724,12 +8726,12 @@ } }, "node_modules/@smithy/invalid-dependency": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.5.tgz", - "integrity": "sha512-2L2erASEro1WC5nV+plwIMxrTXpvpfzl4e+Nre6vBVRR2HKeGGcvpJyyL3/PpiSg+cJG2KpTmZmq934Olb6e5A==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.6.tgz", + "integrity": "sha512-E4t/V/q2T46RY21fpfznd1iSLTvCXKNKo4zJ1QuEFN4SE9gKfu2vb6bgq35LpufkQ+SETWIC7ZAf2GGvTlBaMQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.10.0", "tslib": "^2.6.2" }, "engines": { @@ -8763,13 +8765,13 @@ } }, "node_modules/@smithy/middleware-content-length": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.5.tgz", - "integrity": "sha512-Y/RabVa5vbl5FuHYV2vUCwvh/dqzrEY/K2yWPSqvhFUwIY0atLqO4TienjBXakoy4zrKAMCZwg+YEqmH7jaN7A==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.6.tgz", + "integrity": "sha512-0cjqjyfj+Gls30ntq45SsBtqF3dfJQCeqQPyGz58Pk8OgrAr5YiB7ZvDzjCA94p4r6DCI4qLm7FKobqBjf515w==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", + "@smithy/protocol-http": "^5.3.6", + "@smithy/types": "^4.10.0", "tslib": "^2.6.2" }, "engines": { @@ -8777,18 +8779,18 @@ } }, "node_modules/@smithy/middleware-endpoint": { - "version": "4.3.14", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.3.14.tgz", - "integrity": "sha512-v0q4uTKgBM8dsqGjqsabZQyH85nFaTnFcgpWU1uydKFsdyyMzfvOkNum9G7VK+dOP01vUnoZxIeRiJ6uD0kjIg==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.0.tgz", + "integrity": "sha512-M6qWfUNny6NFNy8amrCGIb9TfOMUkHVtg9bHtEFGRgfH7A7AtPpn/fcrToGPjVDK1ECuMVvqGQOXcZxmu9K+7A==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.18.7", - "@smithy/middleware-serde": "^4.2.6", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", - "@smithy/util-middleware": "^4.2.5", + "@smithy/core": "^3.19.0", + "@smithy/middleware-serde": "^4.2.7", + "@smithy/node-config-provider": "^4.3.6", + "@smithy/shared-ini-file-loader": "^4.4.1", + "@smithy/types": "^4.10.0", + "@smithy/url-parser": "^4.2.6", + "@smithy/util-middleware": "^4.2.6", "tslib": "^2.6.2" }, "engines": { @@ -8796,18 +8798,18 @@ } }, "node_modules/@smithy/middleware-retry": { - "version": "4.4.14", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.14.tgz", - "integrity": "sha512-Z2DG8Ej7FyWG1UA+7HceINtSLzswUgs2np3sZX0YBBxCt+CXG4QUxv88ZDS3+2/1ldW7LqtSY1UO/6VQ1pND8Q==", + "version": "4.4.16", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.16.tgz", + "integrity": "sha512-XPpNhNRzm3vhYm7YCsyw3AtmWggJbg1wNGAoqb7NBYr5XA5isMRv14jgbYyUV6IvbTBFZQdf2QpeW43LrRdStQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/service-error-classification": "^4.2.5", - "@smithy/smithy-client": "^4.9.10", - "@smithy/types": "^4.9.0", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-retry": "^4.2.5", + "@smithy/node-config-provider": "^4.3.6", + "@smithy/protocol-http": "^5.3.6", + "@smithy/service-error-classification": "^4.2.6", + "@smithy/smithy-client": "^4.10.1", + "@smithy/types": "^4.10.0", + "@smithy/util-middleware": "^4.2.6", + "@smithy/util-retry": "^4.2.6", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" }, @@ -8816,13 +8818,13 @@ } }, "node_modules/@smithy/middleware-serde": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.6.tgz", - "integrity": "sha512-VkLoE/z7e2g8pirwisLz8XJWedUSY8my/qrp81VmAdyrhi94T+riBfwP+AOEEFR9rFTSonC/5D2eWNmFabHyGQ==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.7.tgz", + "integrity": "sha512-PFMVHVPgtFECeu4iZ+4SX6VOQT0+dIpm4jSPLLL6JLSkp9RohGqKBKD0cbiXdeIFS08Forp0UHI6kc0gIHenSA==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", + "@smithy/protocol-http": "^5.3.6", + "@smithy/types": "^4.10.0", "tslib": "^2.6.2" }, "engines": { @@ -8830,12 +8832,12 @@ } }, "node_modules/@smithy/middleware-stack": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.5.tgz", - "integrity": "sha512-bYrutc+neOyWxtZdbB2USbQttZN0mXaOyYLIsaTbJhFsfpXyGWUxJpEuO1rJ8IIJm2qH4+xJT0mxUSsEDTYwdQ==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.6.tgz", + "integrity": "sha512-JSbALU3G+JS4kyBZPqnJ3hxIYwOVRV7r9GNQMS6j5VsQDo5+Es5nddLfr9TQlxZLNHPvKSh+XSB0OuWGfSWFcA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.10.0", "tslib": "^2.6.2" }, "engines": { @@ -8843,14 +8845,14 @@ } }, "node_modules/@smithy/node-config-provider": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.5.tgz", - "integrity": "sha512-UTurh1C4qkVCtqggI36DGbLB2Kv8UlcFdMXDcWMbqVY2uRg0XmT9Pb4Vj6oSQ34eizO1fvR0RnFV4Axw4IrrAg==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.6.tgz", + "integrity": "sha512-fYEyL59Qe82Ha1p97YQTMEQPJYmBS+ux76foqluaTVWoG9Px5J53w6NvXZNE3wP7lIicLDF7Vj1Em18XTX7fsA==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@smithy/property-provider": "^4.2.6", + "@smithy/shared-ini-file-loader": "^4.4.1", + "@smithy/types": "^4.10.0", "tslib": "^2.6.2" }, "engines": { @@ -8858,15 +8860,15 @@ } }, "node_modules/@smithy/node-http-handler": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.5.tgz", - "integrity": "sha512-CMnzM9R2WqlqXQGtIlsHMEZfXKJVTIrqCNoSd/QpAyp+Dw0a1Vps13l6ma1fH8g7zSPNsA59B/kWgeylFuA/lw==", + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.6.tgz", + "integrity": "sha512-Gsb9jf4ido5BhPfani4ggyrKDd3ZK+vTFWmUaZeFg5G3E5nhFmqiTzAIbHqmPs1sARuJawDiGMGR/nY+Gw6+aQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^4.2.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/querystring-builder": "^4.2.5", - "@smithy/types": "^4.9.0", + "@smithy/abort-controller": "^4.2.6", + "@smithy/protocol-http": "^5.3.6", + "@smithy/querystring-builder": "^4.2.6", + "@smithy/types": "^4.10.0", "tslib": "^2.6.2" }, "engines": { @@ -8874,12 +8876,12 @@ } }, "node_modules/@smithy/property-provider": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.5.tgz", - "integrity": "sha512-8iLN1XSE1rl4MuxvQ+5OSk/Zb5El7NJZ1td6Tn+8dQQHIjp59Lwl6bd0+nzw6SKm2wSSriH2v/I9LPzUic7EOg==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.6.tgz", + "integrity": "sha512-a/tGSLPtaia2krbRdwR4xbZKO8lU67DjMk/jfY4QKt4PRlKML+2tL/gmAuhNdFDioO6wOq0sXkfnddNFH9mNUA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.10.0", "tslib": "^2.6.2" }, "engines": { @@ -8887,12 +8889,12 @@ } }, "node_modules/@smithy/protocol-http": { - "version": "5.3.5", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.5.tgz", - "integrity": "sha512-RlaL+sA0LNMp03bf7XPbFmT5gN+w3besXSWMkA8rcmxLSVfiEXElQi4O2IWwPfxzcHkxqrwBFMbngB8yx/RvaQ==", + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.6.tgz", + "integrity": "sha512-qLRZzP2+PqhE3OSwvY2jpBbP0WKTZ9opTsn+6IWYI0SKVpbG+imcfNxXPq9fj5XeaUTr7odpsNpK6dmoiM1gJQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.10.0", "tslib": "^2.6.2" }, "engines": { @@ -8900,12 +8902,12 @@ } }, "node_modules/@smithy/querystring-builder": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.5.tgz", - "integrity": "sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.6.tgz", + "integrity": "sha512-MeM9fTAiD3HvoInK/aA8mgJaKQDvm8N0dKy6EiFaCfgpovQr4CaOkJC28XqlSRABM+sHdSQXbC8NZ0DShBMHqg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.10.0", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" }, @@ -8914,12 +8916,12 @@ } }, "node_modules/@smithy/querystring-parser": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.5.tgz", - "integrity": "sha512-031WCTdPYgiQRYNPXznHXof2YM0GwL6SeaSyTH/P72M1Vz73TvCNH2Nq8Iu2IEPq9QP2yx0/nrw5YmSeAi/AjQ==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.6.tgz", + "integrity": "sha512-YmWxl32SQRw/kIRccSOxzS/Ib8/b5/f9ex0r5PR40jRJg8X1wgM3KrR2In+8zvOGVhRSXgvyQpw9yOSlmfmSnA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.10.0", "tslib": "^2.6.2" }, "engines": { @@ -8927,24 +8929,24 @@ } }, "node_modules/@smithy/service-error-classification": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.5.tgz", - "integrity": "sha512-8fEvK+WPE3wUAcDvqDQG1Vk3ANLR8Px979te96m84CbKAjBVf25rPYSzb4xU4hlTyho7VhOGnh5i62D/JVF0JQ==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.6.tgz", + "integrity": "sha512-Q73XBrzJlGTut2nf5RglSntHKgAG0+KiTJdO5QQblLfr4TdliGwIAha1iZIjwisc3rA5ulzqwwsYC6xrclxVQg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0" + "@smithy/types": "^4.10.0" }, "engines": { "node": ">=18.0.0" } }, "node_modules/@smithy/shared-ini-file-loader": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.0.tgz", - "integrity": "sha512-5WmZ5+kJgJDjwXXIzr1vDTG+RhF9wzSODQBfkrQ2VVkYALKGvZX1lgVSxEkgicSAFnFhPj5rudJV0zoinqS0bA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.1.tgz", + "integrity": "sha512-tph+oQYPbpN6NamF030hx1gb5YN2Plog+GLaRHpoEDwp8+ZPG26rIJvStG9hkWzN2HBn3HcWg0sHeB0tmkYzqA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.10.0", "tslib": "^2.6.2" }, "engines": { @@ -8952,16 +8954,16 @@ } }, "node_modules/@smithy/signature-v4": { - "version": "5.3.5", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.5.tgz", - "integrity": "sha512-xSUfMu1FT7ccfSXkoLl/QRQBi2rOvi3tiBZU2Tdy3I6cgvZ6SEi9QNey+lqps/sJRnogIS+lq+B1gxxbra2a/w==", + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.6.tgz", + "integrity": "sha512-P1TXDHuQMadTMTOBv4oElZMURU4uyEhxhHfn+qOc2iofW9Rd4sZtBGx58Lzk112rIGVEYZT8eUMK4NftpewpRA==", "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^4.2.0", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", + "@smithy/protocol-http": "^5.3.6", + "@smithy/types": "^4.10.0", "@smithy/util-hex-encoding": "^4.2.0", - "@smithy/util-middleware": "^4.2.5", + "@smithy/util-middleware": "^4.2.6", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" @@ -8971,17 +8973,17 @@ } }, "node_modules/@smithy/smithy-client": { - "version": "4.9.10", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.9.10.tgz", - "integrity": "sha512-Jaoz4Jw1QYHc1EFww/E6gVtNjhoDU+gwRKqXP6C3LKYqqH2UQhP8tMP3+t/ePrhaze7fhLE8vS2q6vVxBANFTQ==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.10.1.tgz", + "integrity": "sha512-1ovWdxzYprhq+mWqiGZlt3kF69LJthuQcfY9BIyHx9MywTFKzFapluku1QXoaBB43GCsLDxNqS+1v30ure69AA==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.18.7", - "@smithy/middleware-endpoint": "^4.3.14", - "@smithy/middleware-stack": "^4.2.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", - "@smithy/util-stream": "^4.5.6", + "@smithy/core": "^3.19.0", + "@smithy/middleware-endpoint": "^4.4.0", + "@smithy/middleware-stack": "^4.2.6", + "@smithy/protocol-http": "^5.3.6", + "@smithy/types": "^4.10.0", + "@smithy/util-stream": "^4.5.7", "tslib": "^2.6.2" }, "engines": { @@ -8989,9 +8991,9 @@ } }, "node_modules/@smithy/types": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.9.0.tgz", - "integrity": "sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.10.0.tgz", + "integrity": "sha512-K9mY7V/f3Ul+/Gz4LJANZ3vJ/yiBIwCyxe0sPT4vNJK63Srvd+Yk1IzP0t+nE7XFSpIGtzR71yljtnqpUTYFlQ==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -9001,13 +9003,13 @@ } }, "node_modules/@smithy/url-parser": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.5.tgz", - "integrity": "sha512-VaxMGsilqFnK1CeBX+LXnSuaMx4sTL/6znSZh2829txWieazdVxr54HmiyTsIbpOTLcf5nYpq9lpzmwRdxj6rQ==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.6.tgz", + "integrity": "sha512-tVoyzJ2vXp4R3/aeV4EQjBDmCuWxRa8eo3KybL7Xv4wEM16nObYh7H1sNfcuLWHAAAzb0RVyxUz1S3sGj4X+Tg==", "license": "Apache-2.0", "dependencies": { - "@smithy/querystring-parser": "^4.2.5", - "@smithy/types": "^4.9.0", + "@smithy/querystring-parser": "^4.2.6", + "@smithy/types": "^4.10.0", "tslib": "^2.6.2" }, "engines": { @@ -9078,14 +9080,14 @@ } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.3.13", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.13.tgz", - "integrity": "sha512-hlVLdAGrVfyNei+pKIgqDTxfu/ZI2NSyqj4IDxKd5bIsIqwR/dSlkxlPaYxFiIaDVrBy0he8orsFy+Cz119XvA==", + "version": "4.3.15", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.15.tgz", + "integrity": "sha512-LiZQVAg/oO8kueX4c+oMls5njaD2cRLXRfcjlTYjhIqmwHnCwkQO5B3dMQH0c5PACILxGAQf6Mxsq7CjlDc76A==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.2.5", - "@smithy/smithy-client": "^4.9.10", - "@smithy/types": "^4.9.0", + "@smithy/property-provider": "^4.2.6", + "@smithy/smithy-client": "^4.10.1", + "@smithy/types": "^4.10.0", "tslib": "^2.6.2" }, "engines": { @@ -9093,17 +9095,17 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.2.16", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.16.tgz", - "integrity": "sha512-F1t22IUiJLHrxW9W1CQ6B9PN+skZ9cqSuzB18Eh06HrJPbjsyZ7ZHecAKw80DQtyGTRcVfeukKaCRYebFwclbg==", + "version": "4.2.18", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.18.tgz", + "integrity": "sha512-Kw2J+KzYm9C9Z9nY6+W0tEnoZOofstVCMTshli9jhQbQCy64rueGfKzPfuFBnVUqZD9JobxTh2DzHmPkp/Va/Q==", "license": "Apache-2.0", "dependencies": { - "@smithy/config-resolver": "^4.4.3", - "@smithy/credential-provider-imds": "^4.2.5", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/smithy-client": "^4.9.10", - "@smithy/types": "^4.9.0", + "@smithy/config-resolver": "^4.4.4", + "@smithy/credential-provider-imds": "^4.2.6", + "@smithy/node-config-provider": "^4.3.6", + "@smithy/property-provider": "^4.2.6", + "@smithy/smithy-client": "^4.10.1", + "@smithy/types": "^4.10.0", "tslib": "^2.6.2" }, "engines": { @@ -9111,13 +9113,13 @@ } }, "node_modules/@smithy/util-endpoints": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.5.tgz", - "integrity": "sha512-3O63AAWu2cSNQZp+ayl9I3NapW1p1rR5mlVHcF6hAB1dPZUQFfRPYtplWX/3xrzWthPGj5FqB12taJJCfH6s8A==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.6.tgz", + "integrity": "sha512-v60VNM2+mPvgHCBXEfMCYrQ0RepP6u6xvbAkMenfe4Mi872CqNkJzgcnQL837e8NdeDxBgrWQRTluKq5Lqdhfg==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.5", - "@smithy/types": "^4.9.0", + "@smithy/node-config-provider": "^4.3.6", + "@smithy/types": "^4.10.0", "tslib": "^2.6.2" }, "engines": { @@ -9137,12 +9139,12 @@ } }, "node_modules/@smithy/util-middleware": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.5.tgz", - "integrity": "sha512-6Y3+rvBF7+PZOc40ybeZMcGln6xJGVeY60E7jy9Mv5iKpMJpHgRE6dKy9ScsVxvfAYuEX4Q9a65DQX90KaQ3bA==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.6.tgz", + "integrity": "sha512-qrvXUkxBSAFomM3/OEMuDVwjh4wtqK8D2uDZPShzIqOylPst6gor2Cdp6+XrH4dyksAWq/bE2aSDYBTTnj0Rxg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.10.0", "tslib": "^2.6.2" }, "engines": { @@ -9150,13 +9152,13 @@ } }, "node_modules/@smithy/util-retry": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.5.tgz", - "integrity": "sha512-GBj3+EZBbN4NAqJ/7pAhsXdfzdlznOh8PydUijy6FpNIMnHPSMO2/rP4HKu+UFeikJxShERk528oy7GT79YiJg==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.6.tgz", + "integrity": "sha512-x7CeDQLPQ9cb6xN7fRJEjlP9NyGW/YeXWc4j/RUhg4I+H60F0PEeRc2c/z3rm9zmsdiMFzpV/rT+4UHW6KM1SA==", "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^4.2.5", - "@smithy/types": "^4.9.0", + "@smithy/service-error-classification": "^4.2.6", + "@smithy/types": "^4.10.0", "tslib": "^2.6.2" }, "engines": { @@ -9164,14 +9166,14 @@ } }, "node_modules/@smithy/util-stream": { - "version": "4.5.6", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.6.tgz", - "integrity": "sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ==", + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.7.tgz", + "integrity": "sha512-Uuy4S5Aj4oF6k1z+i2OtIBJUns4mlg29Ph4S+CqjR+f4XXpSFVgTCYLzMszHJTicYDBxKFtwq2/QSEDSS5l02A==", "license": "Apache-2.0", "dependencies": { - "@smithy/fetch-http-handler": "^5.3.6", - "@smithy/node-http-handler": "^4.4.5", - "@smithy/types": "^4.9.0", + "@smithy/fetch-http-handler": "^5.3.7", + "@smithy/node-http-handler": "^4.4.6", + "@smithy/types": "^4.10.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", @@ -27140,7 +27142,9 @@ "name": "nhs-notify-supplier-api-letter-test-data-utility", "version": "0.0.1", "dependencies": { + "@aws-sdk/client-dynamodb": "^3.954.0", "@aws-sdk/client-s3": "^3.858.0", + "@aws-sdk/lib-dynamodb": "^3.954.0", "@internal/datastore": "*", "esbuild": "^0.25.11", "pino": "^9.7.0", @@ -27154,144 +27158,1324 @@ "typescript": "^5.9.3" } }, - "scripts/utilities/letter-test-data/node_modules/ansi-regex": { - "version": "6.2.2", - "license": "MIT", - "engines": { - "node": ">=12" + "scripts/utilities/letter-test-data/node_modules/@aws-sdk/client-dynamodb": { + "version": "3.954.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.954.0.tgz", + "integrity": "sha512-QhdiE3W9jC8wAmkjQhiwDBdpaK3/y/kc9aXfTyj8TaPP+B39zt56zA0VT4GtmXauyiYb81qcvKIjIDTvr95Y4A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.954.0", + "@aws-sdk/credential-provider-node": "3.954.0", + "@aws-sdk/dynamodb-codec": "3.954.0", + "@aws-sdk/middleware-endpoint-discovery": "3.953.0", + "@aws-sdk/middleware-host-header": "3.953.0", + "@aws-sdk/middleware-logger": "3.953.0", + "@aws-sdk/middleware-recursion-detection": "3.953.0", + "@aws-sdk/middleware-user-agent": "3.954.0", + "@aws-sdk/region-config-resolver": "3.953.0", + "@aws-sdk/types": "3.953.0", + "@aws-sdk/util-endpoints": "3.953.0", + "@aws-sdk/util-user-agent-browser": "3.953.0", + "@aws-sdk/util-user-agent-node": "3.954.0", + "@smithy/config-resolver": "^4.4.4", + "@smithy/core": "^3.19.0", + "@smithy/fetch-http-handler": "^5.3.7", + "@smithy/hash-node": "^4.2.6", + "@smithy/invalid-dependency": "^4.2.6", + "@smithy/middleware-content-length": "^4.2.6", + "@smithy/middleware-endpoint": "^4.4.0", + "@smithy/middleware-retry": "^4.4.16", + "@smithy/middleware-serde": "^4.2.7", + "@smithy/middleware-stack": "^4.2.6", + "@smithy/node-config-provider": "^4.3.6", + "@smithy/node-http-handler": "^4.4.6", + "@smithy/protocol-http": "^5.3.6", + "@smithy/smithy-client": "^4.10.1", + "@smithy/types": "^4.10.0", + "@smithy/url-parser": "^4.2.6", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.15", + "@smithy/util-defaults-mode-node": "^4.2.18", + "@smithy/util-endpoints": "^3.2.6", + "@smithy/util-middleware": "^4.2.6", + "@smithy/util-retry": "^4.2.6", + "@smithy/util-utf8": "^4.2.0", + "@smithy/util-waiter": "^4.2.6", + "tslib": "^2.6.2" }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "engines": { + "node": ">=18.0.0" } }, - "scripts/utilities/letter-test-data/node_modules/ansi-styles": { - "version": "6.2.3", - "license": "MIT", - "engines": { - "node": ">=12" + "scripts/utilities/letter-test-data/node_modules/@aws-sdk/client-sso": { + "version": "3.954.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.954.0.tgz", + "integrity": "sha512-FVyMAvlFhLK68DHWB1lSkCRTm25xl38bIZDd+jKt5+yDolCrG5+n9aIN8AA8jNO1HNGhZuMjSIQm9r5rGmJH8g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.954.0", + "@aws-sdk/middleware-host-header": "3.953.0", + "@aws-sdk/middleware-logger": "3.953.0", + "@aws-sdk/middleware-recursion-detection": "3.953.0", + "@aws-sdk/middleware-user-agent": "3.954.0", + "@aws-sdk/region-config-resolver": "3.953.0", + "@aws-sdk/types": "3.953.0", + "@aws-sdk/util-endpoints": "3.953.0", + "@aws-sdk/util-user-agent-browser": "3.953.0", + "@aws-sdk/util-user-agent-node": "3.954.0", + "@smithy/config-resolver": "^4.4.4", + "@smithy/core": "^3.19.0", + "@smithy/fetch-http-handler": "^5.3.7", + "@smithy/hash-node": "^4.2.6", + "@smithy/invalid-dependency": "^4.2.6", + "@smithy/middleware-content-length": "^4.2.6", + "@smithy/middleware-endpoint": "^4.4.0", + "@smithy/middleware-retry": "^4.4.16", + "@smithy/middleware-serde": "^4.2.7", + "@smithy/middleware-stack": "^4.2.6", + "@smithy/node-config-provider": "^4.3.6", + "@smithy/node-http-handler": "^4.4.6", + "@smithy/protocol-http": "^5.3.6", + "@smithy/smithy-client": "^4.10.1", + "@smithy/types": "^4.10.0", + "@smithy/url-parser": "^4.2.6", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.15", + "@smithy/util-defaults-mode-node": "^4.2.18", + "@smithy/util-endpoints": "^3.2.6", + "@smithy/util-middleware": "^4.2.6", + "@smithy/util-retry": "^4.2.6", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "engines": { + "node": ">=18.0.0" } }, - "scripts/utilities/letter-test-data/node_modules/cliui": { - "version": "9.0.1", - "license": "ISC", + "scripts/utilities/letter-test-data/node_modules/@aws-sdk/core": { + "version": "3.954.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.954.0.tgz", + "integrity": "sha512-5oYO5RP+mvCNXNj8XnF9jZo0EP0LTseYOJVNQYcii1D9DJqzHL3HJWurYh7cXxz7G7eDyvVYA01O9Xpt34TdoA==", + "license": "Apache-2.0", "dependencies": { - "string-width": "^7.2.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" + "@aws-sdk/types": "3.953.0", + "@aws-sdk/xml-builder": "3.953.0", + "@smithy/core": "^3.19.0", + "@smithy/node-config-provider": "^4.3.6", + "@smithy/property-provider": "^4.2.6", + "@smithy/protocol-http": "^5.3.6", + "@smithy/signature-v4": "^5.3.6", + "@smithy/smithy-client": "^4.10.1", + "@smithy/types": "^4.10.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.6", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=20" + "node": ">=18.0.0" } }, - "scripts/utilities/letter-test-data/node_modules/emoji-regex": { - "version": "10.6.0", - "license": "MIT" - }, - "scripts/utilities/letter-test-data/node_modules/pino": { - "version": "9.14.0", - "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz", - "integrity": "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==", - "license": "MIT", + "scripts/utilities/letter-test-data/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.954.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.954.0.tgz", + "integrity": "sha512-2HNkqBjfsvyoRuPAiFh86JBFMFyaCNhL4VyH6XqwTGKZffjG7hdBmzXPy7AT7G3oFh1k/1Zc27v0qxaKoK7mBA==", + "license": "Apache-2.0", "dependencies": { - "@pinojs/redact": "^0.4.0", - "atomic-sleep": "^1.0.0", - "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^2.0.0", - "pino-std-serializers": "^7.0.0", - "process-warning": "^5.0.0", - "quick-format-unescaped": "^4.0.3", - "real-require": "^0.2.0", - "safe-stable-stringify": "^2.3.1", - "sonic-boom": "^4.0.1", - "thread-stream": "^3.0.0" + "@aws-sdk/core": "3.954.0", + "@aws-sdk/types": "3.953.0", + "@smithy/property-provider": "^4.2.6", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" }, - "bin": { - "pino": "bin.js" + "engines": { + "node": ">=18.0.0" } }, - "scripts/utilities/letter-test-data/node_modules/string-width": { - "version": "7.2.0", - "license": "MIT", + "scripts/utilities/letter-test-data/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.954.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.954.0.tgz", + "integrity": "sha512-CrWD5300+NE1OYRnSVDxoG7G0b5cLIZb7yp+rNQ5Jq/kqnTmyJXpVAsivq+bQIDaGzPXhadzpAMIoo7K/aHaag==", + "license": "Apache-2.0", "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" + "@aws-sdk/core": "3.954.0", + "@aws-sdk/types": "3.953.0", + "@smithy/fetch-http-handler": "^5.3.7", + "@smithy/node-http-handler": "^4.4.6", + "@smithy/property-provider": "^4.2.6", + "@smithy/protocol-http": "^5.3.6", + "@smithy/smithy-client": "^4.10.1", + "@smithy/types": "^4.10.0", + "@smithy/util-stream": "^4.5.7", + "tslib": "^2.6.2" }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18.0.0" } }, - "scripts/utilities/letter-test-data/node_modules/strip-ansi": { - "version": "7.1.2", - "license": "MIT", + "scripts/utilities/letter-test-data/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.954.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.954.0.tgz", + "integrity": "sha512-WAFD8pVwRSoBsuXcoD+s/hrdsP9Z0PNUedSgkOGExuJVAabpM2cIIMzYNsdHio9XFZUSqHkv8mF5mQXuIZvuzg==", + "license": "Apache-2.0", "dependencies": { - "ansi-regex": "^6.0.1" + "@aws-sdk/core": "3.954.0", + "@aws-sdk/credential-provider-env": "3.954.0", + "@aws-sdk/credential-provider-http": "3.954.0", + "@aws-sdk/credential-provider-login": "3.954.0", + "@aws-sdk/credential-provider-process": "3.954.0", + "@aws-sdk/credential-provider-sso": "3.954.0", + "@aws-sdk/credential-provider-web-identity": "3.954.0", + "@aws-sdk/nested-clients": "3.954.0", + "@aws-sdk/types": "3.953.0", + "@smithy/credential-provider-imds": "^4.2.6", + "@smithy/property-provider": "^4.2.6", + "@smithy/shared-ini-file-loader": "^4.4.1", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "node": ">=18.0.0" } }, - "scripts/utilities/letter-test-data/node_modules/wrap-ansi": { - "version": "9.0.2", - "license": "MIT", + "scripts/utilities/letter-test-data/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.954.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.954.0.tgz", + "integrity": "sha512-UPBjw7Lnly5i+/rES8Z5U+nPaumzEUYOE/wrHkxyH6JjwFWn8w7R07fE5Z5cgYlIq1U1lQ7sxYwB3wHPpQ65Aw==", + "license": "Apache-2.0", "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" + "@aws-sdk/credential-provider-env": "3.954.0", + "@aws-sdk/credential-provider-http": "3.954.0", + "@aws-sdk/credential-provider-ini": "3.954.0", + "@aws-sdk/credential-provider-process": "3.954.0", + "@aws-sdk/credential-provider-sso": "3.954.0", + "@aws-sdk/credential-provider-web-identity": "3.954.0", + "@aws-sdk/types": "3.953.0", + "@smithy/credential-provider-imds": "^4.2.6", + "@smithy/property-provider": "^4.2.6", + "@smithy/shared-ini-file-loader": "^4.4.1", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": ">=18.0.0" } }, - "scripts/utilities/letter-test-data/node_modules/yargs": { - "version": "18.0.0", - "license": "MIT", + "scripts/utilities/letter-test-data/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.954.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.954.0.tgz", + "integrity": "sha512-Y1/0O2LgbKM8iIgcVj/GNEQW6p90LVTCOzF2CI1pouoKqxmZ/1F7F66WHoa6XUOfKaCRj/R6nuMR3om9ThaM5A==", + "license": "Apache-2.0", "dependencies": { - "cliui": "^9.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "string-width": "^7.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^22.0.0" + "@aws-sdk/core": "3.954.0", + "@aws-sdk/types": "3.953.0", + "@smithy/property-provider": "^4.2.6", + "@smithy/shared-ini-file-loader": "^4.4.1", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=23" + "node": ">=18.0.0" } }, - "scripts/utilities/letter-test-data/node_modules/yargs-parser": { - "version": "22.0.0", - "license": "ISC", + "scripts/utilities/letter-test-data/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.954.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.954.0.tgz", + "integrity": "sha512-UXxGfkp/plFRdyidMLvNul5zoLKmHhVQOCrD2OgR/lg9jNqNmJ7abF+Qu8abo902iDkhU21Qj4M398cx6l8Kng==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.954.0", + "@aws-sdk/core": "3.954.0", + "@aws-sdk/token-providers": "3.954.0", + "@aws-sdk/types": "3.953.0", + "@smithy/property-provider": "^4.2.6", + "@smithy/shared-ini-file-loader": "^4.4.1", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=23" + "node": ">=18.0.0" } }, - "scripts/utilities/supplier-data": { - "name": "nhs-notify-supplier-api-suppliers-data-utility", - "version": "0.0.1", + "scripts/utilities/letter-test-data/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.954.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.954.0.tgz", + "integrity": "sha512-XEyf1T08q1tG4zkTS4Dnf1cAQyrJUo/xlvi6XNpqGhY3bOmKUYE2h/K6eITIdytDL9VuCpWYQ6YRcIVtL29E0w==", + "license": "Apache-2.0", "dependencies": { - "@internal/datastore": "*", - "esbuild": "^0.25.11", - "pino": "^9.7.0", - "yargs": "^17.7.2" + "@aws-sdk/core": "3.954.0", + "@aws-sdk/nested-clients": "3.954.0", + "@aws-sdk/types": "3.953.0", + "@smithy/property-provider": "^4.2.6", + "@smithy/shared-ini-file-loader": "^4.4.1", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "scripts/utilities/letter-test-data/node_modules/@aws-sdk/dynamodb-codec": { + "version": "3.954.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/dynamodb-codec/-/dynamodb-codec-3.954.0.tgz", + "integrity": "sha512-fNY0L1l9e36pLJef4NY5k7Q1SJnm5rgLSxSRPT8xL+bhQBtEn2E2t4JdBiTqlxgeyuOrafWOawbg8yS3pRPcLw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.954.0", + "@smithy/core": "^3.19.0", + "@smithy/smithy-client": "^4.10.1", + "@smithy/types": "^4.10.0", + "@smithy/util-base64": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-dynamodb": "^3.954.0" + } + }, + "scripts/utilities/letter-test-data/node_modules/@aws-sdk/endpoint-cache": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/endpoint-cache/-/endpoint-cache-3.953.0.tgz", + "integrity": "sha512-pz67DoHk5WNmvMuyNDiomUS2xo0mq6Z3TdfLJZlWVbSKi3h8hYxVQchJ2kzgTr6wu6zt3UBbtKV9yY1IBhKMVA==", + "license": "Apache-2.0", + "dependencies": { + "mnemonist": "0.38.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "scripts/utilities/letter-test-data/node_modules/@aws-sdk/lib-dynamodb": { + "version": "3.954.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-dynamodb/-/lib-dynamodb-3.954.0.tgz", + "integrity": "sha512-DBEzAW7WfvNy6bQ32TfpvuFSVewj5eE8O1sTQWfvylNSZu81u6sPwb+eHPEclbvC84zAANl72JnkETOIkL4r8g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.954.0", + "@aws-sdk/util-dynamodb": "3.954.0", + "@smithy/core": "^3.19.0", + "@smithy/smithy-client": "^4.10.1", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-dynamodb": "^3.954.0" + } + }, + "scripts/utilities/letter-test-data/node_modules/@aws-sdk/middleware-endpoint-discovery": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint-discovery/-/middleware-endpoint-discovery-3.953.0.tgz", + "integrity": "sha512-/YKB1/OiWr7TwOfmkqzv8x1xgOpU71yciQTfsq6erB3dTQhdukPADt/CMJOhWFKC6Q1D5cDN8381nsGmnNuBVg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/endpoint-cache": "3.953.0", + "@aws-sdk/types": "3.953.0", + "@smithy/node-config-provider": "^4.3.6", + "@smithy/protocol-http": "^5.3.6", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "scripts/utilities/letter-test-data/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.953.0.tgz", + "integrity": "sha512-jTGhfkONav+r4E6HLOrl5SzBqDmPByUYCkyB/c/3TVb8jX3wAZx8/q9bphKpCh+G5ARi3IdbSisgkZrJYqQ19Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.953.0", + "@smithy/protocol-http": "^5.3.6", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "scripts/utilities/letter-test-data/node_modules/@aws-sdk/middleware-logger": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.953.0.tgz", + "integrity": "sha512-PlWdVYgcuptkIC0ZKqVUhWNtSHXJSx7U9V8J7dJjRmsXC40X7zpEycvrkzDMJjeTDGcCceYbyYAg/4X1lkcIMw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.953.0", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "scripts/utilities/letter-test-data/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.953.0.tgz", + "integrity": "sha512-cmIJx0gWeesUKK4YwgE+VQL3mpACr3/J24fbwnc1Z5tntC86b+HQFzU5vsBDw6lLwyD46dBgWdsXFh1jL+ZaFw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.953.0", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.6", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "scripts/utilities/letter-test-data/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.954.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.954.0.tgz", + "integrity": "sha512-5PX8JDe3dB2+MqXeGIhmgFnm2rbVsSxhz+Xyuu1oxLtbOn+a9UDA+sNBufEBjt3UxWy5qwEEY1fxdbXXayjlGg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.954.0", + "@aws-sdk/types": "3.953.0", + "@aws-sdk/util-endpoints": "3.953.0", + "@smithy/core": "^3.19.0", + "@smithy/protocol-http": "^5.3.6", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "scripts/utilities/letter-test-data/node_modules/@aws-sdk/nested-clients": { + "version": "3.954.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.954.0.tgz", + "integrity": "sha512-JLUhf35fTQIDPLk6G5KPggL9tV//Hjhy6+N2zZeis76LuBRNhKDq8z1CFyKhjf00vXi/tDYdn9D7y9emI+5Y/g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.954.0", + "@aws-sdk/middleware-host-header": "3.953.0", + "@aws-sdk/middleware-logger": "3.953.0", + "@aws-sdk/middleware-recursion-detection": "3.953.0", + "@aws-sdk/middleware-user-agent": "3.954.0", + "@aws-sdk/region-config-resolver": "3.953.0", + "@aws-sdk/types": "3.953.0", + "@aws-sdk/util-endpoints": "3.953.0", + "@aws-sdk/util-user-agent-browser": "3.953.0", + "@aws-sdk/util-user-agent-node": "3.954.0", + "@smithy/config-resolver": "^4.4.4", + "@smithy/core": "^3.19.0", + "@smithy/fetch-http-handler": "^5.3.7", + "@smithy/hash-node": "^4.2.6", + "@smithy/invalid-dependency": "^4.2.6", + "@smithy/middleware-content-length": "^4.2.6", + "@smithy/middleware-endpoint": "^4.4.0", + "@smithy/middleware-retry": "^4.4.16", + "@smithy/middleware-serde": "^4.2.7", + "@smithy/middleware-stack": "^4.2.6", + "@smithy/node-config-provider": "^4.3.6", + "@smithy/node-http-handler": "^4.4.6", + "@smithy/protocol-http": "^5.3.6", + "@smithy/smithy-client": "^4.10.1", + "@smithy/types": "^4.10.0", + "@smithy/url-parser": "^4.2.6", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.15", + "@smithy/util-defaults-mode-node": "^4.2.18", + "@smithy/util-endpoints": "^3.2.6", + "@smithy/util-middleware": "^4.2.6", + "@smithy/util-retry": "^4.2.6", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "scripts/utilities/letter-test-data/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.953.0.tgz", + "integrity": "sha512-5MJgnsc+HLO+le0EK1cy92yrC7kyhGZSpaq8PcQvKs9qtXCXT5Tb6tMdkr5Y07JxYsYOV1omWBynvL6PWh08tQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.953.0", + "@smithy/config-resolver": "^4.4.4", + "@smithy/node-config-provider": "^4.3.6", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "scripts/utilities/letter-test-data/node_modules/@aws-sdk/token-providers": { + "version": "3.954.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.954.0.tgz", + "integrity": "sha512-rDyN3oQQKMOJgyQ9/LNbh4fAGAj8ePMGOAQzSP/kyzizmViI6STpBW1o/VRqiTgMNi1bvA9ZasDtfrJqcVt0iA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.954.0", + "@aws-sdk/nested-clients": "3.954.0", + "@aws-sdk/types": "3.953.0", + "@smithy/property-provider": "^4.2.6", + "@smithy/shared-ini-file-loader": "^4.4.1", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "scripts/utilities/letter-test-data/node_modules/@aws-sdk/types": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.953.0.tgz", + "integrity": "sha512-M9Iwg9kTyqTErI0vOTVVpcnTHWzS3VplQppy8MuL02EE+mJ0BIwpWfsaAPQW+/XnVpdNpWZTsHcNE29f1+hR8g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "scripts/utilities/letter-test-data/node_modules/@aws-sdk/util-dynamodb": { + "version": "3.954.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-dynamodb/-/util-dynamodb-3.954.0.tgz", + "integrity": "sha512-PXjS9P98OMMVXl4ledr/nTHGmNx6oF2rqTBvSQKKWi8dB4mcHjSaU5ASsklQC944xLs1IGsUkrJ39vu7bEqLoA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-dynamodb": "^3.954.0" + } + }, + "scripts/utilities/letter-test-data/node_modules/@aws-sdk/util-endpoints": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.953.0.tgz", + "integrity": "sha512-rjaS6jrFksopXvNg6YeN+D1lYwhcByORNlFuYesFvaQNtPOufbE5tJL4GJ3TMXyaY0uFR28N5BHHITPyWWfH/g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.953.0", + "@smithy/types": "^4.10.0", + "@smithy/url-parser": "^4.2.6", + "@smithy/util-endpoints": "^3.2.6", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "scripts/utilities/letter-test-data/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.953.0.tgz", + "integrity": "sha512-UF5NeqYesWuFao+u7LJvpV1SJCaLml5BtFZKUdTnNNMeN6jvV+dW/eQoFGpXF94RCqguX0XESmRuRRPQp+/rzQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.953.0", + "@smithy/types": "^4.10.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "scripts/utilities/letter-test-data/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.954.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.954.0.tgz", + "integrity": "sha512-fB5S5VOu7OFkeNzcblQlez4AjO5hgDFaa7phYt7716YWisY3RjAaQPlxgv+G3GltHHDJIfzEC5aRxdf62B9zMg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.954.0", + "@aws-sdk/types": "3.953.0", + "@smithy/node-config-provider": "^4.3.6", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "scripts/utilities/letter-test-data/node_modules/@aws-sdk/xml-builder": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.953.0.tgz", + "integrity": "sha512-Zmrj21jQ2OeOJGr9spPiN00aQvXa/WUqRXcTVENhrMt+OFoSOfDFpYhUj9NQ09QmQ8KMWFoWuWW6iKurNqLvAA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.10.0", + "fast-xml-parser": "5.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "scripts/utilities/letter-test-data/node_modules/@smithy/util-waiter": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.6.tgz", + "integrity": "sha512-xU9HwUSik9UUCJmm530yvBy0AwlQFICveKmqvaaTukKkXEAhyiBdHtSrhPrH3rH+uz0ykyaE3LdgsX86C6mDCQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.2.6", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "scripts/utilities/letter-test-data/node_modules/ansi-regex": { + "version": "6.2.2", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "scripts/utilities/letter-test-data/node_modules/ansi-styles": { + "version": "6.2.3", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "scripts/utilities/letter-test-data/node_modules/cliui": { + "version": "9.0.1", + "license": "ISC", + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "scripts/utilities/letter-test-data/node_modules/emoji-regex": { + "version": "10.6.0", + "license": "MIT" + }, + "scripts/utilities/letter-test-data/node_modules/pino": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz", + "integrity": "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==", + "license": "MIT", + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "scripts/utilities/letter-test-data/node_modules/string-width": { + "version": "7.2.0", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "scripts/utilities/letter-test-data/node_modules/strip-ansi": { + "version": "7.1.2", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "scripts/utilities/letter-test-data/node_modules/wrap-ansi": { + "version": "9.0.2", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "scripts/utilities/letter-test-data/node_modules/yargs": { + "version": "18.0.0", + "license": "MIT", + "dependencies": { + "cliui": "^9.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "string-width": "^7.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^22.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "scripts/utilities/letter-test-data/node_modules/yargs-parser": { + "version": "22.0.0", + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "scripts/utilities/supplier-data": { + "name": "nhs-notify-supplier-api-suppliers-data-utility", + "version": "0.0.1", + "dependencies": { + "@aws-sdk/client-dynamodb": "^3.954.0", + "@aws-sdk/lib-dynamodb": "^3.954.0", + "@internal/datastore": "*", + "esbuild": "^0.25.11", + "pino": "^9.7.0", + "yargs": "^17.7.2" + }, + "devDependencies": { + "@tsconfig/node22": "^22.0.2", + "@types/jest": "^30.0.0", + "jest": "^30.2.0", + "jest-mock-extended": "^4.0.0", + "typescript": "^5.8.3" + } + }, + "scripts/utilities/supplier-data/node_modules/@aws-sdk/client-dynamodb": { + "version": "3.954.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.954.0.tgz", + "integrity": "sha512-QhdiE3W9jC8wAmkjQhiwDBdpaK3/y/kc9aXfTyj8TaPP+B39zt56zA0VT4GtmXauyiYb81qcvKIjIDTvr95Y4A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.954.0", + "@aws-sdk/credential-provider-node": "3.954.0", + "@aws-sdk/dynamodb-codec": "3.954.0", + "@aws-sdk/middleware-endpoint-discovery": "3.953.0", + "@aws-sdk/middleware-host-header": "3.953.0", + "@aws-sdk/middleware-logger": "3.953.0", + "@aws-sdk/middleware-recursion-detection": "3.953.0", + "@aws-sdk/middleware-user-agent": "3.954.0", + "@aws-sdk/region-config-resolver": "3.953.0", + "@aws-sdk/types": "3.953.0", + "@aws-sdk/util-endpoints": "3.953.0", + "@aws-sdk/util-user-agent-browser": "3.953.0", + "@aws-sdk/util-user-agent-node": "3.954.0", + "@smithy/config-resolver": "^4.4.4", + "@smithy/core": "^3.19.0", + "@smithy/fetch-http-handler": "^5.3.7", + "@smithy/hash-node": "^4.2.6", + "@smithy/invalid-dependency": "^4.2.6", + "@smithy/middleware-content-length": "^4.2.6", + "@smithy/middleware-endpoint": "^4.4.0", + "@smithy/middleware-retry": "^4.4.16", + "@smithy/middleware-serde": "^4.2.7", + "@smithy/middleware-stack": "^4.2.6", + "@smithy/node-config-provider": "^4.3.6", + "@smithy/node-http-handler": "^4.4.6", + "@smithy/protocol-http": "^5.3.6", + "@smithy/smithy-client": "^4.10.1", + "@smithy/types": "^4.10.0", + "@smithy/url-parser": "^4.2.6", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.15", + "@smithy/util-defaults-mode-node": "^4.2.18", + "@smithy/util-endpoints": "^3.2.6", + "@smithy/util-middleware": "^4.2.6", + "@smithy/util-retry": "^4.2.6", + "@smithy/util-utf8": "^4.2.0", + "@smithy/util-waiter": "^4.2.6", + "tslib": "^2.6.2" }, - "devDependencies": { - "@tsconfig/node22": "^22.0.2", - "@types/jest": "^30.0.0", - "jest": "^30.2.0", - "jest-mock-extended": "^4.0.0", - "typescript": "^5.8.3" + "engines": { + "node": ">=18.0.0" + } + }, + "scripts/utilities/supplier-data/node_modules/@aws-sdk/client-sso": { + "version": "3.954.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.954.0.tgz", + "integrity": "sha512-FVyMAvlFhLK68DHWB1lSkCRTm25xl38bIZDd+jKt5+yDolCrG5+n9aIN8AA8jNO1HNGhZuMjSIQm9r5rGmJH8g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.954.0", + "@aws-sdk/middleware-host-header": "3.953.0", + "@aws-sdk/middleware-logger": "3.953.0", + "@aws-sdk/middleware-recursion-detection": "3.953.0", + "@aws-sdk/middleware-user-agent": "3.954.0", + "@aws-sdk/region-config-resolver": "3.953.0", + "@aws-sdk/types": "3.953.0", + "@aws-sdk/util-endpoints": "3.953.0", + "@aws-sdk/util-user-agent-browser": "3.953.0", + "@aws-sdk/util-user-agent-node": "3.954.0", + "@smithy/config-resolver": "^4.4.4", + "@smithy/core": "^3.19.0", + "@smithy/fetch-http-handler": "^5.3.7", + "@smithy/hash-node": "^4.2.6", + "@smithy/invalid-dependency": "^4.2.6", + "@smithy/middleware-content-length": "^4.2.6", + "@smithy/middleware-endpoint": "^4.4.0", + "@smithy/middleware-retry": "^4.4.16", + "@smithy/middleware-serde": "^4.2.7", + "@smithy/middleware-stack": "^4.2.6", + "@smithy/node-config-provider": "^4.3.6", + "@smithy/node-http-handler": "^4.4.6", + "@smithy/protocol-http": "^5.3.6", + "@smithy/smithy-client": "^4.10.1", + "@smithy/types": "^4.10.0", + "@smithy/url-parser": "^4.2.6", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.15", + "@smithy/util-defaults-mode-node": "^4.2.18", + "@smithy/util-endpoints": "^3.2.6", + "@smithy/util-middleware": "^4.2.6", + "@smithy/util-retry": "^4.2.6", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "scripts/utilities/supplier-data/node_modules/@aws-sdk/core": { + "version": "3.954.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.954.0.tgz", + "integrity": "sha512-5oYO5RP+mvCNXNj8XnF9jZo0EP0LTseYOJVNQYcii1D9DJqzHL3HJWurYh7cXxz7G7eDyvVYA01O9Xpt34TdoA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.953.0", + "@aws-sdk/xml-builder": "3.953.0", + "@smithy/core": "^3.19.0", + "@smithy/node-config-provider": "^4.3.6", + "@smithy/property-provider": "^4.2.6", + "@smithy/protocol-http": "^5.3.6", + "@smithy/signature-v4": "^5.3.6", + "@smithy/smithy-client": "^4.10.1", + "@smithy/types": "^4.10.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.6", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "scripts/utilities/supplier-data/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.954.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.954.0.tgz", + "integrity": "sha512-2HNkqBjfsvyoRuPAiFh86JBFMFyaCNhL4VyH6XqwTGKZffjG7hdBmzXPy7AT7G3oFh1k/1Zc27v0qxaKoK7mBA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.954.0", + "@aws-sdk/types": "3.953.0", + "@smithy/property-provider": "^4.2.6", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "scripts/utilities/supplier-data/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.954.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.954.0.tgz", + "integrity": "sha512-CrWD5300+NE1OYRnSVDxoG7G0b5cLIZb7yp+rNQ5Jq/kqnTmyJXpVAsivq+bQIDaGzPXhadzpAMIoo7K/aHaag==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.954.0", + "@aws-sdk/types": "3.953.0", + "@smithy/fetch-http-handler": "^5.3.7", + "@smithy/node-http-handler": "^4.4.6", + "@smithy/property-provider": "^4.2.6", + "@smithy/protocol-http": "^5.3.6", + "@smithy/smithy-client": "^4.10.1", + "@smithy/types": "^4.10.0", + "@smithy/util-stream": "^4.5.7", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "scripts/utilities/supplier-data/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.954.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.954.0.tgz", + "integrity": "sha512-WAFD8pVwRSoBsuXcoD+s/hrdsP9Z0PNUedSgkOGExuJVAabpM2cIIMzYNsdHio9XFZUSqHkv8mF5mQXuIZvuzg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.954.0", + "@aws-sdk/credential-provider-env": "3.954.0", + "@aws-sdk/credential-provider-http": "3.954.0", + "@aws-sdk/credential-provider-login": "3.954.0", + "@aws-sdk/credential-provider-process": "3.954.0", + "@aws-sdk/credential-provider-sso": "3.954.0", + "@aws-sdk/credential-provider-web-identity": "3.954.0", + "@aws-sdk/nested-clients": "3.954.0", + "@aws-sdk/types": "3.953.0", + "@smithy/credential-provider-imds": "^4.2.6", + "@smithy/property-provider": "^4.2.6", + "@smithy/shared-ini-file-loader": "^4.4.1", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "scripts/utilities/supplier-data/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.954.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.954.0.tgz", + "integrity": "sha512-UPBjw7Lnly5i+/rES8Z5U+nPaumzEUYOE/wrHkxyH6JjwFWn8w7R07fE5Z5cgYlIq1U1lQ7sxYwB3wHPpQ65Aw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.954.0", + "@aws-sdk/credential-provider-http": "3.954.0", + "@aws-sdk/credential-provider-ini": "3.954.0", + "@aws-sdk/credential-provider-process": "3.954.0", + "@aws-sdk/credential-provider-sso": "3.954.0", + "@aws-sdk/credential-provider-web-identity": "3.954.0", + "@aws-sdk/types": "3.953.0", + "@smithy/credential-provider-imds": "^4.2.6", + "@smithy/property-provider": "^4.2.6", + "@smithy/shared-ini-file-loader": "^4.4.1", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "scripts/utilities/supplier-data/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.954.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.954.0.tgz", + "integrity": "sha512-Y1/0O2LgbKM8iIgcVj/GNEQW6p90LVTCOzF2CI1pouoKqxmZ/1F7F66WHoa6XUOfKaCRj/R6nuMR3om9ThaM5A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.954.0", + "@aws-sdk/types": "3.953.0", + "@smithy/property-provider": "^4.2.6", + "@smithy/shared-ini-file-loader": "^4.4.1", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "scripts/utilities/supplier-data/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.954.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.954.0.tgz", + "integrity": "sha512-UXxGfkp/plFRdyidMLvNul5zoLKmHhVQOCrD2OgR/lg9jNqNmJ7abF+Qu8abo902iDkhU21Qj4M398cx6l8Kng==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.954.0", + "@aws-sdk/core": "3.954.0", + "@aws-sdk/token-providers": "3.954.0", + "@aws-sdk/types": "3.953.0", + "@smithy/property-provider": "^4.2.6", + "@smithy/shared-ini-file-loader": "^4.4.1", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "scripts/utilities/supplier-data/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.954.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.954.0.tgz", + "integrity": "sha512-XEyf1T08q1tG4zkTS4Dnf1cAQyrJUo/xlvi6XNpqGhY3bOmKUYE2h/K6eITIdytDL9VuCpWYQ6YRcIVtL29E0w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.954.0", + "@aws-sdk/nested-clients": "3.954.0", + "@aws-sdk/types": "3.953.0", + "@smithy/property-provider": "^4.2.6", + "@smithy/shared-ini-file-loader": "^4.4.1", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "scripts/utilities/supplier-data/node_modules/@aws-sdk/dynamodb-codec": { + "version": "3.954.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/dynamodb-codec/-/dynamodb-codec-3.954.0.tgz", + "integrity": "sha512-fNY0L1l9e36pLJef4NY5k7Q1SJnm5rgLSxSRPT8xL+bhQBtEn2E2t4JdBiTqlxgeyuOrafWOawbg8yS3pRPcLw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.954.0", + "@smithy/core": "^3.19.0", + "@smithy/smithy-client": "^4.10.1", + "@smithy/types": "^4.10.0", + "@smithy/util-base64": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-dynamodb": "^3.954.0" + } + }, + "scripts/utilities/supplier-data/node_modules/@aws-sdk/endpoint-cache": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/endpoint-cache/-/endpoint-cache-3.953.0.tgz", + "integrity": "sha512-pz67DoHk5WNmvMuyNDiomUS2xo0mq6Z3TdfLJZlWVbSKi3h8hYxVQchJ2kzgTr6wu6zt3UBbtKV9yY1IBhKMVA==", + "license": "Apache-2.0", + "dependencies": { + "mnemonist": "0.38.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "scripts/utilities/supplier-data/node_modules/@aws-sdk/lib-dynamodb": { + "version": "3.954.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-dynamodb/-/lib-dynamodb-3.954.0.tgz", + "integrity": "sha512-DBEzAW7WfvNy6bQ32TfpvuFSVewj5eE8O1sTQWfvylNSZu81u6sPwb+eHPEclbvC84zAANl72JnkETOIkL4r8g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.954.0", + "@aws-sdk/util-dynamodb": "3.954.0", + "@smithy/core": "^3.19.0", + "@smithy/smithy-client": "^4.10.1", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-dynamodb": "^3.954.0" + } + }, + "scripts/utilities/supplier-data/node_modules/@aws-sdk/middleware-endpoint-discovery": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint-discovery/-/middleware-endpoint-discovery-3.953.0.tgz", + "integrity": "sha512-/YKB1/OiWr7TwOfmkqzv8x1xgOpU71yciQTfsq6erB3dTQhdukPADt/CMJOhWFKC6Q1D5cDN8381nsGmnNuBVg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/endpoint-cache": "3.953.0", + "@aws-sdk/types": "3.953.0", + "@smithy/node-config-provider": "^4.3.6", + "@smithy/protocol-http": "^5.3.6", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "scripts/utilities/supplier-data/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.953.0.tgz", + "integrity": "sha512-jTGhfkONav+r4E6HLOrl5SzBqDmPByUYCkyB/c/3TVb8jX3wAZx8/q9bphKpCh+G5ARi3IdbSisgkZrJYqQ19Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.953.0", + "@smithy/protocol-http": "^5.3.6", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "scripts/utilities/supplier-data/node_modules/@aws-sdk/middleware-logger": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.953.0.tgz", + "integrity": "sha512-PlWdVYgcuptkIC0ZKqVUhWNtSHXJSx7U9V8J7dJjRmsXC40X7zpEycvrkzDMJjeTDGcCceYbyYAg/4X1lkcIMw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.953.0", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "scripts/utilities/supplier-data/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.953.0.tgz", + "integrity": "sha512-cmIJx0gWeesUKK4YwgE+VQL3mpACr3/J24fbwnc1Z5tntC86b+HQFzU5vsBDw6lLwyD46dBgWdsXFh1jL+ZaFw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.953.0", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.6", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "scripts/utilities/supplier-data/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.954.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.954.0.tgz", + "integrity": "sha512-5PX8JDe3dB2+MqXeGIhmgFnm2rbVsSxhz+Xyuu1oxLtbOn+a9UDA+sNBufEBjt3UxWy5qwEEY1fxdbXXayjlGg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.954.0", + "@aws-sdk/types": "3.953.0", + "@aws-sdk/util-endpoints": "3.953.0", + "@smithy/core": "^3.19.0", + "@smithy/protocol-http": "^5.3.6", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "scripts/utilities/supplier-data/node_modules/@aws-sdk/nested-clients": { + "version": "3.954.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.954.0.tgz", + "integrity": "sha512-JLUhf35fTQIDPLk6G5KPggL9tV//Hjhy6+N2zZeis76LuBRNhKDq8z1CFyKhjf00vXi/tDYdn9D7y9emI+5Y/g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.954.0", + "@aws-sdk/middleware-host-header": "3.953.0", + "@aws-sdk/middleware-logger": "3.953.0", + "@aws-sdk/middleware-recursion-detection": "3.953.0", + "@aws-sdk/middleware-user-agent": "3.954.0", + "@aws-sdk/region-config-resolver": "3.953.0", + "@aws-sdk/types": "3.953.0", + "@aws-sdk/util-endpoints": "3.953.0", + "@aws-sdk/util-user-agent-browser": "3.953.0", + "@aws-sdk/util-user-agent-node": "3.954.0", + "@smithy/config-resolver": "^4.4.4", + "@smithy/core": "^3.19.0", + "@smithy/fetch-http-handler": "^5.3.7", + "@smithy/hash-node": "^4.2.6", + "@smithy/invalid-dependency": "^4.2.6", + "@smithy/middleware-content-length": "^4.2.6", + "@smithy/middleware-endpoint": "^4.4.0", + "@smithy/middleware-retry": "^4.4.16", + "@smithy/middleware-serde": "^4.2.7", + "@smithy/middleware-stack": "^4.2.6", + "@smithy/node-config-provider": "^4.3.6", + "@smithy/node-http-handler": "^4.4.6", + "@smithy/protocol-http": "^5.3.6", + "@smithy/smithy-client": "^4.10.1", + "@smithy/types": "^4.10.0", + "@smithy/url-parser": "^4.2.6", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.15", + "@smithy/util-defaults-mode-node": "^4.2.18", + "@smithy/util-endpoints": "^3.2.6", + "@smithy/util-middleware": "^4.2.6", + "@smithy/util-retry": "^4.2.6", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "scripts/utilities/supplier-data/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.953.0.tgz", + "integrity": "sha512-5MJgnsc+HLO+le0EK1cy92yrC7kyhGZSpaq8PcQvKs9qtXCXT5Tb6tMdkr5Y07JxYsYOV1omWBynvL6PWh08tQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.953.0", + "@smithy/config-resolver": "^4.4.4", + "@smithy/node-config-provider": "^4.3.6", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "scripts/utilities/supplier-data/node_modules/@aws-sdk/token-providers": { + "version": "3.954.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.954.0.tgz", + "integrity": "sha512-rDyN3oQQKMOJgyQ9/LNbh4fAGAj8ePMGOAQzSP/kyzizmViI6STpBW1o/VRqiTgMNi1bvA9ZasDtfrJqcVt0iA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.954.0", + "@aws-sdk/nested-clients": "3.954.0", + "@aws-sdk/types": "3.953.0", + "@smithy/property-provider": "^4.2.6", + "@smithy/shared-ini-file-loader": "^4.4.1", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "scripts/utilities/supplier-data/node_modules/@aws-sdk/types": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.953.0.tgz", + "integrity": "sha512-M9Iwg9kTyqTErI0vOTVVpcnTHWzS3VplQppy8MuL02EE+mJ0BIwpWfsaAPQW+/XnVpdNpWZTsHcNE29f1+hR8g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "scripts/utilities/supplier-data/node_modules/@aws-sdk/util-dynamodb": { + "version": "3.954.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-dynamodb/-/util-dynamodb-3.954.0.tgz", + "integrity": "sha512-PXjS9P98OMMVXl4ledr/nTHGmNx6oF2rqTBvSQKKWi8dB4mcHjSaU5ASsklQC944xLs1IGsUkrJ39vu7bEqLoA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-dynamodb": "^3.954.0" + } + }, + "scripts/utilities/supplier-data/node_modules/@aws-sdk/util-endpoints": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.953.0.tgz", + "integrity": "sha512-rjaS6jrFksopXvNg6YeN+D1lYwhcByORNlFuYesFvaQNtPOufbE5tJL4GJ3TMXyaY0uFR28N5BHHITPyWWfH/g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.953.0", + "@smithy/types": "^4.10.0", + "@smithy/url-parser": "^4.2.6", + "@smithy/util-endpoints": "^3.2.6", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "scripts/utilities/supplier-data/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.953.0.tgz", + "integrity": "sha512-UF5NeqYesWuFao+u7LJvpV1SJCaLml5BtFZKUdTnNNMeN6jvV+dW/eQoFGpXF94RCqguX0XESmRuRRPQp+/rzQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.953.0", + "@smithy/types": "^4.10.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "scripts/utilities/supplier-data/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.954.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.954.0.tgz", + "integrity": "sha512-fB5S5VOu7OFkeNzcblQlez4AjO5hgDFaa7phYt7716YWisY3RjAaQPlxgv+G3GltHHDJIfzEC5aRxdf62B9zMg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.954.0", + "@aws-sdk/types": "3.953.0", + "@smithy/node-config-provider": "^4.3.6", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "scripts/utilities/supplier-data/node_modules/@aws-sdk/xml-builder": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.953.0.tgz", + "integrity": "sha512-Zmrj21jQ2OeOJGr9spPiN00aQvXa/WUqRXcTVENhrMt+OFoSOfDFpYhUj9NQ09QmQ8KMWFoWuWW6iKurNqLvAA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.10.0", + "fast-xml-parser": "5.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "scripts/utilities/supplier-data/node_modules/@smithy/util-waiter": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.6.tgz", + "integrity": "sha512-xU9HwUSik9UUCJmm530yvBy0AwlQFICveKmqvaaTukKkXEAhyiBdHtSrhPrH3rH+uz0ykyaE3LdgsX86C6mDCQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.2.6", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "scripts/utilities/supplier-data/node_modules/cliui": { diff --git a/scripts/tests/lint.sh b/scripts/tests/lint.sh new file mode 100755 index 00000000..2ace8310 --- /dev/null +++ b/scripts/tests/lint.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +set -euo pipefail + +cd "$(git rev-parse --show-toplevel)" + +# This file is for you! Edit it to call your unit test suite. Note that the same +# file will be called if you run it locally as if you run it on CI. + +# Replace the following line with something like: +# +# rails test:unit +# python manage.py test +# npm run test +# +# or whatever is appropriate to your project. You should *only* run your fast +# tests from here. If you want to run other test suites, see the predefined +# tasks in scripts/test.mk. + +# run tests +npm ci +npm run lint --workspaces diff --git a/scripts/tests/typecheck.sh b/scripts/tests/typecheck.sh new file mode 100755 index 00000000..2ed6c2e0 --- /dev/null +++ b/scripts/tests/typecheck.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +set -euo pipefail + +cd "$(git rev-parse --show-toplevel)" + +# This file is for you! Edit it to call your unit test suite. Note that the same +# file will be called if you run it locally as if you run it on CI. + +# Replace the following line with something like: +# +# rails test:unit +# python manage.py test +# npm run test +# +# or whatever is appropriate to your project. You should *only* run your fast +# tests from here. If you want to run other test suites, see the predefined +# tasks in scripts/test.mk. + +# run tests +npm ci +npm run typecheck --workspaces diff --git a/scripts/utilities/letter-test-data/.eslintignore b/scripts/utilities/letter-test-data/.eslintignore deleted file mode 100644 index 1521c8b7..00000000 --- a/scripts/utilities/letter-test-data/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -dist diff --git a/scripts/utilities/letter-test-data/jest.config.ts b/scripts/utilities/letter-test-data/jest.config.ts index cc9a39c9..445325b8 100644 --- a/scripts/utilities/letter-test-data/jest.config.ts +++ b/scripts/utilities/letter-test-data/jest.config.ts @@ -1,7 +1,7 @@ -import type { Config } from 'jest'; +import type { Config } from "jest"; export const baseJestConfig: Config = { - preset: 'ts-jest', + preset: "ts-jest", // Automatically clear mock calls, instances, contexts and results before every test clearMocks: true, @@ -10,10 +10,10 @@ export const baseJestConfig: Config = { collectCoverage: true, // The directory where Jest should output its coverage files - coverageDirectory: './.reports/unit/coverage', + coverageDirectory: "./.reports/unit/coverage", // Indicates which provider should be used to instrument code for coverage - coverageProvider: 'babel', + coverageProvider: "babel", coverageThreshold: { global: { @@ -24,38 +24,38 @@ export const baseJestConfig: Config = { }, }, - coveragePathIgnorePatterns: ['/__tests__/'], - transform: { '^.+\\.ts$': 'ts-jest' }, - testPathIgnorePatterns: ['.build'], - testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'], + coveragePathIgnorePatterns: ["/__tests__/"], + transform: { "^.+\\.ts$": "ts-jest" }, + testPathIgnorePatterns: [".build"], + testMatch: ["**/?(*.)+(spec|test).[jt]s?(x)"], // Use this configuration option to add custom reporters to Jest reporters: [ - 'default', + "default", [ - 'jest-html-reporter', + "jest-html-reporter", { - pageTitle: 'Test Report', - outputPath: './.reports/unit/test-report.html', + pageTitle: "Test Report", + outputPath: "./.reports/unit/test-report.html", includeFailureMsg: true, }, ], ], // The test environment that will be used for testing - testEnvironment: 'jsdom', + testEnvironment: "jsdom", }; const utilsJestConfig = { ...baseJestConfig, - testEnvironment: 'node', + testEnvironment: "node", coveragePathIgnorePatterns: [ ...(baseJestConfig.coveragePathIgnorePatterns ?? []), - 'cli/index.ts', - 'helpers/s3_helpers.ts', - 'letter-repo-factory.ts', + "cli/index.ts", + "helpers/s3_helpers.ts", + "letter-repo-factory.ts", ], }; diff --git a/scripts/utilities/letter-test-data/package.json b/scripts/utilities/letter-test-data/package.json index a98c7161..45a7d644 100644 --- a/scripts/utilities/letter-test-data/package.json +++ b/scripts/utilities/letter-test-data/package.json @@ -1,6 +1,8 @@ { "dependencies": { + "@aws-sdk/client-dynamodb": "^3.954.0", "@aws-sdk/client-s3": "^3.858.0", + "@aws-sdk/lib-dynamodb": "^3.954.0", "@internal/datastore": "*", "esbuild": "^0.25.11", "pino": "^9.7.0", diff --git a/scripts/utilities/letter-test-data/src/__test__/helpers/create_letter_helpers.test.ts b/scripts/utilities/letter-test-data/src/__test__/helpers/create-letter-helpers.test.ts similarity index 92% rename from scripts/utilities/letter-test-data/src/__test__/helpers/create_letter_helpers.test.ts rename to scripts/utilities/letter-test-data/src/__test__/helpers/create-letter-helpers.test.ts index a8b2a0db..36217a5c 100644 --- a/scripts/utilities/letter-test-data/src/__test__/helpers/create_letter_helpers.test.ts +++ b/scripts/utilities/letter-test-data/src/__test__/helpers/create-letter-helpers.test.ts @@ -1,9 +1,12 @@ import { LetterRepository } from "@internal/datastore/src/letter-repository"; import { LetterStatusType } from "@internal/datastore"; -import { createLetter, createLetterDto } from "../../helpers/create_letter_helpers"; -import { uploadFile } from "../../helpers/s3_helpers"; +import { + createLetter, + createLetterDto, +} from "../../helpers/create-letter-helpers"; +import uploadFile from "../../helpers/s3-helpers"; -jest.mock("../../helpers/s3_helpers"); +jest.mock("../../helpers/s3-helpers"); describe("Create letter helpers", () => { beforeEach(() => { diff --git a/scripts/utilities/letter-test-data/src/__test__/helpers/s3-helpers.test.ts b/scripts/utilities/letter-test-data/src/__test__/helpers/s3-helpers.test.ts new file mode 100644 index 00000000..22e56cd5 --- /dev/null +++ b/scripts/utilities/letter-test-data/src/__test__/helpers/s3-helpers.test.ts @@ -0,0 +1,87 @@ +/** + * Unit tests for s3-helpers.ts + * + * Mocks: + * - @aws-sdk/client-s3: S3Client (with send) and PutObjectCommand + * - node:fs.readFileSync to avoid touching the filesystem + */ + +import * as s3Module from "@aws-sdk/client-s3"; +import uploadFile from "../../helpers/s3-helpers"; + +jest.mock("@aws-sdk/client-s3", () => { + const sendMock = jest.fn(); + class PutObjectCommand { + input: any; + + constructor(input: any) { + this.input = input; + } + } + const S3Client = jest.fn().mockImplementation(() => ({ send: sendMock })); + return { + S3Client, + PutObjectCommand, + __sendMock: sendMock, + __esModule: true, + }; +}); + +jest.mock("node:fs", () => ({ + readFileSync: jest.fn().mockReturnValue(Buffer.from("fake-pdf-bytes")), +})); + +describe("uploadFile", () => { + const bucket = "my-bucket"; + const supplierId = "supplier-1"; + const sourceFilename = "some.pdf"; + const targetFilename = "target.pdf"; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("calls S3Client.send with a PutObjectCommand containing correct params", async () => { + const sendMock = (s3Module as any).__sendMock as jest.Mock; + sendMock.mockResolvedValue({ ETag: '"etag-value"' }); + + await expect( + uploadFile(bucket, supplierId, sourceFilename, targetFilename), + ).resolves.toBeDefined(); + + // S3Client is a mocked constructor — grab the instance that was created + const S3ClientMock = (s3Module as any).S3Client as jest.Mock; + expect(S3ClientMock).toHaveBeenCalled(); + + const instance = S3ClientMock.mock.results[0].value; + expect(instance.send).toHaveBeenCalledTimes(1); + + const calledWith = instance.send.mock.calls[0][0]; + // The mocked PutObjectCommand stores input as `input` property + expect(calledWith).toHaveProperty("input"); + expect(calledWith.input).toEqual({ + Bucket: bucket, + Key: `${supplierId}/${targetFilename}`, + Body: Buffer.from("fake-pdf-bytes"), + ContentType: "application/pdf", + }); + }); + + it("logs and rethrows when S3Client.send rejects", async () => { + const sendMock = (s3Module as any).__sendMock as jest.Mock; + const err = new Error("upload-failed"); + sendMock.mockRejectedValueOnce(err); + + const consoleSpy = jest + .spyOn(console, "error") + .mockImplementation(() => {}); + + await expect( + uploadFile(bucket, supplierId, sourceFilename, targetFilename), + ).rejects.toThrow("upload-failed"); + + expect(consoleSpy).toHaveBeenCalledWith("Error uploading file:", err); + + consoleSpy.mockRestore(); + }); +}); diff --git a/scripts/utilities/letter-test-data/src/cli/index.ts b/scripts/utilities/letter-test-data/src/cli/index.ts index 915c392b..97b6e038 100644 --- a/scripts/utilities/letter-test-data/src/cli/index.ts +++ b/scripts/utilities/letter-test-data/src/cli/index.ts @@ -1,10 +1,13 @@ import { hideBin } from "yargs/helpers"; -import yargs from 'yargs'; +import yargs from "yargs"; import { LetterStatusType } from "@internal/datastore/src/types"; -import { randomUUID } from "crypto"; -import { createLetter, createLetterDto } from "../helpers/create_letter_helpers"; +import { randomUUID } from "node:crypto"; +import { + createLetter, + createLetterDto, +} from "../helpers/create-letter-helpers"; import { createLetterRepository } from "../infrastructure/letter-repo-factory"; -import { uploadFile } from "../helpers/s3_helpers"; +import { uploadFile } from "../helpers/s3-helpers"; async function main() { await yargs(hideBin(process.argv)) @@ -60,17 +63,15 @@ async function main() { }, }, async (argv) => { - const supplierId = argv.supplierId; - const letterId = argv.letterId ? argv.letterId : randomUUID(); + const { supplierId } = argv; + const letterId = argv.letterId ?? randomUUID(); const bucketName = `nhs-${argv.awsAccountId}-eu-west-2-${argv.environment}-supapi-test-letters`; const targetFilename = `${letterId}.pdf`; - const groupId = argv.groupId ? argv.groupId : randomUUID(); - const specificationId = argv.specificationId - ? argv.specificationId - : randomUUID(); - const status = argv.status; - const environment = argv.environment; - const ttlHours = argv.ttlHours; + const groupId = argv.groupId ?? randomUUID(); + const specificationId = argv.specificationId ?? randomUUID(); + const { status } = argv; + const { environment } = argv; + const { ttlHours } = argv; const letterRepository = createLetterRepository(environment, ttlHours); createLetter({ @@ -114,7 +115,7 @@ async function main() { demandOption: false, default: 336, }, - "count": { + count: { type: "number", demandOption: true, }, @@ -137,41 +138,44 @@ async function main() { }, }, async (argv) => { - // set batch ID const batchId = randomUUID(); // parse args - const supplierId = argv.supplierId; - const groupId = argv.groupId ? argv.groupId : randomUUID(); - const specificationId = argv.specificationId - ? argv.specificationId - : randomUUID(); - const status = argv.status; - const environment = argv.environment; - const ttlHours = argv.ttlHours; + const { supplierId } = argv; + const groupId = argv.groupId ?? randomUUID(); + const specificationId = argv.specificationId ?? randomUUID(); + const { status } = argv; + const { environment } = argv; + const { ttlHours } = argv; const letterRepository = createLetterRepository(environment, ttlHours); - const count = argv.count; - + const { count } = argv; // Upload a test file for this batch const bucketName = `nhs-${argv.awsAccountId}-eu-west-2-${argv.environment}-supapi-test-letters`; const targetFilename = `${batchId}-${status}.pdf`; const url = `s3://${bucketName}/${batchId}/${targetFilename}`; - await uploadFile(bucketName, batchId, "../../test_letter.pdf", targetFilename); + await uploadFile( + bucketName, + batchId, + "../../test_letter.pdf", + targetFilename, + ); // Create letter DTOs - let letterDtos = []; + const letterDtos = []; for (let i = 0; i < count; i++) { - letterDtos.push(createLetterDto({ - letterId: randomUUID(), - supplierId, - groupId, - specificationId, - status: status as LetterStatusType, - url, - })); - }; + letterDtos.push( + createLetterDto({ + letterId: randomUUID(), + supplierId, + groupId, + specificationId, + status: status as LetterStatusType, + url, + }), + ); + } // Upload Letters await letterRepository.putLetterBatch(letterDtos); @@ -184,8 +188,8 @@ async function main() { } if (require.main === module) { - main().catch((err) => { - console.error(err); + main().catch((error) => { + console.error(error); process.exitCode = 1; }); } diff --git a/scripts/utilities/letter-test-data/src/helpers/create_letter_helpers.ts b/scripts/utilities/letter-test-data/src/helpers/create-letter-helpers.ts similarity index 88% rename from scripts/utilities/letter-test-data/src/helpers/create_letter_helpers.ts rename to scripts/utilities/letter-test-data/src/helpers/create-letter-helpers.ts index 062154ec..827c6801 100644 --- a/scripts/utilities/letter-test-data/src/helpers/create_letter_helpers.ts +++ b/scripts/utilities/letter-test-data/src/helpers/create-letter-helpers.ts @@ -1,9 +1,9 @@ import { - LetterRepository, Letter, + LetterRepository, LetterStatusType, } from "@internal/datastore"; -import { uploadFile } from "./s3_helpers"; +import uploadFile from "./s3-helpers"; export async function createLetter(params: { letterId: string; @@ -16,14 +16,14 @@ export async function createLetter(params: { letterRepository: LetterRepository; }) { const { - letterId, bucketName, - supplierId, - targetFilename, - specificationId, groupId, - status, + letterId, letterRepository, + specificationId, + status, + supplierId, + targetFilename, } = params; await uploadFile( @@ -39,7 +39,7 @@ export async function createLetter(params: { specificationId, groupId, url: `s3://${bucketName}/${supplierId}/${targetFilename}`, - status: status, + status, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }; @@ -56,22 +56,16 @@ export function createLetterDto(params: { status: LetterStatusType; url: string; }) { - const { - letterId, - supplierId, - specificationId, - groupId, - status, - url, - } = params; + const { groupId, letterId, specificationId, status, supplierId, url } = + params; const letter: Omit = { id: letterId, supplierId, specificationId, groupId, - url: url, - status: status, + url, + status, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }; diff --git a/scripts/utilities/letter-test-data/src/helpers/s3_helpers.ts b/scripts/utilities/letter-test-data/src/helpers/s3-helpers.ts similarity index 53% rename from scripts/utilities/letter-test-data/src/helpers/s3_helpers.ts rename to scripts/utilities/letter-test-data/src/helpers/s3-helpers.ts index 89ef72a4..b88bbf74 100644 --- a/scripts/utilities/letter-test-data/src/helpers/s3_helpers.ts +++ b/scripts/utilities/letter-test-data/src/helpers/s3-helpers.ts @@ -1,9 +1,13 @@ -import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3"; -import { readFileSync } from "fs"; -import path from "path"; +import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3"; +import { readFileSync } from "node:fs"; +import path from "node:path"; - -export async function uploadFile(bucketName: string, supplierId: string, sourceFilename: string, targetFilename: string) { +export default async function uploadFile( + bucketName: string, + supplierId: string, + sourceFilename: string, + targetFilename: string, +) { try { const s3 = new S3Client(); const filePath = path.join(__dirname, sourceFilename); @@ -18,7 +22,8 @@ export async function uploadFile(bucketName: string, supplierId: string, sourceF const command = new PutObjectCommand(uploadParams); return await s3.send(command); - } catch (err) { - console.error("Error uploading file:", err); + } catch (error) { + console.error("Error uploading file:", error); + throw error; } } diff --git a/scripts/utilities/letter-test-data/src/infrastructure/letter-repo-factory.ts b/scripts/utilities/letter-test-data/src/infrastructure/letter-repo-factory.ts index e0060337..c6440bf1 100644 --- a/scripts/utilities/letter-test-data/src/infrastructure/letter-repo-factory.ts +++ b/scripts/utilities/letter-test-data/src/infrastructure/letter-repo-factory.ts @@ -1,9 +1,12 @@ -import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; -import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb'; -import { pino } from 'pino'; -import { LetterRepository } from '@internal/datastore'; +import { DynamoDBClient } from "@aws-sdk/client-dynamodb"; +import { DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb"; +import { pino } from "pino"; +import { LetterRepository } from "@internal/datastore"; -export function createLetterRepository(environment: string, ttlHours:number): LetterRepository { +export default function createLetterRepository( + environment: string, + ttlHours: number, +): LetterRepository { const ddbClient = new DynamoDBClient({}); const docClient = DynamoDBDocumentClient.from(ddbClient); const log = pino(); diff --git a/scripts/utilities/supplier-data/.eslintignore b/scripts/utilities/supplier-data/.eslintignore deleted file mode 100644 index 1521c8b7..00000000 --- a/scripts/utilities/supplier-data/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -dist diff --git a/scripts/utilities/supplier-data/jest.config.ts b/scripts/utilities/supplier-data/jest.config.ts index f2972c27..4bb3369a 100644 --- a/scripts/utilities/supplier-data/jest.config.ts +++ b/scripts/utilities/supplier-data/jest.config.ts @@ -1,7 +1,7 @@ -import type { Config } from 'jest'; +import type { Config } from "jest"; export const baseJestConfig: Config = { - preset: 'ts-jest', + preset: "ts-jest", // Automatically clear mock calls, instances, contexts and results before every test clearMocks: true, @@ -10,10 +10,10 @@ export const baseJestConfig: Config = { collectCoverage: true, // The directory where Jest should output its coverage files - coverageDirectory: './.reports/unit/coverage', + coverageDirectory: "./.reports/unit/coverage", // Indicates which provider should be used to instrument code for coverage - coverageProvider: 'babel', + coverageProvider: "babel", coverageThreshold: { global: { @@ -24,37 +24,37 @@ export const baseJestConfig: Config = { }, }, - coveragePathIgnorePatterns: ['/__tests__/'], - transform: { '^.+\\.ts$': 'ts-jest' }, - testPathIgnorePatterns: ['.build'], - testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'], + coveragePathIgnorePatterns: ["/__tests__/"], + transform: { "^.+\\.ts$": "ts-jest" }, + testPathIgnorePatterns: [".build"], + testMatch: ["**/?(*.)+(spec|test).[jt]s?(x)"], // Use this configuration option to add custom reporters to Jest reporters: [ - 'default', + "default", [ - 'jest-html-reporter', + "jest-html-reporter", { - pageTitle: 'Test Report', - outputPath: './.reports/unit/test-report.html', + pageTitle: "Test Report", + outputPath: "./.reports/unit/test-report.html", includeFailureMsg: true, }, ], ], // The test environment that will be used for testing - testEnvironment: 'jsdom', + testEnvironment: "jsdom", }; const utilsJestConfig = { ...baseJestConfig, - testEnvironment: 'node', + testEnvironment: "node", coveragePathIgnorePatterns: [ ...(baseJestConfig.coveragePathIgnorePatterns ?? []), - 'cli/index.ts', - 'suppliers-repo-factory.ts', + "cli/index.ts", + "suppliers-repo-factory.ts", ], }; diff --git a/scripts/utilities/supplier-data/package.json b/scripts/utilities/supplier-data/package.json index a74ca076..ebeb6a55 100644 --- a/scripts/utilities/supplier-data/package.json +++ b/scripts/utilities/supplier-data/package.json @@ -1,5 +1,7 @@ { "dependencies": { + "@aws-sdk/client-dynamodb": "^3.954.0", + "@aws-sdk/lib-dynamodb": "^3.954.0", "@internal/datastore": "*", "esbuild": "^0.25.11", "pino": "^9.7.0", diff --git a/scripts/utilities/supplier-data/src/cli/index.ts b/scripts/utilities/supplier-data/src/cli/index.ts index 3efab279..cc60ae5e 100644 --- a/scripts/utilities/supplier-data/src/cli/index.ts +++ b/scripts/utilities/supplier-data/src/cli/index.ts @@ -1,11 +1,7 @@ import { hideBin } from "yargs/helpers"; -import yargs from 'yargs'; -import { LetterStatusType } from "@internal/datastore/src/types"; -import { randomUUID } from "crypto"; +import yargs from "yargs"; import { createSupplierRepository } from "../infrastructure/suppliers-repo-factory"; - - async function main() { await yargs(hideBin(process.argv)) .command( @@ -16,35 +12,32 @@ async function main() { type: "string", demandOption: true, }, - "id": { + id: { type: "string", demandOption: true, }, - "name": { + name: { type: "string", demandOption: true, }, - "apimId": { + apimId: { type: "string", demandOption: true, }, status: { type: "string", demandOption: true, - choices: [ - "ENABLED", - "DISABLED" - ], + choices: ["ENABLED", "DISABLED"], }, }, async (argv) => { // parse args - const id = argv.id; - const name = argv.name; - const apimId = argv.apimId; + const { id } = argv; + const { name } = argv; + const { apimId } = argv; const status = argv.status as "ENABLED" | "DISABLED"; - const environment = argv.environment; + const { environment } = argv; const supplierRepository = createSupplierRepository(environment); @@ -56,13 +49,13 @@ async function main() { }); console.log(`PUT successful ${JSON.stringify(putResult)}`); - } + }, ) .command( "get-supplier-by-id", "Get a supplier by their Supplier ID", { - "id": { + id: { type: "string", demandOption: true, }, @@ -72,9 +65,8 @@ async function main() { }, }, async (argv) => { - - const id = argv.id; - const environment = argv.environment; + const { id } = argv; + const { environment } = argv; const supplierRepository = createSupplierRepository(environment); @@ -87,7 +79,7 @@ async function main() { "get-supplier-by-apim-id", "Get a supplier by their APIM ID", { - "apimId": { + apimId: { type: "string", demandOption: true, }, @@ -97,9 +89,8 @@ async function main() { }, }, async (argv) => { - - const apimId = argv.apimId; - const environment = argv.environment; + const { apimId } = argv; + const { environment } = argv; const supplierRepository = createSupplierRepository(environment); @@ -113,8 +104,8 @@ async function main() { } if (require.main === module) { - main().catch((err) => { - console.error(err); + main().catch((error) => { + console.error(error); process.exitCode = 1; }); } diff --git a/scripts/utilities/supplier-data/src/infrastructure/suppliers-repo-factory.ts b/scripts/utilities/supplier-data/src/infrastructure/suppliers-repo-factory.ts index 3bddc616..59ce4c3a 100644 --- a/scripts/utilities/supplier-data/src/infrastructure/suppliers-repo-factory.ts +++ b/scripts/utilities/supplier-data/src/infrastructure/suppliers-repo-factory.ts @@ -1,9 +1,11 @@ -import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; -import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb'; -import { pino } from 'pino'; -import { SupplierRepository } from '@internal/datastore'; +import { DynamoDBClient } from "@aws-sdk/client-dynamodb"; +import { DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb"; +import { pino } from "pino"; +import { SupplierRepository } from "@internal/datastore"; -export function createSupplierRepository(environment: string): SupplierRepository { +export default function createSupplierRepository( + environment: string, +): SupplierRepository { const ddbClient = new DynamoDBClient({}); const docClient = DynamoDBDocumentClient.from(ddbClient); const log = pino(); diff --git a/tests/component-tests/apiGateway-tests/create-mi.spec.ts b/tests/component-tests/apiGateway-tests/create-mi.spec.ts new file mode 100644 index 00000000..cef62679 --- /dev/null +++ b/tests/component-tests/apiGateway-tests/create-mi.spec.ts @@ -0,0 +1,134 @@ +import { expect, test } from "@playwright/test"; +import { getRestApiGatewayBaseUrl } from "../../helpers/aws-gateway-helper"; +import { MI_ENDPOINT } from "../../constants/api-constants"; +import { + createHeaderWithNoCorrelationId, + createHeaderWithNoRequestId, + createInvalidRequestHeaders, + createValidRequestHeaders, +} from "../../constants/request-headers"; +import { + miInvalidDateRequest, + miInvalidRequest, + miValidRequest, +} from "./testCases/create-mi"; +import { + error400InvalidDate, + error400ResponseBody, + error403ResponseBody, + requestId500Error, +} from "../../helpers/common-types"; + +let baseUrl: string; + +test.beforeAll(async () => { + baseUrl = await getRestApiGatewayBaseUrl(); +}); + +test.describe("API Gateway Tests to Verify Mi Endpoint", () => { + test(`Post /mi returns 200 when a valid request is passed`, async ({ + request, + }) => { + const headers = createValidRequestHeaders(); + const body = miValidRequest(); + + const response = await request.post(`${baseUrl}/${MI_ENDPOINT}`, { + headers, + data: body, + }); + + const responseBody = await response.json(); + expect(response.status()).toBe(201); + expect(responseBody.data.attributes).toMatchObject({ + groupId: "group123", + lineItem: "envelope-business-standard", + quantity: 10, + specificationId: "Test-Spec-Id", + stockRemaining: 100, + timestamp: body.data.attributes.timestamp, + }); + expect(responseBody.data.type).toBe("ManagementInformation"); + }); + + test(`Post /mi returns 400 when a invalid request is passed`, async ({ + request, + }) => { + const headers = createValidRequestHeaders(); + const body = miInvalidRequest(); + + const response = await request.post(`${baseUrl}/${MI_ENDPOINT}`, { + headers, + data: body, + }); + + const responseBody = await response.json(); + expect(response.status()).toBe(400); + expect(responseBody).toMatchObject(error400ResponseBody()); + }); + + test(`Post /mi returns 403 when a authentication header is not passed`, async ({ + request, + }) => { + const headers = createInvalidRequestHeaders(); + const body = miValidRequest(); + + const response = await request.post(`${baseUrl}/${MI_ENDPOINT}`, { + headers, + data: body, + }); + + const responseBody = await response.json(); + expect(response.status()).toBe(403); + expect(responseBody).toMatchObject(error403ResponseBody()); + }); + + test(`Post /mi returns 500 when a correlationId is not passed`, async ({ + request, + }) => { + const headers = createHeaderWithNoCorrelationId(); + const body = miValidRequest(); + + const response = await request.post(`${baseUrl}/${MI_ENDPOINT}`, { + headers, + data: body, + }); + + const res = await response.json(); + expect(response.status()).toBe(500); + expect(res.errors[0].detail).toBe( + "The request headers don't contain the APIM correlation id", + ); + }); + + test(`Post /mi returns 500 when a x-request-id is not passed`, async ({ + request, + }) => { + const headers = createHeaderWithNoRequestId(); + const body = miValidRequest(); + + const response = await request.post(`${baseUrl}/${MI_ENDPOINT}`, { + headers, + data: body, + }); + + const responseBody = await response.json(); + expect(response.status()).toBe(500); + expect(responseBody).toMatchObject(requestId500Error()); + }); + + test(`Post /mi returns 400 when a invalid Date is passed`, async ({ + request, + }) => { + const headers = createValidRequestHeaders(); + const body = miInvalidDateRequest(); + + const response = await request.post(`${baseUrl}/${MI_ENDPOINT}`, { + headers, + data: body, + }); + + const responseBody = await response.json(); + expect(response.status()).toBe(400); + expect(responseBody).toMatchObject(error400InvalidDate()); + }); +}); diff --git a/tests/component-tests/apiGateway-tests/createMi.spec.ts b/tests/component-tests/apiGateway-tests/createMi.spec.ts deleted file mode 100644 index 0455c06d..00000000 --- a/tests/component-tests/apiGateway-tests/createMi.spec.ts +++ /dev/null @@ -1,110 +0,0 @@ -import {test, expect} from '@playwright/test'; -import { getRestApiGatewayBaseUrl } from "../../helpers/awsGatewayHelper"; -import { MI_ENDPOINT } from '../../constants/api_constants'; -import { createHeaderWithNoCorrelationId, createHeaderWithNoRequestId, createInvalidRequestHeaders, createValidRequestHeaders } from '../../constants/request_headers'; -import { miInvalidDateRequest, miInvalidRequest, miValidRequest } from './testCases/createMi'; -import { time } from 'console'; -import { error400InvalidDate, error400ResponseBody, error403ResponseBody, error404ResponseBody, requestId500Error } from '../../helpers/commonTypes'; - -let baseUrl: string; - -test.beforeAll(async () => { - baseUrl = await getRestApiGatewayBaseUrl(); -}); - -test.describe('API Gateway Tests to Verify Mi Endpoint', () => { - test(`Post /mi returns 200 when a valid request is passed`, async ({ request }) => { - - const headers = createValidRequestHeaders(); - const body = miValidRequest(); - - const response = await request.post(`${baseUrl}/${MI_ENDPOINT}`, { - headers: headers, - data: body - }); - - const responseBody = await response.json(); - expect(response.status()).toBe(201); - expect(responseBody.data.attributes).toMatchObject({ - groupId: 'group123', - lineItem: 'envelope-business-standard', - quantity: 10, - specificationId: 'Test-Spec-Id', - stockRemaining: 100, - timestamp: body.data.attributes.timestamp, - }); - expect(responseBody.data.type).toBe('ManagementInformation'); - }); - - test(`Post /mi returns 400 when a invalid request is passed`, async ({ request }) => { - const headers = createValidRequestHeaders(); - const body = miInvalidRequest(); - - const response = await request.post(`${baseUrl}/${MI_ENDPOINT}`, { - headers: headers, - data: body - }); - - const responseBody = await response.json(); - expect(response.status()).toBe(400); - expect(responseBody).toMatchObject(error400ResponseBody()); - }); - - test(`Post /mi returns 403 when a authentication header is not passed`, async ({ request }) => { - const headers = createInvalidRequestHeaders(); - const body = miValidRequest(); - - const response = await request.post(`${baseUrl}/${MI_ENDPOINT}`, { - headers: headers, - data: body - }); - - const responseBody = await response.json(); - expect(response.status()).toBe(403); - expect(responseBody).toMatchObject(error403ResponseBody()); - }); - - test(`Post /mi returns 500 when a correlationId is not passed`, async ({ request }) => { - const headers = createHeaderWithNoCorrelationId(); - const body = miValidRequest(); - - const response = await request.post(`${baseUrl}/${MI_ENDPOINT}`, { - headers: headers, - data: body - }); - - const res = await response.json(); - expect(response.status()).toBe(500); - expect(res.errors[0].detail).toBe("The request headers don't contain the APIM correlation id"); - }); - - test(`Post /mi returns 500 when a x-request-id is not passed`, async ({ request }) => { - const headers = createHeaderWithNoRequestId(); - const body = miValidRequest(); - - const response = await request.post(`${baseUrl}/${MI_ENDPOINT}`, { - headers: headers, - data: body - }); - - const responseBody = await response.json(); - expect(response.status()).toBe(500); - expect(responseBody).toMatchObject(requestId500Error()); - }); - - test(`Post /mi returns 400 when a invalid Date is passed`, async ({ request }) => { - const headers = createValidRequestHeaders(); - const body = miInvalidDateRequest(); - - const response = await request.post(`${baseUrl}/${MI_ENDPOINT}`, { - headers: headers, - data: body - }); - - const responseBody = await response.json(); - expect(response.status()).toBe(400); - expect(responseBody).toMatchObject(error400InvalidDate()); - }); - - -}); diff --git a/tests/component-tests/apiGateway-tests/get-letter-status.spec.ts b/tests/component-tests/apiGateway-tests/get-letter-status.spec.ts new file mode 100644 index 00000000..909f0573 --- /dev/null +++ b/tests/component-tests/apiGateway-tests/get-letter-status.spec.ts @@ -0,0 +1,79 @@ +import { expect, test } from "@playwright/test"; +import { getRestApiGatewayBaseUrl } from "../../helpers/aws-gateway-helper"; +import { getLettersBySupplier } from "../../helpers/generate-fetch-test-data"; +import { SUPPLIERID, SUPPLIER_LETTERS } from "../../constants/api-constants"; +import { createValidRequestHeaders } from "../../constants/request-headers"; +import { + error404ResponseBody, + error500ResponseBody, +} from "../../helpers/common-types"; + +let baseUrl: string; + +test.beforeAll(async () => { + baseUrl = await getRestApiGatewayBaseUrl(); +}); + +test.describe("API Gateway Tests to Verify Get Letter Status Endpoint", () => { + test(`Get /letters/{id} returns 200 and valid response for a given id`, async ({ + request, + }) => { + const letters = await getLettersBySupplier(SUPPLIERID, "PENDING", 1); + + if (!letters?.length) { + test.fail(true, `No PENDING letters found for supplier ${SUPPLIERID}`); + return; + } + const letter = letters[0]; + const headers = createValidRequestHeaders(); + const response = await request.get( + `${baseUrl}/${SUPPLIER_LETTERS}/${letter.id}`, + { + headers, + }, + ); + + const responseBody = await response.json(); + + expect(response.status()).toBe(200); + expect(responseBody).toMatchObject({ + data: { + attributes: { + status: "PENDING", + specificationId: letter.specificationId, + groupId: letter.groupId, + }, + id: letter.id, + type: "Letter", + }, + }); + }); + + test(`Get /letters/{id} returns 404 if no resource is found for id`, async ({ + request, + }) => { + const id = "11"; + const headers = createValidRequestHeaders(); + const response = await request.get(`${baseUrl}/${SUPPLIER_LETTERS}/${id}`, { + headers, + }); + + const responseBody = await response.json(); + expect(response.status()).toBe(404); + expect(responseBody).toMatchObject(error404ResponseBody()); + }); + + test(`Get /letters/{id} returns 500 if letter is not found for supplierId ${SUPPLIERID}`, async ({ + request, + }) => { + const id = "non-existing-id-12345"; + const headers = createValidRequestHeaders(); + const response = await request.get(`${baseUrl}/${SUPPLIER_LETTERS}/${id}`, { + headers, + }); + + const responseBody = await response.json(); + expect(response.status()).toBe(500); + expect(responseBody).toMatchObject(error500ResponseBody(id)); + }); +}); diff --git a/tests/component-tests/apiGateway-tests/get-letters.spec.ts b/tests/component-tests/apiGateway-tests/get-letters.spec.ts new file mode 100644 index 00000000..2a094b17 --- /dev/null +++ b/tests/component-tests/apiGateway-tests/get-letters.spec.ts @@ -0,0 +1,107 @@ +import { expect, test } from "@playwright/test"; +import { SUPPLIER_LETTERS } from "../../constants/api-constants"; +import { + createHeaderWithNoCorrelationId, + createInvalidRequestHeaders, + createValidRequestHeaders, +} from "../../constants/request-headers"; +import { getRestApiGatewayBaseUrl } from "../../helpers/aws-gateway-helper"; +import { validateApiResponse } from "../../helpers/validate-json-schema"; + +let baseUrl: string; + +test.beforeAll(async () => { + baseUrl = await getRestApiGatewayBaseUrl(); +}); + +test.describe("API Gateway Tests To Get List Of Pending Letters", () => { + test("GET /letters should return 200 and list items", async ({ request }) => { + const header = createValidRequestHeaders(); + const response = await request.get(`${baseUrl}/${SUPPLIER_LETTERS}`, { + headers: header, + params: { + limit: "2", + }, + }); + + expect(response.status()).toBe(200); + const responseBody = await response.json(); + expect(responseBody.data.length.toString()).toEqual("2"); + + const validationResult = validateApiResponse( + "get", + "/letters", + response.status(), + responseBody, + ); + if (validationResult) { + console.error("API response validation failed:", validationResult); + } + expect(validationResult).toBeUndefined(); + }); + + test("GET /letters with invalid authentication should return 403", async ({ + request, + }) => { + const header = createInvalidRequestHeaders(); + const response = await request.get(`${baseUrl}/${SUPPLIER_LETTERS}`, { + headers: header, + params: { + limit: "2", + }, + }); + expect(response.status()).toBe(403); + const responseBody = await response.json(); + expect(responseBody).toMatchObject({ + Message: + "User is not authorized to access this resource with an explicit deny in an identity-based policy", + }); + }); + + test("GET /letters with empty correlationId should return 500", async ({ + request, + }) => { + const header = createHeaderWithNoCorrelationId(); + const response = await request.get(`${baseUrl}/${SUPPLIER_LETTERS}`, { + headers: header, + params: { + limit: "2", + }, + }); + expect(response.status()).toBe(500); + const responseBody = await response.json(); + expect(responseBody.errors[0].code).toBe("NOTIFY_INTERNAL_SERVER_ERROR"); + expect(responseBody.errors[0].detail).toBe( + "The request headers don't contain the APIM correlation id", + ); + }); + + test("GET /letters with invalid query param return 400", async ({ + request, + }) => { + const header = createValidRequestHeaders(); + const response = await request.get(`${baseUrl}/${SUPPLIER_LETTERS}`, { + headers: header, + params: { + limit: "?", + }, + }); + expect(response.status()).toBe(400); + const responseBody = await response.json(); + expect(responseBody).toMatchObject({ + errors: [ + { + id: "12345", + code: "NOTIFY_INVALID_REQUEST", + links: { + about: + "https://digital.nhs.uk/developer/api-catalogue/nhs-notify-supplier", + }, + status: "400", + title: "Invalid request", + detail: "The limit parameter is not a number", + }, + ], + }); + }); +}); diff --git a/tests/component-tests/apiGateway-tests/getLetterStatus.spec.ts b/tests/component-tests/apiGateway-tests/getLetterStatus.spec.ts deleted file mode 100644 index 7382cb79..00000000 --- a/tests/component-tests/apiGateway-tests/getLetterStatus.spec.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { test, expect } from '@playwright/test'; -import { getRestApiGatewayBaseUrl } from '../../helpers/awsGatewayHelper'; -import { getLettersBySupplier } from '../../helpers/generate_fetch_testData'; -import { SUPPLIER_LETTERS, SUPPLIERID } from '../../constants/api_constants'; -import { createValidRequestHeaders } from '../../constants/request_headers'; -import { error404ResponseBody, error500ResponseBody } from '../../helpers/commonTypes'; - -let baseUrl: string; - -test.beforeAll(async () => { - baseUrl = await getRestApiGatewayBaseUrl(); -}); - -test.describe('API Gateway Tests to Verify Get Letter Status Endpoint', () => { - test(`Get /letters/{id} returns 200 and valid response for a given id`, async ({ request }) => { - - const letters = await getLettersBySupplier(SUPPLIERID, 'PENDING', 1); - - if (!letters?.length) { - test.fail(true, `No PENDING letters found for supplier ${SUPPLIERID}`); - return; - } - const letter = letters[0]; - const headers = createValidRequestHeaders(); - const response = await request.get(`${baseUrl}/${SUPPLIER_LETTERS}/${letter.id}`, { - headers: headers, - }); - - const responseBody = await response.json(); - - expect(response.status()).toBe(200); - expect(responseBody).toMatchObject({ - data:{ - attributes: { - status: 'PENDING', - specificationId: letter.specificationId, - groupId: letter.groupId, - }, - id: letter.id, - type: 'Letter' - } - }); - }); - - test(`Get /letters/{id} returns 404 if no resource is found for id`, async ({ request }) => - { - const id = '11'; - const headers = createValidRequestHeaders(); - const response = await request.get(`${baseUrl}/${SUPPLIER_LETTERS}/${id}`, { - headers: headers, - }); - - const responseBody = await response.json(); - expect(response.status()).toBe(404); - expect(responseBody).toMatchObject(error404ResponseBody()); - }); - - test(`Get /letters/{id} returns 500 if letter is not found for supplierId ${SUPPLIERID}`, async ({ request }) => - { - const id = 'non-existing-id-12345'; - const headers = createValidRequestHeaders(); - const response = await request.get(`${baseUrl}/${SUPPLIER_LETTERS}/${id}`, { - headers: headers, - }); - - const responseBody = await response.json(); - expect(response.status()).toBe(500); - expect(responseBody).toMatchObject(error500ResponseBody(id)); - }); - -}); diff --git a/tests/component-tests/apiGateway-tests/getLetters.spec.ts b/tests/component-tests/apiGateway-tests/getLetters.spec.ts deleted file mode 100644 index b21fa5b9..00000000 --- a/tests/component-tests/apiGateway-tests/getLetters.spec.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { test, expect } from '@playwright/test'; -import { SUPPLIER_LETTERS } from '../../constants/api_constants'; -import { createHeaderWithNoCorrelationId, createInvalidRequestHeaders, createValidRequestHeaders } from '../../constants/request_headers'; -import { getRestApiGatewayBaseUrl } from '../../helpers/awsGatewayHelper'; -import { validateApiResponse } from '../../helpers/validateJsonSchema'; - -let baseUrl: string; - -test.beforeAll(async () => { - baseUrl = await getRestApiGatewayBaseUrl(); -}); - -test.describe('API Gateway Tests To Get List Of Pending Letters', () => -{ - test('GET /letters should return 200 and list items', async ({ request }) => - { - const header = createValidRequestHeaders(); - const response = await request.get(`${baseUrl}/${SUPPLIER_LETTERS}` ,{ - headers: header, - params: { - limit:'2'}, - }, - ); - - expect(response.status()).toBe(200); - const responseBody = await response.json(); - expect(responseBody.data.length.toString()).toEqual('2'); - - const validationResult = validateApiResponse("get", "/letters", response.status(), responseBody); - if (validationResult) { - console.error("API response validation failed:", validationResult); - } - expect(validationResult).toBeUndefined(); - }); - - test('GET /letters with invalid authentication should return 403', async ({ request }) => { - const header = createInvalidRequestHeaders(); - const response = await request.get(`${baseUrl}/${SUPPLIER_LETTERS}` ,{ - headers: header, - params:{ - limit:'2' - }, - }, - ); - expect(response.status()).toBe(403); - const responseBody = await response.json(); - expect(responseBody).toMatchObject({ - Message : 'User is not authorized to access this resource with an explicit deny in an identity-based policy' } - ); - }); - - test('GET /letters with empty correlationId should return 500', async ({ request }) => { - const header = createHeaderWithNoCorrelationId(); - const response = await request.get(`${baseUrl}/${SUPPLIER_LETTERS}` ,{ - headers: header, - params:{ - limit:'2' - }, - }, - ); - expect(response.status()).toBe(500); - const responseBody = await response.json(); - expect(responseBody.errors[0].code).toBe('NOTIFY_INTERNAL_SERVER_ERROR'); - expect(responseBody.errors[0].detail).toBe("The request headers don't contain the APIM correlation id"); - - }); - - test('GET /letters with invalid query param return 400', async ({ request }) => { - const header = createValidRequestHeaders(); - const response = await request.get(`${baseUrl}/${SUPPLIER_LETTERS}` ,{ - headers: header, - params:{ - limit:'?' - }, - }); - expect(response.status()).toBe(400); - const responseBody = await response.json(); - expect(responseBody).toMatchObject({ - errors: [ - { - id: '12345', - code: 'NOTIFY_INVALID_REQUEST', - links: { - about: 'https://digital.nhs.uk/developer/api-catalogue/nhs-notify-supplier' - }, - status: '400', - title: 'Invalid request', - detail: 'The limit parameter is not a number' - } - ] - }); - }); -}); diff --git a/tests/component-tests/apiGateway-tests/testCases/create-mi.ts b/tests/component-tests/apiGateway-tests/testCases/create-mi.ts new file mode 100644 index 00000000..e3f60db9 --- /dev/null +++ b/tests/component-tests/apiGateway-tests/testCases/create-mi.ts @@ -0,0 +1,61 @@ +export type MiRequestBody = { + data: { + type: string; + attributes: { + groupId: string; + lineItem: string; + quantity: number; + specificationId: string; + stockRemaining: number; + timestamp: string; + }; + }; +}; + +export function miValidRequest(): MiRequestBody { + return { + data: { + attributes: { + groupId: "group123", + lineItem: "envelope-business-standard", + quantity: 10, + specificationId: "Test-Spec-Id", + stockRemaining: 100, + timestamp: new Date().toISOString(), + }, + type: "ManagementInformation", + }, + }; +} + +export function miInvalidRequest(): MiRequestBody { + return { + data: { + attributes: { + groupId: "group123", + lineItem: "envelope-business-standard", + quantity: 10, + specificationId: "Test-Spec-Id", + stockRemaining: 100, + timestamp: new Date().toISOString(), + }, + type: "?", + }, + }; +} + +export function miInvalidDateRequest(): MiRequestBody { + return { + data: { + attributes: { + groupId: "group123", + lineItem: "envelope-business-standard", + quantity: 10, + specificationId: "Test-Spec-Id", + stockRemaining: 100, + timestamp: "2021-10-28T", + }, + type: "ManagementInformation", + }, + }; +} diff --git a/tests/component-tests/apiGateway-tests/testCases/createMi.ts b/tests/component-tests/apiGateway-tests/testCases/createMi.ts deleted file mode 100644 index f7f0b222..00000000 --- a/tests/component-tests/apiGateway-tests/testCases/createMi.ts +++ /dev/null @@ -1,70 +0,0 @@ - - - -export type MiRequestBody = { - data: { - type: string; - attributes: { - groupId: string; - lineItem: string; - quantity: number; - specificationId: string; - stockRemaining: number; - timestamp: string; - }; - }; -}; - -export function miValidRequest() : MiRequestBody{ - let requestBody: MiRequestBody; - - requestBody = { - data: { -  attributes: { - groupId: 'group123', - lineItem: 'envelope-business-standard', - quantity: 10, - specificationId: 'Test-Spec-Id', - stockRemaining: 100, - timestamp: new Date().toISOString(), - }, - type: 'ManagementInformation', - }}; - return requestBody; -} - -export function miInvalidRequest() : MiRequestBody{ - let requestBody: MiRequestBody; - - requestBody = { - data: { -  attributes: { - groupId: 'group123', - lineItem: 'envelope-business-standard', - quantity: 10, - specificationId: 'Test-Spec-Id', - stockRemaining: 100, - timestamp: new Date().toISOString(), - }, - type: '?', - }}; - return requestBody; -} - -export function miInvalidDateRequest() : MiRequestBody{ - let requestBody: MiRequestBody; - - requestBody = { - data: { -  attributes: { - groupId: 'group123', - lineItem: 'envelope-business-standard', - quantity: 10, - specificationId: 'Test-Spec-Id', - stockRemaining: 100, - timestamp: '2021-10-28T', - }, - type: 'ManagementInformation', - }}; - return requestBody; -} diff --git a/tests/component-tests/apiGateway-tests/testCases/update-letter-status.ts b/tests/component-tests/apiGateway-tests/testCases/update-letter-status.ts new file mode 100644 index 00000000..10a9184f --- /dev/null +++ b/tests/component-tests/apiGateway-tests/testCases/update-letter-status.ts @@ -0,0 +1,106 @@ +import { RequestHeaders } from "../../../constants/request-headers"; +import { SUPPLIERID } from "../../../constants/api-constants"; +import { ErrorMessageBody } from "../../../helpers/common-types"; + +export type PatchMessageRequestBody = { + data: { + type: string; + id: string; + attributes: { + reasonCode?: string; + reasonText?: string; + status: string; + }; + }; +}; + +export type PatchMessageResponseBody = { + data: { + type: string; + id: string; + attributes: { + reasonCode?: string; + reasonText?: string; + status: string; + specificationId: string; + groupId?: string; + }; + }; +}; + +export function patchRequestHeaders(): RequestHeaders { + return { + headerauth1: process.env.HEADERAUTH || "", + "NHSD-Supplier-ID": SUPPLIERID, + "NHSD-Correlation-ID": "12344", + "X-Request-ID": "requestId1", + }; +} + +export function patchValidRequestBody( + id: string, + status: string, +): PatchMessageRequestBody { + return { + data: { + attributes: { + status, + }, + type: "Letter", + id, + }, + }; +} + +export function patchFailureRequestBody( + id: string, + status: string, +): PatchMessageRequestBody { + return { + data: { + attributes: { + status, + reasonCode: "R01", + reasonText: "Test Reason", + }, + type: "Letter", + id, + }, + }; +} + +export function patch400ErrorResponseBody(): ErrorMessageBody { + return { + errors: [ + { + id: "12344", + code: "NOTIFY_INVALID_REQUEST", + links: { + about: + "https://digital.nhs.uk/developer/api-catalogue/nhs-notify-supplier", + }, + status: "400", + title: "Invalid request", + detail: "The request body is invalid", + }, + ], + }; +} + +export function patch500ErrorResponseBody(id: string): ErrorMessageBody { + return { + errors: [ + { + id: "12344", + code: "NOTIFY_INTERNAL_SERVER_ERROR", + links: { + about: + "https://digital.nhs.uk/developer/api-catalogue/nhs-notify-supplier", + }, + status: "500", + title: "Internal server error", + detail: `Letter with id ${id} not found for supplier ${SUPPLIERID}`, + }, + ], + }; +} diff --git a/tests/component-tests/apiGateway-tests/testCases/updateLetterStatus.ts b/tests/component-tests/apiGateway-tests/testCases/updateLetterStatus.ts deleted file mode 100644 index 1dc60d4d..00000000 --- a/tests/component-tests/apiGateway-tests/testCases/updateLetterStatus.ts +++ /dev/null @@ -1,114 +0,0 @@ - -import { RequestHeaders } from '../../../constants/request_headers'; -import { SUPPLIERID } from '../../../constants/api_constants'; -import { ErrorMessageBody } from '../../../helpers/commonTypes'; - -export type PatchMessageRequestBody = { - data: { - type: string; - id: string; - attributes: { - reasonCode?: string; - reasonText?: string; - status: string; - }; - }; -}; - -export type PatchMessageResponseBody = { - data: { - type: string; - id: string; - attributes: { - reasonCode?: string; - reasonText?: string; - status: string; - specificationId:string; - groupId?:string; - }; - }; -}; - -export function patchRequestHeaders(): RequestHeaders { - let requestHeaders: RequestHeaders; - requestHeaders = { - headerauth1: process.env.HEADERAUTH || '', - 'NHSD-Supplier-ID': SUPPLIERID, - 'NHSD-Correlation-ID': '12344', - 'X-Request-ID': 'requestId1' - }; - return requestHeaders; -}; - - -export function patchValidRequestBody (id: string, status: string) : PatchMessageRequestBody{ - let requestBody: PatchMessageRequestBody; - - requestBody = { - data: { -  attributes: { -   status: status, - }, - type: 'Letter', - id: id - } - - }; - return requestBody; -} - -export function patchFailureRequestBody (id: string, status: string) : PatchMessageRequestBody{ - let requestBody: PatchMessageRequestBody; - - requestBody = { - data: { - attributes: { - status: status, - reasonCode: 'R01', - reasonText: 'Test Reason' - }, - type: 'Letter', - id: id - } - - }; - return requestBody; -} - -export function patch400ErrorResponseBody () : ErrorMessageBody{ - let responseBody: ErrorMessageBody; - responseBody = { - errors: [ - { - id : '12344', - code : 'NOTIFY_INVALID_REQUEST', - "links": { - "about": "https://digital.nhs.uk/developer/api-catalogue/nhs-notify-supplier" - }, - "status": "400", - "title": "Invalid request", - "detail": "The request body is invalid" - } - ] - }; - return responseBody; -}; - -export function patch500ErrorResponseBody (id: string) : ErrorMessageBody{ - let responseBody: ErrorMessageBody; - responseBody = { - errors: [ - { - id: "12344", - code: "NOTIFY_INTERNAL_SERVER_ERROR", - links: { - "about": "https://digital.nhs.uk/developer/api-catalogue/nhs-notify-supplier" - }, - status: "500", - title: "Internal server error", - detail: `Letter with id ${id} not found for supplier ${SUPPLIERID}` - } - ] - }; - return responseBody; -} diff --git a/tests/component-tests/apiGateway-tests/update-letter-status.spec.ts b/tests/component-tests/apiGateway-tests/update-letter-status.spec.ts new file mode 100644 index 00000000..74c03f21 --- /dev/null +++ b/tests/component-tests/apiGateway-tests/update-letter-status.spec.ts @@ -0,0 +1,165 @@ +import { expect, test } from "@playwright/test"; +import { randomUUID } from "node:crypto"; +import { SUPPLIERID, SUPPLIER_LETTERS } from "../../constants/api-constants"; +import { getRestApiGatewayBaseUrl } from "../../helpers/aws-gateway-helper"; +import { + patch400ErrorResponseBody, + patch500ErrorResponseBody, + patchFailureRequestBody, + patchRequestHeaders, + patchValidRequestBody, +} from "./testCases/update-letter-status"; +import { + createTestData, + deleteLettersBySupplier, + getLettersBySupplier, +} from "../../helpers/generate-fetch-test-data"; +import { createInvalidRequestHeaders } from "../../constants/request-headers"; +import { error403ResponseBody } from "../../helpers/common-types"; + +let baseUrl: string; + +test.beforeAll(async () => { + baseUrl = await getRestApiGatewayBaseUrl(); +}); + +test.describe("API Gateway Tests to Verify Patch Status Endpoint", () => { + test(`Patch /letters returns 200 and status is updated to ACCEPTED`, async ({ + request, + }) => { + await createTestData(SUPPLIERID); + const letters = await getLettersBySupplier(SUPPLIERID, "PENDING", 1); + + if (!letters?.length) { + test.fail(true, `No PENDING letters found for supplier ${SUPPLIERID}`); + return; + } + const letter = letters[0]; + const headers = patchRequestHeaders(); + const body = patchValidRequestBody(letter.id, "ACCEPTED"); + + const response = await request.patch( + `${baseUrl}/${SUPPLIER_LETTERS}/${letter.id}`, + { + headers, + data: body, + }, + ); + + const responseBody = await response.json(); + expect(response.status()).toBe(200); + expect(responseBody).toMatchObject({ + data: { + attributes: { + status: "ACCEPTED", + specificationId: letter.specificationId, + groupId: letter.groupId, + }, + id: letter.id, + type: "Letter", + }, + }); + + await deleteLettersBySupplier(letter.id); + }); + + test(`Patch /letters returns 200 and status is updated to REJECTED`, async ({ + request, + }) => { + await createTestData(SUPPLIERID); + const letters = await getLettersBySupplier(SUPPLIERID, "PENDING", 1); + + if (!letters?.length) { + test.fail(true, `No PENDING letters found for supplier ${SUPPLIERID}`); + return; + } + const letter = letters[0]; + const headers = patchRequestHeaders(); + const body = patchFailureRequestBody(letter.id, "REJECTED"); + + const response = await request.patch( + `${baseUrl}/${SUPPLIER_LETTERS}/${letter.id}`, + { + headers, + data: body, + }, + ); + + const responseBody = await response.json(); + expect(response.status()).toBe(200); + expect(responseBody).toMatchObject({ + data: { + attributes: { + status: "REJECTED", + specificationId: letter.specificationId, + groupId: letter.groupId, + }, + id: letter.id, + type: "Letter", + }, + }); + + await deleteLettersBySupplier(letter.id); + }); + + test(`Patch /letters returns 400 if request Body is invalid`, async ({ + request, + }) => { + const id = randomUUID(); + const headers = patchRequestHeaders(); + const body = patchValidRequestBody(id, ""); + + const response = await request.patch( + `${baseUrl}/${SUPPLIER_LETTERS}/${id}`, + { + headers, + data: body, + }, + ); + + const responseBody = await response.json(); + + expect(response.status()).toBe(400); + expect(responseBody).toMatchObject(patch400ErrorResponseBody()); + }); + + test(`Patch /letters returns 500 if Id doesn't exist for SupplierId`, async ({ + request, + }) => { + const headers = patchRequestHeaders(); + const id = randomUUID(); + const body = patchValidRequestBody(id, "PENDING"); + + const response = await request.patch( + `${baseUrl}/${SUPPLIER_LETTERS}/${id}`, + { + headers, + data: body, + }, + ); + + const responseBody = await response.json(); + expect(response.status()).toBe(500); + expect(responseBody).toMatchObject(patch500ErrorResponseBody(id)); + }); + + test(`Patch /letters returns 403 for invalid headers`, async ({ + request, + }) => { + const headers = createInvalidRequestHeaders(); + const id = randomUUID(); + const body = patchValidRequestBody(id, "PENDING"); + + const response = await request.patch( + `${baseUrl}/${SUPPLIER_LETTERS}/${id}`, + { + headers, + data: body, + }, + ); + + const responseBody = await response.json(); + expect(response.status()).toBe(403); + expect(responseBody).toMatchObject(error403ResponseBody()); + }); +}); diff --git a/tests/component-tests/apiGateway-tests/updateLetterStatus.spec.ts b/tests/component-tests/apiGateway-tests/updateLetterStatus.spec.ts deleted file mode 100644 index 99484bf0..00000000 --- a/tests/component-tests/apiGateway-tests/updateLetterStatus.spec.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { test, expect } from '@playwright/test'; -import { SUPPLIER_LETTERS, SUPPLIERID } from '../../constants/api_constants'; -import { getRestApiGatewayBaseUrl } from '../../helpers/awsGatewayHelper'; -import { patch400ErrorResponseBody, patch500ErrorResponseBody, patchFailureRequestBody, patchRequestHeaders, patchValidRequestBody } from './testCases/updateLetterStatus'; -import { createTestData, deleteLettersBySupplier, getLettersBySupplier } from '../../helpers/generate_fetch_testData'; -import { randomUUID } from 'crypto'; -import { createInvalidRequestHeaders } from '../../constants/request_headers'; -import { error403ResponseBody } from '../../helpers/commonTypes'; - -let baseUrl: string; - -test.beforeAll(async () => { - baseUrl = await getRestApiGatewayBaseUrl(); -}); - -test.describe('API Gateway Tests to Verify Patch Status Endpoint', () => { - test(`Patch /letters returns 200 and status is updated to ACCEPTED`, async ({ request }) => { - - await createTestData(SUPPLIERID); - const letters = await getLettersBySupplier(SUPPLIERID, 'PENDING', 1); - - if (!letters?.length) { - test.fail(true, `No PENDING letters found for supplier ${SUPPLIERID}`); - return; - } - const letter = letters[0]; - const headers = patchRequestHeaders(); - const body = patchValidRequestBody(letter.id, 'ACCEPTED'); - - const response = await request.patch(`${baseUrl}/${SUPPLIER_LETTERS}/${letter.id}`, { - headers: headers, - data: body - }); - - const responseBody = await response.json(); - expect(response.status()).toBe(200); - expect(responseBody).toMatchObject({ - data:{ - attributes: { - status: 'ACCEPTED', - specificationId: letter.specificationId, - groupId: letter.groupId, - }, - id: letter.id, - type: 'Letter' - } - }); - - await deleteLettersBySupplier(letter.id); - }); - - test(`Patch /letters returns 200 and status is updated to REJECTED`, async ({ request }) => { - - await createTestData(SUPPLIERID); - const letters = await getLettersBySupplier(SUPPLIERID, 'PENDING', 1); - - if (!letters?.length) { - test.fail(true, `No PENDING letters found for supplier ${SUPPLIERID}`); - return; - } - const letter = letters[0]; - const headers = patchRequestHeaders(); - const body = patchFailureRequestBody(letter.id, 'REJECTED'); - - const response = await request.patch(`${baseUrl}/${SUPPLIER_LETTERS}/${letter.id}`, { - headers: headers, - data: body - }); - - const responseBody = await response.json(); - expect(response.status()).toBe(200); - expect(responseBody).toMatchObject({ - data:{ - attributes: { - status: 'REJECTED', - specificationId: letter.specificationId, - groupId: letter.groupId, - }, - id: letter.id, - type: 'Letter' - } - }); - - await deleteLettersBySupplier(letter.id); - }); - - test(`Patch /letters returns 400 if request Body is invalid`, async ({ request }) => { - - const id = randomUUID() - const headers = patchRequestHeaders(); - const body = patchValidRequestBody(id, ''); - - const response = await request.patch(`${baseUrl}/${SUPPLIER_LETTERS}/${id}`, { - headers: headers, - data: body - }); - - const responseBody = await response.json(); - - expect(response.status()).toBe(400); - expect(responseBody).toMatchObject(patch400ErrorResponseBody()); - }); - - test(`Patch /letters returns 500 if Id doesn't exist for SupplierId`, async ({ request }) => { - const headers = patchRequestHeaders(); - const id = randomUUID() - const body = patchValidRequestBody(id, 'PENDING'); - - const response = await request.patch(`${baseUrl}/${SUPPLIER_LETTERS}/${id}`, { - headers: headers, - data: body - }); - - const responseBody = await response.json(); - expect(response.status()).toBe(500); - expect(responseBody).toMatchObject(patch500ErrorResponseBody(id)); - }); - - test(`Patch /letters returns 403 for invalid headers`, async ({ request }) => { - const headers = createInvalidRequestHeaders(); - const id = randomUUID() - const body = patchValidRequestBody(id, 'PENDING'); - - const response = await request.patch(`${baseUrl}/${SUPPLIER_LETTERS}/${id}`, { - headers: headers, - data: body - }); - - const responseBody = await response.json(); - expect(response.status()).toBe(403); - expect(responseBody).toMatchObject(error403ResponseBody()); - }); -}); diff --git a/tests/config/main.config.ts b/tests/config/main.config.ts index 4c87088a..fa60a5be 100644 --- a/tests/config/main.config.ts +++ b/tests/config/main.config.ts @@ -1,17 +1,17 @@ -import {defineConfig, PlaywrightTestConfig } from '@playwright/test'; -import baseConfig from './playwright.base.config'; -import { getReporters } from './reporters'; -import path from 'path'; +import { PlaywrightTestConfig, defineConfig } from "@playwright/test"; +import path from "node:path"; +import baseConfig from "./playwright.base.config"; +import { getReporters } from "./reporters"; const localConfig: PlaywrightTestConfig = { ...baseConfig, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: getReporters('api-test'), + reporter: getReporters("api-test"), projects: [ { - name: 'component-tests', - testDir: path.resolve(__dirname, '../component-tests'), - testMatch: '**/*.spec.ts', + name: "component-tests", + testDir: path.resolve(__dirname, "../component-tests"), + testMatch: "**/*.spec.ts", }, ], }; diff --git a/tests/config/playwright.base.config.ts b/tests/config/playwright.base.config.ts index c6edb35a..12c9edf7 100644 --- a/tests/config/playwright.base.config.ts +++ b/tests/config/playwright.base.config.ts @@ -1,8 +1,8 @@ -import { defineConfig, PlaywrightTestConfig } from '@playwright/test'; -import 'dotenv/config'; +import { PlaywrightTestConfig, defineConfig } from "@playwright/test"; +import "dotenv/config"; -const baseUrl = process.env.NHSD_APIM_PROXY_URL || 'http://localhost:3000/'; -const envMaxInstances = Number.parseInt(process.env.WORKERS_MAX_INST!) || 10; +const envMaxInstances = + Number.parseInt(process.env.WORKERS_MAX_INST!, 10) || 10; /** * See https://playwright.dev/docs/test-configuration. */ diff --git a/tests/config/reporters.ts b/tests/config/reporters.ts index 94a68a63..27be1ac8 100644 --- a/tests/config/reporters.ts +++ b/tests/config/reporters.ts @@ -1,17 +1,17 @@ -import type { ReporterDescription } from '@playwright/test'; +import type { ReporterDescription } from "@playwright/test"; -const resultsDir = process.env.RESULTS_DIR || 'results'; -const reportsDir = process.env.REPORTS_DIR || 'reports'; +const resultsDir = process.env.RESULTS_DIR || "results"; +const reportsDir = process.env.REPORTS_DIR || "reports"; export function getReporters(allureFolder: string) { return [ [ - 'allure-playwright', + "allure-playwright", { outputFolder: `./target/reports/allure-results/${allureFolder}`, detail: false, suiteTitle: true, - open: 'always', + open: "always", environmentInfo: { E2E_NODE_VERSION: process.env.ENVIRONMENT, E2E_OS: process.platform, @@ -19,21 +19,21 @@ export function getReporters(allureFolder: string) { }, ], [ - 'html', + "html", { outputFolder: `../target/test-artifacts/${reportsDir}/html-report`, - open: process.env.CI ? 'never' : 'on-failure', + open: process.env.CI ? "never" : "on-failure", }, ], - ['list', { printSteps: true }], + ["list", { printSteps: true }], [ - 'junit', + "junit", { outputFile: `../target/test-artifacts/${resultsDir}/junit-results.xml`, }, ], [ - 'json', + "json", { outputFile: `../target/test-artifacts/${resultsDir}/json-results.json`, }, diff --git a/tests/config/sandbox.config.ts b/tests/config/sandbox.config.ts index c7295100..6f63788b 100644 --- a/tests/config/sandbox.config.ts +++ b/tests/config/sandbox.config.ts @@ -1,17 +1,17 @@ -import type { PlaywrightTestConfig } from '@playwright/test'; -import { config as baseConfig } from './playwright.base.config'; -import { getReporters } from './reporters'; -import path from 'path'; +import type { PlaywrightTestConfig } from "@playwright/test"; +import path from "node:path"; +import { config as baseConfig } from "./playwright.base.config"; +import { getReporters } from "./reporters"; const localConfig: PlaywrightTestConfig = { /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: getReporters('api-test'), + reporter: getReporters("api-test"), ...baseConfig, projects: [ { - name: 'sandbox', - testDir: path.resolve(__dirname, '../sandbox'), - testMatch: '*.spec.ts', + name: "sandbox", + testDir: path.resolve(__dirname, "../sandbox"), + testMatch: "*.spec.ts", }, ], }; diff --git a/tests/constants/api-constants.ts b/tests/constants/api-constants.ts new file mode 100644 index 00000000..f1b11e0c --- /dev/null +++ b/tests/constants/api-constants.ts @@ -0,0 +1,9 @@ +export const SUPPLIER_LETTERS = "letters"; +export const SUPPLIER_API_URL_SANDBOX = + "https://internal-dev-sandbox.api.service.nhs.uk/nhs-notify-supplier"; +export const AWS_REGION = "eu-west-2"; +export const envName = process.env.PR_NUMBER ?? "main"; +export const API_NAME = `nhs-${envName}-supapi`; +export const LETTERSTABLENAME = `nhs-${envName}-supapi-letters`; +export const SUPPLIERID = "TestSupplier1"; +export const MI_ENDPOINT = "mi"; diff --git a/tests/constants/api_constants.ts b/tests/constants/api_constants.ts deleted file mode 100644 index 53e1d703..00000000 --- a/tests/constants/api_constants.ts +++ /dev/null @@ -1,8 +0,0 @@ -export const SUPPLIER_LETTERS = 'letters'; -export const SUPPLIER_API_URL_SANDBOX = 'https://internal-dev-sandbox.api.service.nhs.uk/nhs-notify-supplier'; -export const AWS_REGION = 'eu-west-2'; -export const envName = process.env.PR_NUMBER ?? 'main'; -export const API_NAME = `nhs-${envName}-supapi`; -export const LETTERSTABLENAME = `nhs-${envName}-supapi-letters`; -export const SUPPLIERID = 'TestSupplier1'; -export const MI_ENDPOINT = 'mi'; diff --git a/tests/constants/request-headers.ts b/tests/constants/request-headers.ts new file mode 100644 index 00000000..720b5db8 --- /dev/null +++ b/tests/constants/request-headers.ts @@ -0,0 +1,58 @@ +import { randomUUID } from "node:crypto"; +import { SUPPLIERID } from "./api-constants"; + +export const sandBoxHeader: RequestSandBoxHeaders = { + "X-Request-ID": randomUUID(), + "Content-Type": "application/vnd.api+json", + "X-Correlation-ID": randomUUID(), +}; + +export interface RequestHeaders { + headerauth1: string; + "NHSD-Supplier-ID": string; + "NHSD-Correlation-ID": string; + [key: string]: string; +} + +export interface RequestSandBoxHeaders { + "X-Request-ID": string; + "Content-Type": string; + "X-Correlation-ID": string; + [key: string]: string; +} + +export function createInvalidRequestHeaders(): RequestHeaders { + return { + headerauth1: "", + "NHSD-Supplier-ID": SUPPLIERID, + "NHSD-Correlation-ID": "1234", + "X-Request-ID": "requestId1", + }; +} + +export function createHeaderWithNoCorrelationId(): RequestHeaders { + return { + headerauth1: process.env.HEADERAUTH || "", + "NHSD-Supplier-ID": SUPPLIERID, + "NHSD-Correlation-ID": "", + "X-Request-ID": "requestId1", + }; +} + +export function createValidRequestHeaders(): RequestHeaders { + return { + headerauth1: process.env.HEADERAUTH || "", + "NHSD-Supplier-ID": SUPPLIERID, + "NHSD-Correlation-ID": "12345", + "X-Request-ID": "requestId1", + }; +} + +export function createHeaderWithNoRequestId(): RequestHeaders { + return { + headerauth1: process.env.HEADERAUTH || "", + "NHSD-Supplier-ID": SUPPLIERID, + "NHSD-Correlation-ID": "1234", + "X-Request-ID": "", + }; +} diff --git a/tests/constants/request_headers.ts b/tests/constants/request_headers.ts deleted file mode 100644 index 5440f818..00000000 --- a/tests/constants/request_headers.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { randomUUID } from 'node:crypto'; -import { SUPPLIERID } from './api_constants'; - -export const sandBoxHeader: RequestSandBoxHeaders = { - 'X-Request-ID': randomUUID(), - 'Content-Type': 'application/vnd.api+json', - 'X-Correlation-ID': randomUUID(), -}; - -export interface RequestHeaders { - headerauth1: string; - 'NHSD-Supplier-ID': string; - 'NHSD-Correlation-ID': string; - [key: string]: string; -} - -export interface RequestSandBoxHeaders { - 'X-Request-ID': string; - 'Content-Type': string; - 'X-Correlation-ID': string; - [key: string]: string; -} - -export function createInvalidRequestHeaders(): RequestHeaders { - let requestHeaders: RequestHeaders; - requestHeaders = { - headerauth1: '', - 'NHSD-Supplier-ID': SUPPLIERID, - 'NHSD-Correlation-ID': '1234', - 'X-Request-ID': 'requestId1' - }; - return requestHeaders; -} - -export function createHeaderWithNoCorrelationId(): RequestHeaders { - let requestHeaders: RequestHeaders; - requestHeaders = { - headerauth1: process.env.HEADERAUTH || '', - 'NHSD-Supplier-ID': SUPPLIERID, - 'NHSD-Correlation-ID': '', - 'X-Request-ID': 'requestId1' - }; - return requestHeaders; -} - -export function createValidRequestHeaders(): RequestHeaders{ - let requestHeaders: RequestHeaders; - requestHeaders = { - headerauth1: process.env.HEADERAUTH || '', - 'NHSD-Supplier-ID': SUPPLIERID, - 'NHSD-Correlation-ID': '12345', - 'X-Request-ID': 'requestId1' - }; - return requestHeaders; -} - -export function createHeaderWithNoRequestId(): RequestHeaders { - let requestHeaders: RequestHeaders; - requestHeaders = { - headerauth1: process.env.HEADERAUTH || '', - 'NHSD-Supplier-ID': SUPPLIERID, - 'NHSD-Correlation-ID': '1234', - 'X-Request-ID': '' - }; - return requestHeaders; -} diff --git a/tests/helpers/awsGatewayHelper.ts b/tests/helpers/aws-gateway-helper.ts similarity index 73% rename from tests/helpers/awsGatewayHelper.ts rename to tests/helpers/aws-gateway-helper.ts index 9e0db8bb..320a17bf 100644 --- a/tests/helpers/awsGatewayHelper.ts +++ b/tests/helpers/aws-gateway-helper.ts @@ -1,8 +1,10 @@ -import { APIGatewayClient, GetRestApisCommand } from "@aws-sdk/client-api-gateway"; -import { AWS_REGION, API_NAME } from "../constants/api_constants"; +import { + APIGatewayClient, + GetRestApisCommand, +} from "@aws-sdk/client-api-gateway"; +import { API_NAME, AWS_REGION } from "../constants/api-constants"; export async function getRestApiGatewayBaseUrl(): Promise { - const region = AWS_REGION; const client = new APIGatewayClient({ region }); diff --git a/tests/helpers/common-types.ts b/tests/helpers/common-types.ts new file mode 100644 index 00000000..1f781002 --- /dev/null +++ b/tests/helpers/common-types.ts @@ -0,0 +1,116 @@ +import { SUPPLIERID } from "../constants/api-constants"; + +export type ErrorLink = { + about: string; +}; + +type ErrorResponse = { + id: string; + code: string; + links: ErrorLink; + status: string; + title: string; + detail: string; +}; + +export type ErrorMessageBody = { + errors: ErrorResponse[]; +}; + +export function error404ResponseBody(): ErrorMessageBody { + return { + errors: [ + { + id: "12345", + code: "NOTIFY_LETTER_NOT_FOUND", + links: { + about: + "https://digital.nhs.uk/developer/api-catalogue/nhs-notify-supplier", + }, + status: "404", + title: "Not found", + detail: "No resource found with that ID", + }, + ], + }; +} + +export function error500ResponseBody(id: string): ErrorMessageBody { + return { + errors: [ + { + id: "12345", + code: "NOTIFY_INTERNAL_SERVER_ERROR", + links: { + about: + "https://digital.nhs.uk/developer/api-catalogue/nhs-notify-supplier", + }, + status: "500", + title: "Internal server error", + detail: `Letter with id ${id} not found for supplier ${SUPPLIERID}`, + }, + ], + }; +} + +export function error400ResponseBody(): ErrorMessageBody { + return { + errors: [ + { + id: "12345", + code: "NOTIFY_INVALID_REQUEST", + links: { + about: + "https://digital.nhs.uk/developer/api-catalogue/nhs-notify-supplier", + }, + status: "400", + title: "Invalid request", + detail: "The request body is invalid", + }, + ], + }; +} + +export function requestId500Error(): ErrorMessageBody { + return { + errors: [ + { + id: "1234", + code: "NOTIFY_INTERNAL_SERVER_ERROR", + links: { + about: + "https://digital.nhs.uk/developer/api-catalogue/nhs-notify-supplier", + }, + status: "500", + title: "Internal server error", + detail: "The request headers don't contain the x-request-id", + }, + ], + }; +} + +export function error403ResponseBody(): { Message: string } { + return { + Message: + "User is not authorized to access this resource with an explicit deny in an identity-based policy", + }; +} + +export function error400InvalidDate(): ErrorMessageBody { + return { + errors: [ + { + id: "12345", + code: "NOTIFY_INVALID_REQUEST", + links: { + about: + "https://digital.nhs.uk/developer/api-catalogue/nhs-notify-supplier", + }, + status: "400", + title: "Invalid request", + detail: + "Timestamps should be UTC date/times in ISO8601 format, with a Z suffix", + }, + ], + }; +} diff --git a/tests/helpers/commonTypes.ts b/tests/helpers/commonTypes.ts deleted file mode 100644 index c76ef4f7..00000000 --- a/tests/helpers/commonTypes.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { error } from "console"; -import { SUPPLIERID } from "../constants/api_constants"; - -export type ErrorLink = { - about: string; -}; - -type ErrorResponse = { - id: string; - code: string; - links: ErrorLink; - status: string; - title: string; - detail: string; -}; - -export type ErrorMessageBody = { - errors: ErrorResponse[]; -}; - -export function error404ResponseBody () : ErrorMessageBody{ - let responseBody: ErrorMessageBody; - responseBody = { - errors: [ - { - id: "12345", - code: "NOTIFY_LETTER_NOT_FOUND", - links: { - "about": "https://digital.nhs.uk/developer/api-catalogue/nhs-notify-supplier" - }, - status: "404", - title: "Not found", - detail: "No resource found with that ID" - } - ] - }; - return responseBody; -}; - -export function error500ResponseBody (id: string) : ErrorMessageBody{ - let responseBody: ErrorMessageBody; - responseBody = { - errors: [ - { - id: "12345", - code: "NOTIFY_INTERNAL_SERVER_ERROR", - links: { - "about": "https://digital.nhs.uk/developer/api-catalogue/nhs-notify-supplier" - }, - status: "500", - title: "Internal server error", - detail: `Letter with id ${id} not found for supplier ${SUPPLIERID}` - } - ] - }; - return responseBody; -} - - -export function error400ResponseBody () : ErrorMessageBody{ - - let responseBody: ErrorMessageBody; - responseBody = { - errors: [ - { - id: '12345', - code: "NOTIFY_INVALID_REQUEST", - links: { - "about": "https://digital.nhs.uk/developer/api-catalogue/nhs-notify-supplier" - }, - status: "400", - title: "Invalid request", - detail: "The request body is invalid" - } - ] - }; - return responseBody; -} - -export function requestId500Error () : ErrorMessageBody{ - let responseBody: ErrorMessageBody; - - responseBody = { - errors: [ - { - id: "1234", - code: "NOTIFY_INTERNAL_SERVER_ERROR", - links: { - "about": "https://digital.nhs.uk/developer/api-catalogue/nhs-notify-supplier" - }, - status: "500", - title: "Internal server error", - detail: "The request headers don't contain the x-request-id" - } - ] - }; - return responseBody; -} - -export function error403ResponseBody () : { Message: string }{ - return { - Message : 'User is not authorized to access this resource with an explicit deny in an identity-based policy' - }; -} - -export function error400InvalidDate () : ErrorMessageBody{ - - let responseBody: ErrorMessageBody; - responseBody = { - errors: [ - { - id: '12345', - code: "NOTIFY_INVALID_REQUEST", - links: { - "about": "https://digital.nhs.uk/developer/api-catalogue/nhs-notify-supplier" - }, - status: "400", - title: "Invalid request", - detail: "Timestamps should be UTC date/times in ISO8601 format, with a Z suffix" - } - ] - }; - return responseBody; -} diff --git a/tests/helpers/generate-fetch-test-data.ts b/tests/helpers/generate-fetch-test-data.ts new file mode 100644 index 00000000..0bbb1336 --- /dev/null +++ b/tests/helpers/generate-fetch-test-data.ts @@ -0,0 +1,80 @@ +import { DynamoDBClient } from "@aws-sdk/client-dynamodb"; +import { + DeleteCommand, + DynamoDBDocumentClient, + QueryCommand, +} from "@aws-sdk/lib-dynamodb"; +import { + LETTERSTABLENAME, + SUPPLIERID, + envName, +} from "../constants/api-constants"; +import { runCreateLetter } from "./pnpm-helpers"; + +const ddb = new DynamoDBClient({}); +const docClient = DynamoDBDocumentClient.from(ddb); + +export interface SupplierApiLetters { + supplierId: string; + specificationId: string; + supplierStatus: string; + createdAt: string; + supplierStatusSk: string; + updatedAt: string; + groupId: string; + reasonCode: string; + id: string; + url: string; + ttl: string; + reasonText: string; + status: string; +} + +export async function createTestData(supplierId: string): Promise { + await runCreateLetter({ + filter: "nhs-notify-supplier-api-data-generator", + supplierId, + environment: envName, + awsAccountId: "820178564574", + groupId: "TestGroupID", + specificationId: "TestSpecificationID", + status: "PENDING", + count: 1, + }); +} + +export const getLettersBySupplier = async ( + supplierId: string, + status: string, + limit: number, +) => { + const supplierStatus = `${supplierId}#${status}`; + const params = { + TableName: LETTERSTABLENAME, + IndexName: "supplierStatus-index", + KeyConditionExpression: "supplierStatus = :supplierStatus", + ProjectionExpression: + "id, specificationId, groupId, reasonCode, reasonText", + ExpressionAttributeValues: { + ":supplierStatus": supplierStatus, + }, + Limit: limit, + }; + + const { Items } = await docClient.send(new QueryCommand(params)); + if (!Items || Items.length === 0) { + throw new Error(`Unexpectedly found no data found for ${supplierId}.`); + } + return Items as SupplierApiLetters[]; +}; + +export const deleteLettersBySupplier = async (id: string) => { + const resp = await docClient.send( + new DeleteCommand({ + TableName: LETTERSTABLENAME, + Key: { supplierId: SUPPLIERID, id }, + ReturnValues: "ALL_OLD", + }), + ); + return resp.Attributes; +}; diff --git a/tests/helpers/generate_fetch_testData.ts b/tests/helpers/generate_fetch_testData.ts deleted file mode 100644 index 2fd63aa6..00000000 --- a/tests/helpers/generate_fetch_testData.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { envName, LETTERSTABLENAME, SUPPLIERID } from "../constants/api_constants"; -import { runCreateLetter } from "./pnpmHelpers"; -import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; -import { DeleteCommand, DynamoDBDocumentClient, QueryCommand } from '@aws-sdk/lib-dynamodb'; - -const ddb = new DynamoDBClient({}); -const docClient = DynamoDBDocumentClient.from(ddb); - -export interface SupplierApiLetters { - supplierId: string, - specificationId: string, - supplierStatus: string, - createdAt: string, - supplierStatusSk: string, - updatedAt: string, - groupId: string, - reasonCode: string, - id: string, - url: string, - ttl: string, - reasonText: string, - status: string -}; - -export async function createTestData(supplierId: string): Promise { - - await runCreateLetter({ - filter: 'nhs-notify-supplier-api-data-generator', - supplierId: supplierId, - environment: envName, - awsAccountId: '820178564574', - groupId: 'TestGroupID', - specificationId: 'TestSpecificationID', - status: 'PENDING', - count: 1, - }); -}; - -export const getLettersBySupplier = async(supplierId: string, status: string, limit: number) => { - - const supplierStatus = `${supplierId}#${status}`; - const params = { - TableName: LETTERSTABLENAME, - IndexName: 'supplierStatus-index', - KeyConditionExpression: 'supplierStatus = :supplierStatus', - ProjectionExpression: - 'id, specificationId, groupId, reasonCode, reasonText', - ExpressionAttributeValues: { - ':supplierStatus': supplierStatus, - }, - Limit: limit, - }; - - const { Items } = await docClient.send(new QueryCommand(params)); - if (!Items || Items.length === 0) { - throw new Error(`Unexpectedly found no data found for ${supplierId}.`); - } - return Items as SupplierApiLetters[]; -}; - -export const deleteLettersBySupplier = async(id: string) => { - const resp = await docClient.send( - new DeleteCommand({ - TableName: LETTERSTABLENAME, - Key: { supplierId: SUPPLIERID, id }, - ReturnValues: 'ALL_OLD', - }) - ) - return resp.Attributes; -} diff --git a/tests/helpers/pnpmHelpers.ts b/tests/helpers/pnpm-helpers.ts similarity index 50% rename from tests/helpers/pnpmHelpers.ts rename to tests/helpers/pnpm-helpers.ts index e0e748c0..c423b4b8 100644 --- a/tests/helpers/pnpmHelpers.ts +++ b/tests/helpers/pnpm-helpers.ts @@ -1,5 +1,5 @@ -import { spawn } from 'child_process'; -import path from 'path'; +import { spawn } from "node:child_process"; +import path from "node:path"; /** * Runs the "create-letter" CLI command via npm. @@ -17,60 +17,61 @@ export async function runCreateLetter(options: { count: number; }) { const { - filter, - supplierId, - environment, awsAccountId, + count, + environment, + filter, groupId, specificationId, status, - count, + supplierId, } = options; - const workspaceRoot = path.resolve(__dirname, '../../scripts/test-data'); - const cmd = process.platform === 'win32' ? 'npm.cmd' : 'npm'; - const root = path.resolve(workspaceRoot); - console.log('Workspace root:', root); + const workspaceRoot = path.resolve(__dirname, "../../scripts/test-data"); + const cmd = process.platform === "win32" ? "npm.cmd" : "npm"; + const root = path.resolve(workspaceRoot); + console.log("Workspace root:", root); // Build arguments array const args = [ - '-w', String(filter), - // '--filter', String(filter), - 'run', - 'cli', - '--', - 'create-letter', - '--supplier-id', + "-w", + String(filter), + // '--filter', String(filter), + "run", + "cli", + "--", + "create-letter", + "--supplier-id", supplierId, - '--environment', + "--environment", environment, - '--awsAccountId', + "--awsAccountId", awsAccountId, - '--group-id', + "--group-id", groupId, - '--specification-id', + "--specification-id", specificationId, - '--status', + "--status", status, - '--count', + "--count", String(count), ]; - console.log('🚀 Running:', [cmd, ...args].join(' ')); + console.log("🚀 Running:", [cmd, ...args].join(" ")); await new Promise((resolve, reject) => { - let output = ''; const child = spawn(cmd, args, { - stdio: 'inherit', + stdio: "inherit", cwd: root, shell: false, }); - child.stdout?.on('id', (id) => { + child.stdout?.on("id", (id) => { const text = id.toString(); - output += text; process.stdout.write(text); }); - child.on('close', (code) => code === 0 ? resolve() : reject(new Error(`pnpm exited with ${code}`))); - child.on('error', reject); + child.on("close", (code) => + code === 0 ? resolve() : reject(new Error(`pnpm exited with ${code}`)), + ); + child.on("error", reject); }); } diff --git a/tests/helpers/validate-json-schema.ts b/tests/helpers/validate-json-schema.ts new file mode 100644 index 00000000..cc509447 --- /dev/null +++ b/tests/helpers/validate-json-schema.ts @@ -0,0 +1,52 @@ +import OpenAPIResponseValidator from "openapi-response-validator"; +import path from "node:path"; +import fs from "node:fs"; + +const filePath = path.resolve(__dirname, "../../build/notify-supplier.json"); +const openapiDoc = JSON.parse(fs.readFileSync(filePath, "utf8")); + +type ValidationResult = ReturnType< + OpenAPIResponseValidator["validateResponse"] +>; +/** + * Validate a response against the OpenAPI spec for a given endpoint and method. + * + * @param method - HTTP method in lowercase ('get', 'post', etc.) + * @param responsePath - API path (must match OpenAPI path exactly, e.g., '/items') + * @param status - HTTP status code returned by the API + * @param body - Response body to validate + * @returns ValidationResult or undefined if valid + */ +export function validateApiResponse( + method: string, + responsePath: string, + status: number, + body: any, +): ValidationResult { + const pathItem = (openapiDoc.paths as Record)[responsePath]; + + if (!pathItem) + throw new Error(`Path ${responsePath} not found in OpenAPI spec`); + + const operation = pathItem[method]; + if (!operation) + throw new Error( + `Method ${method.toUpperCase()} not defined for ${responsePath}`, + ); + + // Find the response schema for the actual status code + const responseSchema = + operation.responses[status] || operation.responses.default; + if (!responseSchema) { + throw new Error( + `No schema defined for status ${status} at ${method.toUpperCase()} ${responsePath}`, + ); + } + + const validator = new OpenAPIResponseValidator({ + responses: { [status]: responseSchema }, + components: openapiDoc.components, + }); + + return validator.validateResponse(status, body); +} diff --git a/tests/helpers/validateJsonSchema.ts b/tests/helpers/validateJsonSchema.ts deleted file mode 100644 index e8e70cce..00000000 --- a/tests/helpers/validateJsonSchema.ts +++ /dev/null @@ -1,42 +0,0 @@ -import OpenAPIResponseValidator from "openapi-response-validator"; -import path from 'path'; - -const paths = path.resolve(__dirname, '../../build/notify-supplier.json'); -const openapiDoc = require(paths); - -type ValidationResult = ReturnType; -/** - * Validate a response against the OpenAPI spec for a given endpoint and method. - * - * @param method - HTTP method in lowercase ('get', 'post', etc.) - * @param path - API path (must match OpenAPI path exactly, e.g., '/items') - * @param status - HTTP status code returned by the API - * @param body - Response body to validate - * @returns ValidationResult or undefined if valid - */ -export function validateApiResponse( - method: string, - path: string, - status: number, - body: any -): ValidationResult { - const pathItem = (openapiDoc.paths as Record)[path]; - - if (!pathItem) throw new Error(`Path ${path} not found in OpenAPI spec`); - - const operation = pathItem[method]; - if (!operation) throw new Error(`Method ${method.toUpperCase()} not defined for ${path}`); - - // Find the response schema for the actual status code - const responseSchema = operation.responses[status] || operation.responses["default"]; - if (!responseSchema) { - throw new Error(`No schema defined for status ${status} at ${method.toUpperCase()} ${path}`); - } - - const validator = new OpenAPIResponseValidator({ - responses: { [status]: responseSchema }, - components: openapiDoc.components, - }); - - return validator.validateResponse(status, body); -} diff --git a/tests/mtls/mtls_test.spec.ts b/tests/mtls/mtls-test.spec.ts similarity index 68% rename from tests/mtls/mtls_test.spec.ts rename to tests/mtls/mtls-test.spec.ts index b0990efd..256b3c96 100644 --- a/tests/mtls/mtls_test.spec.ts +++ b/tests/mtls/mtls-test.spec.ts @@ -1,31 +1,29 @@ -import { test, expect, request, APIRequestContext } from '@playwright/test'; +import { APIRequestContext, expect, request, test } from "@playwright/test"; // Assume you have your constants in a config file -const PROXY_URL = process.env.PROXY_URL; - -test('should fail when connecting without client certificate', async () => { +const { PROXY_URL } = process.env; +test("should fail when connecting without client certificate", async () => { let apiContext: APIRequestContext | null = null; try { apiContext = await request.newContext(); const response = await apiContext.get(PROXY_URL!, { - headers: { "X-Client-Id": "hello" } + headers: { "X-Client-Id": "hello" }, }); // Check if request succeeded or failed if (response.ok()) { throw new Error( - `Expected connection failure, but got success with status ${response.status()}` + `Expected connection failure, but got success with status ${response.status()}`, ); } // Assert on the actual error code returned by the gateway // For mTLS, often 401, 403, or 502 depending on infra config expect(response.ok()).toBeFalsy(); - - } catch (err: any) { + } catch (error: any) { // If the request truly fails at the TLS layer, Playwright will throw instead - expect(err.message).toMatch(/SSL|certificate|ECONNRESET|socket/i); + expect(error.message).toMatch(/SSL|certificate|ECONNRESET|socket/i); } finally { if (apiContext) { await apiContext.dispose(); diff --git a/tests/sandbox/get-letter-status.spec.ts b/tests/sandbox/get-letter-status.spec.ts new file mode 100644 index 00000000..dd2af877 --- /dev/null +++ b/tests/sandbox/get-letter-status.spec.ts @@ -0,0 +1,29 @@ +import { expect, test } from "@playwright/test"; +import { + SUPPLIER_API_URL_SANDBOX, + SUPPLIER_LETTERS, +} from "../constants/api-constants"; +import { apiSandboxGetLetterStatusTestData } from "./testCases/get-letter-status-test-cases"; + +test.describe("Sandbox Tests To Get Letter Status", () => { + for (const { + expectedResponse, + header, + id, + testCase, + } of apiSandboxGetLetterStatusTestData) { + test(`Get Letter Status endpoint returns ${testCase}`, async ({ + request, + }) => { + const response = await request.get( + `${SUPPLIER_API_URL_SANDBOX}/${SUPPLIER_LETTERS}/${id}`, + { + headers: header, + }, + ); + + const res = await response.json(); + expect(res).toEqual(expectedResponse); + }); + } +}); diff --git a/tests/sandbox/get-list-of-letters.spec.ts b/tests/sandbox/get-list-of-letters.spec.ts new file mode 100644 index 00000000..624a2227 --- /dev/null +++ b/tests/sandbox/get-list-of-letters.spec.ts @@ -0,0 +1,35 @@ +import { expect, test } from "@playwright/test"; +import { + SUPPLIER_API_URL_SANDBOX, + SUPPLIER_LETTERS, +} from "../constants/api-constants"; +import { apiSandboxGetLettersRequestTestData } from "./testCases/get-list-of-letters-test-cases"; + +test.describe("Sandbox Tests To Get List Of Pending Letters ", () => { + for (const { + expectedResponse, + expectedStatus, + header, + limit, + testCase, + } of apiSandboxGetLettersRequestTestData) { + test(`Get /Letters endpoint returns ${testCase}`, async ({ request }) => { + const response = await request.get( + `${SUPPLIER_API_URL_SANDBOX}/${SUPPLIER_LETTERS}`, + { + headers: header, + params: { + limit, + }, + }, + ); + + const res = await response.json(); + await expect(response.status()).toBe(expectedStatus); + expect(res).toEqual(expectedResponse); + if (response.status() === 200) { + expect(res.data.length.toString()).toEqual(limit); + } + }); + } +}); diff --git a/tests/sandbox/getLetterStatus.spec.ts b/tests/sandbox/getLetterStatus.spec.ts deleted file mode 100644 index 3d10845d..00000000 --- a/tests/sandbox/getLetterStatus.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { test, expect, request } from '@playwright/test'; -import { SUPPLIER_API_URL_SANDBOX, SUPPLIER_LETTERS} from '../constants/api_constants'; -import { apiSandboxGetLetterStatusTestData } from './testCases/getLetterStatus_testCases'; - - -test.describe('Sandbox Tests To Get Letter Status', () => -{ - apiSandboxGetLetterStatusTestData.forEach(({ testCase, header, id, expectedStatus, expectedResponse }) => { - test(`Get Letter Status endpoint returns ${testCase}`, async ({ request }) => { - - const response = await request.get(`${SUPPLIER_API_URL_SANDBOX}/${SUPPLIER_LETTERS}/${id}` ,{ - headers: header - }, - ); - - const res = await response.json(); - expect(res).toEqual(expectedResponse); - - }); - }); -}); diff --git a/tests/sandbox/getListOfLetters.spec.ts b/tests/sandbox/getListOfLetters.spec.ts deleted file mode 100644 index e2bca376..00000000 --- a/tests/sandbox/getListOfLetters.spec.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { test, expect, request } from '@playwright/test'; -import { SUPPLIER_API_URL_SANDBOX, SUPPLIER_LETTERS} from '../constants/api_constants'; -import { apiSandboxGetLettersRequestTestData } from './testCases/getListOfLetters_testCases'; - - -test.describe('Sandbox Tests To Get List Of Pending Letters ', () => -{ - apiSandboxGetLettersRequestTestData.forEach(({ testCase, header, limit, expectedStatus, expectedResponse }) => { - test(`Get /Letters endpoint returns ${testCase}`, async ({ request }) => { - - const response = await request.get(`${SUPPLIER_API_URL_SANDBOX}/${SUPPLIER_LETTERS}` ,{ - headers: header, - params:{ - limit: limit - }, - }, - ); - - const res = await response.json(); - await expect(response.status()).toBe(expectedStatus); - expect(res).toEqual(expectedResponse); - if (response.status() === 200){ - expect(res.data.length.toString()).toEqual(limit); - } - }); - }); -}); diff --git a/tests/sandbox/testCases/get-letter-status-test-cases.ts b/tests/sandbox/testCases/get-letter-status-test-cases.ts new file mode 100644 index 00000000..36c9c327 --- /dev/null +++ b/tests/sandbox/testCases/get-letter-status-test-cases.ts @@ -0,0 +1,140 @@ +import { + RequestSandBoxHeaders, + sandBoxHeader, +} from "../../constants/request-headers"; +import { NoRequestIdHeaders } from "./get-list-of-letters-test-cases"; + +type ApiSandboxGetLetterStatusTestCase = { + testCase: string; + id: string; + header?: RequestSandBoxHeaders | NoRequestIdHeaders; + expectedStatus: number; + expectedResponse?: + | GetLetterStatusResponse + | GetLetterStatusErrorResponse + | GetRejectedLetterResponse; +}; + +export type GetLetterStatusResponse = { + data: GetLetterData; +}; + +export type GetRejectedLetterResponse = { + data: RejectedLetterData; +}; + +type GetLetterData = { + type: string; + id: string; + attributes: { + specificationId: string; + groupId: string; + status: string; + }; +}; + +type RejectedLetterData = { + type: string; + id: string; + attributes: { + specificationId: string; + groupId: string; + status: string; + reasonCode: string; + reasonText: string; + }; +}; + +export type GetLetterStatusErrorResponse = { + errors: ApiErrors[]; +}; + +type ApiErrors = { + code: string; + detail: string; + id: string; + links: { + about: string; + }; + status: string; + title: string; +}; + +export const apiSandboxGetLetterStatusTestData: ApiSandboxGetLetterStatusTestCase[] = + [ + { + testCase: "200 response and ACCEPTED record is fetched successfully", + id: "2AL5eYSWGzCHlGmzNxuqVusPxDg", + header: sandBoxHeader, + expectedStatus: 200, + expectedResponse: { + data: { + id: "2AL5eYSWGzCHlGmzNxuqVusPxDg", + type: "Letter", + attributes: { + specificationId: "2WL5eYSWGzCHlGmzNxuqVusPxDg", + groupId: "c5d93f917f5546d08beccf770a915d96", + status: "ACCEPTED", + }, + }, + }, + }, + { + testCase: "200 response and REJECTED record is fetched successfully", + id: "2WL5eYSWGzCHlGmzNxuqVusPxDg", + header: sandBoxHeader, + expectedStatus: 200, + expectedResponse: { + data: { + id: "2WL5eYSWGzCHlGmzNxuqVusPxDg", + type: "Letter", + attributes: { + specificationId: "2WL5eYSWGzCHlGmzNxuqVusPxDg", + groupId: "c5d93f917f5546d08beccf770a915d96", + status: "REJECTED", + reasonCode: "R01", + reasonText: "failed validation", + }, + }, + }, + }, + { + testCase: "200 response and CANCELLED record is fetched successfully", + id: "2XL5eYSWGzCHlGmzNxuqVusPxDg", + header: sandBoxHeader, + expectedStatus: 200, + expectedResponse: { + data: { + id: "2XL5eYSWGzCHlGmzNxuqVusPxDg", + type: "Letter", + attributes: { + specificationId: "2WL5eYSWGzCHlGmzNxuqVusPxDg", + groupId: "c5d93f917f5546d08beccf770a915d96", + status: "CANCELLED", + reasonCode: "R01", + }, + }, + }, + }, + { + testCase: "404 response when no record is found for the given id", + id: "24L5eYSWGzCHlGmzNxuqVusP", + header: sandBoxHeader, + expectedStatus: 200, + expectedResponse: { + errors: [ + { + status: "404", + title: "Resource not found", + code: "NOTIFY_RESOURCE_NOT_FOUND", + detail: "No resource found with that ID", + id: "rrt-1931948104716186917-c-geu2-10664-3111479-3.0", + links: { + about: + "https://digital.nhs.uk/developer/api-catalogue/nhs-notify-supplier", + }, + }, + ], + }, + }, + ]; diff --git a/tests/sandbox/testCases/get-list-of-letters-test-cases.ts b/tests/sandbox/testCases/get-list-of-letters-test-cases.ts new file mode 100644 index 00000000..b41534b3 --- /dev/null +++ b/tests/sandbox/testCases/get-list-of-letters-test-cases.ts @@ -0,0 +1,101 @@ +import { randomUUID } from "node:crypto"; +import { + RequestSandBoxHeaders, + sandBoxHeader, +} from "../../constants/request-headers"; + +type ApiSandboxGetLettersRequestTestCase = { + testCase: string; + limit: string; + header?: RequestSandBoxHeaders | NoRequestIdHeaders; + expectedStatus: number; + expectedResponse?: SandboxSuccessResponse | SandboxErrorResponse; +}; + +export type SandboxSuccessResponse = { + data: ApiData[]; +}; + +type ApiData = { + type: string; + id: string; + attributes: { + specificationId: string; + groupId: string; + status: string; + }; +}; + +export type SandboxErrorResponse = { + message: string; + errors: ApiErrors[]; +}; + +type ApiErrors = { + path: string; + message: string; + errorCode: string; +}; + +export type NoRequestIdHeaders = Omit; + +const NoRequestIdHeaders: NoRequestIdHeaders = { + "Content-Type": "application/vnd.api+json", + "X-Correlation-ID": randomUUID(), +}; + +export const apiSandboxGetLettersRequestTestData: ApiSandboxGetLettersRequestTestCase[] = + [ + { + testCase: "200 response if record is fetched successfully", + limit: "1", + header: sandBoxHeader, + expectedStatus: 200, + expectedResponse: { + data: [ + { + id: "fcfd849ceec940e8832b41f4fc161e09", + type: "Letter", + attributes: { + specificationId: "2WL5eYSWGzCHlGmzNxuqVusPxDg", + groupId: "c5d93f917f5546d08beccf770a915d96", + status: "PENDING", + }, + }, + ], + }, + }, + { + testCase: "400 response if invalid limit is passed", + limit: "XX", + header: sandBoxHeader, + expectedStatus: 400, + expectedResponse: { + message: "request.query.limit should be number", + errors: [ + { + path: ".query.limit", + message: "should be number", + errorCode: "type.openapi.validation", + }, + ], + }, + }, + + { + testCase: "400 response if invalid headers are passed", + limit: "2", + header: NoRequestIdHeaders, + expectedStatus: 400, + expectedResponse: { + message: "request.headers should have required property 'x-request-id'", + errors: [ + { + path: ".headers.x-request-id", + message: "should have required property 'x-request-id'", + errorCode: "required.openapi.validation", + }, + ], + }, + }, + ]; diff --git a/tests/sandbox/testCases/getLetterStatus_testCases.ts b/tests/sandbox/testCases/getLetterStatus_testCases.ts deleted file mode 100644 index 6e52bf4c..00000000 --- a/tests/sandbox/testCases/getLetterStatus_testCases.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { randomUUID } from "node:crypto"; -import { RequestSandBoxHeaders, sandBoxHeader} from "../../constants/request_headers"; -import { NoRequestIdHeaders, SandboxErrorResponse, SandboxSuccessResponse } from "./getListOfLetters_testCases"; - -type ApiSandboxGetLetterStatusTestCase = { - testCase: string; - id: string, - header?: RequestSandBoxHeaders | NoRequestIdHeaders; - expectedStatus: number; - expectedResponse?: GetLetterStatusResponse | GetLetterStatusErrorResponse | GetRejectedLetterResponse; -}; - -export type GetLetterStatusResponse = { - data: GetLetterData; -}; - -export type GetRejectedLetterResponse = { - data: RejectedLetterData; -}; - -type GetLetterData = -{ - type: string; - id: string; - attributes: { - specificationId: string; - groupId: string; - status: string; - } -}; - -type RejectedLetterData = -{ - type: string; - id: string; - attributes: { - specificationId: string; - groupId: string; - status: string; - reasonCode: string; - reasonText: string; - } -}; - -export type GetLetterStatusErrorResponse = { - errors: ApiErrors[]; -}; - -type ApiErrors = { - code: string; - detail: string; - id: string; - links:{ - about: string; - }, - status: string; - title: string; -}; - -export const apiSandboxGetLetterStatusTestData: ApiSandboxGetLetterStatusTestCase[] = [ -{ - testCase: '200 response and ACCEPTED record is fetched successfully', - id: '2AL5eYSWGzCHlGmzNxuqVusPxDg', - header: sandBoxHeader, - expectedStatus: 200, - expectedResponse: { - data: - { - id: '2AL5eYSWGzCHlGmzNxuqVusPxDg', - type: 'Letter', - attributes: { - specificationId: '2WL5eYSWGzCHlGmzNxuqVusPxDg', - groupId: 'c5d93f917f5546d08beccf770a915d96', - status: 'ACCEPTED', - }, - } - } -}, -{ - testCase: '200 response and REJECTED record is fetched successfully', - id: '2WL5eYSWGzCHlGmzNxuqVusPxDg', - header: sandBoxHeader, - expectedStatus: 200, - expectedResponse: { - data: - { - id: '2WL5eYSWGzCHlGmzNxuqVusPxDg', - type: 'Letter', - attributes: { - specificationId: '2WL5eYSWGzCHlGmzNxuqVusPxDg', - groupId: 'c5d93f917f5546d08beccf770a915d96', - status: 'REJECTED', - reasonCode: 'R01', - reasonText: "failed validation", - }, - } - } -}, -{ - testCase: '200 response and CANCELLED record is fetched successfully', - id: '2XL5eYSWGzCHlGmzNxuqVusPxDg', - header: sandBoxHeader, - expectedStatus: 200, - expectedResponse: { - data: - { - id: '2XL5eYSWGzCHlGmzNxuqVusPxDg', - type: 'Letter', - attributes: { - specificationId: '2WL5eYSWGzCHlGmzNxuqVusPxDg', - groupId: 'c5d93f917f5546d08beccf770a915d96', - status: 'CANCELLED', - reasonCode: 'R01' - }, - } - } -}, -{ - testCase: '404 response when no record is found for the given id', - id: '24L5eYSWGzCHlGmzNxuqVusP', - header: sandBoxHeader, - expectedStatus: 200, - expectedResponse: { - errors: [ - { - status: '404', - title: 'Resource not found', - code:'NOTIFY_RESOURCE_NOT_FOUND', - detail: 'No resource found with that ID', - id: 'rrt-1931948104716186917-c-geu2-10664-3111479-3.0', - links: { - about: 'https://digital.nhs.uk/developer/api-catalogue/nhs-notify-supplier' - } - } - ] - } -}]; diff --git a/tests/sandbox/testCases/getListOfLetters_testCases.ts b/tests/sandbox/testCases/getListOfLetters_testCases.ts deleted file mode 100644 index 422786d0..00000000 --- a/tests/sandbox/testCases/getListOfLetters_testCases.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { RequestSandBoxHeaders, sandBoxHeader} from '../../constants/request_headers'; -import { randomUUID } from 'node:crypto'; - - -type ApiSandboxGetLettersRequestTestCase = { - testCase: string; - limit: string, - header?: RequestSandBoxHeaders | NoRequestIdHeaders; - expectedStatus: number; - expectedResponse?: SandboxSuccessResponse | SandboxErrorResponse; -}; - -export type SandboxSuccessResponse = { - data: ApiData []; -}; - -type ApiData = -{ - type: string; - id: string; - attributes: { - specificationId: string; - groupId: string; - status: string; - } -}; - -export type SandboxErrorResponse = { - message: string; - errors: ApiErrors[]; -}; - -type ApiErrors = { - path: string; - message: string; - errorCode: string; -}; - -export type NoRequestIdHeaders = Omit; - -const NoRequestIdHeaders: NoRequestIdHeaders = { - 'Content-Type': 'application/vnd.api+json', - 'X-Correlation-ID': randomUUID(), -}; - -export const apiSandboxGetLettersRequestTestData: ApiSandboxGetLettersRequestTestCase[] = [ -{ - testCase: '200 response if record is fetched successfully', - limit: '1', - header: sandBoxHeader, - expectedStatus: 200, - expectedResponse: { - data: [ - { - id: 'fcfd849ceec940e8832b41f4fc161e09', - type: 'Letter', - attributes: { - specificationId: '2WL5eYSWGzCHlGmzNxuqVusPxDg', - groupId: 'c5d93f917f5546d08beccf770a915d96', - status: 'PENDING', - }, - }] - }, -}, -{ - testCase: '400 response if invalid limit is passed', - limit: 'XX', - header: sandBoxHeader, - expectedStatus: 400, - expectedResponse: { - message: 'request.query.limit should be number', - errors: [ - { - path:'.query.limit', - message:'should be number', - errorCode:'type.openapi.validation' - } - ] - } -}, - -{ - testCase: '400 response if invalid headers are passed', - limit: '2', - header: NoRequestIdHeaders, - expectedStatus: 400, - expectedResponse: { - message: "request.headers should have required property 'x-request-id'", - errors: [ - { - path: '.headers.x-request-id', - message: "should have required property 'x-request-id'", - errorCode:'required.openapi.validation' - } - ] - } -}]; diff --git a/tests/sandbox/testCases/update-letter-status-test-cases.ts b/tests/sandbox/testCases/update-letter-status-test-cases.ts new file mode 100644 index 00000000..aca61977 --- /dev/null +++ b/tests/sandbox/testCases/update-letter-status-test-cases.ts @@ -0,0 +1,138 @@ +import { + PatchMessageRequestBody, + PatchMessageResponseBody, +} from "../../component-tests/apiGateway-tests/testCases/update-letter-status"; +import { + RequestSandBoxHeaders, + sandBoxHeader, +} from "../../constants/request-headers"; +import { ErrorMessageBody } from "../../helpers/common-types"; +import { SandboxErrorResponse } from "./get-list-of-letters-test-cases"; + +export type ApiSandboxUpdateLetterStatusTestData = { + testCase: string; + id: string; + header: RequestSandBoxHeaders; + body?: PatchMessageRequestBody; + expectedStatus: number; + expectedResponse?: + | PatchMessageResponseBody + | SandboxErrorResponse + | ErrorMessageBody; +}; + +export const apiSandboxUpdateLetterStatusTestData: ApiSandboxUpdateLetterStatusTestData[] = + [ + { + testCase: "200 response if record is updated with status PENDING", + id: "2WL5eYSWGzCHlGmzNxuqVusPxDg", + header: sandBoxHeader, + body: { + data: { + type: "Letter", + id: "2WL5eYSWGzCHlGmzNxuqVusPxDg", + attributes: { + status: "PENDING", + }, + }, + }, + expectedStatus: 200, + expectedResponse: { + data: { + type: "Letter", + id: "2WL5eYSWGzCHlGmzNxuqVusPxDg", + attributes: { + status: "PENDING", + specificationId: "2WL5eYSWGzCHlGmzNxuqVusPxDg", + }, + }, + }, + }, + + { + testCase: "200 response if record is updated with status REJECTED", + id: "2WL5eYSWGzCHlGmzNxuqVusPxDg", + header: sandBoxHeader, + body: { + data: { + type: "Letter", + id: "2WL5eYSWGzCHlGmzNxuqVusPxDg", + attributes: { + status: "REJECTED", + reasonCode: "R01", + reasonText: "failed validation", + }, + }, + }, + expectedStatus: 200, + expectedResponse: { + data: { + type: "Letter", + id: "2WL5eYSWGzCHlGmzNxuqVusPxDg", + attributes: { + reasonCode: "R01", + reasonText: "failed validation", + status: "REJECTED", + specificationId: "2WL5eYSWGzCHlGmzNxuqVusPxDg", + }, + }, + }, + }, + { + testCase: "404 response if no resource is found for the given id", + id: "0", + header: sandBoxHeader, + body: { + data: { + type: "Letter", + id: "0", + attributes: { + status: "PENDING", + }, + }, + }, + expectedStatus: 404, + expectedResponse: { + errors: [ + { + id: "rrt-1931948104716186917-c-geu2-10664-3111479-3.0", + code: "NOTIFY_RESOURCE_NOT_FOUND", + links: { + about: + "https://digital.nhs.uk/developer/api-catalogue/nhs-notify-supplier", + }, + status: "404", + title: "Resource not found", + detail: "No resource found with that ID", + }, + ], + }, + }, + { + testCase: "400 response if request body is invalid", + id: "2WL5eYSWGzCHlGmzNxuqVusPxDg", + header: sandBoxHeader, + body: { + data: { + type: "Letter", + id: "2WL5eYSWGzCHlGmzNxuqVusPxDg", + attributes: { + status: "NO_STATUS", + }, + }, + }, + expectedStatus: 400, + expectedResponse: { + message: + "request.body.data.attributes.status should be equal to one of the allowed values: PENDING, ACCEPTED, REJECTED, PRINTED, ENCLOSED, CANCELLED, DISPATCHED, DELIVERED, FAILED, RETURNED, FORWARDED", + errors: [ + { + path: ".body.data.attributes.status", + message: + "should be equal to one of the allowed values: PENDING, ACCEPTED, REJECTED, PRINTED, ENCLOSED, CANCELLED, DISPATCHED, DELIVERED, FAILED, RETURNED, FORWARDED", + errorCode: "enum.openapi.validation", + }, + ], + }, + }, + ]; diff --git a/tests/sandbox/testCases/update-multiple-status-test-cases.ts b/tests/sandbox/testCases/update-multiple-status-test-cases.ts new file mode 100644 index 00000000..f9c8d5d9 --- /dev/null +++ b/tests/sandbox/testCases/update-multiple-status-test-cases.ts @@ -0,0 +1,132 @@ +import { + RequestSandBoxHeaders, + sandBoxHeader, +} from "../../constants/request-headers"; + +export type ApiSandboxUpdateLetterStatusTestData = { + testCase: string; + header: RequestSandBoxHeaders; + body: PostMessageRequestBody; + expectedStatus: number; +}; + +type PostMessageRequestBody = { + data: PostRequest[]; +}; + +type PostRequest = { + type: string; + id: string; + attributes: { + reasonCode?: string; + reasonText?: string; + status: string; + }; +}; + +export const apiSandboxMultipleLetterStatusTestData: ApiSandboxUpdateLetterStatusTestData[] = + [ + { + testCase: "200 response if records are updated", + header: sandBoxHeader, + body: { + data: [ + { + attributes: { + status: "PENDING", + }, + id: "2WL5eYSWGzCHlGmzNxuqVusPxDg", + type: "Letter", + }, + { + attributes: { + status: "ACCEPTED", + }, + id: "2WL5eYSWGzCHlGmzNxuqVusPxDg", + type: "Letter", + }, + { + attributes: { + status: "PRINTED", + }, + id: "2WL5eYSWGzCHlGmzNxuqVusPxDg", + type: "Letter", + }, + { + attributes: { + status: "ENCLOSED", + }, + id: "2WL5eYSWGzCHlGmzNxuqVusPxDg", + type: "Letter", + }, + { + attributes: { + status: "DISPATCHED", + }, + id: "2WL5eYSWGzCHlGmzNxuqVusPxDg", + type: "Letter", + }, + { + attributes: { + status: "DELIVERED", + }, + id: "2WL5eYSWGzCHlGmzNxuqVusPxDg", + type: "Letter", + }, + { + attributes: { + reasonCode: "R01", + reasonText: "failed validation", + status: "RETURNED", + }, + id: "2WL5eYSWGzCHlGmzNxuqVusPxDg", + type: "Letter", + }, + { + attributes: { + reasonCode: "R01", + reasonText: "failed validation", + status: "CANCELLED", + }, + id: "2WL5eYSWGzCHlGmzNxuqVusPxDg", + type: "Letter", + }, + { + attributes: { + reasonCode: "R01", + reasonText: "failed validation", + status: "FAILED", + }, + id: "2WL5eYSWGzCHlGmzNxuqVusPxDg", + type: "Letter", + }, + { + attributes: { + reasonCode: "R01", + reasonText: "failed validation", + status: "RETURNED", + }, + id: "2WL5eYSWGzCHlGmzNxuqVusPxDg", + type: "Letter", + }, + ], + }, + expectedStatus: 200, + }, + { + testCase: "404 response if invalid request is passed", + header: sandBoxHeader, + body: { + data: [ + { + attributes: { + status: "PENDING", + }, + id: "1234", + type: "Letter", + }, + ], + }, + expectedStatus: 404, + }, + ]; diff --git a/tests/sandbox/testCases/updateLetterStatus_testCases.ts b/tests/sandbox/testCases/updateLetterStatus_testCases.ts deleted file mode 100644 index 1932aac2..00000000 --- a/tests/sandbox/testCases/updateLetterStatus_testCases.ts +++ /dev/null @@ -1,123 +0,0 @@ - -import { PatchMessageRequestBody, PatchMessageResponseBody } from '../../component-tests/apiGateway-tests/testCases/updateLetterStatus'; -import { RequestSandBoxHeaders, sandBoxHeader } from '../../constants/request_headers'; -import { ErrorMessageBody } from '../../helpers/commonTypes'; -import { SandboxErrorResponse } from './getListOfLetters_testCases'; - - -export type ApiSandboxUpdateLetterStatusTestData = { - testCase: string; - id: string, - header: RequestSandBoxHeaders; - body?: PatchMessageRequestBody; - expectedStatus: number; - expectedResponse?: PatchMessageResponseBody | SandboxErrorResponse | ErrorMessageBody; -}; - -export const apiSandboxUpdateLetterStatusTestData: ApiSandboxUpdateLetterStatusTestData[] = [ - { - testCase: '200 response if record is updated with status PENDING', - id: '2WL5eYSWGzCHlGmzNxuqVusPxDg', - header: sandBoxHeader, - body: { - data: { - type: 'Letter', - id: '2WL5eYSWGzCHlGmzNxuqVusPxDg', - attributes: { - status: 'PENDING', - }, - } - }, - expectedStatus: 200, - expectedResponse: { - data: { - type: 'Letter', - id: '2WL5eYSWGzCHlGmzNxuqVusPxDg', - attributes: { - status: 'PENDING', - specificationId:'2WL5eYSWGzCHlGmzNxuqVusPxDg', - }, - } - }, - }, - - { - testCase: '200 response if record is updated with status REJECTED', - id: '2WL5eYSWGzCHlGmzNxuqVusPxDg', - header: sandBoxHeader, - body: { - data: { - type: 'Letter', - id: '2WL5eYSWGzCHlGmzNxuqVusPxDg', - attributes: { - status: 'REJECTED', - reasonCode: 'R01', - reasonText: 'failed validation', - }, - } - }, - expectedStatus: 200, - expectedResponse: { - data: { - type: 'Letter', - id: '2WL5eYSWGzCHlGmzNxuqVusPxDg', - attributes: { - reasonCode: 'R01', - reasonText: 'failed validation', - status: 'REJECTED', - specificationId:'2WL5eYSWGzCHlGmzNxuqVusPxDg', - }, - } - }, - }, - { - testCase: '404 response if no resource is found for the given id', - id: '0', - header: sandBoxHeader, - body: { - data: { - type: 'Letter', - id: '0', - attributes: { - status: 'PENDING', - }, - } - }, - expectedStatus: 404, - expectedResponse: { - errors: [{ - id: 'rrt-1931948104716186917-c-geu2-10664-3111479-3.0', - code: 'NOTIFY_RESOURCE_NOT_FOUND', - links: { - about: "https://digital.nhs.uk/developer/api-catalogue/nhs-notify-supplier" - }, - status: '404', - title: 'Resource not found', - detail: 'No resource found with that ID' - }] - }, - }, - { - testCase: '400 response if request body is invalid', - id: '2WL5eYSWGzCHlGmzNxuqVusPxDg', - header: sandBoxHeader, - body: { - data: { - type: 'Letter', - id: '2WL5eYSWGzCHlGmzNxuqVusPxDg', - attributes: { - status: 'NO_STATUS', - }, - } - }, - expectedStatus: 400, - expectedResponse: { - message: 'request.body.data.attributes.status should be equal to one of the allowed values: PENDING, ACCEPTED, REJECTED, PRINTED, ENCLOSED, CANCELLED, DISPATCHED, DELIVERED, FAILED, RETURNED, FORWARDED', - errors: [{ - path: '.body.data.attributes.status', - message: 'should be equal to one of the allowed values: PENDING, ACCEPTED, REJECTED, PRINTED, ENCLOSED, CANCELLED, DISPATCHED, DELIVERED, FAILED, RETURNED, FORWARDED', - errorCode: 'enum.openapi.validation' - }] - }, - }, -]; diff --git a/tests/sandbox/testCases/updateMultipleStatus_testCases.ts b/tests/sandbox/testCases/updateMultipleStatus_testCases.ts deleted file mode 100644 index dd6fdc7b..00000000 --- a/tests/sandbox/testCases/updateMultipleStatus_testCases.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { RequestSandBoxHeaders, sandBoxHeader } from "../../constants/request_headers"; - -export type ApiSandboxUpdateLetterStatusTestData = { - testCase: string; - header: RequestSandBoxHeaders; - body: PostMessageRequestBody; - expectedStatus: number; -}; - -type PostMessageRequestBody = { - data: postRequest [] -} - -type postRequest = { - type: string; - id: string; - attributes: { - reasonCode?: string; - reasonText?: string; - status: string; - } -}; - -export const apiSandboxMultipleLetterStatusTestData: ApiSandboxUpdateLetterStatusTestData[] = -[{ - testCase: '200 response if records are updated', - header: sandBoxHeader, - body:{ - data : - [{ - attributes: { - status: 'PENDING' - }, - id: '2WL5eYSWGzCHlGmzNxuqVusPxDg', - type: 'Letter' - }, - { - attributes: { - 'status': 'ACCEPTED' - }, - id: '2WL5eYSWGzCHlGmzNxuqVusPxDg', - type: 'Letter' - }, - { - attributes: { - status: 'PRINTED' - }, - id: '2WL5eYSWGzCHlGmzNxuqVusPxDg', - type: 'Letter' - }, - { - attributes: { - status: 'ENCLOSED' - }, - id: '2WL5eYSWGzCHlGmzNxuqVusPxDg', - type: 'Letter' - }, - { - attributes: { - status: 'DISPATCHED' - }, - id: '2WL5eYSWGzCHlGmzNxuqVusPxDg', - type: 'Letter' - }, - { - attributes: { - status: 'DELIVERED' - }, - id: '2WL5eYSWGzCHlGmzNxuqVusPxDg', - type: 'Letter' - }, - { - attributes: { - reasonCode: 'R01', - reasonText: 'failed validation', - status: 'RETURNED' - }, - id: '2WL5eYSWGzCHlGmzNxuqVusPxDg', - type: 'Letter' - }, - { - attributes: { - reasonCode: 'R01', - reasonText: 'failed validation', - status: 'CANCELLED' - }, - id: '2WL5eYSWGzCHlGmzNxuqVusPxDg', - type: 'Letter' - }, - { - attributes: { - reasonCode: 'R01', - reasonText: 'failed validation', - status: 'FAILED' - }, - id: '2WL5eYSWGzCHlGmzNxuqVusPxDg', - type: 'Letter' - }, - { - attributes: { - reasonCode: 'R01', - reasonText: 'failed validation', - status: 'RETURNED' - }, - id: '2WL5eYSWGzCHlGmzNxuqVusPxDg', - type: 'Letter' - } - ]}, - expectedStatus: 200 -}, -{ - testCase: '404 response if invalid request is passed', - header: sandBoxHeader, - body:{ - data : - [{ - attributes: { - status: 'PENDING' - }, - id: '1234', - type: 'Letter' - }] - }, - expectedStatus:404, -}]; diff --git a/tests/sandbox/update-letter-status.spec.ts b/tests/sandbox/update-letter-status.spec.ts new file mode 100644 index 00000000..dcd724fd --- /dev/null +++ b/tests/sandbox/update-letter-status.spec.ts @@ -0,0 +1,31 @@ +import { expect, test } from "@playwright/test"; +import { + SUPPLIER_API_URL_SANDBOX, + SUPPLIER_LETTERS, +} from "../constants/api-constants"; +import { apiSandboxUpdateLetterStatusTestData } from "./testCases/update-letter-status-test-cases"; + +test.describe("Sandbox Tests To Update Letter Status", () => { + for (const { + body, + expectedResponse, + expectedStatus, + header, + id, + testCase, + } of apiSandboxUpdateLetterStatusTestData) { + test(`Patch /Letters endpoint returns ${testCase}`, async ({ request }) => { + const response = await request.patch( + `${SUPPLIER_API_URL_SANDBOX}/${SUPPLIER_LETTERS}/${id}`, + { + headers: header, + data: body, + }, + ); + + const res = await response.json(); + expect(response.status()).toBe(expectedStatus); + expect(res).toEqual(expectedResponse); + }); + } +}); diff --git a/tests/sandbox/update-multiple-letter-status.spec.ts b/tests/sandbox/update-multiple-letter-status.spec.ts new file mode 100644 index 00000000..f7e30198 --- /dev/null +++ b/tests/sandbox/update-multiple-letter-status.spec.ts @@ -0,0 +1,26 @@ +import { expect, test } from "@playwright/test"; +import { + SUPPLIER_API_URL_SANDBOX, + SUPPLIER_LETTERS, +} from "../constants/api-constants"; +import { apiSandboxMultipleLetterStatusTestData } from "./testCases/update-multiple-status-test-cases"; + +test.describe("Sandbox Tests To Update Multiple Letter Status", () => { + for (const { + body, + expectedStatus, + header, + testCase, + } of apiSandboxMultipleLetterStatusTestData) { + test(`Patch /Letters endpoint returns ${testCase}`, async ({ request }) => { + const response = await request.post( + `${SUPPLIER_API_URL_SANDBOX}/${SUPPLIER_LETTERS}`, + { + headers: header, + data: body, + }, + ); + expect(response.status()).toBe(expectedStatus); + }); + } +}); diff --git a/tests/sandbox/updateLetterStatus.spec.ts b/tests/sandbox/updateLetterStatus.spec.ts deleted file mode 100644 index 9e0d9a4d..00000000 --- a/tests/sandbox/updateLetterStatus.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { test, expect, request } from '@playwright/test'; -import { SUPPLIER_API_URL_SANDBOX, SUPPLIER_LETTERS} from '../constants/api_constants'; -import { apiSandboxUpdateLetterStatusTestData } from './testCases/updateLetterStatus_testCases'; - - -test.describe('Sandbox Tests To Update Letter Status', () => -{ - apiSandboxUpdateLetterStatusTestData.forEach(({ testCase, header, id, body, expectedStatus, expectedResponse }) => { - test(`Patch /Letters endpoint returns ${testCase}`, async ({ request }) => { - - const response = await request.patch(`${SUPPLIER_API_URL_SANDBOX}/${SUPPLIER_LETTERS}/${id}` ,{ - headers: header, - data: body - }); - - const res = await response.json(); - expect(response.status()).toBe(expectedStatus); - expect(res).toEqual(expectedResponse); - - }); - }); -}); diff --git a/tests/sandbox/updateMultipleLetterStatus.spec.ts b/tests/sandbox/updateMultipleLetterStatus.spec.ts deleted file mode 100644 index 524229bf..00000000 --- a/tests/sandbox/updateMultipleLetterStatus.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { test, expect, request } from '@playwright/test'; -import { SUPPLIER_API_URL_SANDBOX, SUPPLIER_LETTERS} from '../constants/api_constants'; -import { apiSandboxMultipleLetterStatusTestData } from './testCases/updateMultipleStatus_testCases'; - - -test.describe('Sandbox Tests To Update Multiple Letter Status', () => -{ - apiSandboxMultipleLetterStatusTestData.forEach(({ testCase, header, body, expectedStatus }) => { - test(`Patch /Letters endpoint returns ${testCase}`, async ({ request }) => { - - const response = await request.post(`${SUPPLIER_API_URL_SANDBOX}/${SUPPLIER_LETTERS}` ,{ - headers: header, - data: body - }); - expect(response.status()).toBe(expectedStatus); - }); - }); -});