diff --git a/Makefile b/Makefile index 28fb24c7..91e8d688 100644 --- a/Makefile +++ b/Makefile @@ -98,6 +98,8 @@ config:: _install-dependencies version # Configure development environment (main npm install (cd docs && make install && cd ..) +test-component: + (cd tests && npm install && npm run test:component) version: rm -f .version diff --git a/package-lock.json b/package-lock.json index 224a5f2a..bfe4a46a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,11 @@ "docs" ], "dependencies": { + "@aws-sdk/client-api-gateway": "^3.906.0", + "@playwright/test": "^1.55.1", + "ajv": "^8.17.1", + "js-yaml": "^4.1.0", + "openapi-response-validator": "^12.1.3", "serve": "^14.2.4" }, "devDependencies": { @@ -20,6 +25,7 @@ "@redocly/cli": "^1.34.5", "@tsconfig/node22": "^22.0.2", "@types/jest": "^29.5.14", + "@types/js-yaml": "^4.0.9", "@typescript-eslint/eslint-plugin": "^8.27.0", "@typescript-eslint/parser": "^8.27.0", "esbuild": "^0.24.0", @@ -305,54 +311,573 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-crypto/supports-web-crypto": { - "version": "5.2.0", + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-api-gateway": { + "version": "3.906.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-api-gateway/-/client-api-gateway-3.906.0.tgz", + "integrity": "sha512-7iD94D2OWvP6qTdeUNgfnzk7El+ZurdXXaGx5vSTWRz7Hs32MI+0ZLPKQ/2NCeaMZu12YpxQiDBYO6MZ/H5fTQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.906.0", + "@aws-sdk/credential-provider-node": "3.906.0", + "@aws-sdk/middleware-host-header": "3.901.0", + "@aws-sdk/middleware-logger": "3.901.0", + "@aws-sdk/middleware-recursion-detection": "3.901.0", + "@aws-sdk/middleware-sdk-api-gateway": "3.901.0", + "@aws-sdk/middleware-user-agent": "3.906.0", + "@aws-sdk/region-config-resolver": "3.901.0", + "@aws-sdk/types": "3.901.0", + "@aws-sdk/util-endpoints": "3.901.0", + "@aws-sdk/util-user-agent-browser": "3.901.0", + "@aws-sdk/util-user-agent-node": "3.906.0", + "@smithy/config-resolver": "^4.3.0", + "@smithy/core": "^3.14.0", + "@smithy/fetch-http-handler": "^5.3.0", + "@smithy/hash-node": "^4.2.0", + "@smithy/invalid-dependency": "^4.2.0", + "@smithy/middleware-content-length": "^4.2.0", + "@smithy/middleware-endpoint": "^4.3.0", + "@smithy/middleware-retry": "^4.4.0", + "@smithy/middleware-serde": "^4.2.0", + "@smithy/middleware-stack": "^4.2.0", + "@smithy/node-config-provider": "^4.3.0", + "@smithy/node-http-handler": "^4.3.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/smithy-client": "^4.7.0", + "@smithy/types": "^4.6.0", + "@smithy/url-parser": "^4.2.0", + "@smithy/util-base64": "^4.2.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.0", + "@smithy/util-defaults-mode-browser": "^4.2.0", + "@smithy/util-defaults-mode-node": "^4.2.0", + "@smithy/util-endpoints": "^3.2.0", + "@smithy/util-middleware": "^4.2.0", + "@smithy/util-retry": "^4.2.0", + "@smithy/util-stream": "^4.4.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-api-gateway/node_modules/@aws-sdk/client-sso": { + "version": "3.906.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.906.0.tgz", + "integrity": "sha512-GGDwjW2cLzoEF5A1tBlZQZXzhlZzuM6cKNbSxUsCcBXtPAX03eb2GKApVy1SzpD03nTJk5T6GicGAm+BzK+lEg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.906.0", + "@aws-sdk/middleware-host-header": "3.901.0", + "@aws-sdk/middleware-logger": "3.901.0", + "@aws-sdk/middleware-recursion-detection": "3.901.0", + "@aws-sdk/middleware-user-agent": "3.906.0", + "@aws-sdk/region-config-resolver": "3.901.0", + "@aws-sdk/types": "3.901.0", + "@aws-sdk/util-endpoints": "3.901.0", + "@aws-sdk/util-user-agent-browser": "3.901.0", + "@aws-sdk/util-user-agent-node": "3.906.0", + "@smithy/config-resolver": "^4.3.0", + "@smithy/core": "^3.14.0", + "@smithy/fetch-http-handler": "^5.3.0", + "@smithy/hash-node": "^4.2.0", + "@smithy/invalid-dependency": "^4.2.0", + "@smithy/middleware-content-length": "^4.2.0", + "@smithy/middleware-endpoint": "^4.3.0", + "@smithy/middleware-retry": "^4.4.0", + "@smithy/middleware-serde": "^4.2.0", + "@smithy/middleware-stack": "^4.2.0", + "@smithy/node-config-provider": "^4.3.0", + "@smithy/node-http-handler": "^4.3.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/smithy-client": "^4.7.0", + "@smithy/types": "^4.6.0", + "@smithy/url-parser": "^4.2.0", + "@smithy/util-base64": "^4.2.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.0", + "@smithy/util-defaults-mode-browser": "^4.2.0", + "@smithy/util-defaults-mode-node": "^4.2.0", + "@smithy/util-endpoints": "^3.2.0", + "@smithy/util-middleware": "^4.2.0", + "@smithy/util-retry": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-api-gateway/node_modules/@aws-sdk/core": { + "version": "3.906.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.906.0.tgz", + "integrity": "sha512-+FuwAcozee8joVfjwly/8kSFNCvQOkcQYjINUckqBkdjO4iCRfOgSaz+0JMpMcYgVPnnyZv62gJ2g0bj0U+YDQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.901.0", + "@aws-sdk/xml-builder": "3.901.0", + "@smithy/core": "^3.14.0", + "@smithy/node-config-provider": "^4.3.0", + "@smithy/property-provider": "^4.2.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/signature-v4": "^5.3.0", + "@smithy/smithy-client": "^4.7.0", + "@smithy/types": "^4.6.0", + "@smithy/util-base64": "^4.2.0", + "@smithy/util-middleware": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-api-gateway/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.906.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.906.0.tgz", + "integrity": "sha512-vtMDguMci2aXhkgEqg1iqyQ7vVcafpx9uypksM6FQsNr3Cc/8I6HgfBAja6BuPwkaCn9NoMnG0/iuuOWr8P9dg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.906.0", + "@aws-sdk/types": "3.901.0", + "@smithy/property-provider": "^4.2.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-api-gateway/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.906.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.906.0.tgz", + "integrity": "sha512-L97N2SUkZp03s1LJZ1sCkUaUZ7m9T72faaadn05wyst/iXonSZKPHYMQVWGYhTC2OtRV0FQvBXIAqFZsNGQD0Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.906.0", + "@aws-sdk/types": "3.901.0", + "@smithy/fetch-http-handler": "^5.3.0", + "@smithy/node-http-handler": "^4.3.0", + "@smithy/property-provider": "^4.2.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/smithy-client": "^4.7.0", + "@smithy/types": "^4.6.0", + "@smithy/util-stream": "^4.4.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-api-gateway/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.906.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.906.0.tgz", + "integrity": "sha512-r7TbHD80WXo42kTEC5bqa4b87ho3T3yd2VEKo1qbEmOUovocntO8HC3JxHYr0XSeZ82DEYxLARb84akWjabPzg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.906.0", + "@aws-sdk/credential-provider-env": "3.906.0", + "@aws-sdk/credential-provider-http": "3.906.0", + "@aws-sdk/credential-provider-process": "3.906.0", + "@aws-sdk/credential-provider-sso": "3.906.0", + "@aws-sdk/credential-provider-web-identity": "3.906.0", + "@aws-sdk/nested-clients": "3.906.0", + "@aws-sdk/types": "3.901.0", + "@smithy/credential-provider-imds": "^4.2.0", + "@smithy/property-provider": "^4.2.0", + "@smithy/shared-ini-file-loader": "^4.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-api-gateway/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.906.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.906.0.tgz", + "integrity": "sha512-xga127vP0rFxiHjEUjLe6Yf4hQ/AZinOF4AqQr/asWQO+/uwh3aH8nXcS4lkpZNygxMHbuNXm7Xg504GKCMlLQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.906.0", + "@aws-sdk/credential-provider-http": "3.906.0", + "@aws-sdk/credential-provider-ini": "3.906.0", + "@aws-sdk/credential-provider-process": "3.906.0", + "@aws-sdk/credential-provider-sso": "3.906.0", + "@aws-sdk/credential-provider-web-identity": "3.906.0", + "@aws-sdk/types": "3.901.0", + "@smithy/credential-provider-imds": "^4.2.0", + "@smithy/property-provider": "^4.2.0", + "@smithy/shared-ini-file-loader": "^4.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-api-gateway/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.906.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.906.0.tgz", + "integrity": "sha512-P8R4GpDLppe+8mp+SOj1fKaY3AwDULCi/fqMSJjvf8qN6OM+vGGpFP3iXvkjFYyyV+8nRXY+HQCLRoZKpRtzMg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.906.0", + "@aws-sdk/types": "3.901.0", + "@smithy/property-provider": "^4.2.0", + "@smithy/shared-ini-file-loader": "^4.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-api-gateway/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.906.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.906.0.tgz", + "integrity": "sha512-wYljHU7yNEzt7ngZZ21FWh+RlO16gTpWvXyRqlryuCgIWugHD8bl7JphGnUN1md5/v+mCRuGK58JoFGZq+qrjA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.906.0", + "@aws-sdk/core": "3.906.0", + "@aws-sdk/token-providers": "3.906.0", + "@aws-sdk/types": "3.901.0", + "@smithy/property-provider": "^4.2.0", + "@smithy/shared-ini-file-loader": "^4.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-api-gateway/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.906.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.906.0.tgz", + "integrity": "sha512-V9PurepVko8+iyEvI9WAlk5dXJ1uWIW03RPLnNBEmeCqFjjit16HrNaaVvnp9fQbG7CSKSGqK026SjDgtKGKYA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.906.0", + "@aws-sdk/nested-clients": "3.906.0", + "@aws-sdk/types": "3.901.0", + "@smithy/property-provider": "^4.2.0", + "@smithy/shared-ini-file-loader": "^4.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-api-gateway/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.901.0.tgz", + "integrity": "sha512-yWX7GvRmqBtbNnUW7qbre3GvZmyYwU0WHefpZzDTYDoNgatuYq6LgUIQ+z5C04/kCRoFkAFrHag8a3BXqFzq5A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.901.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-api-gateway/node_modules/@aws-sdk/middleware-logger": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.901.0.tgz", + "integrity": "sha512-UoHebjE7el/tfRo8/CQTj91oNUm+5Heus5/a4ECdmWaSCHCS/hXTsU3PTTHAY67oAQR8wBLFPfp3mMvXjB+L2A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.901.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-api-gateway/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.901.0.tgz", + "integrity": "sha512-Wd2t8qa/4OL0v/oDpCHHYkgsXJr8/ttCxrvCKAt0H1zZe2LlRhY9gpDVKqdertfHrHDj786fOvEQA28G1L75Dg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.901.0", + "@aws/lambda-invoke-store": "^0.0.1", + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-api-gateway/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.906.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.906.0.tgz", + "integrity": "sha512-CMAjq2oCEv5EEvmlFvio8t4KQL2jGORyDQu7oLj4l0a2biPgxbwL3utalbm9yKty1rQM5zKpaa7id7ZG3X1f6A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.906.0", + "@aws-sdk/types": "3.901.0", + "@aws-sdk/util-endpoints": "3.901.0", + "@smithy/core": "^3.14.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-api-gateway/node_modules/@aws-sdk/nested-clients": { + "version": "3.906.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.906.0.tgz", + "integrity": "sha512-0/r0bh/9Bm14lVe+jAzQQB2ufq9S4Vd9Wg5rZn8RhrhKl6y/DC1aRzOo2kJTNu5pCbVfQsd/VXLLnkcbOrDy6A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.906.0", + "@aws-sdk/middleware-host-header": "3.901.0", + "@aws-sdk/middleware-logger": "3.901.0", + "@aws-sdk/middleware-recursion-detection": "3.901.0", + "@aws-sdk/middleware-user-agent": "3.906.0", + "@aws-sdk/region-config-resolver": "3.901.0", + "@aws-sdk/types": "3.901.0", + "@aws-sdk/util-endpoints": "3.901.0", + "@aws-sdk/util-user-agent-browser": "3.901.0", + "@aws-sdk/util-user-agent-node": "3.906.0", + "@smithy/config-resolver": "^4.3.0", + "@smithy/core": "^3.14.0", + "@smithy/fetch-http-handler": "^5.3.0", + "@smithy/hash-node": "^4.2.0", + "@smithy/invalid-dependency": "^4.2.0", + "@smithy/middleware-content-length": "^4.2.0", + "@smithy/middleware-endpoint": "^4.3.0", + "@smithy/middleware-retry": "^4.4.0", + "@smithy/middleware-serde": "^4.2.0", + "@smithy/middleware-stack": "^4.2.0", + "@smithy/node-config-provider": "^4.3.0", + "@smithy/node-http-handler": "^4.3.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/smithy-client": "^4.7.0", + "@smithy/types": "^4.6.0", + "@smithy/url-parser": "^4.2.0", + "@smithy/util-base64": "^4.2.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.0", + "@smithy/util-defaults-mode-browser": "^4.2.0", + "@smithy/util-defaults-mode-node": "^4.2.0", + "@smithy/util-endpoints": "^3.2.0", + "@smithy/util-middleware": "^4.2.0", + "@smithy/util-retry": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-api-gateway/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.901.0.tgz", + "integrity": "sha512-7F0N888qVLHo4CSQOsnkZ4QAp8uHLKJ4v3u09Ly5k4AEStrSlFpckTPyUx6elwGL+fxGjNE2aakK8vEgzzCV0A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.901.0", + "@smithy/node-config-provider": "^4.3.0", + "@smithy/types": "^4.6.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-middleware": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-api-gateway/node_modules/@aws-sdk/token-providers": { + "version": "3.906.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.906.0.tgz", + "integrity": "sha512-gdxXleCjMUAKnyR/1ksdnv3Fuifr9iuaeEtINRHkwVluwcORabEdOlxW36th2QdkpTTyP1hW35VATz2R6v/i2Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.906.0", + "@aws-sdk/nested-clients": "3.906.0", + "@aws-sdk/types": "3.901.0", + "@smithy/property-provider": "^4.2.0", + "@smithy/shared-ini-file-loader": "^4.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-api-gateway/node_modules/@aws-sdk/types": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.901.0.tgz", + "integrity": "sha512-FfEM25hLEs4LoXsLXQ/q6X6L4JmKkKkbVFpKD4mwfVHtRVQG6QxJiCPcrkcPISquiy6esbwK2eh64TWbiD60cg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-api-gateway/node_modules/@aws-sdk/util-endpoints": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.901.0.tgz", + "integrity": "sha512-5nZP3hGA8FHEtKvEQf4Aww5QZOkjLW1Z+NixSd+0XKfHvA39Ah5sZboScjLx0C9kti/K3OGW1RCx5K9Zc3bZqg==", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/types": "3.901.0", + "@smithy/types": "^4.6.0", + "@smithy/url-parser": "^4.2.0", + "@smithy/util-endpoints": "^3.2.0", "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@aws-crypto/util": { - "version": "5.2.0", + "node_modules/@aws-sdk/client-api-gateway/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.901.0.tgz", + "integrity": "sha512-Ntb6V/WFI21Ed4PDgL/8NSfoZQQf9xzrwNgiwvnxgAl/KvAvRBgQtqj5gHsDX8Nj2YmJuVoHfH9BGjL9VQ4WNg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.222.0", - "@smithy/util-utf8": "^2.0.0", + "@aws-sdk/types": "3.901.0", + "@smithy/types": "^4.6.0", + "bowser": "^2.11.0", "tslib": "^2.6.2" } }, - "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { - "version": "2.2.0", + "node_modules/@aws-sdk/client-api-gateway/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.906.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.906.0.tgz", + "integrity": "sha512-9Gaglw80E9UZ5FctCp5pZAzT40/vC4Oo0fcNXsfplLkpWqTU+NTdTRMYe3TMZ1/v1/JZKuGUVyHiuo/xLu3NmA==", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/middleware-user-agent": "3.906.0", + "@aws-sdk/types": "3.901.0", + "@smithy/node-config-provider": "^4.3.0", + "@smithy/types": "^4.6.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } } }, - "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { - "version": "2.2.0", + "node_modules/@aws-sdk/client-api-gateway/node_modules/@aws-sdk/xml-builder": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.901.0.tgz", + "integrity": "sha512-pxFCkuAP7Q94wMTNPAwi6hEtNrp/BdFf+HOrIEeFQsk4EoOmpKY3I6S+u6A9Wg295J80Kh74LqDWM22ux3z6Aw==", "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^2.2.0", + "@smithy/types": "^4.6.0", + "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-api-gateway/node_modules/fast-xml-parser": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", + "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" + "strnum": "^2.1.0" }, - "engines": { - "node": ">=14.0.0" + "bin": { + "fxparser": "src/cli/cli.js" } }, + "node_modules/@aws-sdk/client-api-gateway/node_modules/strnum": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", + "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, "node_modules/@aws-sdk/client-dynamodb": { "version": "3.864.0", "license": "Apache-2.0", @@ -1433,6 +1958,34 @@ "node": ">=18.0.0" } }, + "node_modules/@aws-sdk/middleware-sdk-api-gateway": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-api-gateway/-/middleware-sdk-api-gateway-3.901.0.tgz", + "integrity": "sha512-Bfn8ciaiPdiuZZpIVwTG2aGuLxSXuOKJNFVEFNmIdFhEU51b4yVQGzcdvgFeohfulCz05cL1Uc/f6cxWQoHEaQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.901.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-api-gateway/node_modules/@aws-sdk/types": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.901.0.tgz", + "integrity": "sha512-FfEM25hLEs4LoXsLXQ/q6X6L4JmKkKkbVFpKD4mwfVHtRVQG6QxJiCPcrkcPISquiy6esbwK2eh64TWbiD60cg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@aws-sdk/middleware-sdk-s3": { "version": "3.896.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.896.0.tgz", @@ -4194,6 +4747,21 @@ "url": "https://opencollective.com/pkgr" } }, + "node_modules/@playwright/test": { + "version": "1.55.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.55.1.tgz", + "integrity": "sha512-IVAh/nOJaw6W9g+RJVlIQJ6gSiER+ae6mKQ5CX1bERzQgbC1VSeBlwdvczT7pxb0GWiyrxH4TGKbMfDb4Sq/ig==", + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.55.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "dev": true, @@ -4488,15 +5056,15 @@ } }, "node_modules/@smithy/config-resolver": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.2.2.tgz", - "integrity": "sha512-IT6MatgBWagLybZl1xQcURXRICvqz1z3APSCAI9IqdvfCkrA7RaQIEfgC6G/KvfxnDfQUDqFV+ZlixcuFznGBQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.3.0.tgz", + "integrity": "sha512-9oH+n8AVNiLPK/iK/agOsoWfrKZ3FGP3502tkksd6SRsKMYiu7AFX0YXo6YBADdsAj7C+G/aLKdsafIJHxuCkQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.2.2", - "@smithy/types": "^4.5.0", - "@smithy/util-config-provider": "^4.1.0", - "@smithy/util-middleware": "^4.1.1", + "@smithy/node-config-provider": "^4.3.0", + "@smithy/types": "^4.6.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-middleware": "^4.2.0", "tslib": "^2.6.2" }, "engines": { @@ -4504,18 +5072,18 @@ } }, "node_modules/@smithy/core": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.14.0.tgz", - "integrity": "sha512-XJ4z5FxvY/t0Dibms/+gLJrI5niRoY0BCmE02fwmPcRYFPI4KI876xaE79YGWIKnEslMbuQPsIEsoU/DXa0DoA==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.15.0.tgz", + "integrity": "sha512-VJWncXgt+ExNn0U2+Y7UywuATtRYaodGQKFo9mDyh70q+fJGedfrqi2XuKU1BhiLeXgg6RZrW7VEKfeqFhHAJA==", "license": "Apache-2.0", "dependencies": { "@smithy/middleware-serde": "^4.2.0", "@smithy/protocol-http": "^5.3.0", "@smithy/types": "^4.6.0", - "@smithy/util-base64": "^4.2.0", + "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.0", - "@smithy/util-stream": "^4.4.0", + "@smithy/util-stream": "^4.5.0", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" @@ -4525,15 +5093,15 @@ } }, "node_modules/@smithy/credential-provider-imds": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.1.2.tgz", - "integrity": "sha512-JlYNq8TShnqCLg0h+afqe2wLAwZpuoSgOyzhYvTgbiKBWRov+uUve+vrZEQO6lkdLOWPh7gK5dtb9dS+KGendg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.0.tgz", + "integrity": "sha512-SOhFVvFH4D5HJZytb0bLKxCrSnwcqPiNlrw+S4ZXjMnsC+o9JcUQzbZOEQcA8yv9wJFNhfsUiIUKiEnYL68Big==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.2.2", - "@smithy/property-provider": "^4.1.1", - "@smithy/types": "^4.5.0", - "@smithy/url-parser": "^4.1.1", + "@smithy/node-config-provider": "^4.3.0", + "@smithy/property-provider": "^4.2.0", + "@smithy/types": "^4.6.0", + "@smithy/url-parser": "^4.2.0", "tslib": "^2.6.2" }, "engines": { @@ -4611,15 +5179,15 @@ } }, "node_modules/@smithy/fetch-http-handler": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.0.tgz", - "integrity": "sha512-BG3KSmsx9A//KyIfw+sqNmWFr1YBUr+TwpxFT7yPqAk0yyDh7oSNgzfNH7pS6OC099EGx2ltOULvumCFe8bcgw==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.1.tgz", + "integrity": "sha512-3AvYYbB+Dv5EPLqnJIAgYw/9+WzeBiUYS8B+rU0pHq5NMQMvrZmevUROS4V2GAt0jEOn9viBzPLrZE+riTNd5Q==", "license": "Apache-2.0", "dependencies": { "@smithy/protocol-http": "^5.3.0", "@smithy/querystring-builder": "^4.2.0", "@smithy/types": "^4.6.0", - "@smithy/util-base64": "^4.2.0", + "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" }, "engines": { @@ -4642,14 +5210,14 @@ } }, "node_modules/@smithy/hash-node": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.1.1.tgz", - "integrity": "sha512-H9DIU9WBLhYrvPs9v4sYvnZ1PiAI0oc8CgNQUJ1rpN3pP7QADbTOUjchI2FB764Ub0DstH5xbTqcMJu1pnVqxA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.0.tgz", + "integrity": "sha512-ugv93gOhZGysTctZh9qdgng8B+xO0cj+zN0qAZ+Sgh7qTQGPOJbMdIuyP89KNfUyfAqFSNh5tMvC+h2uCpmTtA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.5.0", - "@smithy/util-buffer-from": "^4.1.0", - "@smithy/util-utf8": "^4.1.0", + "@smithy/types": "^4.6.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { @@ -4671,12 +5239,12 @@ } }, "node_modules/@smithy/invalid-dependency": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.1.1.tgz", - "integrity": "sha512-1AqLyFlfrrDkyES8uhINRlJXmHA2FkG+3DY8X+rmLSqmFwk3DJnvhyGzyByPyewh2jbmV+TYQBEfngQax8IFGg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.0.tgz", + "integrity": "sha512-ZmK5X5fUPAbtvRcUPtk28aqIClVhbfcmfoS4M7UQBTnDdrNxhsrxYVv0ZEl5NaPSyExsPWqL4GsPlRvtlwg+2A==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.5.0", + "@smithy/types": "^4.6.0", "tslib": "^2.6.2" }, "engines": { @@ -4710,13 +5278,13 @@ } }, "node_modules/@smithy/middleware-content-length": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.1.1.tgz", - "integrity": "sha512-9wlfBBgTsRvC2JxLJxv4xDGNBrZuio3AgSl0lSFX7fneW2cGskXTYpFxCdRYD2+5yzmsiTuaAJD1Wp7gWt9y9w==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.0.tgz", + "integrity": "sha512-6ZAnwrXFecrA4kIDOcz6aLBhU5ih2is2NdcZtobBDSdSHtE9a+MThB5uqyK4XXesdOCvOcbCm2IGB95birTSOQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.2.1", - "@smithy/types": "^4.5.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", "tslib": "^2.6.2" }, "engines": { @@ -4724,12 +5292,12 @@ } }, "node_modules/@smithy/middleware-endpoint": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.3.0.tgz", - "integrity": "sha512-jFVjuQeV8TkxaRlcCNg0GFVgg98tscsmIrIwRFeC74TIUyLE3jmY9xgc1WXrPQYRjQNK3aRoaIk6fhFRGOIoGw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.3.1.tgz", + "integrity": "sha512-JtM4SjEgImLEJVXdsbvWHYiJ9dtuKE8bqLlvkvGi96LbejDL6qnVpVxEFUximFodoQbg0Gnkyff9EKUhFhVJFw==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.14.0", + "@smithy/core": "^3.15.0", "@smithy/middleware-serde": "^4.2.0", "@smithy/node-config-provider": "^4.3.0", "@smithy/shared-ini-file-loader": "^4.3.0", @@ -4743,19 +5311,19 @@ } }, "node_modules/@smithy/middleware-retry": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.3.0.tgz", - "integrity": "sha512-qhEX9745fAxZvtLM4bQJAVC98elWjiMO2OiHl1s6p7hUzS4QfZO1gXUYNwEK8m0J6NoCD5W52ggWxbIDHI0XSg==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.1.tgz", + "integrity": "sha512-wXxS4ex8cJJteL0PPQmWYkNi9QKDWZIpsndr0wZI2EL+pSSvA/qqxXU60gBOJoIc2YgtZSWY/PE86qhKCCKP1w==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.2.2", - "@smithy/protocol-http": "^5.2.1", - "@smithy/service-error-classification": "^4.1.2", - "@smithy/smithy-client": "^4.6.4", - "@smithy/types": "^4.5.0", - "@smithy/util-middleware": "^4.1.1", - "@smithy/util-retry": "^4.1.2", - "@smithy/uuid": "^1.0.0", + "@smithy/node-config-provider": "^4.3.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/service-error-classification": "^4.2.0", + "@smithy/smithy-client": "^4.7.1", + "@smithy/types": "^4.6.0", + "@smithy/util-middleware": "^4.2.0", + "@smithy/util-retry": "^4.2.0", + "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" }, "engines": { @@ -4874,12 +5442,12 @@ } }, "node_modules/@smithy/service-error-classification": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.1.2.tgz", - "integrity": "sha512-Kqd8wyfmBWHZNppZSMfrQFpc3M9Y/kjyN8n8P4DqJJtuwgK1H914R471HTw7+RL+T7+kI1f1gOnL7Vb5z9+NgQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.0.tgz", + "integrity": "sha512-Ylv1ttUeKatpR0wEOMnHf1hXMktPUMObDClSWl2TpCVT4DwtJhCeighLzSLbgH3jr5pBNM0LDXT5yYxUvZ9WpA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.5.0" + "@smithy/types": "^4.6.0" }, "engines": { "node": ">=18.0.0" @@ -4918,17 +5486,17 @@ } }, "node_modules/@smithy/smithy-client": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.7.0.tgz", - "integrity": "sha512-3BDx/aCCPf+kkinYf5QQhdQ9UAGihgOVqI3QO5xQfSaIWvUE4KYLtiGRWsNe1SR7ijXC0QEPqofVp5Sb0zC8xQ==", + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.7.1.tgz", + "integrity": "sha512-WXVbiyNf/WOS/RHUoFMkJ6leEVpln5ojCjNBnzoZeMsnCg3A0BRhLK3WYc4V7PmYcYPZh9IYzzAg9XcNSzYxYQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.14.0", - "@smithy/middleware-endpoint": "^4.3.0", + "@smithy/core": "^3.15.0", + "@smithy/middleware-endpoint": "^4.3.1", "@smithy/middleware-stack": "^4.2.0", "@smithy/protocol-http": "^5.3.0", "@smithy/types": "^4.6.0", - "@smithy/util-stream": "^4.4.0", + "@smithy/util-stream": "^4.5.0", "tslib": "^2.6.2" }, "engines": { @@ -4962,9 +5530,9 @@ } }, "node_modules/@smithy/util-base64": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.2.0.tgz", - "integrity": "sha512-+erInz8WDv5KPe7xCsJCp+1WCjSbah9gWcmUXc9NqmhyPx59tf7jqFz+za1tRG1Y5KM1Cy1rWCcGypylFp4mvA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", + "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", "license": "Apache-2.0", "dependencies": { "@smithy/util-buffer-from": "^4.2.0", @@ -4988,9 +5556,9 @@ } }, "node_modules/@smithy/util-body-length-node": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.1.0.tgz", - "integrity": "sha512-BOI5dYjheZdgR9XiEM3HJcEMCXSoqbzu7CzIgYrx0UtmvtC3tC2iDGpJLsSRFffUpy8ymsg2ARMP5fR8mtuUQQ==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", + "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -5025,15 +5593,14 @@ } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.1.4.tgz", - "integrity": "sha512-mLDJ1s4eA3vwOGaQOEPlg5LB4LdZUUMpB5UMOMofeGhWqiS7WR7dTpLiNi9zVn+YziKUd3Af5NLfxDs7NJqmIw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.0.tgz", + "integrity": "sha512-H4MAj8j8Yp19Mr7vVtGgi7noJjvjJbsKQJkvNnLlrIFduRFT5jq5Eri1k838YW7rN2g5FTnXpz5ktKVr1KVgPQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.1.1", - "@smithy/smithy-client": "^4.6.4", - "@smithy/types": "^4.5.0", - "bowser": "^2.11.0", + "@smithy/property-provider": "^4.2.0", + "@smithy/smithy-client": "^4.7.1", + "@smithy/types": "^4.6.0", "tslib": "^2.6.2" }, "engines": { @@ -5041,17 +5608,17 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.1.4.tgz", - "integrity": "sha512-pjX2iMTcOASaSanAd7bu6i3fcMMezr3NTr8Rh64etB0uHRZi+Aw86DoCxPESjY4UTIuA06hhqtTtw95o//imYA==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.1.tgz", + "integrity": "sha512-PuDcgx7/qKEMzV1QFHJ7E4/MMeEjaA7+zS5UNcHCLPvvn59AeZQ0DSDGMpqC2xecfa/1cNGm4l8Ec/VxCuY7Ug==", "license": "Apache-2.0", "dependencies": { - "@smithy/config-resolver": "^4.2.2", - "@smithy/credential-provider-imds": "^4.1.2", - "@smithy/node-config-provider": "^4.2.2", - "@smithy/property-provider": "^4.1.1", - "@smithy/smithy-client": "^4.6.4", - "@smithy/types": "^4.5.0", + "@smithy/config-resolver": "^4.3.0", + "@smithy/credential-provider-imds": "^4.2.0", + "@smithy/node-config-provider": "^4.3.0", + "@smithy/property-provider": "^4.2.0", + "@smithy/smithy-client": "^4.7.1", + "@smithy/types": "^4.6.0", "tslib": "^2.6.2" }, "engines": { @@ -5059,13 +5626,13 @@ } }, "node_modules/@smithy/util-endpoints": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.1.2.tgz", - "integrity": "sha512-+AJsaaEGb5ySvf1SKMRrPZdYHRYSzMkCoK16jWnIMpREAnflVspMIDeCVSZJuj+5muZfgGpNpijE3mUNtjv01Q==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.0.tgz", + "integrity": "sha512-TXeCn22D56vvWr/5xPqALc9oO+LN+QpFjrSM7peG/ckqEPoI3zaKZFp+bFwfmiHhn5MGWPaLCqDOJPPIixk9Wg==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.2.2", - "@smithy/types": "^4.5.0", + "@smithy/node-config-provider": "^4.3.0", + "@smithy/types": "^4.6.0", "tslib": "^2.6.2" }, "engines": { @@ -5098,13 +5665,13 @@ } }, "node_modules/@smithy/util-retry": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.1.2.tgz", - "integrity": "sha512-NCgr1d0/EdeP6U5PSZ9Uv5SMR5XRRYoVr1kRVtKZxWL3tixEL3UatrPIMFZSKwHlCcp2zPLDvMubVDULRqeunA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.0.tgz", + "integrity": "sha512-BWSiuGbwRnEE2SFfaAZEX0TqaxtvtSYPM/J73PFVm+A29Fg1HTPiYFb8TmX1DXp4hgcdyJcNQmprfd5foeORsg==", "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^4.1.2", - "@smithy/types": "^4.5.0", + "@smithy/service-error-classification": "^4.2.0", + "@smithy/types": "^4.6.0", "tslib": "^2.6.2" }, "engines": { @@ -5112,15 +5679,15 @@ } }, "node_modules/@smithy/util-stream": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.4.0.tgz", - "integrity": "sha512-vtO7ktbixEcrVzMRmpQDnw/Ehr9UWjBvSJ9fyAbadKkC4w5Cm/4lMO8cHz8Ysb8uflvQUNRcuux/oNHKPXkffg==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.0.tgz", + "integrity": "sha512-0TD5M5HCGu5diEvZ/O/WquSjhJPasqv7trjoqHyWjNh/FBeBl7a0ztl9uFMOsauYtRfd8jvpzIAQhDHbx+nvZw==", "license": "Apache-2.0", "dependencies": { - "@smithy/fetch-http-handler": "^5.3.0", + "@smithy/fetch-http-handler": "^5.3.1", "@smithy/node-http-handler": "^4.3.0", "@smithy/types": "^4.6.0", - "@smithy/util-base64": "^4.2.0", + "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", @@ -5377,6 +5944,13 @@ "pretty-format": "^29.0.0" } }, + "node_modules/@types/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/jsdom": { "version": "21.1.7", "dev": true, @@ -5778,9 +6352,9 @@ }, "node_modules/ajv": { "version": "8.17.1", - "dev": true, + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -6054,7 +6628,6 @@ }, "node_modules/argparse": { "version": "2.0.1", - "dev": true, "license": "Python-2.0" }, "node_modules/aria-query": { @@ -9449,7 +10022,6 @@ }, "node_modules/fast-uri": { "version": "3.0.6", - "dev": true, "funding": [ { "type": "github", @@ -9460,8 +10032,7 @@ "url": "https://opencollective.com/fastify" } ], - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/fast-xml-parser": { "version": "4.5.3", @@ -9698,6 +10269,20 @@ "dev": true, "license": "ISC" }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "dev": true, @@ -11804,7 +12389,8 @@ }, "node_modules/js-yaml": { "version": "4.1.0", - "dev": true, + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -12828,6 +13414,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/openapi-response-validator": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-response-validator/-/openapi-response-validator-12.1.3.tgz", + "integrity": "sha512-beZNb6r1SXAg1835S30h9XwjE596BYzXQFAEZlYAoO2imfxAu5S7TvNFws5k/MMKMCOFTzBXSjapqEvAzlblrQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.4.0", + "openapi-types": "^12.1.3" + } + }, "node_modules/openapi-sampler": { "version": "1.6.1", "dev": true, @@ -12838,6 +13434,12 @@ "json-pointer": "0.6.2" } }, + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "license": "MIT" + }, "node_modules/openapi-typescript": { "version": "7.9.1", "dev": true, @@ -13251,6 +13853,36 @@ "node": ">=8" } }, + "node_modules/playwright": { + "version": "1.55.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.55.1.tgz", + "integrity": "sha512-cJW4Xd/G3v5ovXtJJ52MAOclqeac9S/aGGgRzLabuF8TnIb6xHvMzKIa6JmrRzUkeXJgfL1MhukP0NK6l39h3A==", + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.55.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.55.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.1.tgz", + "integrity": "sha512-Z6Mh9mkwX+zxSlHqdr5AOcJnfp+xUWLCt9uKV18fhzA8eyxUd8NUWzAjxUh55RZKSYwDGX0cfaySdhZJGMoJ+w==", + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/pluralize": { "version": "8.0.0", "dev": true, @@ -15540,6 +16172,21 @@ "@esbuild/win32-x64": "0.25.9" } }, + "node_modules/tsx/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/tweetnacl": { "version": "0.14.5", "dev": true, diff --git a/package.json b/package.json index 25ea45f5..b4388e9e 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,10 @@ { "dependencies": { + "@aws-sdk/client-api-gateway": "^3.906.0", + "@playwright/test": "^1.55.1", + "ajv": "^8.17.1", + "js-yaml": "^4.1.0", + "openapi-response-validator": "^12.1.3", "serve": "^14.2.4" }, "devDependencies": { @@ -7,6 +12,7 @@ "@redocly/cli": "^1.34.5", "@tsconfig/node22": "^22.0.2", "@types/jest": "^29.5.14", + "@types/js-yaml": "^4.0.9", "@typescript-eslint/eslint-plugin": "^8.27.0", "@typescript-eslint/parser": "^8.27.0", "esbuild": "^0.24.0", diff --git a/tests/.gitignore b/tests/.gitignore index b85301a8..57b879f0 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -7,3 +7,5 @@ node_modules/ /playwright/.cache/ /allure-results /target +/playwright/.auth/ +/allure-report diff --git a/tests/component-tests/apiGateway-tests/getLetters.spec.ts b/tests/component-tests/apiGateway-tests/getLetters.spec.ts new file mode 100644 index 00000000..08b30252 --- /dev/null +++ b/tests/component-tests/apiGateway-tests/getLetters.spec.ts @@ -0,0 +1,94 @@ +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'; +import { link } from 'fs'; + +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/UpdateLetterStatus.ts b/tests/component-tests/apiGateway-tests/testCases/UpdateLetterStatus.ts new file mode 100644 index 00000000..f747f03e --- /dev/null +++ b/tests/component-tests/apiGateway-tests/testCases/UpdateLetterStatus.ts @@ -0,0 +1,114 @@ + +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 | number; + reasonText?: string; + status: string; + }; + }; +}; + +export type PatchMessageResponseBody = { + data: { + type: string; + id: string; + attributes: { + reasonCode?: number; + 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: 123, + 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/updateLetterStatus.spec.ts b/tests/component-tests/apiGateway-tests/updateLetterStatus.spec.ts new file mode 100644 index 00000000..c7211ca9 --- /dev/null +++ b/tests/component-tests/apiGateway-tests/updateLetterStatus.spec.ts @@ -0,0 +1,131 @@ +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'; + +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 res = await response.json(); + expect(response.status()).toBe(200); + expect(res).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 res = await response.json(); + expect(response.status()).toBe(200); + expect(res).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 res = await response.json(); + + expect(response.status()).toBe(400); + expect(res).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 res = await response.json(); + expect(response.status()).toBe(500); + expect(res).toMatchObject(patch500ErrorResponseBody(id)); + }); + + test(`Patch /letters returns 403 for invalid headers`, async ({ request }) => { + const headers = await createInvalidRequestHeaders(); + const id = randomUUID() + const body = patchValidRequestBody(id, 'PENDING'); + + const response = await request.patch(`${baseUrl}/${SUPPLIER_LETTERS}/${id}`, { + headers: headers, + data: body + }); + + const res = await response.json(); + expect(response.status()).toBe(403); + }); +}); diff --git a/tests/config/main.config.ts b/tests/config/main.config.ts index e25b2f62..4c87088a 100644 --- a/tests/config/main.config.ts +++ b/tests/config/main.config.ts @@ -1,20 +1,19 @@ -import type { PlaywrightTestConfig } from '@playwright/test'; -import { config as baseConfig } from './playwright.base.config'; -import { getReporters } from './reporters'; +import {defineConfig, PlaywrightTestConfig } from '@playwright/test'; +import baseConfig from './playwright.base.config'; +import { getReporters } from './reporters'; +import path from 'path'; const localConfig: PlaywrightTestConfig = { + ...baseConfig, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ reporter: getReporters('api-test'), - ...baseConfig, - //globalSetup: require.resolve('./setup/globalSetup'), - //globalTeardown: require.resolve('./setup/globalTeardown'), - testIgnore: [], projects: [ { - name: 'sandbox', - testMatch: 'tests/messages/get_single_letter/*.spec.ts', + name: 'component-tests', + testDir: path.resolve(__dirname, '../component-tests'), + testMatch: '**/*.spec.ts', }, ], }; -export default localConfig; +export default defineConfig(localConfig); diff --git a/tests/config/playwright.base.config.ts b/tests/config/playwright.base.config.ts index 8016adc7..c6edb35a 100644 --- a/tests/config/playwright.base.config.ts +++ b/tests/config/playwright.base.config.ts @@ -1,4 +1,5 @@ -import type { PlaywrightTestConfig } from '@playwright/test'; +import { defineConfig, PlaywrightTestConfig } 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; @@ -6,8 +7,6 @@ const envMaxInstances = Number.parseInt(process.env.WORKERS_MAX_INST!) || 10; * See https://playwright.dev/docs/test-configuration. */ export const config: PlaywrightTestConfig = { - testDir: '../sandbox/messages/get_single_letter/', - testMatch: '*.spec.ts/', /* Maximum time one test can run for. */ timeout: 60 * 1000, workers: envMaxInstances, @@ -24,19 +23,5 @@ export const config: PlaywrightTestConfig = { forbidOnly: !!process.env.CI, /* Retry on CI only */ retries: process.env.CI ? 2 : 0, - - /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ - use: { - /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ - actionTimeout: 0, - /* Base URL to use in actions like `await page.goto('/')`. */ - baseURL: baseUrl, - ignoreHTTPSErrors: true, - trace: 'on-first-retry', - /* Slows down Playwright operations by the specified amount of milliseconds. */ - launchOptions: { - slowMo: 0, - }, - }, }; -export default config; +export default defineConfig(config); diff --git a/tests/config/sandbox.config.ts b/tests/config/sandbox.config.ts new file mode 100644 index 00000000..c7295100 --- /dev/null +++ b/tests/config/sandbox.config.ts @@ -0,0 +1,19 @@ +import type { PlaywrightTestConfig } from '@playwright/test'; +import { config as baseConfig } from './playwright.base.config'; +import { getReporters } from './reporters'; +import path from 'path'; + +const localConfig: PlaywrightTestConfig = { + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: getReporters('api-test'), + ...baseConfig, + projects: [ + { + name: 'sandbox', + testDir: path.resolve(__dirname, '../sandbox'), + testMatch: '*.spec.ts', + }, + ], +}; + +export default localConfig; diff --git a/tests/constants/api_constants.ts b/tests/constants/api_constants.ts index 5eae3ba5..fc7f3487 100644 --- a/tests/constants/api_constants.ts +++ b/tests/constants/api_constants.ts @@ -1 +1,10 @@ -export const LETTERS_ENDPOINT = 'letters'; +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`; + +// Test +export const supplierId = 'TestSupplier1'; diff --git a/tests/constants/request_headers.ts b/tests/constants/request_headers.ts new file mode 100644 index 00000000..02e2d33b --- /dev/null +++ b/tests/constants/request_headers.ts @@ -0,0 +1,55 @@ +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; +} diff --git a/tests/helpers/awsGatewayHelper.ts b/tests/helpers/awsGatewayHelper.ts new file mode 100644 index 00000000..9e0db8bb --- /dev/null +++ b/tests/helpers/awsGatewayHelper.ts @@ -0,0 +1,15 @@ +import { APIGatewayClient, GetRestApisCommand } from "@aws-sdk/client-api-gateway"; +import { AWS_REGION, API_NAME } from "../constants/api_constants"; + +export async function getRestApiGatewayBaseUrl(): Promise { + + const region = AWS_REGION; + const client = new APIGatewayClient({ region }); + + const apis = await client.send(new GetRestApisCommand({})); + const api = apis.items?.find((a) => a.name === API_NAME); + + if (!api?.id) throw new Error(`API with name "${API_NAME}" not found.`); + + return `https://${api.id}.execute-api.${region}.amazonaws.com/main`; +} diff --git a/tests/helpers/commonTypes.ts b/tests/helpers/commonTypes.ts new file mode 100644 index 00000000..465ea0ff --- /dev/null +++ b/tests/helpers/commonTypes.ts @@ -0,0 +1,16 @@ +export type ErrorLink = { + about: string; +}; + +type ErrorResponse = { + id: string; + code: string; + links: ErrorLink; + status: string; + title: string; + detail: string; +}; + +export type ErrorMessageBody = { + errors: ErrorResponse[]; +}; diff --git a/tests/helpers/generate_fetch_testData.ts b/tests/helpers/generate_fetch_testData.ts new file mode 100644 index 00000000..3dafa9a9 --- /dev/null +++ b/tests/helpers/generate_fetch_testData.ts @@ -0,0 +1,70 @@ +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, id }, + ReturnValues: 'ALL_OLD', + }) + ) + return resp.Attributes; +} diff --git a/tests/helpers/pnpmHelpers.ts b/tests/helpers/pnpmHelpers.ts new file mode 100644 index 00000000..e0e748c0 --- /dev/null +++ b/tests/helpers/pnpmHelpers.ts @@ -0,0 +1,76 @@ +import { spawn } from 'child_process'; +import path from 'path'; + +/** + * Runs the "create-letter" CLI command via npm. + * + * @param options Command-line options for the script. + */ +export async function runCreateLetter(options: { + filter?: string; + supplierId: string; + environment: string; + awsAccountId: string; + groupId: string; + specificationId: string; + status: string; + count: number; +}) { + const { + filter, + supplierId, + environment, + awsAccountId, + groupId, + specificationId, + status, + count, + } = 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); + + // Build arguments array + const args = [ + '-w', String(filter), + // '--filter', String(filter), + 'run', + 'cli', + '--', + 'create-letter', + '--supplier-id', + supplierId, + '--environment', + environment, + '--awsAccountId', + awsAccountId, + '--group-id', + groupId, + '--specification-id', + specificationId, + '--status', + status, + '--count', + String(count), + ]; + console.log('🚀 Running:', [cmd, ...args].join(' ')); + + await new Promise((resolve, reject) => { + let output = ''; + const child = spawn(cmd, args, { + stdio: 'inherit', + cwd: root, + shell: false, + }); + 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); + }); +} diff --git a/tests/helpers/validateJsonSchema.ts b/tests/helpers/validateJsonSchema.ts new file mode 100644 index 00000000..e8e70cce --- /dev/null +++ b/tests/helpers/validateJsonSchema.ts @@ -0,0 +1,42 @@ +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/package.json b/tests/package.json index 53926dff..78743234 100644 --- a/tests/package.json +++ b/tests/package.json @@ -1,20 +1,23 @@ { "author": "", "dependencies": { + "@aws-sdk/client-api-gateway": "^3.906.0", + "@aws-sdk/client-dynamodb": "^3.858.0", + "@aws-sdk/lib-dynamodb": "^3.858.0", "allure-js-commons": "^3.3.3", "charenc": "^0.0.2", "crypt": "^0.0.2", "dotenv": "^17.2.2", - "fsevents": "^2.3.2", "is-buffer": "^1.1.6", "md5": "^2.3.0", + "openapi-response-validator": "^12.1.3", "playwright": "^1.54.2", "playwright-core": "^1.54.2", "undici-types": "^7.10.0" }, "description": "", "devDependencies": { - "@playwright/test": "^1.55.0", + "@playwright/test": "^1.55.1", "@types/node": "^24.3.1", "allure-commandline": "^2.34.1", "allure-playwright": "^3.3.3" @@ -22,10 +25,11 @@ "keywords": [], "license": "ISC", "main": "index.js", - "name": "tests", + "name": "nhs-notify-supplier-api-tests", "scripts": { "clean": "rimraf $(pwd)/target", - "test": "playwright test tests --config=config/main.config.ts --max-failures=10" + "test:component": "playwright test --config=config/main.config.ts --max-failures=10 --project=component-tests", + "test:sandbox": "playwright test --config=config/sandbox.config.ts --max-failures=10 --project=sandbox" }, "version": "1.0.0" } diff --git a/tests/sandbox/getLetterStatus.spec.ts b/tests/sandbox/getLetterStatus.spec.ts new file mode 100644 index 00000000..3d10845d --- /dev/null +++ b/tests/sandbox/getLetterStatus.spec.ts @@ -0,0 +1,21 @@ +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 new file mode 100644 index 00000000..e2bca376 --- /dev/null +++ b/tests/sandbox/getListOfLetters.spec.ts @@ -0,0 +1,27 @@ +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/messages/get_single_letter/test_401.spec.ts b/tests/sandbox/messages/get_single_letter/test_401.spec.ts deleted file mode 100644 index 98fde1f4..00000000 --- a/tests/sandbox/messages/get_single_letter/test_401.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { test, expect } from '@playwright/test'; -import { LETTERS_ENDPOINT} from '../../../constants/api_constants'; - -// Constants -const status = "PENDING"; - -test("401 when invalid APIKEY is passed", async ({ request }) => { - const headers = { - headerauth1 : 'headervalue1', - apikey : '', - Authorization: '1234' - }; - - const API_URL = process.env.DEV_API_GATEWAY_URL; - const API_GATEWAY_URL = `${API_URL}${LETTERS_ENDPOINT}`; - - const response = await request.get(API_GATEWAY_URL, { - params: { - status: `${status}` - }, - headers - }); - - await expect(response.status()).toBe(401); - }); diff --git a/tests/sandbox/messages/get_single_letter/test_success.spec.ts b/tests/sandbox/messages/get_single_letter/test_success.spec.ts deleted file mode 100644 index 27e04f5a..00000000 --- a/tests/sandbox/messages/get_single_letter/test_success.spec.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { test, expect } from '@playwright/test'; -import { LETTERS_ENDPOINT} from '../../../constants/api_constants'; - -// Constants -const STATUS = 'PENDING'; - -test("200 when valid input is passed", async ({ request }) => { - const headers = { - headerauth1: 'headervalue1', - apikey: process.env.API_KEY!, - Authorization: '1234' - }; - - const API_URL = process.env.DEV_API_GATEWAY_URL; - const API_GATEWAY_URL = `${API_URL}${LETTERS_ENDPOINT}`; - - const response = await request.get(API_GATEWAY_URL, { - params: { - status: `${STATUS}` - }, - headers - }); - - await expect(response.status()).toBe(200); - expect(response.ok()).toBeTruthy(); - }); diff --git a/tests/sandbox/testCases/getLetterStatus_testCases.ts b/tests/sandbox/testCases/getLetterStatus_testCases.ts new file mode 100644 index 00000000..54333de1 --- /dev/null +++ b/tests/sandbox/testCases/getLetterStatus_testCases.ts @@ -0,0 +1,137 @@ +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: number; + 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: 100, + 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: 100 + }, + } + } +}, +{ + 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 new file mode 100644 index 00000000..422786d0 --- /dev/null +++ b/tests/sandbox/testCases/getListOfLetters_testCases.ts @@ -0,0 +1,97 @@ +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/updateLetterStatus_testCases.ts b/tests/sandbox/testCases/updateLetterStatus_testCases.ts new file mode 100644 index 00000000..35765676 --- /dev/null +++ b/tests/sandbox/testCases/updateLetterStatus_testCases.ts @@ -0,0 +1,123 @@ + +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: 100, + reasonText: 'failed validation', + }, + } + }, + expectedStatus: 200, + expectedResponse: { + data: { + type: 'Letter', + id: '2WL5eYSWGzCHlGmzNxuqVusPxDg', + attributes: { + reasonCode: 100, + 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, DESTROYED, 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, DESTROYED, FORWARDED', + errorCode: 'enum.openapi.validation' + }] + }, + }, +]; diff --git a/tests/sandbox/testCases/updateMultipleStatus_testCases.ts b/tests/sandbox/testCases/updateMultipleStatus_testCases.ts new file mode 100644 index 00000000..f94a17e7 --- /dev/null +++ b/tests/sandbox/testCases/updateMultipleStatus_testCases.ts @@ -0,0 +1,125 @@ +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 | number; + 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: 100, + reasonText: 'failed validation', + status: 'RETURNED' + }, + id: '2WL5eYSWGzCHlGmzNxuqVusPxDg', + type: 'Letter' + }, + { + attributes: { + reasonCode: 100, + reasonText: 'failed validation', + status: 'CANCELLED' + }, + id: '2WL5eYSWGzCHlGmzNxuqVusPxDg', + type: 'Letter' + }, + { + attributes: { + reasonCode: 100, + reasonText: 'failed validation', + status: 'FAILED' + }, + id: '2WL5eYSWGzCHlGmzNxuqVusPxDg', + type: 'Letter' + }, + { + attributes: { + reasonCode: 100, + 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/updateLetterStatus.spec.ts b/tests/sandbox/updateLetterStatus.spec.ts new file mode 100644 index 00000000..9e0d9a4d --- /dev/null +++ b/tests/sandbox/updateLetterStatus.spec.ts @@ -0,0 +1,22 @@ +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 new file mode 100644 index 00000000..524229bf --- /dev/null +++ b/tests/sandbox/updateMultipleLetterStatus.spec.ts @@ -0,0 +1,18 @@ +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); + }); + }); +});