diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 633834be3a..5cc663dae1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,7 @@ jobs: echo "TAG_FORMAT=$TAG_FORMAT" >> "$GITHUB_OUTPUT" quality_checks: - uses: NHSDigital/eps-common-workflows/.github/workflows/quality-checks.yml@b933ef1bb3527fd7e7d5a7629fbd4e4dd94bf1b4 + uses: NHSDigital/eps-common-workflows/.github/workflows/quality-checks.yml@e31e25273fb87450be4ef763ddbed4f531c45f8e needs: [get_asdf_version] with: asdfVersion: ${{ needs.get_asdf_version.outputs.asdf_version }} @@ -46,7 +46,7 @@ jobs: tag_release: needs: [quality_checks, get_commit_id, get_asdf_version] - uses: NHSDigital/eps-common-workflows/.github/workflows/tag-release.yml@b933ef1bb3527fd7e7d5a7629fbd4e4dd94bf1b4 + uses: NHSDigital/eps-common-workflows/.github/workflows/tag-release.yml@e31e25273fb87450be4ef763ddbed4f531c45f8e with: dry_run: true asdfVersion: ${{ needs.get_asdf_version.outputs.asdf_version }} diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 316fbd5b8f..5d1fc53342 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -10,7 +10,7 @@ env: jobs: dependabot-auto-approve-and-merge: needs: quality_checks - uses: NHSDigital/eps-common-workflows/.github/workflows/dependabot-auto-approve-and-merge.yml@b933ef1bb3527fd7e7d5a7629fbd4e4dd94bf1b4 + uses: NHSDigital/eps-common-workflows/.github/workflows/dependabot-auto-approve-and-merge.yml@e31e25273fb87450be4ef763ddbed4f531c45f8e secrets: AUTOMERGE_APP_ID: ${{ secrets.AUTOMERGE_APP_ID }} AUTOMERGE_PEM: ${{ secrets.AUTOMERGE_PEM }} @@ -50,7 +50,7 @@ jobs: quality_checks: # always run, but only block in the non-skip case needs: [get_commit_message, get_asdf_version] - uses: NHSDigital/eps-common-workflows/.github/workflows/quality-checks.yml@b933ef1bb3527fd7e7d5a7629fbd4e4dd94bf1b4 + uses: NHSDigital/eps-common-workflows/.github/workflows/quality-checks.yml@e31e25273fb87450be4ef763ddbed4f531c45f8e with: asdfVersion: ${{ needs.get_asdf_version.outputs.asdf_version }} secrets: @@ -120,7 +120,7 @@ jobs: run: echo "Skipping QC gate per commit message." pr_title_format_check: - uses: NHSDigital/eps-common-workflows/.github/workflows/pr_title_check.yml@b933ef1bb3527fd7e7d5a7629fbd4e4dd94bf1b4 + uses: NHSDigital/eps-common-workflows/.github/workflows/pr_title_check.yml@e31e25273fb87450be4ef763ddbed4f531c45f8e get_issue_number: runs-on: ubuntu-22.04 @@ -150,7 +150,7 @@ jobs: tag_release: needs: [get_asdf_version] - uses: NHSDigital/eps-common-workflows/.github/workflows/tag-release.yml@b933ef1bb3527fd7e7d5a7629fbd4e4dd94bf1b4 + uses: NHSDigital/eps-common-workflows/.github/workflows/tag-release.yml@e31e25273fb87450be4ef763ddbed4f531c45f8e with: dry_run: true asdfVersion: ${{ needs.get_asdf_version.outputs.asdf_version }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3be4ed34bc..5edc698308 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,7 +25,7 @@ jobs: TAG_FORMAT=$(yq '.TAG_FORMAT' .github/config/settings.yml) echo "TAG_FORMAT=$TAG_FORMAT" >> "$GITHUB_OUTPUT" quality_checks: - uses: NHSDigital/eps-common-workflows/.github/workflows/quality-checks.yml@b933ef1bb3527fd7e7d5a7629fbd4e4dd94bf1b4 + uses: NHSDigital/eps-common-workflows/.github/workflows/quality-checks.yml@e31e25273fb87450be4ef763ddbed4f531c45f8e needs: [get_asdf_version] with: asdfVersion: ${{ needs.get_asdf_version.outputs.asdf_version }} @@ -44,7 +44,7 @@ jobs: tag_release: needs: [quality_checks, get_commit_id, get_asdf_version] - uses: NHSDigital/eps-common-workflows/.github/workflows/tag-release.yml@b933ef1bb3527fd7e7d5a7629fbd4e4dd94bf1b4 + uses: NHSDigital/eps-common-workflows/.github/workflows/tag-release.yml@e31e25273fb87450be4ef763ddbed4f531c45f8e with: dry_run: false asdfVersion: ${{ needs.get_asdf_version.outputs.asdf_version }} diff --git a/.github/workflows/run_regression_tests.yml b/.github/workflows/run_regression_tests.yml index 7af591cba5..02229286f5 100644 --- a/.github/workflows/run_regression_tests.yml +++ b/.github/workflows/run_regression_tests.yml @@ -73,8 +73,8 @@ jobs: GITHUB-TOKEN: ${{ steps.generate-token.outputs.token }} run: | if [[ "$TARGET_ENVIRONMENT" != "prod" && "$TARGET_ENVIRONMENT" != "ref" ]]; then - REGRESSION_TEST_REPO_TAG="v3.8.10" # This is the tag or branch of the regression test code to run, usually a version tag like v3.1.0 or a branch name - REGRESSION_TEST_WORKFLOW_TAG="v3.8.10" # This is the tag of the github workflow to run, usually the same as REGRESSION_TEST_REPO_TAG + REGRESSION_TEST_REPO_TAG="v3.8.20" # This is the tag or branch of the regression test code to run, usually a version tag like v3.1.0 or a branch name + REGRESSION_TEST_WORKFLOW_TAG="v3.8.20" # This is the tag of the github workflow to run, usually the same as REGRESSION_TEST_REPO_TAG if [[ -z "$REGRESSION_TEST_REPO_TAG" || -z "$REGRESSION_TEST_WORKFLOW_TAG" ]]; then diff --git a/.trivyignore.yaml b/.trivyignore.yaml index c2260d5d38..cc00f1064e 100644 --- a/.trivyignore.yaml +++ b/.trivyignore.yaml @@ -9,3 +9,8 @@ vulnerabilities: - "package-lock.json" statement: downstream dependency just used in build stage expired_at: 2026-06-01 + - id: CVE-2026-25128 + paths: + - "package-lock.json" + statement: downstream dependency of fast-xml-parser + expired_at: 2026-06-01 diff --git a/.vscode/eps-prescription-tracker-ui.code-workspace b/.vscode/eps-prescription-tracker-ui.code-workspace index 4dd24e7aee..ea4e4dcf8b 100644 --- a/.vscode/eps-prescription-tracker-ui.code-workspace +++ b/.vscode/eps-prescription-tracker-ui.code-workspace @@ -96,7 +96,11 @@ "packages/staticContent", "packages/common/testing", "packages/common/commonTypes", - "packages/staticContent" + "packages/staticContent", + "packages/cognito", + "packages/prescriptionDetailsLambda", + "packages/common/authFunctions", + "packages/testingSupport" ], "jest.useJest30": true, "python.testing.pytestEnabled": false, @@ -207,7 +211,8 @@ }, "[scss]": { "editor.defaultFormatter": "vscode.css-language-features" - } + }, + "vitest.configSearchPatternExclude": "{**/node_modules/**,**/.*/**,**/*.d.ts,**/lib/*}" }, "extensions": { "recommendations": [ @@ -229,7 +234,8 @@ "streetsidesoftware.code-spell-checker", "timonwong.shellcheck", "mkhl.direnv", - "tamasfe.even-better-toml" + "tamasfe.even-better-toml", + "vitest.explorer" ] } } diff --git a/Makefile b/Makefile index d880ec03e8..b0d7062e04 100644 --- a/Makefile +++ b/Makefile @@ -72,44 +72,8 @@ test: compile npm run test --workspace packages/testingSupport/clearActiveSessions clean: - rm -rf packages/cdk/coverage - rm -rf packages/cdk/lib - rm -rf packages/CIS2SignOutLambda/coverage - rm -rf packages/CIS2SignOutLambda/lib - rm -rf packages/cloudfrontFunctions/coverage - rm -rf packages/cloudfrontFunctions/lib - rm -rf packages/cognito/coverage - rm -rf packages/cognito/lib - rm -rf packages/cpt-ui/dist - rm -rf packages/cpt-ui/coverage - rm -rf packages/patientSearchLambda/coverage - rm -rf packages/patientSearchLambda/lib - rm -rf packages/prescriptionDetailsLambda/coverage - rm -rf packages/prescriptionDetailsLambda/lib - rm -rf packages/prescriptionListLambda/coverage - rm -rf packages/prescriptionListLambda/lib - rm -rf packages/selectedRoleLambda/coverage - rm -rf packages/selectedRoleLambda/lib - rm -rf packages/trackerUserInfoLambda/coverage - rm -rf packages/trackerUserInfoLambda/lib - rm -rf packages/sessionManagementLambda/coverage - rm -rf packages/sessionManagementLambda/lib - rm -rf packages/common/authFunctions/coverage - rm -rf packages/common/authFunctions/lib - rm -rf packages/common/commonTypes/coverage - rm -rf packages/common/commonTypes/lib - rm -rf packages/common/doHSClient/coverage - rm -rf packages/common/doHSClient/lib - rm -rf packages/common/dynamoFunctions/coverage - rm -rf packages/common/dynamoFunctions/lib - rm -rf packages/common/lambdaUtils/coverage - rm -rf packages/common/lambdaUtils/lib - rm -rf packages/common/middyErrorHandler/coverage - rm -rf packages/common/middyErrorHandler/lib - rm -rf packages/common/pdsClient/coverage - rm -rf packages/common/pdsClient/lib - rm -rf packages/common/testing/coverage - rm -rf packages/common/testing/lib + find . -name 'coverage' -type d -prune -exec rm -rf '{}' + + find . -name 'lib' -type d -prune -exec rm -rf '{}' + rm -rf cdk.out rm -rf .local_config rm -rf cfn_guard_output diff --git a/package-lock.json b/package-lock.json index a1422f11b8..81e02be57d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,9 +38,10 @@ "@eslint/js": "^9.38.0", "@jest/globals": "^30.1.1", "@types/jest": "^30.0.0", - "@types/node": "^25.0.9", + "@types/node": "^25.1.0", "@typescript-eslint/eslint-plugin": "^8.48.0", "@typescript-eslint/parser": "^8.50.1", + "@vitest/coverage-v8": "^4.0.18", "aws-sdk-client-mock": "^4.1.0", "eslint": "^9.39.2", "eslint-config-prettier": "^10.1.8", @@ -55,7 +56,8 @@ "ts-jest": "^29.4.6", "ts-node": "^10.9.2", "typescript": "^5.9.3", - "typescript-eslint": "^8.52.0" + "typescript-eslint": "^8.52.0", + "vitest": "^4.0.18" } }, "node_modules/@adobe/css-tools": { @@ -581,47 +583,47 @@ } }, "node_modules/@aws-sdk/client-dynamodb": { - "version": "3.975.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.975.0.tgz", - "integrity": "sha512-Cq6oGb8XswG56YhF2kHmxuyEnMNayDpL8xDxp9E4zIUqDeSLCKE6lCaqZzo5zpngzqLluFsJbCC9jrMzZejMAA==", + "version": "3.980.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.980.0.tgz", + "integrity": "sha512-1rGhAx4cHZy3pMB3R3r84qMT5WEvQ6ajr2UksnD48fjQxwaUcpI6NsPvU5j/5BI5LqGiUO6ThOrMwSMm95twQA==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.1", - "@aws-sdk/credential-provider-node": "^3.972.1", - "@aws-sdk/dynamodb-codec": "^3.972.2", - "@aws-sdk/middleware-endpoint-discovery": "^3.972.1", - "@aws-sdk/middleware-host-header": "^3.972.1", - "@aws-sdk/middleware-logger": "^3.972.1", - "@aws-sdk/middleware-recursion-detection": "^3.972.1", - "@aws-sdk/middleware-user-agent": "^3.972.2", - "@aws-sdk/region-config-resolver": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "^3.972.1", - "@aws-sdk/util-user-agent-node": "^3.972.1", + "@aws-sdk/core": "^3.973.5", + "@aws-sdk/credential-provider-node": "^3.972.4", + "@aws-sdk/dynamodb-codec": "^3.972.5", + "@aws-sdk/middleware-endpoint-discovery": "^3.972.3", + "@aws-sdk/middleware-host-header": "^3.972.3", + "@aws-sdk/middleware-logger": "^3.972.3", + "@aws-sdk/middleware-recursion-detection": "^3.972.3", + "@aws-sdk/middleware-user-agent": "^3.972.5", + "@aws-sdk/region-config-resolver": "^3.972.3", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.980.0", + "@aws-sdk/util-user-agent-browser": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.3", "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.21.1", + "@smithy/core": "^3.22.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.11", - "@smithy/middleware-retry": "^4.4.27", + "@smithy/middleware-endpoint": "^4.4.12", + "@smithy/middleware-retry": "^4.4.29", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.12", + "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@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.26", - "@smithy/util-defaults-mode-node": "^4.2.29", + "@smithy/util-defaults-mode-browser": "^4.3.28", + "@smithy/util-defaults-mode-node": "^4.2.31", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", @@ -646,6 +648,22 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/client-dynamodb/node_modules/@aws-sdk/util-endpoints": { + "version": "3.980.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.980.0.tgz", + "integrity": "sha512-AjKBNEc+rjOZQE1HwcD9aCELqg1GmUj1rtICKuY8cgwB73xJ4U/kNyqKKpN2k9emGqlfDY2D8itIp/vDc6OKpw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-endpoints": "^3.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/client-dynamodb/node_modules/@smithy/types": { "version": "4.12.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", @@ -1620,26 +1638,26 @@ "license": "MIT" }, "node_modules/@aws-sdk/client-lambda": { - "version": "3.975.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-lambda/-/client-lambda-3.975.0.tgz", - "integrity": "sha512-55+/Ku+fd1HY3TVKep/4GqgiR65p09/Xfgebknx8mqy18lTohO/8VFn7AusoZGOVypfRv3yVuYktCvINBBrkKw==", + "version": "3.978.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-lambda/-/client-lambda-3.978.0.tgz", + "integrity": "sha512-R3XJh7r0m7iimku6IgDJ6mS/s2CUJVA1oicIf9/YVudEVkU3drOV3MZpzBHJwmBvBXVM8jDOA7qkfDmgVHHJSA==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.1", - "@aws-sdk/credential-provider-node": "^3.972.1", - "@aws-sdk/middleware-host-header": "^3.972.1", - "@aws-sdk/middleware-logger": "^3.972.1", - "@aws-sdk/middleware-recursion-detection": "^3.972.1", - "@aws-sdk/middleware-user-agent": "^3.972.2", - "@aws-sdk/region-config-resolver": "^3.972.1", - "@aws-sdk/types": "^3.973.0", + "@aws-sdk/core": "^3.973.4", + "@aws-sdk/credential-provider-node": "^3.972.2", + "@aws-sdk/middleware-host-header": "^3.972.2", + "@aws-sdk/middleware-logger": "^3.972.2", + "@aws-sdk/middleware-recursion-detection": "^3.972.2", + "@aws-sdk/middleware-user-agent": "^3.972.4", + "@aws-sdk/region-config-resolver": "^3.972.2", + "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "^3.972.1", - "@aws-sdk/util-user-agent-node": "^3.972.1", + "@aws-sdk/util-user-agent-browser": "^3.972.2", + "@aws-sdk/util-user-agent-node": "^3.972.2", "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.21.1", + "@smithy/core": "^3.22.0", "@smithy/eventstream-serde-browser": "^4.2.8", "@smithy/eventstream-serde-config-resolver": "^4.3.8", "@smithy/eventstream-serde-node": "^4.2.8", @@ -1647,21 +1665,21 @@ "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.11", - "@smithy/middleware-retry": "^4.4.27", + "@smithy/middleware-endpoint": "^4.4.12", + "@smithy/middleware-retry": "^4.4.29", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.12", + "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@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.26", - "@smithy/util-defaults-mode-node": "^4.2.29", + "@smithy/util-defaults-mode-browser": "^4.3.28", + "@smithy/util-defaults-mode-node": "^4.2.31", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", @@ -2185,45 +2203,45 @@ "license": "MIT" }, "node_modules/@aws-sdk/client-secrets-manager": { - "version": "3.975.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.975.0.tgz", - "integrity": "sha512-KY67ghh2BBBhfaCvOquazOWWTe8CEaEsKOFtNVtECIttRlmm1YAuIDUTk7reaQhTqb+wwuS2xoGsu5z1FZkFyA==", + "version": "3.980.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.980.0.tgz", + "integrity": "sha512-TeDBmkR8x3toPnvkFMBG73QqxsWjksFUMJyR0C4tZjVXjFq9igGwq8nHYDrQA0Hony6tGvH0SyNsjsL5w5qTww==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.1", - "@aws-sdk/credential-provider-node": "^3.972.1", - "@aws-sdk/middleware-host-header": "^3.972.1", - "@aws-sdk/middleware-logger": "^3.972.1", - "@aws-sdk/middleware-recursion-detection": "^3.972.1", - "@aws-sdk/middleware-user-agent": "^3.972.2", - "@aws-sdk/region-config-resolver": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "^3.972.1", - "@aws-sdk/util-user-agent-node": "^3.972.1", + "@aws-sdk/core": "^3.973.5", + "@aws-sdk/credential-provider-node": "^3.972.4", + "@aws-sdk/middleware-host-header": "^3.972.3", + "@aws-sdk/middleware-logger": "^3.972.3", + "@aws-sdk/middleware-recursion-detection": "^3.972.3", + "@aws-sdk/middleware-user-agent": "^3.972.5", + "@aws-sdk/region-config-resolver": "^3.972.3", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.980.0", + "@aws-sdk/util-user-agent-browser": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.3", "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.21.1", + "@smithy/core": "^3.22.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.11", - "@smithy/middleware-retry": "^4.4.27", + "@smithy/middleware-endpoint": "^4.4.12", + "@smithy/middleware-retry": "^4.4.29", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.12", + "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@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.26", - "@smithy/util-defaults-mode-node": "^4.2.29", + "@smithy/util-defaults-mode-browser": "^4.3.28", + "@smithy/util-defaults-mode-node": "^4.2.31", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", @@ -2247,6 +2265,22 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/util-endpoints": { + "version": "3.980.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.980.0.tgz", + "integrity": "sha512-AjKBNEc+rjOZQE1HwcD9aCELqg1GmUj1rtICKuY8cgwB73xJ4U/kNyqKKpN2k9emGqlfDY2D8itIp/vDc6OKpw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-endpoints": "^3.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/client-secrets-manager/node_modules/@smithy/types": { "version": "4.12.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", @@ -2273,44 +2307,44 @@ } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.975.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.975.0.tgz", - "integrity": "sha512-HpgJuleH7P6uILxzJKQOmlHdwaCY+xYC6VgRDzlwVEqU/HXjo4m2gOAyjUbpXlBOCWfGgMUzfBlNJ9z3MboqEQ==", + "version": "3.980.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.980.0.tgz", + "integrity": "sha512-AhNXQaJ46C1I+lQ+6Kj+L24il5K9lqqIanJd8lMszPmP7bLnmX0wTKK0dxywcvrLdij3zhWttjAKEBNgLtS8/A==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.1", - "@aws-sdk/middleware-host-header": "^3.972.1", - "@aws-sdk/middleware-logger": "^3.972.1", - "@aws-sdk/middleware-recursion-detection": "^3.972.1", - "@aws-sdk/middleware-user-agent": "^3.972.2", - "@aws-sdk/region-config-resolver": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "^3.972.1", - "@aws-sdk/util-user-agent-node": "^3.972.1", + "@aws-sdk/core": "^3.973.5", + "@aws-sdk/middleware-host-header": "^3.972.3", + "@aws-sdk/middleware-logger": "^3.972.3", + "@aws-sdk/middleware-recursion-detection": "^3.972.3", + "@aws-sdk/middleware-user-agent": "^3.972.5", + "@aws-sdk/region-config-resolver": "^3.972.3", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.980.0", + "@aws-sdk/util-user-agent-browser": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.3", "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.21.1", + "@smithy/core": "^3.22.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.11", - "@smithy/middleware-retry": "^4.4.27", + "@smithy/middleware-endpoint": "^4.4.12", + "@smithy/middleware-retry": "^4.4.29", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.12", + "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@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.26", - "@smithy/util-defaults-mode-node": "^4.2.29", + "@smithy/util-defaults-mode-browser": "^4.3.28", + "@smithy/util-defaults-mode-node": "^4.2.31", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", @@ -2808,6 +2842,22 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/util-endpoints": { + "version": "3.980.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.980.0.tgz", + "integrity": "sha512-AjKBNEc+rjOZQE1HwcD9aCELqg1GmUj1rtICKuY8cgwB73xJ4U/kNyqKKpN2k9emGqlfDY2D8itIp/vDc6OKpw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-endpoints": "^3.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/client-sso/node_modules/@smithy/types": { "version": "4.12.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", @@ -3306,19 +3356,19 @@ "license": "MIT" }, "node_modules/@aws-sdk/core": { - "version": "3.973.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.3.tgz", - "integrity": "sha512-ZbM2Xy8ytAcfnNpkBltr6Qdw36W/4NW5nZdZieCuTfacoBFpi/NYiwb8U05KNJvLKeZnrV9Vi696i+r2DQFORg==", + "version": "3.973.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.5.tgz", + "integrity": "sha512-IMM7xGfLGW6lMvubsA4j6BHU5FPgGAxoQ/NA63KqNLMwTS+PeMBcx8DPHL12Vg6yqOZnqok9Mu4H2BdQyq7gSA==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/xml-builder": "^3.972.2", - "@smithy/core": "^3.21.1", + "@smithy/core": "^3.22.0", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", - "@smithy/smithy-client": "^4.10.12", + "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", @@ -3368,12 +3418,12 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.2.tgz", - "integrity": "sha512-wzH1EdrZsytG1xN9UHaK12J9+kfrnd2+c8y0LVoS4O4laEjPoie1qVK3k8/rZe7KOtvULzyMnO3FT4Krr9Z0Dg==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.3.tgz", + "integrity": "sha512-OBYNY4xQPq7Rx+oOhtyuyO0AQvdJSpXRg7JuPNBJH4a1XXIzJQl4UHQTPKZKwfJXmYLpv4+OkcFen4LYmDPd3g==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.2", + "@aws-sdk/core": "^3.973.5", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/types": "^4.12.0", @@ -3409,18 +3459,18 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.3.tgz", - "integrity": "sha512-IbBGWhaxiEl64fznwh5PDEB0N7YJEAvK5b6nRtPVUKdKAHlOPgo6B9XB8mqWDs8Ct0oF/E34ZLiq2U0L5xDkrg==", + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.5.tgz", + "integrity": "sha512-GpvBgEmSZPvlDekd26Zi+XsI27Qz7y0utUx0g2fSTSiDzhnd1FSa1owuodxR0BcUKNL7U2cOVhhDxgZ4iSoPVg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.2", + "@aws-sdk/core": "^3.973.5", "@aws-sdk/types": "^3.973.1", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/node-http-handler": "^4.4.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.12", + "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "@smithy/util-stream": "^4.5.10", "tslib": "^2.6.2" @@ -3455,19 +3505,19 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.2.tgz", - "integrity": "sha512-Jrb8sLm6k8+L7520irBrvCtdLxNtrG7arIxe9TCeMJt/HxqMGJdbIjw8wILzkEHLMIi4MecF2FbXCln7OT1Tag==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.973.2", - "@aws-sdk/credential-provider-env": "^3.972.2", - "@aws-sdk/credential-provider-http": "^3.972.3", - "@aws-sdk/credential-provider-login": "^3.972.2", - "@aws-sdk/credential-provider-process": "^3.972.2", - "@aws-sdk/credential-provider-sso": "^3.972.2", - "@aws-sdk/credential-provider-web-identity": "^3.972.2", - "@aws-sdk/nested-clients": "3.975.0", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.3.tgz", + "integrity": "sha512-rMQAIxstP7cLgYfsRGrGOlpyMl0l8JL2mcke3dsIPLWke05zKOFyR7yoJzWCsI/QiIxjRbxpvPiAeKEA6CoYkg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.5", + "@aws-sdk/credential-provider-env": "^3.972.3", + "@aws-sdk/credential-provider-http": "^3.972.5", + "@aws-sdk/credential-provider-login": "^3.972.3", + "@aws-sdk/credential-provider-process": "^3.972.3", + "@aws-sdk/credential-provider-sso": "^3.972.3", + "@aws-sdk/credential-provider-web-identity": "^3.972.3", + "@aws-sdk/nested-clients": "3.980.0", "@aws-sdk/types": "^3.973.1", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", @@ -3505,13 +3555,13 @@ } }, "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.2.tgz", - "integrity": "sha512-mlaw2aiI3DrimW85ZMn3g7qrtHueidS58IGytZ+mbFpsYLK5wMjCAKZQtt7VatLMtSBG/dn/EY4njbnYXIDKeQ==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.3.tgz", + "integrity": "sha512-Gc3O91iVvA47kp2CLIXOwuo5ffo1cIpmmyIewcYjAcvurdFHQ8YdcBe1KHidnbbBO4/ZtywGBACsAX5vr3UdoA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.2", - "@aws-sdk/nested-clients": "3.975.0", + "@aws-sdk/core": "^3.973.5", + "@aws-sdk/nested-clients": "3.980.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", @@ -3549,17 +3599,17 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.2.tgz", - "integrity": "sha512-Lz1J5IZdTjLYTVIcDP5DVDgi1xlgsF3p1cnvmbfKbjCRhQpftN2e2J4NFfRRvPD54W9+bZ8l5VipPXtTYK7aEg==", + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.4.tgz", + "integrity": "sha512-UwerdzosMSY7V5oIZm3NsMDZPv2aSVzSkZxYxIOWHBeKTZlUqW7XpHtJMZ4PZpJ+HMRhgP+MDGQx4THndgqJfQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.2", - "@aws-sdk/credential-provider-http": "^3.972.3", - "@aws-sdk/credential-provider-ini": "^3.972.2", - "@aws-sdk/credential-provider-process": "^3.972.2", - "@aws-sdk/credential-provider-sso": "^3.972.2", - "@aws-sdk/credential-provider-web-identity": "^3.972.2", + "@aws-sdk/credential-provider-env": "^3.972.3", + "@aws-sdk/credential-provider-http": "^3.972.5", + "@aws-sdk/credential-provider-ini": "^3.972.3", + "@aws-sdk/credential-provider-process": "^3.972.3", + "@aws-sdk/credential-provider-sso": "^3.972.3", + "@aws-sdk/credential-provider-web-identity": "^3.972.3", "@aws-sdk/types": "^3.973.1", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", @@ -3597,12 +3647,12 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.2.tgz", - "integrity": "sha512-NLKLTT7jnUe9GpQAVkPTJO+cs2FjlQDt5fArIYS7h/Iw/CvamzgGYGFRVD2SE05nOHCMwafUSi42If8esGFV+g==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.3.tgz", + "integrity": "sha512-xkSY7zjRqeVc6TXK2xr3z1bTLm0wD8cj3lAkproRGaO4Ku7dPlKy843YKnHrUOUzOnMezdZ4xtmFc0eKIDTo2w==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.2", + "@aws-sdk/core": "^3.973.5", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -3639,14 +3689,14 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.2.tgz", - "integrity": "sha512-YpwDn8g3gCGUl61cCV0sRxP2pFIwg+ZsMfWQ/GalSyjXtRkctCMFA+u0yPb/Q4uTfNEiya1Y4nm0C5rIHyPW5Q==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.3.tgz", + "integrity": "sha512-8Ww3F5Ngk8dZ6JPL/V5LhCU1BwMfQd3tLdoEuzaewX8FdnT633tPr+KTHySz9FK7fFPcz5qG3R5edVEhWQD4AA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.975.0", - "@aws-sdk/core": "^3.973.2", - "@aws-sdk/token-providers": "3.975.0", + "@aws-sdk/client-sso": "3.980.0", + "@aws-sdk/core": "^3.973.5", + "@aws-sdk/token-providers": "3.980.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -3683,13 +3733,13 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.2.tgz", - "integrity": "sha512-x9DAiN9Qz+NjJ99ltDiVQ8d511M/tuF/9MFbe2jUgo7HZhD6+x4S3iT1YcP07ndwDUjmzKGmeOEgE24k4qvfdg==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.3.tgz", + "integrity": "sha512-62VufdcH5rRfiRKZRcf1wVbbt/1jAntMj1+J0qAd+r5pQRg2t0/P9/Rz16B1o5/0Se9lVL506LRjrhIJAhYBfA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.2", - "@aws-sdk/nested-clients": "3.975.0", + "@aws-sdk/core": "^3.973.5", + "@aws-sdk/nested-clients": "3.980.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -3726,14 +3776,14 @@ } }, "node_modules/@aws-sdk/dynamodb-codec": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/dynamodb-codec/-/dynamodb-codec-3.972.3.tgz", - "integrity": "sha512-ZkCxaFHN/v30ZLU/F/lok7yNNVwavaAM8IwDVmCAnCvb4bNIueCw40w5iI8DNGMmnRPa+mfp+rM1LIYVSjuMDQ==", + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/dynamodb-codec/-/dynamodb-codec-3.972.5.tgz", + "integrity": "sha512-gFR4w3dIkaZ82kFFjil7RFtukS2y2fXrDNDfgc94DhKjjOQMJEcHM5o1GGaQE4jd2mOQfHvbeQ0ktU8xGXhHjQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.2", - "@smithy/core": "^3.21.1", - "@smithy/smithy-client": "^4.10.12", + "@aws-sdk/core": "^3.973.5", + "@smithy/core": "^3.22.0", + "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" @@ -3742,7 +3792,7 @@ "node": ">=20.0.0" }, "peerDependencies": { - "@aws-sdk/client-dynamodb": "3.975.0" + "@aws-sdk/client-dynamodb": "3.980.0" } }, "node_modules/@aws-sdk/dynamodb-codec/node_modules/@smithy/types": { @@ -3771,15 +3821,15 @@ } }, "node_modules/@aws-sdk/lib-dynamodb": { - "version": "3.975.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/lib-dynamodb/-/lib-dynamodb-3.975.0.tgz", - "integrity": "sha512-BEhzr8atBFT8mJ8KuFne3OHj81zsqQWLP1Is2zzbzbZy4Mjbm1nRceNEwZ603tp9oFrV2mwTNLagFDPEudJC/g==", + "version": "3.980.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-dynamodb/-/lib-dynamodb-3.980.0.tgz", + "integrity": "sha512-rot+9bSIUCjCJCh+BnH++ZYHCEUtTDVKkrBpN+WxbrEEGUvU1RNhkQEPXcLaf57UobRjoTI4m3LNBJCH7E6MCw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.1", - "@aws-sdk/util-dynamodb": "3.975.0", - "@smithy/core": "^3.21.1", - "@smithy/smithy-client": "^4.10.12", + "@aws-sdk/core": "^3.973.5", + "@aws-sdk/util-dynamodb": "3.980.0", + "@smithy/core": "^3.22.0", + "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, @@ -3787,7 +3837,7 @@ "node": ">=20.0.0" }, "peerDependencies": { - "@aws-sdk/client-dynamodb": "3.975.0" + "@aws-sdk/client-dynamodb": "3.980.0" } }, "node_modules/@aws-sdk/lib-dynamodb/node_modules/@smithy/types": { @@ -3803,9 +3853,9 @@ } }, "node_modules/@aws-sdk/middleware-endpoint-discovery": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint-discovery/-/middleware-endpoint-discovery-3.972.2.tgz", - "integrity": "sha512-Pzz/j7wiKibTHVfPDhqjdlhL+GqP/Nsd6mbeG56uc8BsmZGHBrz5TrwLAQXE1mWBliiEDs9fsh0W62CsX/Qy1g==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint-discovery/-/middleware-endpoint-discovery-3.972.3.tgz", + "integrity": "sha512-xAxA8/TOygQmMrzcw9CrlpTHCGWSG/lvzrHCySfSZpDN4/yVSfXO+gUwW9WxeskBmuv9IIFATOVpzc9EzfTZ0Q==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/endpoint-cache": "^3.972.2", @@ -3845,9 +3895,9 @@ } }, "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.2.tgz", - "integrity": "sha512-42hZ8jEXT2uR6YybCzNq9OomqHPw43YIfRfz17biZjMQA4jKSQUaHIl6VvqO2Ddl5904pXg2Yd/ku78S0Ikgog==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.3.tgz", + "integrity": "sha512-aknPTb2M+G3s+0qLCx4Li/qGZH8IIYjugHMv15JTYMe6mgZO8VBpYgeGYsNMGCqCZOcWzuf900jFBG5bopfzmA==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.973.1", @@ -3885,9 +3935,9 @@ } }, "node_modules/@aws-sdk/middleware-logger": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.2.tgz", - "integrity": "sha512-iUzdXKOgi4JVDDEG/VvoNw50FryRCEm0qAudw12DcZoiNJWl0rN6SYVLcL1xwugMfQncCXieK5UBlG6mhH7iYA==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.3.tgz", + "integrity": "sha512-Ftg09xNNRqaz9QNzlfdQWfpqMCJbsQdnZVJP55jfhbKi1+FTWxGuvfPoBhDHIovqWKjqbuiew3HuhxbJ0+OjgA==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.973.1", @@ -3924,9 +3974,9 @@ } }, "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.2.tgz", - "integrity": "sha512-/mzlyzJDtngNFd/rAYvqx29a2d0VuiYKN84Y/Mu9mGw7cfMOCyRK+896tb9wV6MoPRHUX7IXuKCIL8nzz2Pz5A==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.3.tgz", + "integrity": "sha512-PY57QhzNuXHnwbJgbWYTrqIDHYSeOlhfYERTAuc16LKZpTZRJUjzBFokp9hF7u1fuGeE3D70ERXzdbMBOqQz7Q==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.973.1", @@ -3965,15 +4015,15 @@ } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.3.tgz", - "integrity": "sha512-zq6aTiO/BiAIOA8EH8nB+wYvvnZ14Md9Gomm5DDhParshVEVglAyNPO5ADK4ZXFQbftIoO+Vgcvf4gewW/+iYQ==", + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.5.tgz", + "integrity": "sha512-TVZQ6PWPwQbahUI8V+Er+gS41ctIawcI/uMNmQtQ7RMcg3JYn6gyKAFKUb3HFYx2OjYlx1u11sETSwwEUxVHTg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.2", + "@aws-sdk/core": "^3.973.5", "@aws-sdk/types": "^3.973.1", - "@aws-sdk/util-endpoints": "3.972.0", - "@smithy/core": "^3.21.1", + "@aws-sdk/util-endpoints": "3.980.0", + "@smithy/core": "^3.22.0", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" @@ -3995,6 +4045,22 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/middleware-user-agent/node_modules/@aws-sdk/util-endpoints": { + "version": "3.980.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.980.0.tgz", + "integrity": "sha512-AjKBNEc+rjOZQE1HwcD9aCELqg1GmUj1rtICKuY8cgwB73xJ4U/kNyqKKpN2k9emGqlfDY2D8itIp/vDc6OKpw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-endpoints": "^3.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/middleware-user-agent/node_modules/@smithy/types": { "version": "4.12.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", @@ -4008,44 +4074,44 @@ } }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.975.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.975.0.tgz", - "integrity": "sha512-OkeFHPlQj2c/Y5bQGkX14pxhDWUGUFt3LRHhjcDKsSCw6lrxKcxN3WFZN0qbJwKNydP+knL5nxvfgKiCLpTLRA==", + "version": "3.980.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.980.0.tgz", + "integrity": "sha512-/dONY5xc5/CCKzOqHZCTidtAR4lJXWkGefXvTRKdSKMGaYbbKsxDckisd6GfnvPSLxWtvQzwgRGRutMRoYUApQ==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.1", - "@aws-sdk/middleware-host-header": "^3.972.1", - "@aws-sdk/middleware-logger": "^3.972.1", - "@aws-sdk/middleware-recursion-detection": "^3.972.1", - "@aws-sdk/middleware-user-agent": "^3.972.2", - "@aws-sdk/region-config-resolver": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "^3.972.1", - "@aws-sdk/util-user-agent-node": "^3.972.1", + "@aws-sdk/core": "^3.973.5", + "@aws-sdk/middleware-host-header": "^3.972.3", + "@aws-sdk/middleware-logger": "^3.972.3", + "@aws-sdk/middleware-recursion-detection": "^3.972.3", + "@aws-sdk/middleware-user-agent": "^3.972.5", + "@aws-sdk/region-config-resolver": "^3.972.3", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.980.0", + "@aws-sdk/util-user-agent-browser": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.3", "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.21.1", + "@smithy/core": "^3.22.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.11", - "@smithy/middleware-retry": "^4.4.27", + "@smithy/middleware-endpoint": "^4.4.12", + "@smithy/middleware-retry": "^4.4.29", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.12", + "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@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.26", - "@smithy/util-defaults-mode-node": "^4.2.29", + "@smithy/util-defaults-mode-browser": "^4.3.28", + "@smithy/util-defaults-mode-node": "^4.2.31", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", @@ -4069,6 +4135,22 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/util-endpoints": { + "version": "3.980.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.980.0.tgz", + "integrity": "sha512-AjKBNEc+rjOZQE1HwcD9aCELqg1GmUj1rtICKuY8cgwB73xJ4U/kNyqKKpN2k9emGqlfDY2D8itIp/vDc6OKpw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-endpoints": "^3.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/types": { "version": "4.12.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", @@ -4134,9 +4216,9 @@ } }, "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.2.tgz", - "integrity": "sha512-/7vRBsfmiOlg2X67EdKrzzQGw5/SbkXb7ALHQmlQLkZh8qNgvS2G2dDC6NtF3hzFlpP3j2k+KIEtql/6VrI6JA==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.3.tgz", + "integrity": "sha512-v4J8qYAWfOMcZ4MJUyatntOicTzEMaU7j3OpkRCGGFSL2NgXQ5VbxauIyORA+pxdKZ0qQG2tCQjQjZDlXEC3Ow==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.973.1", @@ -4175,14 +4257,14 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.975.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.975.0.tgz", - "integrity": "sha512-AWQt64hkVbDQ+CmM09wnvSk2mVyH4iRROkmYkr3/lmUtFNbE2L/fnw26sckZnUcFCsHPqbkQrcsZAnTcBLbH4w==", + "version": "3.980.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.980.0.tgz", + "integrity": "sha512-1nFileg1wAgDmieRoj9dOawgr2hhlh7xdvcH57b1NnqfPaVlcqVJyPc6k3TLDUFPY69eEwNxdGue/0wIz58vjA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.1", - "@aws-sdk/nested-clients": "3.975.0", - "@aws-sdk/types": "^3.973.0", + "@aws-sdk/core": "^3.973.5", + "@aws-sdk/nested-clients": "3.980.0", + "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", @@ -4243,9 +4325,9 @@ } }, "node_modules/@aws-sdk/util-dynamodb": { - "version": "3.975.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-dynamodb/-/util-dynamodb-3.975.0.tgz", - "integrity": "sha512-ZsziF8m5Syn+kA2YJLEG2kk6zfxea8yRl/7SkSFpAls8RFYkt8EUmVUMBhX2hBpGw+nbZL7+AcRi4S2LxAcYWA==", + "version": "3.980.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-dynamodb/-/util-dynamodb-3.980.0.tgz", + "integrity": "sha512-jG/yzr/JLFl7II9TTDWRKJRHThTXYNDYy694bRTj7JCXCU/Gb11ir5fJ7sV6FhlR9LrIaDb7Fft3RifvEnZcSQ==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -4254,7 +4336,7 @@ "node": ">=20.0.0" }, "peerDependencies": { - "@aws-sdk/client-dynamodb": "3.975.0" + "@aws-sdk/client-dynamodb": "3.980.0" } }, "node_modules/@aws-sdk/util-endpoints": { @@ -4323,9 +4405,9 @@ } }, "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.2.tgz", - "integrity": "sha512-gz76bUyebPZRxIsBHJUd/v+yiyFzm9adHbr8NykP2nm+z/rFyvQneOHajrUejtmnc5tTBeaDPL4X25TnagRk4A==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.3.tgz", + "integrity": "sha512-JurOwkRUcXD/5MTDBcqdyQ9eVedtAsZgw5rBwktsPTN7QtPiS2Ld1jkJepNgYoCufz1Wcut9iup7GJDoIHp8Fw==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.973.1", @@ -4360,12 +4442,12 @@ } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.2.tgz", - "integrity": "sha512-vnxOc4C6AR7hVbwyFo1YuH0GB6dgJlWt8nIOOJpnzJAWJPkUMPJ9Zv2lnKsSU7TTZbhP2hEO8OZ4PYH59XFv8Q==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.3.tgz", + "integrity": "sha512-gqG+02/lXQtO0j3US6EVnxtwwoXQC5l2qkhLCrqUrqdtcQxV7FDMbm9wLjKqoronSHyELGTjbFKK/xV5q1bZNA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "^3.972.3", + "@aws-sdk/middleware-user-agent": "^3.972.5", "@aws-sdk/types": "^3.973.1", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", @@ -8698,6 +8780,13 @@ "node": ">=18.0.0" } }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, "node_modules/@swc/core": { "version": "1.15.11", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.11.tgz", @@ -9175,6 +9264,24 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -9269,9 +9376,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.0.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.10.tgz", - "integrity": "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg==", + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.1.0.tgz", + "integrity": "sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA==", "license": "MIT", "peer": true, "dependencies": { @@ -9906,6 +10013,158 @@ "vite": "^4 || ^5 || ^6 || ^7" } }, + "node_modules/@vitest/coverage-v8": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.18.tgz", + "integrity": "sha512-7i+N2i0+ME+2JFZhfuz7Tg/FqKtilHjGyGvoHYQ6iLV0zahbsJ9sljC9OcFcPDbhYKCet+sG8SsVqlyGvPflZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.0.18", + "ast-v8-to-istanbul": "^0.3.10", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.1", + "obug": "^2.1.1", + "std-env": "^3.10.0", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.0.18", + "vitest": "4.0.18" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/coverage-v8/node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/expect": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz", + "integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "chai": "^6.2.1", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz", + "integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.0.18", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz", + "integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz", + "integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.0.18", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz", + "integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.18", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz", + "integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz", + "integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.18", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -10225,6 +10484,35 @@ "safer-buffer": "~2.1.0" } }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-v8-to-istanbul": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.11.tgz", + "integrity": "sha512-Qya9fkoofMjCBNVdWINMjB5KZvkYfaO9/anwkWnjxibpWUxo5iHl2sOdP7/uAqaRuUYuoo8rDwnbaaKVFxoUvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/async-function": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", @@ -11341,6 +11629,16 @@ "constructs": "^10.0.5" } }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -12139,6 +12437,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -12610,6 +12915,16 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -12688,6 +13003,16 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -15452,6 +15777,28 @@ "lz-string": "bin/bin.js" } }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.1.tgz", + "integrity": "sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "source-map-js": "^1.2.1" + } + }, "node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", @@ -15622,9 +15969,9 @@ } }, "node_modules/mock-jwks": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/mock-jwks/-/mock-jwks-3.2.2.tgz", - "integrity": "sha512-FEeBqYzO/pPuVe9LuaIn2oR6mzgxfQylLARMDcpQyHQOOQIrw53SEM3FdH26126RfwTfqO93kcr5hKpKxtF4rQ==", + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/mock-jwks/-/mock-jwks-3.3.5.tgz", + "integrity": "sha512-dINtHCQ5l4NTbTHHpibIjaJJw+nP7dSn5DbsgervyqwcM8mJsvdbYNV8OSrdOHKnckwbeijy7o55dCgNmhxyRQ==", "dev": true, "license": "MIT", "dependencies": { @@ -16155,6 +16502,17 @@ "integrity": "sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig==", "license": "MIT" }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, "node_modules/on-exit-leak-free": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", @@ -16431,6 +16789,13 @@ "url": "https://opencollective.com/express" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/patientSearchLambda": { "resolved": "packages/patientSearchLambda", "link": true @@ -17291,7 +17656,6 @@ "integrity": "sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", @@ -17508,6 +17872,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/sign-out-lambda": { "resolved": "packages/CIS2SignOutLambda", "link": true @@ -17719,6 +18090,13 @@ "node": ">=8" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", @@ -17729,6 +18107,13 @@ "node": ">= 0.8" } }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", @@ -18182,6 +18567,23 @@ "node": ">=20" } }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -18231,6 +18633,16 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/tldts": { "version": "6.1.86", "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", @@ -18967,6 +19379,98 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/vitest": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz", + "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@vitest/expect": "4.0.18", + "@vitest/mocker": "4.0.18", + "@vitest/pretty-format": "4.0.18", + "@vitest/runner": "4.0.18", + "@vitest/snapshot": "4.0.18", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.18", + "@vitest/browser-preview": "4.0.18", + "@vitest/browser-webdriverio": "4.0.18", + "@vitest/ui": "4.0.18", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", @@ -19170,6 +19674,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -19515,8 +20036,8 @@ "license": "MIT", "dependencies": { "@aws-lambda-powertools/logger": "^2.30.1", - "@aws-sdk/client-dynamodb": "^3.956.0", - "@aws-sdk/lib-dynamodb": "^3.975.0", + "@aws-sdk/client-dynamodb": "^3.980.0", + "@aws-sdk/lib-dynamodb": "^3.980.0", "@cpt-ui-common/authFunctions": "^1.0.0", "@cpt-ui-common/dynamoFunctions": "^1.0.0", "@cpt-ui-common/middyErrorHandler": "^1.0.0", @@ -19548,8 +20069,8 @@ "dependencies": { "@aws-lambda-powertools/logger": "^2.30.1", "@aws-lambda-powertools/parameters": "^2.30.1", - "@aws-sdk/client-dynamodb": "^3.956.0", - "@aws-sdk/lib-dynamodb": "^3.975.0", + "@aws-sdk/client-dynamodb": "^3.980.0", + "@aws-sdk/lib-dynamodb": "^3.980.0", "@cpt-ui-common/authFunctions": "^1.0.0", "@cpt-ui-common/dynamoFunctions": "^1.0.0", "@cpt-ui-common/lambdaUtils": "^1.0.0", @@ -19563,7 +20084,7 @@ }, "devDependencies": { "@types/aws-lambda": "^8.10.159", - "mock-jwks": "3.2.2", + "mock-jwks": "^3.3.5", "nock": "^14.0.10" } }, @@ -19574,7 +20095,7 @@ "dependencies": { "@aws-lambda-powertools/logger": "^2.30.1", "@aws-lambda-powertools/parameters": "^2.30.1", - "@aws-sdk/lib-dynamodb": "^3.975.0", + "@aws-sdk/lib-dynamodb": "^3.980.0", "@cpt-ui-common/dynamoFunctions": "^1.0.0", "@middy/core": "^7.0.2", "aws-lambda": "^1.0.7", @@ -19582,9 +20103,9 @@ "jwks-rsa": "^3.2.2" }, "devDependencies": { - "@aws-sdk/client-dynamodb": "^3.956.0", + "@aws-sdk/client-dynamodb": "^3.980.0", "axios": "^1.13.2", - "mock-jwks": "3.2.2" + "mock-jwks": "^3.3.5" } }, "packages/common/commonTypes": { @@ -19613,7 +20134,7 @@ "license": "MIT", "dependencies": { "@aws-lambda-powertools/logger": "^2.30.1", - "@aws-sdk/lib-dynamodb": "^3.975.0", + "@aws-sdk/lib-dynamodb": "^3.980.0", "@cpt-ui-common/common-types": "^1.0.0" } }, @@ -19734,8 +20255,8 @@ "license": "MIT", "dependencies": { "@aws-lambda-powertools/logger": "^2.30.1", - "@aws-sdk/client-dynamodb": "^3.956.0", - "@aws-sdk/lib-dynamodb": "^3.975.0", + "@aws-sdk/client-dynamodb": "^3.980.0", + "@aws-sdk/lib-dynamodb": "^3.980.0", "@cpt-ui-common/authFunctions": "^1.0.0", "@cpt-ui-common/lambdaUtils": "^1.0.0", "@cpt-ui-common/middyErrorHandler": "^1.0.0", @@ -19759,10 +20280,10 @@ "@aws-lambda-powertools/commons": "^2.30.0", "@aws-lambda-powertools/logger": "^2.30.1", "@aws-lambda-powertools/parameters": "^2.30.1", - "@aws-sdk/client-dynamodb": "^3.956.0", - "@aws-sdk/client-lambda": "^3.975.0", - "@aws-sdk/client-secrets-manager": "^3.975.0", - "@aws-sdk/lib-dynamodb": "^3.975.0", + "@aws-sdk/client-dynamodb": "^3.980.0", + "@aws-sdk/client-lambda": "^3.978.0", + "@aws-sdk/client-secrets-manager": "^3.980.0", + "@aws-sdk/lib-dynamodb": "^3.980.0", "@cpt-ui-common/authFunctions": "^1.0.0", "@cpt-ui-common/common-types": "^1.0.0", "@cpt-ui-common/doHSClient": "^1.0.0", @@ -19780,7 +20301,7 @@ "@types/aws-lambda": "^8.10.159", "@types/fhir": "^0.0.41", "axios-mock-adapter": "^2.0.0", - "mock-jwks": "^3.2.2", + "mock-jwks": "^3.3.5", "nock": "^14.0.10" } }, @@ -19789,8 +20310,8 @@ "license": "MIT", "dependencies": { "@aws-lambda-powertools/logger": "^2.30.1", - "@aws-sdk/client-dynamodb": "^3.956.0", - "@aws-sdk/lib-dynamodb": "^3.975.0", + "@aws-sdk/client-dynamodb": "^3.980.0", + "@aws-sdk/lib-dynamodb": "^3.980.0", "@cpt-ui-common/authFunctions": "^1.0.0", "@cpt-ui-common/lambdaUtils": "^1.0.0", "@cpt-ui-common/middyErrorHandler": "^1.0.0", @@ -19814,8 +20335,8 @@ "license": "MIT", "dependencies": { "@aws-lambda-powertools/logger": "^2.30.1", - "@aws-sdk/client-dynamodb": "^3.956.0", - "@aws-sdk/lib-dynamodb": "^3.975.0", + "@aws-sdk/client-dynamodb": "^3.980.0", + "@aws-sdk/lib-dynamodb": "^3.980.0", "@cpt-ui-common/authFunctions": "^1.0.0", "@cpt-ui-common/dynamoFunctions": "^1.0.0", "@cpt-ui-common/middyErrorHandler": "^1.0.0", @@ -19850,8 +20371,8 @@ "license": "MIT", "dependencies": { "@aws-lambda-powertools/logger": "^2.30.1", - "@aws-sdk/client-dynamodb": "^3.956.0", - "@aws-sdk/lib-dynamodb": "^3.975.0", + "@aws-sdk/client-dynamodb": "^3.980.0", + "@aws-sdk/lib-dynamodb": "^3.980.0", "@cpt-ui-common/authFunctions": "^1.0.0", "@cpt-ui-common/dynamoFunctions": "^1.0.0", "@cpt-ui-common/middyErrorHandler": "^1.0.0", @@ -19872,8 +20393,8 @@ "license": "MIT", "dependencies": { "@aws-lambda-powertools/logger": "^2.30.1", - "@aws-sdk/client-dynamodb": "^3.956.0", - "@aws-sdk/lib-dynamodb": "^3.975.0", + "@aws-sdk/client-dynamodb": "^3.980.0", + "@aws-sdk/lib-dynamodb": "^3.980.0", "@cpt-ui-common/authFunctions": "^1.0.0", "@cpt-ui-common/dynamoFunctions": "^1.0.0", "@cpt-ui-common/middyErrorHandler": "^1.0.0", diff --git a/package.json b/package.json index 27eea993b8..6599768f27 100644 --- a/package.json +++ b/package.json @@ -43,9 +43,10 @@ "@eslint/js": "^9.38.0", "@jest/globals": "^30.1.1", "@types/jest": "^30.0.0", - "@types/node": "^25.0.9", + "@types/node": "^25.1.0", "@typescript-eslint/eslint-plugin": "^8.48.0", "@typescript-eslint/parser": "^8.50.1", + "@vitest/coverage-v8": "^4.0.18", "aws-sdk-client-mock": "^4.1.0", "eslint": "^9.39.2", "eslint-config-prettier": "^10.1.8", @@ -60,7 +61,8 @@ "ts-jest": "^29.4.6", "ts-node": "^10.9.2", "typescript": "^5.9.3", - "typescript-eslint": "^8.52.0" + "typescript-eslint": "^8.52.0", + "vitest": "^4.0.18" }, "dependencies": { "conventional-changelog-eslint": "^6.0.0", diff --git a/packages/CIS2SignOutLambda/package.json b/packages/CIS2SignOutLambda/package.json index 887531f868..1e0cda1978 100644 --- a/packages/CIS2SignOutLambda/package.json +++ b/packages/CIS2SignOutLambda/package.json @@ -14,8 +14,8 @@ }, "dependencies": { "@aws-lambda-powertools/logger": "^2.30.1", - "@aws-sdk/client-dynamodb": "^3.956.0", - "@aws-sdk/lib-dynamodb": "^3.975.0", + "@aws-sdk/client-dynamodb": "^3.980.0", + "@aws-sdk/lib-dynamodb": "^3.980.0", "@cpt-ui-common/authFunctions": "^1.0.0", "@cpt-ui-common/dynamoFunctions": "^1.0.0", "@cpt-ui-common/middyErrorHandler": "^1.0.0", diff --git a/packages/cognito/.jest/setEnvVars.js b/packages/cognito/.jest/setEnvVars.js deleted file mode 100644 index 5fe8c5a047..0000000000 --- a/packages/cognito/.jest/setEnvVars.js +++ /dev/null @@ -1,22 +0,0 @@ -process.env.TokenMappingTableName = "dummyTable" -process.env.jwtPrivateKeyArn = "dummy_jwtPrivateKeyArn" -process.env.jwtKid = "jwt_kid" -process.env.useMock = "false" - -process.env.CIS2_OIDC_ISSUER = "valid_cis2_iss" -process.env.CIS2_OIDC_CLIENT_ID = "valid_cis2_aud" -process.env.CIS2_OIDC_HOST = "https://dummy_cis2_auth.com" -process.env.CIS2_OIDCJWKS_ENDPOINT = `${process.env.CIS2_OIDC_HOST}/.well-known/jwks.json` -process.env.CIS2_USER_INFO_ENDPOINT = `${process.env.CIS2_OIDC_HOST}/userinfo` -process.env.CIS2_USER_POOL_IDP = "CIS2DummyPoolIdentityProvider" -process.env.CIS2_IDP_TOKEN_PATH = `${process.env.CIS2_OIDC_HOST}/token` - -process.env.MOCK_OIDC_ISSUER = "valid_mock_iss" -process.env.MOCK_OIDC_CLIENT_ID = "valid_mock_aud" -process.env.MOCK_OIDC_HOST = "https://dummy_mock_auth.com" -process.env.MOCK_OIDCJWKS_ENDPOINT = `${process.env.MOCK_OIDC_HOST}/.well-known/jwks.json` -process.env.MOCK_USER_INFO_ENDPOINT = `${process.env.MOCK_OIDC_HOST}/userinfo` -process.env.MOCK_USER_POOL_IDP = "MockDummyPoolIdentityProvider" -process.env.MOCK_IDP_TOKEN_PATH = `${process.env.MOCK_OIDC_HOST}/token` -process.env.APIGEE_API_KEY = "apigee_api_key" -process.env.FULL_CLOUDFRONT_DOMAIN = "cpt-ui-pr-854.dev.eps.national.nhs.uk" diff --git a/packages/cognito/jest.config.ts b/packages/cognito/jest.config.ts deleted file mode 100644 index 6865f4dc5c..0000000000 --- a/packages/cognito/jest.config.ts +++ /dev/null @@ -1,10 +0,0 @@ -import defaultConfig from "../../jest.default.config.ts" -import type {JestConfigWithTsJest} from "ts-jest" - -const jestConfig: JestConfigWithTsJest = { - ...defaultConfig, - "rootDir": "./", - setupFiles: ["/.jest/setEnvVars.js"] -} - -export default jestConfig diff --git a/packages/cognito/jest.debug.config.ts b/packages/cognito/jest.debug.config.ts deleted file mode 100644 index a306273831..0000000000 --- a/packages/cognito/jest.debug.config.ts +++ /dev/null @@ -1,9 +0,0 @@ -import config from "./jest.config" -import type {JestConfigWithTsJest} from "ts-jest" - -const debugConfig: JestConfigWithTsJest = { - ...config, - "preset": "ts-jest" -} - -export default debugConfig diff --git a/packages/cognito/package.json b/packages/cognito/package.json index a7103ef3b8..7db6ddc0f5 100644 --- a/packages/cognito/package.json +++ b/packages/cognito/package.json @@ -6,7 +6,7 @@ "author": "NHS Digital", "license": "MIT", "scripts": { - "unit": "POWERTOOLS_DEV=true NODE_OPTIONS=--experimental-vm-modules jest --no-cache --coverage", + "unit": "POWERTOOLS_DEV=true vitest run", "lint": "eslint --max-warnings 0 --fix --config ../../eslint.config.mjs .", "compile": "tsc --build", "test": "npm run compile && npm run unit", @@ -15,8 +15,8 @@ "dependencies": { "@aws-lambda-powertools/logger": "^2.30.1", "@aws-lambda-powertools/parameters": "^2.30.1", - "@aws-sdk/client-dynamodb": "^3.956.0", - "@aws-sdk/lib-dynamodb": "^3.975.0", + "@aws-sdk/client-dynamodb": "^3.980.0", + "@aws-sdk/lib-dynamodb": "^3.980.0", "@cpt-ui-common/authFunctions": "^1.0.0", "@cpt-ui-common/dynamoFunctions": "^1.0.0", "@cpt-ui-common/lambdaUtils": "^1.0.0", @@ -30,7 +30,7 @@ }, "devDependencies": { "@types/aws-lambda": "^8.10.159", - "mock-jwks": "3.2.2", + "mock-jwks": "^3.3.5", "nock": "^14.0.10" } } diff --git a/packages/cognito/tests/test_authorize.test.ts b/packages/cognito/tests/test_authorize.test.ts index 92b5328321..1b0f71f934 100644 --- a/packages/cognito/tests/test_authorize.test.ts +++ b/packages/cognito/tests/test_authorize.test.ts @@ -1,19 +1,21 @@ -import {jest} from "@jest/globals" +import { + beforeEach, + describe, + expect, + it, + vi +} from "vitest" // Set environment variables before importing the handler. -process.env.IDP_AUTHORIZE_PATH = "https://example.com/authorize" -process.env.OIDC_CLIENT_ID = "cis2Client123" process.env.useMock = "true" -process.env.COGNITO_CLIENT_ID = "userPoolClient123" -process.env.FULL_CLOUDFRONT_DOMAIN = "d111111abcdef8.cloudfront.net" // Import the handler after setting the env variables and mocks. import {mockAPIGatewayProxyEvent, mockContext} from "./mockObjects" -const {handler} = await import("../src/authorize") +import {handler} from "../src/authorize" describe("authorize handler", () => { beforeEach(() => { - jest.restoreAllMocks() + vi.restoreAllMocks() }) it("should redirect to CIS2 with correct parameters", async () => { diff --git a/packages/cognito/tests/test_authorizeMock.test.ts b/packages/cognito/tests/test_authorizeMock.test.ts index 775a9b2ea7..72fac5754d 100644 --- a/packages/cognito/tests/test_authorizeMock.test.ts +++ b/packages/cognito/tests/test_authorizeMock.test.ts @@ -1,19 +1,21 @@ -import {jest} from "@jest/globals" +import { + beforeEach, + describe, + expect, + it, + vi +} from "vitest" // Set environment variables before importing the handler. -process.env.IDP_AUTHORIZE_PATH = "https://example.com/authorize" -process.env.OIDC_CLIENT_ID = "cis2Client123" process.env.useMock = "true" -process.env.COGNITO_CLIENT_ID = "userPoolClient123" -process.env.FULL_CLOUDFRONT_DOMAIN = "cpt-ui-pr-854.dev.eps.national.nhs.uk" // Import the handler after setting the env variables and mocks. import {mockAPIGatewayProxyEvent, mockContext} from "./mockObjects" -const {handler} = await import("../src/authorizeMock") +import {handler} from "../src/authorizeMock" describe("authorize mock handler", () => { beforeEach(() => { - jest.restoreAllMocks() + vi.restoreAllMocks() }) it("should redirect to CIS2 with correct parameters", async () => { diff --git a/packages/cognito/tests/test_callback.test.ts b/packages/cognito/tests/test_callback.test.ts index 62da4e6274..ae8875433d 100644 --- a/packages/cognito/tests/test_callback.test.ts +++ b/packages/cognito/tests/test_callback.test.ts @@ -1,17 +1,19 @@ -import {jest} from "@jest/globals" - import {APIGatewayProxyEvent} from "aws-lambda" - -// Set required environment variables before importing the handler. -process.env.COGNITO_DOMAIN = "cognito.example.com" +import { + beforeEach, + describe, + expect, + it, + vi +} from "vitest" // Import the handler after setting env variables and mocks. import {mockAPIGatewayProxyEvent, mockContext} from "./mockObjects" -const {handler} = await import("../src/callback") +import {handler} from "../src/callback" describe("callback handler", () => { beforeEach(() => { - jest.clearAllMocks() + vi.clearAllMocks() }) it("should redirect to Cognito with correct parameters", async () => { diff --git a/packages/cognito/tests/test_callbackMock.test.ts b/packages/cognito/tests/test_callbackMock.test.ts index 8808c3588a..f8e68201e1 100644 --- a/packages/cognito/tests/test_callbackMock.test.ts +++ b/packages/cognito/tests/test_callbackMock.test.ts @@ -1,17 +1,21 @@ -import {jest} from "@jest/globals" import {APIGatewayProxyEvent} from "aws-lambda" - -// Set required environment variables before importing the handler. -process.env.COGNITO_DOMAIN = "cognito.example.com" +import { + beforeEach, + describe, + expect, + it, + test, + vi +} from "vitest" // Import the handler after setting env variables and mocks. import {mockAPIGatewayProxyEvent, mockContext} from "./mockObjects" import {Logger} from "@aws-lambda-powertools/logger" -const {handler} = await import("../src/callbackMock") +import {handler} from "../src/callbackMock" describe("Callback mock handler", () => { beforeEach(() => { - jest.clearAllMocks() + vi.clearAllMocks() }) it("should redirect to pull request with correct parameters", async () => { @@ -64,7 +68,7 @@ describe("Callback mock handler", () => { test("should handle state not being a JSON object", async () => { const state = "non_json_state" - const loggerWarnSpy = jest.spyOn(Logger.prototype, "warn") + const loggerWarnSpy = vi.spyOn(Logger.prototype, "warn") const event: APIGatewayProxyEvent = { ...mockAPIGatewayProxyEvent, queryStringParameters: { diff --git a/packages/cognito/tests/test_rewriteBody.test.ts b/packages/cognito/tests/test_rewriteBody.test.ts index c7116c5506..4f1cfcf7a1 100644 --- a/packages/cognito/tests/test_rewriteBody.test.ts +++ b/packages/cognito/tests/test_rewriteBody.test.ts @@ -1,15 +1,18 @@ -import {jest} from "@jest/globals" - import jwt from "jsonwebtoken" import {Logger} from "@aws-lambda-powertools/logger" import {ParsedUrlQuery} from "querystring" +import { + describe, + expect, + it, + vi +} from "vitest" +import {rewriteRequestBody} from "../src/helpers" // mock jwt.sign before importing rewriteRequestBody -const sign = jest.spyOn(jwt, "sign") +const sign = vi.spyOn(jwt, "sign") sign.mockImplementation(() => "mocked-jwt-token") -const {rewriteRequestBody} = await import("../src/helpers") - describe("rewriteRequestBody tests", () => { const logger = new Logger() const jwtPrivateKey = "mockPrivateKey" diff --git a/packages/cognito/tests/test_token.cis2.test.ts b/packages/cognito/tests/test_token.cis2.test.ts index 25a1f89a9d..4da2c38b15 100644 --- a/packages/cognito/tests/test_token.cis2.test.ts +++ b/packages/cognito/tests/test_token.cis2.test.ts @@ -1,16 +1,37 @@ /* eslint-disable no-console */ import { - expect, + afterEach, + beforeEach, describe, + expect, it, - jest -} from "@jest/globals" + vi +} from "vitest" import createJWKSMock from "mock-jwks" import nock from "nock" import {generateKeyPairSync} from "crypto" import jwksClient from "jwks-rsa" import {OidcConfig} from "@cpt-ui-common/authFunctions" +import {handler} from "../src/token" + +const { + mockVerifyIdToken, + mockInitializeOidcConfig, + mockGetSecret, + mockInsertTokenMapping, + mockGetTokenMapping, + mockTryGetTokenMapping +} = vi.hoisted(() => { + return { + mockVerifyIdToken: vi.fn(), + mockInitializeOidcConfig: vi.fn(), + mockGetSecret: vi.fn(), + mockInsertTokenMapping: vi.fn(), + mockGetTokenMapping: vi.fn(), + mockTryGetTokenMapping: vi.fn() + } +}) // redefining readonly property of the performance object const dummyContext = { @@ -32,22 +53,7 @@ const dummyContext = { const CIS2_OIDC_ISSUER = process.env.CIS2_OIDC_ISSUER const CIS2_OIDC_CLIENT_ID = process.env.CIS2_OIDC_CLIENT_ID const CIS2_OIDC_HOST = process.env.CIS2_OIDC_HOST ?? "" -//const CIS2_OIDCJWKS_ENDPOINT = process.env.CIS2_OIDCJWKS_ENDPOINT -//const CIS2_USER_INFO_ENDPOINT = process.env.CIS2_USER_INFO_ENDPOINT const CIS2_USER_POOL_IDP = process.env.CIS2_USER_POOL_IDP -//const CIS2_IDP_TOKEN_PATH = process.env.CIS2_IDP_TOKEN_PATH ?? "" - -//const MOCK_OIDC_ISSUER = process.env.MOCK_OIDC_ISSUER -//const MOCK_OIDC_CLIENT_ID = process.env.MOCK_OIDC_CLIENT_ID -//const MOCK_OIDC_HOST = process.env.MOCK_OIDC_HOST ?? "" -//const MOCK_OIDCJWKS_ENDPOINT = process.env.MOCK_OIDCJWKS_ENDPOINT -//const MOCK_USER_INFO_ENDPOINT = process.env.MOCK_USER_INFO_ENDPOINT -//const MOCK_USER_POOL_IDP = process.env.MOCK_USER_POOL_IDP -//const MOCK_IDP_TOKEN_PATH = process.env.MOCK_IDP_TOKEN_PATH - -const mockVerifyIdToken = jest.fn() -const mockInitializeOidcConfig = jest.fn() -const mockGetSecret = jest.fn() const { privateKey @@ -63,17 +69,14 @@ const { } }) -const mockInsertTokenMapping = jest.fn() -const mockGetTokenMapping = jest.fn() -const mockTryGetTokenMapping = jest.fn() -jest.unstable_mockModule("@cpt-ui-common/dynamoFunctions", () => { +vi.mock("@cpt-ui-common/dynamoFunctions", () => { return { insertTokenMapping: mockInsertTokenMapping, getTokenMapping: mockGetTokenMapping, tryGetTokenMapping: mockTryGetTokenMapping } }) -jest.unstable_mockModule("@cpt-ui-common/authFunctions", () => { +vi.mock("@cpt-ui-common/authFunctions", () => { const verifyIdToken = mockVerifyIdToken.mockImplementation(async () => { return { sub: "foo", @@ -134,7 +137,7 @@ jest.unstable_mockModule("@cpt-ui-common/authFunctions", () => { } }) -jest.unstable_mockModule("@aws-lambda-powertools/parameters/secrets", () => { +vi.mock("@aws-lambda-powertools/parameters/secrets", () => { const getSecret = mockGetSecret.mockImplementation(async () => { return privateKey }) @@ -144,16 +147,11 @@ jest.unstable_mockModule("@aws-lambda-powertools/parameters/secrets", () => { } }) -process.env.useMock = "false" -process.env.TokenMappingTableName = "test-token-mapping-table" -process.env.SessionManagementTableName = "test-session-management-table" -const {handler} = await import("../src/token") - describe("cis2 token handler", () => { const jwks = createJWKSMock("https://dummyauth.com/") beforeEach(() => { - jest.resetModules() - jest.clearAllMocks() + vi.resetModules() + vi.clearAllMocks() jwks.start() }) @@ -291,7 +289,7 @@ describe("cis2 token handler", () => { // Should insert into token mapping table (not concurrent session) expect(mockInsertTokenMapping).toHaveBeenCalledWith( expect.anything(), - "test-token-mapping-table", // token mapping table + "dummyTable", // token mapping table { username: `${CIS2_USER_POOL_IDP}_foo`, sessionId: "session-id", @@ -339,7 +337,7 @@ describe("cis2 token handler", () => { // Should insert into token mapping table (new user) expect(mockInsertTokenMapping).toHaveBeenCalledWith( expect.anything(), - "test-token-mapping-table", // token mapping table + "dummyTable", // token mapping table { username: `${CIS2_USER_POOL_IDP}_foo`, sessionId: "session-id", @@ -356,7 +354,7 @@ describe("cis2 token handler", () => { it("creates concurrent session when user exists and last activity exactly at 15 minute boundary", async () => { // Mock Date.now() to ensure consistent timing for boundary test const fixedTime = 1000000000000 // Fixed timestamp - const dateNowSpy = jest.spyOn(Date, "now").mockReturnValue(fixedTime) + const dateNowSpy = vi.spyOn(Date, "now").mockReturnValue(fixedTime) const fifteenMinutes = 15 * 60 * 1000 mockTryGetTokenMapping.mockImplementationOnce(() => { diff --git a/packages/cognito/tests/test_token.failure.test.ts b/packages/cognito/tests/test_token.failure.test.ts index c70a379777..f9435768a0 100644 --- a/packages/cognito/tests/test_token.failure.test.ts +++ b/packages/cognito/tests/test_token.failure.test.ts @@ -1,15 +1,18 @@ /* eslint-disable no-console */ import { - expect, + afterEach, + beforeEach, describe, + expect, it, - jest -} from "@jest/globals" + vi +} from "vitest" import createJWKSMock from "mock-jwks" import {generateKeyPairSync} from "crypto" import jwksClient from "jwks-rsa" import {OidcConfig} from "@cpt-ui-common/authFunctions" +import {handler} from "../src/token" // redefining readonly property of the performance object const dummyContext = { @@ -28,9 +31,23 @@ const dummyContext = { succeed: () => console.log("Succeeded!") } -const mockVerifyIdToken = jest.fn() -const mockInitializeOidcConfig = jest.fn() -const mockGetSecret = jest.fn() +const { + mockVerifyIdToken, + mockInitializeOidcConfig, + mockGetSecret, + mockInsertTokenMapping, + mockGetTokenMapping, + mockTryGetTokenMapping +} = vi.hoisted(() => { + return { + mockVerifyIdToken: vi.fn(), + mockInitializeOidcConfig: vi.fn(), + mockGetSecret: vi.fn(), + mockInsertTokenMapping: vi.fn(), + mockGetTokenMapping: vi.fn(), + mockTryGetTokenMapping: vi.fn() + } +}) const { privateKey @@ -45,17 +62,14 @@ const { format: "pem" } }) -const mockInsertTokenMapping = jest.fn() -const mockGetTokenMapping = jest.fn() -const mockTryGetTokenMapping = jest.fn() -jest.unstable_mockModule("@cpt-ui-common/dynamoFunctions", () => { +vi.mock("@cpt-ui-common/dynamoFunctions", () => { return { insertTokenMapping: mockInsertTokenMapping, getTokenMapping: mockGetTokenMapping, tryGetTokenMapping: mockTryGetTokenMapping } }) -jest.unstable_mockModule("@cpt-ui-common/authFunctions", () => { +vi.mock("@cpt-ui-common/authFunctions", () => { const verifyIdToken = mockVerifyIdToken.mockImplementation(async () => { return { sub: "foo", @@ -115,7 +129,7 @@ jest.unstable_mockModule("@cpt-ui-common/authFunctions", () => { } }) -jest.unstable_mockModule("@aws-lambda-powertools/parameters/secrets", () => { +vi.mock("@aws-lambda-powertools/parameters/secrets", () => { const getSecret = mockGetSecret.mockImplementation(async () => { return privateKey }) @@ -125,13 +139,11 @@ jest.unstable_mockModule("@aws-lambda-powertools/parameters/secrets", () => { } }) -const {handler} = await import("../src/token") - describe("cis2 token handler tests - failures", () => { const jwks = createJWKSMock("https://dummyauth.com/") beforeEach(() => { - jest.resetModules() - jest.clearAllMocks() + vi.resetModules() + vi.clearAllMocks() jwks.start() }) diff --git a/packages/cognito/tests/test_token.mock.test.ts b/packages/cognito/tests/test_token.mock.test.ts index 86e562d14b..feb7f0d38f 100644 --- a/packages/cognito/tests/test_token.mock.test.ts +++ b/packages/cognito/tests/test_token.mock.test.ts @@ -1,28 +1,36 @@ /* eslint-disable no-console */ import { - expect, + afterEach, + beforeEach, describe, + expect, it, - jest -} from "@jest/globals" + vi +} from "vitest" import createJWKSMock from "mock-jwks" import {generateKeyPairSync} from "crypto" import jwksClient from "jwks-rsa" import {OidcConfig} from "@cpt-ui-common/authFunctions" +import {handler} from "../src/tokenMock" -process.env.MOCK_USER_INFO_ENDPOINT = "https://dummy_mock_auth.com/userinfo" -process.env.MOCK_OIDC_ISSUER = "https://dummy_mock_auth.com" -process.env.MOCK_OIDC_CLIENT_ID = "test-client-id" -process.env.MOCK_USER_POOL_IDP = "test-idp" -process.env.TokenMappingTableName = "test-token-mapping-table" -process.env.SessionManagementTableName = "test-session-management-table" -process.env.FULL_CLOUDFRONT_DOMAIN = "test.cloudfront.net" -process.env.jwtPrivateKeyArn = "test-private-key-arn" -process.env.jwtKid = "test-kid" -process.env.APIGEE_API_KEY = "test-api-key" -process.env.APIGEE_API_SECRET = "test-api-secret" -process.env.MOCK_OIDC_TOKEN_ENDPOINT = "https://internal-dev.api.service.nhs.uk/oauth2-mock/token" +const { + mockInitializeOidcConfig, + mockGetSecret, + mockInsertTokenMapping, + mockTryGetTokenMapping, + mockFetchUserInfo, + mockExchangeTokenForApigeeAccessToken +} = vi.hoisted(() => { + return { + mockInitializeOidcConfig: vi.fn(), + mockGetSecret: vi.fn(), + mockInsertTokenMapping: vi.fn().mockName("mockInsertTokenMapping"), + mockTryGetTokenMapping: vi.fn().mockName("mockTryGetTokenMapping"), + mockFetchUserInfo: vi.fn().mockName("mockFetchUserInfo"), + mockExchangeTokenForApigeeAccessToken: vi.fn().mockName("mockExchangeTokenForApigeeAccessToken") + } +}) // redefining readonly property of the performance object const dummyContext = { @@ -41,11 +49,6 @@ const dummyContext = { succeed: () => console.log("Succeeded!") } -const MOCK_OIDC_TOKEN_ENDPOINT = "https://internal-dev.api.service.nhs.uk/oauth2-mock/token" - -const mockInitializeOidcConfig = jest.fn() -const mockGetSecret = jest.fn() - const { privateKey } = generateKeyPairSync("rsa", { @@ -60,19 +63,14 @@ const { } }) -const mockInsertTokenMapping = jest.fn().mockName("mockInsertTokenMapping") -const mockTryGetTokenMapping = jest.fn().mockName("mockTryGetTokenMapping") - -jest.unstable_mockModule("@cpt-ui-common/dynamoFunctions", () => { +vi.mock("@cpt-ui-common/dynamoFunctions", () => { return { insertTokenMapping: mockInsertTokenMapping, tryGetTokenMapping: mockTryGetTokenMapping } }) -const mockFetchUserInfo = jest.fn().mockName("mockFetchUserInfo") -const mockExchangeTokenForApigeeAccessToken = jest.fn().mockName("mockExchangeTokenForApigeeAccessToken") -jest.unstable_mockModule("@cpt-ui-common/authFunctions", () => { +vi.mock("@cpt-ui-common/authFunctions", () => { const initializeOidcConfig = mockInitializeOidcConfig.mockImplementation( () => { // Create a JWKS client for cis2 and mock // this is outside functions so it can be re-used @@ -103,6 +101,7 @@ jest.unstable_mockModule("@cpt-ui-common/authFunctions", () => { cacheMaxEntries: 5, cacheMaxAge: 3600000 // 1 hour }) + const MOCK_OIDC_TOKEN_ENDPOINT = "https://internal-dev.api.service.nhs.uk/oauth2-mock/token" const mockOidcConfig: OidcConfig = { oidcIssuer: process.env["MOCK_OIDC_ISSUER"] ?? "", @@ -126,7 +125,7 @@ jest.unstable_mockModule("@cpt-ui-common/authFunctions", () => { } }) -jest.unstable_mockModule("@aws-lambda-powertools/parameters/secrets", () => { +vi.mock("@aws-lambda-powertools/parameters/secrets", () => { const getSecret = mockGetSecret.mockImplementation(async () => { return privateKey }) @@ -136,14 +135,12 @@ jest.unstable_mockModule("@aws-lambda-powertools/parameters/secrets", () => { } }) -const {handler} = await import("../src/tokenMock") - describe("token mock handler", () => { const jwks = createJWKSMock("https://dummy_mock_auth.com/") beforeEach(() => { - jest.resetModules() - jest.clearAllMocks() + vi.resetModules() + vi.clearAllMocks() jwks.start() }) @@ -344,7 +341,7 @@ describe("token mock handler", () => { // check call expect(mockInsertTokenMapping).toHaveBeenCalledWith( expect.anything(), // documentClient - "test-token-mapping-table", // tableName + "dummyTable", // tableName { username: "Mock_user_details_sub", apigeeAccessToken: "new-access-token", @@ -447,7 +444,7 @@ describe("token mock handler", () => { expect.anything(), "https://internal-dev.api.service.nhs.uk/oauth2-mock/token", expect.objectContaining({ - redirect_uri: "https://test.cloudfront.net/oauth2/mock-callback" // PR part removed + redirect_uri: "https://cpt-ui.dev.eps.national.nhs.uk/oauth2/mock-callback" // PR part removed }), expect.anything() ) @@ -491,7 +488,7 @@ describe("token mock handler", () => { // Should insert into token mapping table (not session management table) expect(mockInsertTokenMapping).toHaveBeenCalledWith( expect.anything(), - "test-token-mapping-table", // Should use token mapping table + "dummyTable", // Should use token mapping table expect.anything(), expect.anything() ) diff --git a/packages/cognito/vitest.config.ts b/packages/cognito/vitest.config.ts new file mode 100644 index 0000000000..06fcd25937 --- /dev/null +++ b/packages/cognito/vitest.config.ts @@ -0,0 +1,40 @@ +import {defineConfig, mergeConfig} from "vitest/config" +import sharedVitestConfig from "../../vitest.shared.config" + +const CIS2_OIDC_HOST = "https://dummy_cis2_auth.com" +const MOCK_OIDC_HOST = "https://dummy_mock_auth.com" + +const viteConfig = defineConfig({ + test: { + env: { + TokenMappingTableName: "dummyTable", + jwtPrivateKeyArn: "dummy_jwtPrivateKeyArn", + jwtKid: "jwt_kid", + useMock: "false", + CIS2_OIDC_ISSUER: "valid_cis2_iss", + CIS2_OIDC_CLIENT_ID: "valid_cis2_aud", + CIS2_OIDC_HOST: CIS2_OIDC_HOST, + CIS2_OIDCJWKS_ENDPOINT: `${CIS2_OIDC_HOST}/.well-known/jwks.json`, + CIS2_USER_INFO_ENDPOINT: `${CIS2_OIDC_HOST}/userinfo`, + CIS2_USER_POOL_IDP: "CIS2DummyPoolIdentityProvider", + CIS2_IDP_TOKEN_PATH: `${CIS2_OIDC_HOST}/token`, + MOCK_OIDC_ISSUER: "valid_mock_iss", + MOCK_OIDC_CLIENT_ID: "valid_mock_aud", + MOCK_OIDC_HOST: MOCK_OIDC_HOST, + MOCK_OIDCJWKS_ENDPOINT: `${MOCK_OIDC_HOST}/.well-known/jwks.json`, + MOCK_USER_INFO_ENDPOINT: `${MOCK_OIDC_HOST}/userinfo`, + MOCK_USER_POOL_IDP: "MockDummyPoolIdentityProvider", + MOCK_IDP_TOKEN_PATH: `${MOCK_OIDC_HOST}/token`, + APIGEE_API_KEY: "apigee_api_key", + FULL_CLOUDFRONT_DOMAIN: "cpt-ui-pr-854.dev.eps.national.nhs.uk", + IDP_AUTHORIZE_PATH: "https://example.com/authorize", + OIDC_CLIENT_ID: "cis2Client123", + COGNITO_CLIENT_ID: "userPoolClient123", + COGNITO_DOMAIN: "cognito.example.com", + SessionManagementTableName: "test-session-management-table", + MOCK_OIDC_TOKEN_ENDPOINT: "https://internal-dev.api.service.nhs.uk/oauth2-mock/token" + } + } +}) + +export default mergeConfig(sharedVitestConfig, viteConfig) diff --git a/packages/common/authFunctions/.jest/setEnvVars.js b/packages/common/authFunctions/.jest/setEnvVars.js deleted file mode 100644 index fb09b8ce7c..0000000000 --- a/packages/common/authFunctions/.jest/setEnvVars.js +++ /dev/null @@ -1,32 +0,0 @@ -// put any environment variable setup in here - -process.env.apigeeHost = "https://dummyApigee" -process.env.apigeeCIS2TokenEndpoint = `${process.env.apigeeHost}/cis2_token` -process.env.apigeeMockTokenEndpoint = `${process.env.apigeeHost}/mock_token` -process.env.apigeePrescriptionsEndpoint = `${process.env.apigeeHost}/prescriptions` -process.env.apigeePersonalDemographicsEndpoint = `${process.env.apigeeHost}/Patient` - -process.env.TokenMappingTableName = "dummyTable" -process.env.jwtPrivateKeyArn = "dummy_jwtPrivateKeyArn" -process.env.jwtKid = "jwt_kid" -process.env.roleId = "dummy_role" -process.env.MOCK_MODE_ENABLED = "true" - -process.env.CIS2_OIDC_ISSUER = "valid_cis2_iss" -process.env.CIS2_OIDC_CLIENT_ID = "valid_cis2_aud" -process.env.CIS2_OIDC_HOST = "https://dummyauth.com" -process.env.CIS2_OIDCJWKS_ENDPOINT = `${process.env.CIS2_OIDC_HOST}/.well-known/jwks.json` -process.env.CIS2_USER_INFO_ENDPOINT = `${process.env.CIS2_OIDC_HOST}/userinfo` -process.env.CIS2_USER_POOL_IDP = "CIS2DummyPoolIdentityProvider" -process.env.CIS2_IDP_TOKEN_PATH = `${process.env.CIS2_OIDC_HOST}/token` - -process.env.MOCK_OIDC_ISSUER = "valid_mock_iss" -process.env.MOCK_OIDC_CLIENT_ID = "valid_mock_aud" -process.env.MOCK_OIDC_HOST = "https://dummyauth.com" -process.env.MOCK_OIDCJWKS_ENDPOINT = `${process.env.MOCK_OIDC_HOST}/.well-known/jwks.json` -process.env.MOCK_USER_INFO_ENDPOINT = `${process.env.MOCK_OIDC_HOST}/userinfo` -process.env.MOCK_USER_POOL_IDP = "MockDummyPoolIdentityProvider" -process.env.MOCK_IDP_TOKEN_PATH = `${process.env.MOCK_OIDC_HOST}/token` -process.env.CIS2_IDP_TOKEN_PATH = `${process.env.CIS2_OIDC_HOST}/token` -process.env.MOCK_IDP_TOKEN_PATH = `${process.env.MOCK_OIDC_HOST}/token` -process.env.FULL_CLOUDFRONT_DOMAIN = "cpt-ui-pr-854.dev.eps.national.nhs.uk" diff --git a/packages/common/authFunctions/package.json b/packages/common/authFunctions/package.json index ac98fe7a67..1b6694ee57 100644 --- a/packages/common/authFunctions/package.json +++ b/packages/common/authFunctions/package.json @@ -6,21 +6,21 @@ "license": "MIT", "main": "lib/src/index.js", "scripts": { - "unit": "POWERTOOLS_DEV=true NODE_OPTIONS=--experimental-vm-modules jest --no-cache --coverage", + "unit": "POWERTOOLS_DEV=true vitest run --coverage", "lint": "eslint --max-warnings 0 --fix --config ../../../eslint.config.mjs .", "compile": "tsc --build", "test": "npm run compile && npm run unit", "check-licenses": "license-checker --failOn GPL --failOn LGPL --start ../.." }, "devDependencies": { - "@aws-sdk/client-dynamodb": "^3.956.0", + "@aws-sdk/client-dynamodb": "^3.980.0", "axios": "^1.13.2", - "mock-jwks": "3.2.2" + "mock-jwks": "^3.3.5" }, "dependencies": { "@aws-lambda-powertools/logger": "^2.30.1", "@aws-lambda-powertools/parameters": "^2.30.1", - "@aws-sdk/lib-dynamodb": "^3.975.0", + "@aws-sdk/lib-dynamodb": "^3.980.0", "@cpt-ui-common/dynamoFunctions": "^1.0.0", "@middy/core": "^7.0.2", "aws-lambda": "^1.0.7", diff --git a/packages/common/authFunctions/tests/test_apigeeUtils.test.ts b/packages/common/authFunctions/tests/test_apigeeUtils.test.ts index ed758d1f66..1a878db524 100644 --- a/packages/common/authFunctions/tests/test_apigeeUtils.test.ts +++ b/packages/common/authFunctions/tests/test_apigeeUtils.test.ts @@ -1,44 +1,39 @@ /* eslint-disable @typescript-eslint/consistent-type-assertions */ -import {jest} from "@jest/globals" - -import axios from "axios" +import { + afterAll, + beforeAll, + beforeEach, + describe, + expect, + it, + vi +} from "vitest" + +import type {AxiosInstance} from "axios" import {Logger} from "@aws-lambda-powertools/logger" - -jest.mock("axios") -jest.mock("jsonwebtoken") - -const mockGetTokenMapping = jest.fn() -const mockUpdateTokenMapping = jest.fn() - -jest.unstable_mockModule("@cpt-ui-common/dynamoFunctions", () => { - return { - updateTokenMapping: mockUpdateTokenMapping, - getTokenMapping: mockGetTokenMapping - } -}) +import {exchangeTokenForApigeeAccessToken, refreshApigeeAccessToken, buildApigeeHeaders} from "../src/apigee" const mockLogger: Partial = { - info: jest.fn(), - debug: jest.fn(), - error: jest.fn() + info: vi.fn(), + debug: vi.fn(), + error: vi.fn() } -const {exchangeTokenForApigeeAccessToken, - refreshApigeeAccessToken, - buildApigeeHeaders} = await import("../src/apigee") +const mockAxiosPost = vi.fn() +const axiosInstance = { + post: mockAxiosPost +} as unknown as AxiosInstance describe("apigeeUtils", () => { - const mockAxiosPost = jest.fn(); - (axios.post as unknown as jest.Mock) = mockAxiosPost beforeAll(() => { - jest.spyOn(global.crypto, "randomUUID").mockReturnValue("test-uuid-in-uuid-format") + vi.spyOn(globalThis.crypto, "randomUUID").mockReturnValue("test-uuid-in-uuid-format") }) afterAll(() => { - jest.restoreAllMocks() + vi.restoreAllMocks() }) beforeEach(() => { - jest.clearAllMocks() + vi.clearAllMocks() }) describe("buildApigeeHeaders", () => { @@ -68,7 +63,7 @@ describe("apigeeUtils", () => { mockAxiosPost.mockResolvedValueOnce({data: {access_token: "testToken", expires_in: 3600}} as never) const result = await exchangeTokenForApigeeAccessToken( - axios, + axiosInstance, "https://mock-endpoint", {param: "test"}, mockLogger as Logger @@ -86,7 +81,7 @@ describe("apigeeUtils", () => { mockAxiosPost.mockResolvedValueOnce({data: {}} as never) await expect( exchangeTokenForApigeeAccessToken( - axios, + axiosInstance, "https://mock-endpoint", {param: "test"}, mockLogger as Logger @@ -104,7 +99,7 @@ describe("apigeeUtils", () => { await expect( exchangeTokenForApigeeAccessToken( - axios, + axiosInstance, "https://mock-endpoint", {param: "test"}, mockLogger as Logger @@ -130,7 +125,7 @@ describe("apigeeUtils", () => { })) const result = await refreshApigeeAccessToken( - axios, + axiosInstance, "https://mock-endpoint", "old-refresh-token", "mock-api-key", @@ -161,7 +156,7 @@ describe("apigeeUtils", () => { } as never) const result = await refreshApigeeAccessToken( - axios, + axiosInstance, "https://mock-endpoint", "old-refresh-token", "mock-api-key", @@ -182,7 +177,7 @@ describe("apigeeUtils", () => { await expect( refreshApigeeAccessToken( - axios, + axiosInstance, "https://mock-endpoint", "old-refresh-token", "mock-api-key", diff --git a/packages/common/authFunctions/tests/test_authenticateRequest.test.ts b/packages/common/authFunctions/tests/test_authenticateRequest.test.ts index d0db653833..a3012fc99a 100644 --- a/packages/common/authFunctions/tests/test_authenticateRequest.test.ts +++ b/packages/common/authFunctions/tests/test_authenticateRequest.test.ts @@ -1,60 +1,86 @@ -import {jest} from "@jest/globals" +import { + beforeEach, + describe, + expect, + it, + vi +} from "vitest" import {Logger} from "@aws-lambda-powertools/logger" import {DynamoDBClient} from "@aws-sdk/client-dynamodb" import {DynamoDBDocumentClient} from "@aws-sdk/lib-dynamodb" import {AxiosInstance} from "axios" - -// Mock the jwt module -jest.mock("jsonwebtoken", () => ({ - decode: jest.fn().mockReturnValue({ - header: {kid: "test-kid"}, - payload: { - sub: "test-subject", - exp: Math.floor(Date.now() / 1000) + 3600, - acr: "AAL3_ANY" - } - }), - verify: jest.fn().mockReturnValue({ +import {authenticateRequest} from "../src/authenticateRequest" + +const { + mockJwtDecode, + mockJwtVerify, + mockJwtSign, + mockGetSecret, + mockGetUsernameFromEvent, + mockRefreshApigeeAccessToken, + mockExchangeTokenForApigeeAccessToken, + mockConstructSignedJWTBody, + mockDecodeToken, + mockVerifyIdToken, + mockUpdateTokenMapping, + mockGetTokenMapping, + mockDeleteTokenMapping +} = vi.hoisted(() => { + const jwtPayload = { sub: "test-subject", exp: Math.floor(Date.now() / 1000) + 3600, acr: "AAL3_ANY" - }), - sign: jest.fn().mockReturnValue("signed-jwt-token") -})) + } + + return { + mockJwtDecode: vi.fn().mockReturnValue({ + header: {kid: "test-kid"}, + payload: jwtPayload + }), + mockJwtVerify: vi.fn().mockReturnValue(jwtPayload), + mockJwtSign: vi.fn().mockReturnValue("signed-jwt-token"), + mockGetSecret: vi.fn().mockReturnValue("test-private-key"), + mockGetUsernameFromEvent: vi.fn().mockName("mockGetUsernameFromEvent"), + mockRefreshApigeeAccessToken: vi.fn().mockName("mockRefreshApigeeAccessToken"), + mockExchangeTokenForApigeeAccessToken: vi.fn().mockName("mockExchangeTokenForApigeeAccessToken"), + mockConstructSignedJWTBody: vi.fn().mockName("mockConstructSignedJWTBody"), + mockDecodeToken: vi.fn().mockName("mockDecodeToken"), + mockVerifyIdToken: vi.fn().mockName("mockVerifyIdToken"), + mockUpdateTokenMapping: vi.fn(), + mockGetTokenMapping: vi.fn(), + mockDeleteTokenMapping: vi.fn() + } +}) -const mockGetSecret = jest.fn().mockReturnValue("test-private-key") +vi.mock("jsonwebtoken", () => ({ + default: { + decode: mockJwtDecode, + verify: mockJwtVerify, + sign: mockJwtSign + }, + decode: mockJwtDecode, + verify: mockJwtVerify, + sign: mockJwtSign +})) -jest.unstable_mockModule("@aws-lambda-powertools/parameters/secrets", () => ({ +vi.mock("@aws-lambda-powertools/parameters/secrets", () => ({ getSecret: mockGetSecret })) -// Create mocks for the functions from the index module -const mockGetUsernameFromEvent = jest.fn().mockName("mockGetUsernameFromEvent") -const mockRefreshApigeeAccessToken = jest.fn().mockName("mockRefreshApigeeAccessToken") -const mockExchangeTokenForApigeeAccessToken = jest.fn().mockName("mockExchangeTokenForApigeeAccessToken") -const mockConstructSignedJWTBody = jest.fn().mockName("mockConstructSignedJWTBody") -const mockDecodeToken = jest.fn().mockName("mockDecodeToken") -const mockVerifyIdToken = jest.fn().mockName("mockVerifyIdToken") const dynamoClient = new DynamoDBClient() const documentClient = DynamoDBDocumentClient.from(dynamoClient) const axiosInstance = { - post: jest.fn().mockReturnValue({data: {}}), - get: jest.fn().mockReturnValue({data: {}}) + post: vi.fn().mockReturnValue({data: {}}), + get: vi.fn().mockReturnValue({data: {}}) } as unknown as AxiosInstance -const mockUpdateTokenMapping = jest.fn() -const mockGetTokenMapping = jest.fn() -const mockDeleteTokenMapping = jest.fn() -jest.unstable_mockModule("@cpt-ui-common/dynamoFunctions", () => { - return { - updateTokenMapping: mockUpdateTokenMapping, - getTokenMapping: mockGetTokenMapping, - deleteTokenMapping: mockDeleteTokenMapping - } -}) +vi.mock("@cpt-ui-common/dynamoFunctions", () => ({ + updateTokenMapping: mockUpdateTokenMapping, + getTokenMapping: mockGetTokenMapping, + deleteTokenMapping: mockDeleteTokenMapping +})) -// Mock the index module -jest.unstable_mockModule("../src/index", () => ({ +vi.mock("../src/index", () => ({ getUsernameFromEvent: mockGetUsernameFromEvent, refreshApigeeAccessToken: mockRefreshApigeeAccessToken, exchangeTokenForApigeeAccessToken: mockExchangeTokenForApigeeAccessToken, @@ -63,17 +89,14 @@ jest.unstable_mockModule("../src/index", () => ({ verifyIdToken: mockVerifyIdToken })) -const authModule = await import("../src/authenticateRequest") -const {authenticateRequest} = authModule - describe("authenticateRequest", () => { // Common test setup const mockLogger = { - info: jest.fn(), - debug: jest.fn(), - warn: jest.fn(), - error: jest.fn() + info: vi.fn(), + debug: vi.fn(), + warn: vi.fn(), + error: vi.fn() } as unknown as Logger const mockOptions = { @@ -97,7 +120,7 @@ describe("authenticateRequest", () => { beforeEach(() => { // Clear all mock implementations - jest.clearAllMocks() + vi.clearAllMocks() // Set up default mocks for all tests mockGetUsernameFromEvent.mockReturnValue("test-user") diff --git a/packages/common/authFunctions/tests/test_authenticationConcurrentAwareMiddleware.test.ts b/packages/common/authFunctions/tests/test_authenticationConcurrentAwareMiddleware.test.ts index 13d8627a62..77064f99bb 100644 --- a/packages/common/authFunctions/tests/test_authenticationConcurrentAwareMiddleware.test.ts +++ b/packages/common/authFunctions/tests/test_authenticationConcurrentAwareMiddleware.test.ts @@ -1,32 +1,42 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import {jest} from "@jest/globals" +import { + beforeEach, + describe, + expect, + it, + vi +} from "vitest" import {Logger} from "@aws-lambda-powertools/logger" import {DynamoDBClient} from "@aws-sdk/client-dynamodb" import {DynamoDBDocumentClient} from "@aws-sdk/lib-dynamodb" import {AxiosInstance} from "axios" +import {authenticationConcurrentAwareMiddleware} from "../src/authenticationConcurrentAwareMiddleware" + +const { + mockGetUsernameFromEvent, + mockGetSessionIdFromEvent, + mockAuthenticateRequest, + mockTryGetTokenMapping +} = vi.hoisted(() => ({ + mockGetUsernameFromEvent: vi.fn(), + mockGetSessionIdFromEvent: vi.fn(), + mockAuthenticateRequest: vi.fn(), + mockTryGetTokenMapping: vi.fn() +})) -// Mock dependencies -const mockGetUsernameFromEvent = jest.fn() as jest.MockedFunction -const mockGetSessionIdFromEvent = jest.fn() as jest.MockedFunction -const mockAuthenticateRequest = jest.fn() as jest.MockedFunction -const mockTryGetTokenMapping = jest.fn() as jest.MockedFunction - -jest.unstable_mockModule("../src/event", () => ({ +vi.mock("../src/event", () => ({ getUsernameFromEvent: mockGetUsernameFromEvent, getSessionIdFromEvent: mockGetSessionIdFromEvent })) -jest.unstable_mockModule("../src/authenticateRequest", () => ({ +vi.mock("../src/authenticateRequest", () => ({ authenticateRequest: mockAuthenticateRequest })) -jest.unstable_mockModule("@cpt-ui-common/dynamoFunctions", () => ({ +vi.mock("@cpt-ui-common/dynamoFunctions", () => ({ tryGetTokenMapping: mockTryGetTokenMapping })) -// Import the middleware after mocking -const {authenticationConcurrentAwareMiddleware} = await import("../src/authenticationConcurrentAwareMiddleware") - describe("authenticationConcurrentAwareMiddleware", () => { let logger: Logger let ddbClient: DynamoDBDocumentClient @@ -36,7 +46,7 @@ describe("authenticationConcurrentAwareMiddleware", () => { let mockRequest: any beforeEach(() => { - jest.clearAllMocks() + vi.clearAllMocks() // Reset all mock implementations and return values mockGetUsernameFromEvent.mockReset() @@ -45,16 +55,16 @@ describe("authenticationConcurrentAwareMiddleware", () => { mockTryGetTokenMapping.mockReset() logger = new Logger({serviceName: "test"}) - logger.info = jest.fn() - logger.debug = jest.fn() - logger.error = jest.fn() + logger.info = vi.fn() + logger.debug = vi.fn() + logger.error = vi.fn() const dynamoClient = new DynamoDBClient({}) ddbClient = DynamoDBDocumentClient.from(dynamoClient) axiosInstance = { - post: jest.fn(), - get: jest.fn() + post: vi.fn(), + get: vi.fn() } as unknown as AxiosInstance authOptions = { diff --git a/packages/common/authFunctions/tests/test_authenticationMiddleware.test.ts b/packages/common/authFunctions/tests/test_authenticationMiddleware.test.ts index cffb86695b..f66ec7d4cb 100644 --- a/packages/common/authFunctions/tests/test_authenticationMiddleware.test.ts +++ b/packages/common/authFunctions/tests/test_authenticationMiddleware.test.ts @@ -1,32 +1,42 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import {jest} from "@jest/globals" +import { + beforeEach, + describe, + expect, + it, + vi +} from "vitest" import {Logger} from "@aws-lambda-powertools/logger" import {DynamoDBClient} from "@aws-sdk/client-dynamodb" import {DynamoDBDocumentClient} from "@aws-sdk/lib-dynamodb" import {AxiosInstance} from "axios" +import {authenticationMiddleware} from "../src/authenticationMiddleware" + +const { + mockGetUsernameFromEvent, + mockGetSessionIdFromEvent, + mockAuthenticateRequest, + mockGetTokenMapping +} = vi.hoisted(() => ({ + mockGetUsernameFromEvent: vi.fn(), + mockGetSessionIdFromEvent: vi.fn(), + mockAuthenticateRequest: vi.fn(), + mockGetTokenMapping: vi.fn() +})) -// Mock dependencies -const mockGetUsernameFromEvent = jest.fn() as jest.MockedFunction -const mockGetSessionIdFromEvent = jest.fn() as jest.MockedFunction -const mockAuthenticateRequest = jest.fn() as jest.MockedFunction -const mockGetTokenMapping = jest.fn() as jest.MockedFunction - -jest.unstable_mockModule("../src/event", () => ({ +vi.mock("../src/event", () => ({ getUsernameFromEvent: mockGetUsernameFromEvent, getSessionIdFromEvent: mockGetSessionIdFromEvent })) -jest.unstable_mockModule("../src/authenticateRequest", () => ({ +vi.mock("../src/authenticateRequest", () => ({ authenticateRequest: mockAuthenticateRequest })) -jest.unstable_mockModule("@cpt-ui-common/dynamoFunctions", () => ({ +vi.mock("@cpt-ui-common/dynamoFunctions", () => ({ getTokenMapping: mockGetTokenMapping })) -// Import the middleware after mocking -const {authenticationMiddleware} = await import("../src/authenticationMiddleware") - describe("authenticationMiddleware", () => { let logger: Logger let ddbClient: DynamoDBDocumentClient @@ -36,18 +46,18 @@ describe("authenticationMiddleware", () => { let mockRequest: any beforeEach(() => { - jest.clearAllMocks() + vi.clearAllMocks() logger = new Logger({serviceName: "test"}) - logger.info = jest.fn() - logger.error = jest.fn() + logger.info = vi.fn() + logger.error = vi.fn() const dynamoClient = new DynamoDBClient({}) ddbClient = DynamoDBDocumentClient.from(dynamoClient) axiosInstance = { - post: jest.fn(), - get: jest.fn() + post: vi.fn(), + get: vi.fn() } as unknown as AxiosInstance authOptions = { diff --git a/packages/common/authFunctions/tests/test_cis2TokenHelpers.test.ts b/packages/common/authFunctions/tests/test_cis2TokenHelpers.test.ts index 3c006fde8d..9bbe758454 100644 --- a/packages/common/authFunctions/tests/test_cis2TokenHelpers.test.ts +++ b/packages/common/authFunctions/tests/test_cis2TokenHelpers.test.ts @@ -1,30 +1,26 @@ -import {jest} from "@jest/globals" +import { + afterAll, + beforeAll, + beforeEach, + describe, + expect, + it, + vi +} from "vitest" import {APIGatewayProxyEvent} from "aws-lambda" import {Logger} from "@aws-lambda-powertools/logger" import jwksClient from "jwks-rsa" import jwt from "jsonwebtoken" import createJWKSMock from "mock-jwks" +import {getSigningKey, verifyIdToken} from "../src/cis2" +import {getUsernameFromEvent} from "../src/event" -const mockUpdateTokenMapping = jest.fn() -const mockGetTokenMapping = jest.fn() -jest.unstable_mockModule("@cpt-ui-common/dynamoFunctions", () => { - - return { - updateTokenMapping: mockUpdateTokenMapping, - getTokenMapping: mockGetTokenMapping - } -}) - -const {getSigningKey, - verifyIdToken} = await import("../src/cis2") - -const {getUsernameFromEvent} = await import("../src/event") // Common test setup const logger = new Logger() -const oidcClientId = "valid_aud" -const oidcIssuer = "valid_iss" -const jwksEndpoint = "https://dummyauth.com/.well-known/jwks.json" +const oidcClientId = process.env["CIS2_OIDC_CLIENT_ID"] +const oidcIssuer = process.env["CIS2_OIDC_ISSUER"] +const jwksEndpoint = process.env["CIS2_OIDCJWKS_ENDPOINT"] let jwksMock: ReturnType let stopJwksMock: () => void @@ -39,7 +35,7 @@ afterAll(() => { }) beforeEach(() => { - jest.restoreAllMocks() + vi.restoreAllMocks() }) interface TokenPayload { @@ -87,7 +83,7 @@ describe("getSigningKey", () => { }) beforeEach(() => { - jest.restoreAllMocks() + vi.restoreAllMocks() }) // it("should return the signing key when key is found", async () => { @@ -139,19 +135,19 @@ describe("getUsernameFromEvent", () => { describe("verifyIdToken", () => { beforeAll(() => { - jest.resetAllMocks() + vi.resetAllMocks() }) it("should verify a valid ID token", async () => { const payload = createPayload() const token = createToken(payload) - jest.spyOn(jwt, "verify").mockImplementation(() => payload) + vi.spyOn(jwt, "verify").mockImplementation(() => payload) await expect(verifyIdToken(token, logger)).resolves.toMatchObject(expect.objectContaining( { "acr": "AAL3_ANY", - "aud": ["valid_aud"], - "iss": "valid_iss" + "aud": ["valid_cis2_aud"], + "iss": "valid_cis2_iss" } )) }) @@ -168,7 +164,7 @@ describe("verifyIdToken", () => { const payload = createPayload() const token = createToken(payload) - jest.spyOn(jwt, "decode").mockReturnValueOnce({header: {}}) + vi.spyOn(jwt, "decode").mockReturnValueOnce({header: {}}) await expect(verifyIdToken(token, logger)).rejects.toThrow("Invalid token - no KID present") }) @@ -177,7 +173,7 @@ describe("verifyIdToken", () => { const payload = createPayload() const token = createToken(payload) - jest.spyOn(jwt, "verify").mockImplementation(() => { + vi.spyOn(jwt, "verify").mockImplementation(() => { throw new Error("Invalid signature") }) @@ -217,7 +213,7 @@ describe("verifyIdToken", () => { it("should throw an error when ACR claim is invalid", async () => { const payload = createPayload({acr: "INVALID_ACR"}) const token = createToken(payload) - jest.spyOn(jwt, "verify").mockImplementation(() => payload) + vi.spyOn(jwt, "verify").mockImplementation(() => payload) await expect(verifyIdToken(token, logger)).rejects.toThrow( "Invalid ACR claim in ID token" diff --git a/packages/common/authFunctions/tests/test_constructSignedJWTBody.test.ts b/packages/common/authFunctions/tests/test_constructSignedJWTBody.test.ts index ff18a5d54a..87f9f2a2ff 100644 --- a/packages/common/authFunctions/tests/test_constructSignedJWTBody.test.ts +++ b/packages/common/authFunctions/tests/test_constructSignedJWTBody.test.ts @@ -1,21 +1,18 @@ -import {jest} from "@jest/globals" +import { + describe, + expect, + it, + vi +} from "vitest" import jwt from "jsonwebtoken" import {Logger} from "@aws-lambda-powertools/logger" - -const mockGetTokenMapping = jest.fn() -jest.unstable_mockModule("@cpt-ui-common/dynamoFunctions", () => { - return { - getTokenMapping: mockGetTokenMapping - } -}) +import {constructSignedJWTBody} from "../src/apigee" // mock jwt.sign before importing constructSignedJWTBody -const sign = jest.spyOn(jwt, "sign") +const sign = vi.spyOn(jwt, "sign") sign.mockImplementation(() => "mocked-jwt-token") -const {constructSignedJWTBody} = await import("../src/apigee") - describe("constructSignedJWTBody tests", () => { const logger = new Logger() const jwtPrivateKey = "mockPrivateKey" diff --git a/packages/common/authFunctions/tests/test_userInfoHelpers.test.ts b/packages/common/authFunctions/tests/test_userInfoHelpers.test.ts index 8682e5ef2a..74df3a36b9 100644 --- a/packages/common/authFunctions/tests/test_userInfoHelpers.test.ts +++ b/packages/common/authFunctions/tests/test_userInfoHelpers.test.ts @@ -1,56 +1,59 @@ -import {jest} from "@jest/globals" +import { + afterEach, + beforeEach, + describe, + expect, + it, + vi +} from "vitest" import {Logger} from "@aws-lambda-powertools/logger" import axios from "axios" import jwksClient from "jwks-rsa" +import {fetchUserInfo} from "../src/userInfoHelpers" const oidcClientId = "valid_aud" const oidcIssuer = "valid_iss" const jwksEndpoint = "https://dummyauth.com/.well-known/jwks.json" +const { + mockExtractRoleInformation, + mockVerifyIdToken, + mockDecodeToken +} = vi.hoisted(() => ({ + mockExtractRoleInformation: vi.fn(), + mockVerifyIdToken: vi.fn(), + mockDecodeToken: vi.fn() +})) + const mockLogger: Partial = { - info: jest.fn(), - debug: jest.fn(), - error: jest.fn(), - warn: jest.fn() + info: vi.fn(), + debug: vi.fn(), + error: vi.fn(), + warn: vi.fn() } -const mockExtractRoleInformation = jest.fn() - -jest.unstable_mockModule("@cpt-ui-common/dynamoFunctions", () => { - return { - extractRoleInformation: mockExtractRoleInformation - } -}) - -const mockVerifyIdToken = jest.fn() -const mockDecodeToken = jest.fn() +vi.mock("@cpt-ui-common/dynamoFunctions", () => ({ + extractRoleInformation: mockExtractRoleInformation +})) // We need a dummy verification to pass so we can decode out the selected role ID -jest.unstable_mockModule("../src/cis2", async () => { - const verifyIdToken = mockVerifyIdToken.mockImplementation(async () => { - return { - selected_roleid: "role-id-1" - } - }) +vi.mock("../src/cis2", () => { + mockVerifyIdToken.mockImplementation(async () => ({ + selected_roleid: "role-id-1" + })) - const decodeToken = mockDecodeToken.mockImplementation(() => { - return { - selected_roleid: "role-id-1" - } - }) + mockDecodeToken.mockImplementation(() => ({ + selected_roleid: "role-id-1" + })) return { __esModule: true, - // This will need to be made to return the decoded ID token, which should be like: - // { selected_roleid: "foo" } - verifyIdToken: verifyIdToken, - decodeToken: decodeToken + verifyIdToken: mockVerifyIdToken, + decodeToken: mockDecodeToken } }) -const {fetchUserInfo} = await import("../src/userInfoHelpers") - describe("fetchUserInfo", () => { const accessToken = "test-access-token" const idToken = "test-id-token" @@ -75,7 +78,7 @@ describe("fetchUserInfo", () => { } beforeEach(() => { - jest.restoreAllMocks() + vi.restoreAllMocks() }) afterEach(() => { @@ -136,7 +139,7 @@ describe("fetchUserInfo", () => { ] } - const getSpy = jest.spyOn(axios, "get").mockResolvedValue({data}) + const getSpy = vi.spyOn(axios, "get").mockResolvedValue({data}) mockVerifyIdToken.mockImplementation(async () => { return { @@ -281,7 +284,7 @@ describe("fetchUserInfo", () => { ] } - const getSpy = jest.spyOn(axios, "get").mockResolvedValue({data}) + const getSpy = vi.spyOn(axios, "get").mockResolvedValue({data}) mockVerifyIdToken.mockImplementation(async () => { return { @@ -390,7 +393,7 @@ describe("fetchUserInfo", () => { }) it("should throw an error if axios request fails", async () => { - jest.spyOn(axios, "get").mockRejectedValue(new Error("Network error")) + vi.spyOn(axios, "get").mockRejectedValue(new Error("Network error")) await expect( fetchUserInfo( diff --git a/packages/common/authFunctions/vitest.config.ts b/packages/common/authFunctions/vitest.config.ts new file mode 100644 index 0000000000..2d790760a4 --- /dev/null +++ b/packages/common/authFunctions/vitest.config.ts @@ -0,0 +1,43 @@ +import {defineConfig, mergeConfig} from "vitest/config" +import sharedVitestConfig from "../../../vitest.shared.config" + +const apigeeHost = "https://dummyApigee" +const CIS2_OIDC_HOST = "https://dummy_cis2_auth.com" +const MOCK_OIDC_HOST = "https://dummy_mock_auth.com" + +const viteConfig = defineConfig({ + test: { + env: { + apigeeHost: apigeeHost, + apigeeCIS2TokenEndpoint: `${apigeeHost}/cis2_token`, + apigeeMockTokenEndpoint: `${apigeeHost}/mock_token`, + apigeePrescriptionsEndpoint: `${apigeeHost}/prescriptions`, + apigeePersonalDemographicsEndpoint: `${apigeeHost}/Patient`, + + TokenMappingTableName: "dummyTable", + jwtPrivateKeyArn: "dummy_jwtPrivateKeyArn", + jwtKid: "jwt_kid", + roleId: "dummy_role", + MOCK_MODE_ENABLED: "true", + + CIS2_OIDC_ISSUER: "valid_cis2_iss", + CIS2_OIDC_CLIENT_ID: "valid_cis2_aud", + CIS2_OIDC_HOST: CIS2_OIDC_HOST, + CIS2_OIDCJWKS_ENDPOINT: `${CIS2_OIDC_HOST}/.well-known/jwks.json`, + CIS2_USER_INFO_ENDPOINT: `${CIS2_OIDC_HOST}/userinfo`, + CIS2_USER_POOL_IDP: "CIS2DummyPoolIdentityProvider", + CIS2_IDP_TOKEN_PATH: `${CIS2_OIDC_HOST}/token`, + + MOCK_OIDC_ISSUER: "valid_mock_iss", + MOCK_OIDC_CLIENT_ID: "valid_mock_aud", + MOCK_OIDC_HOST: MOCK_OIDC_HOST, + MOCK_OIDCJWKS_ENDPOINT: `${MOCK_OIDC_HOST}/.well-known/jwks.json`, + MOCK_USER_INFO_ENDPOINT: `${MOCK_OIDC_HOST}/userinfo`, + MOCK_USER_POOL_IDP: "MockDummyPoolIdentityProvider", + MOCK_IDP_TOKEN_PATH: `${MOCK_OIDC_HOST}/token`, + FULL_CLOUDFRONT_DOMAIN: "cpt-ui-pr-854.dev.eps.national.nhs.uk" + } + } +}) + +export default mergeConfig(sharedVitestConfig, viteConfig) diff --git a/packages/common/dynamoFunctions/package.json b/packages/common/dynamoFunctions/package.json index 9831febaaf..be0b5478b3 100644 --- a/packages/common/dynamoFunctions/package.json +++ b/packages/common/dynamoFunctions/package.json @@ -14,7 +14,7 @@ }, "dependencies": { "@aws-lambda-powertools/logger": "^2.30.1", - "@aws-sdk/lib-dynamodb": "^3.975.0", + "@aws-sdk/lib-dynamodb": "^3.980.0", "@cpt-ui-common/common-types": "^1.0.0" } } diff --git a/packages/cpt-ui/__tests__/AccessProvider.test.tsx b/packages/cpt-ui/__tests__/AccessProvider.test.tsx index 9a4c4ede0c..f4b3dff230 100644 --- a/packages/cpt-ui/__tests__/AccessProvider.test.tsx +++ b/packages/cpt-ui/__tests__/AccessProvider.test.tsx @@ -307,10 +307,28 @@ describe("AccessProvider", () => { renderWithProvider() - expect(setIntervalSpy).toHaveBeenCalledWith(expect.any(Function), 300000) + expect(setIntervalSpy).toHaveBeenCalledWith(expect.any(Function), 60000) setIntervalSpy.mockRestore() }) + it("should check user info when component mounts", () => { + mockUpdateTrackerUserInfo.mockResolvedValue({error: null}) + + mockAuthHook.mockReturnValue({ + isSignedIn: true, + isSigningIn: false, + selectedRole: {name: "TestRole"}, + updateTrackerUserInfo: mockUpdateTrackerUserInfo + }) + mockLocationHook.mockReturnValue({pathname: "/search-by-prescription-id"}) + + renderWithProvider() + + expect(logger.debug).toHaveBeenCalledWith("On load user info check") + expect(logger.debug).toHaveBeenCalledWith("Refreshing user info") + expect(mockUpdateTrackerUserInfo).toHaveBeenCalledTimes(1) + }) + it("should clear interval on component unmount", () => { const clearIntervalSpy = jest.spyOn(globalThis, "clearInterval") @@ -346,7 +364,7 @@ describe("AccessProvider", () => { renderWithProvider() act(() => { - jest.advanceTimersByTime(300001) + jest.advanceTimersByTime(60001) }) expect(logger.debug).toHaveBeenCalledWith( @@ -368,7 +386,7 @@ describe("AccessProvider", () => { renderWithProvider() act(() => { - jest.advanceTimersByTime(300001) + jest.advanceTimersByTime(60001) }) expect(logger.debug).toHaveBeenCalledWith( @@ -390,11 +408,11 @@ describe("AccessProvider", () => { renderWithProvider() await act(async () => { - jest.advanceTimersByTime(300001) + jest.advanceTimersByTime(60001) }) - expect(logger.info).toHaveBeenCalledWith("Periodic user info check") - expect(logger.info).toHaveBeenCalledWith("Refreshing user info") + expect(logger.debug).toHaveBeenCalledWith("Periodic user info check") + expect(logger.debug).toHaveBeenCalledWith("Refreshing user info") expect(mockUpdateTrackerUserInfo).toHaveBeenCalled() }) @@ -413,7 +431,7 @@ describe("AccessProvider", () => { renderWithProvider() await act(async () => { - jest.advanceTimersByTime(300001) + jest.advanceTimersByTime(60001) }) expect(mockUpdateTrackerUserInfo).toHaveBeenCalled() @@ -433,7 +451,7 @@ describe("AccessProvider", () => { renderWithProvider() await act(async () => { - jest.advanceTimersByTime(300001) + jest.advanceTimersByTime(60001) }) expect(logger.debug).toHaveBeenCalledWith("Not checking user info") @@ -452,7 +470,7 @@ describe("AccessProvider", () => { renderWithProvider() await act(async () => { - jest.advanceTimersByTime(300001) + jest.advanceTimersByTime(60001) }) // Should call updateTrackerUserInfo when not on restricted paths @@ -461,8 +479,9 @@ describe("AccessProvider", () => { it("should continue running interval after error occurs", async () => { mockUpdateTrackerUserInfo - .mockResolvedValueOnce({error: "First error", invalidSessionCause: "InvalidSession"}) - .mockResolvedValueOnce({error: null}) + .mockResolvedValueOnce({error: null}) // on load check + .mockResolvedValueOnce({error: "First error", invalidSessionCause: "InvalidSession"}) // first periodic check + .mockResolvedValueOnce({error: null}) // second periodic check const authContext = { isSignedIn: true, @@ -477,17 +496,17 @@ describe("AccessProvider", () => { // First interval execution - should error and navigate await act(async () => { - jest.advanceTimersByTime(300001) + jest.advanceTimersByTime(60001) }) expect(handleRestartLogin).toHaveBeenCalledWith(authContext, "InvalidSession") - expect(mockUpdateTrackerUserInfo).toHaveBeenCalledTimes(1) + expect(mockUpdateTrackerUserInfo).toHaveBeenCalledTimes(2) jest.clearAllMocks() // Second interval execution - should succeed await act(async () => { - jest.advanceTimersByTime(300001) + jest.advanceTimersByTime(60001) }) expect(mockUpdateTrackerUserInfo).toHaveBeenCalledTimes(1) diff --git a/packages/cpt-ui/__tests__/App.test.tsx b/packages/cpt-ui/__tests__/App.test.tsx index 9d04688544..904a0dddce 100644 --- a/packages/cpt-ui/__tests__/App.test.tsx +++ b/packages/cpt-ui/__tests__/App.test.tsx @@ -1,8 +1,15 @@ import "@testing-library/jest-dom" -import {render, screen} from "@testing-library/react" -import {BrowserRouter} from "react-router-dom" +import +{render, + screen, + fireEvent, + waitFor, + act} + from "@testing-library/react" +import {BrowserRouter, MemoryRouter} from "react-router-dom" import React from "react" import App from "@/App" +import {FRONTEND_PATHS} from "@/constants/environment" // Mock all the context providers jest.mock("@/context/AuthProvider", () => ({ @@ -64,19 +71,284 @@ jest.mock("@/pages/SessionLoggedOut", () => () =>
Session Logged Out
) // Mock EPSCookieBanner jest.mock("@/components/EPSCookieBanner", () => () =>
Cookie Banner
) +jest.mock("@/pages/TooManySearchResultsPage", () => () =>
Too Many Search Results
) +jest.mock("@/pages/NoPrescriptionsFoundPage", () => () =>
No Prescriptions Found
) +jest.mock("@/pages/NoPatientsFoundPage", () => () =>
No Patients Found
) + +// Mock HEADER_STRINGS to avoid dependency on constants +jest.mock("@/constants/ui-strings/HeaderStrings", () => ({ + HEADER_STRINGS: { + SKIP_TO_MAIN_CONTENT: "Skip to main content" + } +})) + +// Test helper function to render App with specific route +const renderAppAtRoute = (route: string) => { + return render( + + + + ) +} describe("App", () => { - it("renders the skip link for regular pages", () => { - render( - - - - ) - - const skipLink = screen.getByTestId("eps_header_skipLink") - expect(skipLink).toBeInTheDocument() - expect(skipLink).toHaveAttribute("href", "#main-content") - expect(skipLink).toHaveTextContent("Skip to main content") - expect(skipLink).toHaveClass("nhsuk-skip-link") + // Setup function to clear focus before each test + beforeEach(() => { + // Reset focus to body + if (document.activeElement && document.activeElement !== document.body) { + (document.activeElement as HTMLElement).blur?.() + } + // Clear any existing event listeners + jest.clearAllMocks() + }) + + describe("Skip link rendering", () => { + it("renders the skip link for regular pages", () => { + render( + + + + ) + + const skipLink = screen.getByTestId("eps_header_skipLink") + expect(skipLink).toBeInTheDocument() + expect(skipLink).toHaveAttribute("href", "#main-content") + expect(skipLink).toHaveTextContent("Skip to main content") + expect(skipLink).toHaveClass("nhsuk-skip-link") + }) + + it("renders skip link with patient details banner target for prescription list current page", () => { + renderAppAtRoute(FRONTEND_PATHS.PRESCRIPTION_LIST_CURRENT) + + const skipLink = screen.getByTestId("eps_header_skipLink") + expect(skipLink).toHaveAttribute("href", "#patient-details-banner") + }) + + it("renders skip link with patient details banner target for prescription list future page", () => { + renderAppAtRoute(FRONTEND_PATHS.PRESCRIPTION_LIST_FUTURE) + + const skipLink = screen.getByTestId("eps_header_skipLink") + expect(skipLink).toHaveAttribute("href", "#patient-details-banner") + }) + + it("renders skip link with patient details banner target for prescription list past page", () => { + renderAppAtRoute(FRONTEND_PATHS.PRESCRIPTION_LIST_PAST) + + const skipLink = screen.getByTestId("eps_header_skipLink") + expect(skipLink).toHaveAttribute("href", "#patient-details-banner") + }) + + it("renders skip link with patient details banner target for prescription details page", () => { + renderAppAtRoute(`${FRONTEND_PATHS.PRESCRIPTION_DETAILS_PAGE}/123`) + + const skipLink = screen.getByTestId("eps_header_skipLink") + expect(skipLink).toHaveAttribute("href", "#patient-details-banner") + }) + + it("renders skip link with main content target for non-prescription pages", () => { + renderAppAtRoute(FRONTEND_PATHS.SEARCH_BY_PRESCRIPTION_ID) + + const skipLink = screen.getByTestId("eps_header_skipLink") + expect(skipLink).toHaveAttribute("href", "#main-content") + }) + }) + + describe("Skip link keyboard navigation", () => { + it("focuses skip link on first Tab press when page loads without user interaction", async () => { + renderAppAtRoute("/") + + const skipLink = screen.getByTestId("eps_header_skipLink") + + // Simulate Tab key press + fireEvent.keyDown(document, {key: "Tab", code: "Tab"}) + + await waitFor(() => { + expect(skipLink).toHaveFocus() + }) + }) + + it("does not focus skip link on Tab press when user has already clicked on page", async () => { + renderAppAtRoute("/") + + const skipLink = screen.getByTestId("eps_header_skipLink") + + // Simulate user clicking on the page + fireEvent.click(document.body) + + // Simulate Tab key press + fireEvent.keyDown(document, {key: "Tab", code: "Tab"}) + + await waitFor(() => { + expect(skipLink).not.toHaveFocus() + }) + }) + + it("does not focus skip link on Tab press when user has already focused an element", async () => { + renderAppAtRoute("/") + + const skipLink = screen.getByTestId("eps_header_skipLink") + + // Create a focusable element and simulate user focusing it + const button = document.createElement("button") + button.setAttribute("data-testid", "test-button") + document.body.appendChild(button) + fireEvent.focusIn(button) + + // Simulate Tab key press + fireEvent.keyDown(document, {key: "Tab", code: "Tab"}) + + await waitFor(() => { + expect(skipLink).not.toHaveFocus() + }) + + // Cleanup + document.body.removeChild(button) + }) + + it("does not focus skip link on Shift+Tab press", async () => { + renderAppAtRoute("/") + + const skipLink = screen.getByTestId("eps_header_skipLink") + + // Simulate Shift+Tab key press + fireEvent.keyDown(document, {key: "Tab", code: "Tab", shiftKey: true}) + + await waitFor(() => { + expect(skipLink).not.toHaveFocus() + }) + }) + + it("does not trigger skip link behavior on non-Tab key press", async () => { + renderAppAtRoute("/") + + const skipLink = screen.getByTestId("eps_header_skipLink") + + // Simulate Enter key press + fireEvent.keyDown(document, {key: "Enter", code: "Enter"}) + + await waitFor(() => { + expect(skipLink).not.toHaveFocus() + }) + }) + + it("only triggers skip link behavior once per page load", async () => { + renderAppAtRoute("/") + + const skipLink = screen.getByTestId("eps_header_skipLink") + + // First Tab press should focus skip link + fireEvent.keyDown(document, {key: "Tab", code: "Tab"}) + await waitFor(() => { + expect(skipLink).toHaveFocus() + }) + + // Blur the skip link + skipLink.blur() + + // Second Tab press should not focus skip link again + fireEvent.keyDown(document, {key: "Tab", code: "Tab"}) + + // Give some time to ensure focus doesn't change + await new Promise(resolve => setTimeout(resolve, 50)) + expect(skipLink).not.toHaveFocus() + }) + + it("resets skip link behavior when navigating to a new page", async () => { + const {rerender} = render( + + + + ) + + let skipLink = screen.getByTestId("eps_header_skipLink") + + // First Tab press should focus skip link + fireEvent.keyDown(document, {key: "Tab", code: "Tab"}) + await waitFor(() => { + expect(skipLink).toHaveFocus() + }) + + // Navigate to a new page by re-rendering with a different route + await act(async () => { + rerender( + + + + ) + }) + + // Get the new skip link element after rerender + skipLink = screen.getByTestId("eps_header_skipLink") + + // Tab press should focus skip link again after navigation + fireEvent.keyDown(document, {key: "Tab", code: "Tab"}) + await waitFor(() => { + expect(skipLink).toHaveFocus() + }) + }) + + it("handles case when skip link element is not found", async () => { + renderAppAtRoute("/") + + // Mock querySelector to return null + const originalQuerySelector = document.querySelector + document.querySelector = jest.fn().mockReturnValue(null) + + // This should not throw an error + fireEvent.keyDown(document, {key: "Tab", code: "Tab"}) + + // Restore original querySelector + document.querySelector = originalQuerySelector + }) + + it("detects user interaction when an element already has focus on page load", async () => { + // Create a focusable element and focus it before rendering + const input = document.createElement("input") + input.setAttribute("data-testid", "pre-focused-input") + document.body.appendChild(input) + input.focus() + + renderAppAtRoute("/") + + const skipLink = screen.getByTestId("eps_header_skipLink") + + // Tab press should not focus skip link since user has already interacted + fireEvent.keyDown(document, {key: "Tab", code: "Tab"}) + + await waitFor(() => { + expect(skipLink).not.toHaveFocus() + }) + + // Cleanup + document.body.removeChild(input) + }) + }) + + describe("Route handling", () => { + it("renders the app with routing components", () => { + render( + + + + ) + + // Verify the basic app structure is rendered + expect(screen.getByTestId("eps_header_skipLink")).toBeInTheDocument() + expect(screen.getByTestId("layout")).toBeInTheDocument() + }) + + it("handles different route paths without errors", () => { + expect(() => { + render( + + + + ) + }).not.toThrow() + + expect(screen.getByTestId("eps_header_skipLink")).toBeInTheDocument() + }) }) }) + +// Removed duplicate describe block diff --git a/packages/cpt-ui/__tests__/LogoutPage.test.tsx b/packages/cpt-ui/__tests__/LogoutPage.test.tsx index fa0fe04880..dabf2a3e8c 100644 --- a/packages/cpt-ui/__tests__/LogoutPage.test.tsx +++ b/packages/cpt-ui/__tests__/LogoutPage.test.tsx @@ -46,7 +46,8 @@ jest.mock("@/helpers/utils", () => ({ // Mock logger jest.mock("@/helpers/logger", () => ({ logger: { - info: jest.fn() + info: jest.fn(), + debug: jest.fn() } })) diff --git a/packages/cpt-ui/src/App.tsx b/packages/cpt-ui/src/App.tsx index 67c2b9caef..20121daa86 100644 --- a/packages/cpt-ui/src/App.tsx +++ b/packages/cpt-ui/src/App.tsx @@ -34,17 +34,23 @@ import {HEADER_STRINGS} from "@/constants/ui-strings/HeaderStrings" function AppContent() { const location = useLocation() - // this useEffect ensures that focus starts with skip link when using tab navigation + // this useEffect ensures that focus starts with skip link when using tab navigation, + // unless the user has already interacted with the page useEffect(() => { let hasTabbed = false + let hasUserInteracted = false const activeElement = document.activeElement as HTMLElement - if (activeElement && activeElement !== document.body) { - activeElement.blur() + if (activeElement && activeElement !== document.body && activeElement.tagName !== "HTML") { + hasUserInteracted = true + } + + const handleUserInteraction = () => { + hasUserInteracted = true } const handleKeyDown = (e: KeyboardEvent) => { - if (e.key === "Tab" && !hasTabbed && !e.shiftKey) { + if (e.key === "Tab" && !hasTabbed && !hasUserInteracted && !e.shiftKey) { hasTabbed = true e.preventDefault() const skipLink = document.querySelector(".nhsuk-skip-link") as HTMLElement @@ -52,13 +58,19 @@ function AppContent() { skipLink.focus() } document.removeEventListener("keydown", handleKeyDown) + document.removeEventListener("click", handleUserInteraction) + document.removeEventListener("focusin", handleUserInteraction) } } + document.addEventListener("click", handleUserInteraction) + document.addEventListener("focusin", handleUserInteraction) document.addEventListener("keydown", handleKeyDown) return () => { document.removeEventListener("keydown", handleKeyDown) + document.removeEventListener("click", handleUserInteraction) + document.removeEventListener("focusin", handleUserInteraction) } }, [location.pathname]) diff --git a/packages/cpt-ui/src/constants/ui-strings/PrivacyNoticeStrings.ts b/packages/cpt-ui/src/constants/ui-strings/PrivacyNoticeStrings.ts index 1a23f2d2c6..6eac044ffb 100644 --- a/packages/cpt-ui/src/constants/ui-strings/PrivacyNoticeStrings.ts +++ b/packages/cpt-ui/src/constants/ui-strings/PrivacyNoticeStrings.ts @@ -1,5 +1,5 @@ export const PrivacyNoticeStrings = { - pageTitle: "Privacy notice - Prescription tracker", + pageTitle: "Privacy notice – Prescription tracker", home: "Home", privacyNotice: "Privacy Notice", header: "Privacy notice for the Prescription Tracker", @@ -22,12 +22,12 @@ export const PrivacyNoticeStrings = { dataCollected: { header: "What data we collect", explanation: "We collect a range of your personal data, including:", - identifiable: "identifiable data - through signing in using ", + identifiable: "identifiable data – through signing in using ", cis2Link: "NHS CIS2 Authentication (opens in new tab)", stores: ", we store your CIS2 Unique User ID (UUID), name, role and organisation name", - usage: "usage data - information about how you interact with the Prescription Tracker, including your IP address," + usage: "usage data – information about how you interact with the Prescription Tracker, including your IP address," + " browser type, operating system and activity logs", - device: "device information - information about the device you use to access the Prescription Tracker," + device: "device information – information about the device you use to access the Prescription Tracker," + " such as device type and device identifiers" }, dataSource: { @@ -38,22 +38,22 @@ export const PrivacyNoticeStrings = { usage: { header: "How we use your data", explanation: "We use your personal data for the following purposes: ", - functionality: "to provide core functionality -" + functionality: "to provide core functionality –" + " we process your data to enable you to use the Prescription Tracker", - serviceImprovement: "service improvement - we analyse your usage patterns to understand how you use" + serviceImprovement: "service improvement – we analyse your usage patterns to understand how you use" + " the Prescription Tracker and improve its functionality." + " To do this, we put small files called 'analytic cookies' on your device using software called" + " Amazon CloudWatch RUM. These cookies are optional. The information collected includes:" + " the type of device you used, your browser type, your operating system, the date and time" + " you used the service and how you interacted with the service. For more information, see our ", cookie: "cookie policy", - audit: "audit and security - we collect activity logs of actions you have taken for monitoring and audit purposes." + audit: "audit and security – we collect activity logs of actions you have taken for monitoring and audit purposes." + " We process your authentication credentials, usage data and device information to ensure the security" + " of the service", - answer: "answer enquiries and investigate issues with the service -" + answer: "answer enquiries and investigate issues with the service –" + " if you contact us about any issues you are having with the service, we need to process your data to investigate" + " and resolve the issue you raised", - legal: "to comply with our legal obligations -" + legal: "to comply with our legal obligations –" + " we process your data to comply with our legal obligation to operate the service and any" + " other applicable laws or regulations" }, @@ -74,9 +74,9 @@ export const PrivacyNoticeStrings = { sharing: { header: "Who we share data with", mayShare: "We may share your data with:", - share1: "your employing organisation - if misuse is detected or reported," + share1: "your employing organisation – if misuse is detected or reported," + " we may need to share your data with your employing organisation for investigation purposes", - share2: "legal authorities - if required by law, we may disclose your personal data" + share2: "legal authorities – if required by law, we may disclose your personal data" + " to law enforcement or other legal entities" }, retention: { diff --git a/packages/cpt-ui/src/context/AccessProvider.tsx b/packages/cpt-ui/src/context/AccessProvider.tsx index 64db3b1262..2281647f88 100644 --- a/packages/cpt-ui/src/context/AccessProvider.tsx +++ b/packages/cpt-ui/src/context/AccessProvider.tsx @@ -92,6 +92,26 @@ export const AccessProvider = ({children}: { children: ReactNode }) => { } } + const checkUserInfo = () => { + // Check if a user is signed in, if it fails sign the user out. + if (auth.isSigningIn === true || ALLOWED_NO_ROLE_PATHS.includes(location.pathname)) { + logger.debug("Not checking user info") + return + } + + if (auth.isSignedIn) { + logger.debug("Refreshing user info") + auth.updateTrackerUserInfo().then((response) => { + if (response.error) { + logger.debug("Restarting login") + handleRestartLogin(auth, response.invalidSessionCause) + } + }) + } + + return + } + useEffect(() => { const currentPath = location.pathname const onSelectYourRole = currentPath === FRONTEND_PATHS.SELECT_YOUR_ROLE @@ -109,27 +129,17 @@ export const AccessProvider = ({children}: { children: ReactNode }) => { ]) useEffect(() => { - // If user is signedIn, every 5 minutes call tracker user info. If it fails, sign the user out. - const internalId = setInterval(() => { - const currentPath = location.pathname + // Check if user is logged in on page load. + logger.debug("On load user info check") + checkUserInfo() - if (auth.isSigningIn === true || ALLOWED_NO_ROLE_PATHS.includes(currentPath)) { - logger.debug("Not checking user info") - return - } - - logger.info("Periodic user info check") - if (auth.isSignedIn) { - logger.info("Refreshing user info") - auth.updateTrackerUserInfo().then((response) => { - if (response.error) { - handleRestartLogin(auth, response.invalidSessionCause) - } - }) - } - }, 300000) // 300000 ms = 5 minutes + // Then check every minute + const interval = setInterval(() => { + logger.debug("Periodic user info check") + checkUserInfo() + }, 60000) // 60000 ms = 1 minute - return () => clearInterval(internalId) + return () => clearInterval(interval) }, [auth.isSignedIn, auth.isSigningIn, location.pathname]) if (shouldBlockChildren()) { diff --git a/packages/patientSearchLambda/package.json b/packages/patientSearchLambda/package.json index b5a774d164..1caeb38356 100644 --- a/packages/patientSearchLambda/package.json +++ b/packages/patientSearchLambda/package.json @@ -14,8 +14,8 @@ }, "dependencies": { "@aws-lambda-powertools/logger": "^2.30.1", - "@aws-sdk/client-dynamodb": "^3.956.0", - "@aws-sdk/lib-dynamodb": "^3.975.0", + "@aws-sdk/client-dynamodb": "^3.980.0", + "@aws-sdk/lib-dynamodb": "^3.980.0", "@cpt-ui-common/authFunctions": "^1.0.0", "@cpt-ui-common/lambdaUtils": "^1.0.0", "@cpt-ui-common/middyErrorHandler": "^1.0.0", diff --git a/packages/prescriptionDetailsLambda/.jest/setEnvVars.js b/packages/prescriptionDetailsLambda/.jest/setEnvVars.js deleted file mode 100644 index 1a68ba2808..0000000000 --- a/packages/prescriptionDetailsLambda/.jest/setEnvVars.js +++ /dev/null @@ -1,28 +0,0 @@ - -process.env.apigeeHost = "https://dummyApigee" -process.env.apigeeCIS2TokenEndpoint = `${process.env.apigeeHost}/cis2_token` -process.env.apigeeMockTokenEndpoint = `${process.env.apigeeHost}/mock_token` -process.env.apigeePrescriptionsEndpoint = `${process.env.apigeeHost}/prescriptions` -process.env.apigeePersonalDemographicsEndpoint = `${process.env.apigeeHost}/Patient` - -process.env.TokenMappingTableName = "dummyTable" -process.env.jwtPrivateKeyArn = "dummy_jwtPrivateKeyArn" -process.env.jwtKid = "jwt_kid" -process.env.roleId = "dummy_role" -process.env.MOCK_MODE_ENABLED = "true" - -process.env.CIS2_OIDC_ISSUER = "valid_cis2_iss" -process.env.CIS2_OIDC_CLIENT_ID = "valid_cis2_aud" -process.env.CIS2_OIDC_HOST = "https://dummy_cis2_auth.com" -process.env.CIS2_OIDCJWKS_ENDPOINT = `${process.env.CIS2_OIDC_HOST}/.well-known/jwks.json` -process.env.CIS2_USER_INFO_ENDPOINT = `${process.env.CIS2_OIDC_HOST}/userinfo` -process.env.CIS2_USER_POOL_IDP = "CIS2DummyPoolIdentityProvider" -process.env.CIS2_IDP_TOKEN_PATH = `${process.env.CIS2_OIDC_HOST}/token` - -process.env.MOCK_OIDC_ISSUER = "valid_mock_iss" -process.env.MOCK_OIDC_CLIENT_ID = "valid_mock_aud" -process.env.MOCK_OIDC_HOST = "https://dummy_mock_auth.com" -process.env.MOCK_OIDCJWKS_ENDPOINT = `${process.env.MOCK_OIDC_HOST}/.well-known/jwks.json` -process.env.MOCK_USER_INFO_ENDPOINT = `${process.env.MOCK_OIDC_HOST}/userinfo` -process.env.MOCK_USER_POOL_IDP = "MockDummyPoolIdentityProvider" -process.env.MOCK_IDP_TOKEN_PATH = `${process.env.MOCK_OIDC_HOST}/token` diff --git a/packages/prescriptionDetailsLambda/jest.config.ts b/packages/prescriptionDetailsLambda/jest.config.ts deleted file mode 100644 index 1ad45dd190..0000000000 --- a/packages/prescriptionDetailsLambda/jest.config.ts +++ /dev/null @@ -1,10 +0,0 @@ -import defaultConfig from "../../jest.default.config.ts" -import type {JestConfigWithTsJest} from "ts-jest" - -const jestConfig: JestConfigWithTsJest = { - ...defaultConfig, - rootDir: "./", - setupFiles: ["/.jest/setEnvVars.js"] -} - -export default jestConfig diff --git a/packages/prescriptionDetailsLambda/jest.debug.config.ts b/packages/prescriptionDetailsLambda/jest.debug.config.ts deleted file mode 100644 index a306273831..0000000000 --- a/packages/prescriptionDetailsLambda/jest.debug.config.ts +++ /dev/null @@ -1,9 +0,0 @@ -import config from "./jest.config" -import type {JestConfigWithTsJest} from "ts-jest" - -const debugConfig: JestConfigWithTsJest = { - ...config, - "preset": "ts-jest" -} - -export default debugConfig diff --git a/packages/prescriptionDetailsLambda/package.json b/packages/prescriptionDetailsLambda/package.json index 4267abbd47..efab63791c 100644 --- a/packages/prescriptionDetailsLambda/package.json +++ b/packages/prescriptionDetailsLambda/package.json @@ -6,7 +6,7 @@ "author": "NHS Digital", "license": "MIT", "scripts": { - "unit": "POWERTOOLS_DEV=true NODE_OPTIONS=--experimental-vm-modules jest --no-cache --coverage", + "unit": "POWERTOOLS_DEV=true vitest run --coverage", "lint": "eslint --max-warnings 0 --fix --config ../../eslint.config.mjs .", "compile": "tsc --build", "test": "npm run compile && npm run unit", @@ -16,15 +16,15 @@ "@aws-lambda-powertools/commons": "^2.30.0", "@aws-lambda-powertools/logger": "^2.30.1", "@aws-lambda-powertools/parameters": "^2.30.1", - "@aws-sdk/client-dynamodb": "^3.956.0", - "@aws-sdk/client-lambda": "^3.975.0", - "@aws-sdk/client-secrets-manager": "^3.975.0", - "@aws-sdk/lib-dynamodb": "^3.975.0", + "@aws-sdk/client-dynamodb": "^3.980.0", + "@aws-sdk/client-lambda": "^3.978.0", + "@aws-sdk/client-secrets-manager": "^3.980.0", + "@aws-sdk/lib-dynamodb": "^3.980.0", "@cpt-ui-common/authFunctions": "^1.0.0", "@cpt-ui-common/common-types": "^1.0.0", "@cpt-ui-common/doHSClient": "^1.0.0", - "@cpt-ui-common/middyErrorHandler": "^1.0.0", "@cpt-ui-common/lambdaUtils": "^1.0.0", + "@cpt-ui-common/middyErrorHandler": "^1.0.0", "@cpt-ui-common/pdsClient": "^1.0.0", "@middy/core": "^7.0.2", "@middy/input-output-logger": "^7.0.2", @@ -37,7 +37,7 @@ "@types/aws-lambda": "^8.10.159", "@types/fhir": "^0.0.41", "axios-mock-adapter": "^2.0.0", - "mock-jwks": "^3.2.2", + "mock-jwks": "^3.3.5", "nock": "^14.0.10" } } diff --git a/packages/prescriptionDetailsLambda/tests/test_extensionUtils.test.ts b/packages/prescriptionDetailsLambda/tests/test_extensionUtils.test.ts index 7463799573..920495d471 100644 --- a/packages/prescriptionDetailsLambda/tests/test_extensionUtils.test.ts +++ b/packages/prescriptionDetailsLambda/tests/test_extensionUtils.test.ts @@ -1,4 +1,6 @@ +import {describe, expect, it} from "vitest" + import {Extension} from "fhir/r4" import { findExtensionByKey, diff --git a/packages/prescriptionDetailsLambda/tests/test_getPatientDetails.test.ts b/packages/prescriptionDetailsLambda/tests/test_getPatientDetails.test.ts index 705a9f0c50..833c616aca 100644 --- a/packages/prescriptionDetailsLambda/tests/test_getPatientDetails.test.ts +++ b/packages/prescriptionDetailsLambda/tests/test_getPatientDetails.test.ts @@ -1,4 +1,10 @@ -import {jest} from "@jest/globals" +import { + afterAll, + describe, + expect, + it, + vi +} from "vitest" import {Logger} from "@aws-lambda-powertools/logger" import {PatientSummary} from "@cpt-ui-common/common-types" @@ -6,19 +12,41 @@ import {PatientSummary} from "@cpt-ui-common/common-types" const apigeePersonalDemographicsEndpoint = process.env.apigeePersonalDemographicsEndpoint as string ?? "" const logger: Logger = new Logger({serviceName: "getPatientDetails", logLevel: "DEBUG"}) -const mockGetPatientDetails = jest.fn() -jest.unstable_mockModule("@cpt-ui-common/pdsClient", () => ({ - Client: jest.fn().mockImplementation(() => ({ - with_access_token: jest.fn().mockImplementation(() => ({ - with_role_id: jest.fn().mockImplementation(() => ({ - with_org_code: jest.fn().mockImplementation(() => ({ - with_correlation_id:jest.fn().mockImplementation(() => ({ - getPatientDetails: mockGetPatientDetails - })) - })) - })) - })) - })), +const { + mockGetPatientDetails, + mockClient +} = vi.hoisted(() => { + const mockGetPatientDetails = vi.fn() + const mockWithCorrelationId = vi.fn().mockImplementation(() => ({ + getPatientDetails: mockGetPatientDetails + })) + const mockWithOrgCode = vi.fn().mockImplementation(() => ({ + with_correlation_id: mockWithCorrelationId + })) + const mockWithRoleId = vi.fn().mockImplementation(() => ({ + with_org_code: mockWithOrgCode + })) + const mockWithAccessToken = vi.fn().mockImplementation(() => ({ + with_role_id: mockWithRoleId + })) + const mockClient = vi.fn().mockImplementation(function () { + return { + with_access_token: mockWithAccessToken + } + }) + + return { + mockGetPatientDetails, + mockClient, + mockWithAccessToken, + mockWithRoleId, + mockWithOrgCode, + mockWithCorrelationId + } +}) + +vi.mock("@cpt-ui-common/pdsClient", () => ({ + Client: mockClient, patientDetails: { OutcomeType: { SUCCESS: "SUCCESS", @@ -48,8 +76,8 @@ const {getPatientDetails} = await import("../src/services/getPatientDetails") describe("Get Patient Details", () => { afterAll(() => { - jest.resetModules() - jest.resetAllMocks() + vi.resetModules() + vi.clearAllMocks() }) it("returns patient details when PDS called successfully", async () => { diff --git a/packages/prescriptionDetailsLambda/tests/test_handler.test.ts b/packages/prescriptionDetailsLambda/tests/test_handler.test.ts index 7ab8bf5110..d8cd32d205 100644 --- a/packages/prescriptionDetailsLambda/tests/test_handler.test.ts +++ b/packages/prescriptionDetailsLambda/tests/test_handler.test.ts @@ -1,34 +1,51 @@ -import {jest} from "@jest/globals" +import { + beforeEach, + describe, + expect, + it, + vi +} from "vitest" import {mockAPIGatewayProxyEvent, mockContext, mockMergedResponse} from "./mockObjects" import {Logger} from "@aws-lambda-powertools/logger" import {AxiosInstance} from "axios" import {AuthenticateRequestOptions} from "@cpt-ui-common/authFunctions" import {DynamoDBDocumentClient} from "@aws-sdk/lib-dynamodb" +import {newHandler} from "../src/handler" + +const { + mockProcessPrescriptionRequest, + mockAuthParametersFromEnv, + mockBuildApigeeHeaders, + mockAuthenticationMiddleware +} = vi.hoisted(() => { + return { + mockProcessPrescriptionRequest: vi.fn(() => Promise.resolve(mockMergedResponse)), + mockAuthParametersFromEnv: vi.fn(), + mockBuildApigeeHeaders: vi.fn(), + mockAuthenticationMiddleware: vi.fn(() => ({before: () => {}})) + } +}) -const mockProcessPrescriptionRequest = jest.fn(() => Promise.resolve(mockMergedResponse)) -jest.unstable_mockModule("../src/services/prescriptionService", () => { +vi.mock("../src/services/prescriptionService", () => { return { processPrescriptionRequest: mockProcessPrescriptionRequest } }) -// Needed to avoid issues with ESM imports in jest -jest.unstable_mockModule("@cpt-ui-common/authFunctions", () => ({ - authParametersFromEnv: jest.fn(), - buildApigeeHeaders: jest.fn(), - authenticationMiddleware: () => ({before: () => {}}) +// Needed to avoid issues with ESM imports in vitest +vi.mock("@cpt-ui-common/authFunctions", () => ({ + authParametersFromEnv: mockAuthParametersFromEnv, + buildApigeeHeaders: mockBuildApigeeHeaders, + authenticationMiddleware: mockAuthenticationMiddleware })) -// Import the handler after the mocks have been defined. -const {newHandler} = await import("../src/handler") - describe("Lambda Handler Tests", () => { // Create copy of the event for testing. let logger = new Logger({serviceName: "prescriptionDetailsLambda"}) - logger.warn = jest.fn() - logger.error = jest.fn() - logger.info = jest.fn() + logger.warn = vi.fn() + logger.error = vi.fn() + logger.info = vi.fn() const handler = newHandler({ errorResponseBody: {message: "A system error has occurred"}, logger: logger, @@ -43,8 +60,8 @@ describe("Lambda Handler Tests", () => { beforeEach(() => { // Reset mocks before each test. - jest.resetModules() - jest.clearAllMocks() + vi.resetModules() + vi.clearAllMocks() event.pathParameters = {prescriptionId: "dummy_prescription_id"} event.requestContext.authorizer = {roleId: "dummy_role", orgCode: "dummy_org"} }) diff --git a/packages/prescriptionDetailsLambda/tests/test_prescriptionService.test.ts b/packages/prescriptionDetailsLambda/tests/test_prescriptionService.test.ts index 8aabae7f75..7b84375eed 100644 --- a/packages/prescriptionDetailsLambda/tests/test_prescriptionService.test.ts +++ b/packages/prescriptionDetailsLambda/tests/test_prescriptionService.test.ts @@ -1,38 +1,60 @@ /* eslint-disable max-len */ -import {jest} from "@jest/globals" +import { + afterEach, + beforeEach, + describe, + expect, + it, + vi +} from "vitest" import nock from "nock" import type {Logger} from "@aws-lambda-powertools/logger" +const { + mockUuid, + mockDoHSClient, + mockGetPatient, + mockMergePrescriptionDetails, + mockAuthParametersFromEnv, + mockBuildApigeeHeaders, + mockAuthenticationMiddleware +} = vi.hoisted(() => ({ + mockUuid: vi.fn(() => "test-uuid"), + mockDoHSClient: vi.fn(), + mockGetPatient: vi.fn(), + mockMergePrescriptionDetails: vi.fn(), + mockAuthParametersFromEnv: vi.fn(), + mockBuildApigeeHeaders: vi.fn().mockImplementation(() => ({ + Authorization: "Bearer someAccessToken" + })), + mockAuthenticationMiddleware: vi.fn(() => ({before: () => {}})) +})) + // Mock uuid so that it is predictable. -jest.unstable_mockModule("uuid", () => ({ - v4: jest.fn(() => "test-uuid") +vi.mock("uuid", () => ({ + v4: mockUuid })) // Create a mock for the doHSClient function. -const mockDoHSClient = jest.fn() -jest.unstable_mockModule("@cpt-ui-common/doHSClient", () => ({ +vi.mock("@cpt-ui-common/doHSClient", () => ({ doHSClient: mockDoHSClient })) -const mockGetPatient = jest.fn() -jest.unstable_mockModule("../src/services/getPatientDetails", () => ({ +vi.mock("../src/services/getPatientDetails", () => ({ getPatientDetails: mockGetPatient })) // Mock mergePrescriptionDetails from responseMapper. -const mockMergePrescriptionDetails = jest.fn() -jest.unstable_mockModule("../src/utils/responseMapper", () => ({ +vi.mock("../src/utils/responseMapper", () => ({ mergePrescriptionDetails: mockMergePrescriptionDetails })) -// Needed to avoid issues with ESM imports in jest -jest.unstable_mockModule("@cpt-ui-common/authFunctions", () => ({ - authParametersFromEnv: jest.fn(), - buildApigeeHeaders: jest.fn().mockImplementation(() => ({ - Authorization: `Bearer someAccessToken` - })), - authenticationMiddleware: () => ({before: () => {}}) +// Needed to avoid issues with ESM imports in vitest +vi.mock("@cpt-ui-common/authFunctions", () => ({ + authParametersFromEnv: mockAuthParametersFromEnv, + buildApigeeHeaders: mockBuildApigeeHeaders, + authenticationMiddleware: mockAuthenticationMiddleware })) // Import some mock objects to use in our tests. @@ -70,13 +92,13 @@ describe("prescriptionService", () => { let logger: Logger beforeEach(() => { - jest.restoreAllMocks() + vi.clearAllMocks() // Clean up any pending nock interceptors nock.cleanAll() logger = { - info: jest.fn(), - warn: jest.fn(), - error: jest.fn() + info: vi.fn(), + warn: vi.fn(), + error: vi.fn() } as unknown as Logger }) diff --git a/packages/prescriptionDetailsLambda/tests/test_responseMapper.test.ts b/packages/prescriptionDetailsLambda/tests/test_responseMapper.test.ts index 2109facd8d..238c8d1d10 100644 --- a/packages/prescriptionDetailsLambda/tests/test_responseMapper.test.ts +++ b/packages/prescriptionDetailsLambda/tests/test_responseMapper.test.ts @@ -1,3 +1,10 @@ +import { + beforeEach, + describe, + expect, + it +} from "vitest" + import { Bundle, FhirResource, diff --git a/packages/prescriptionDetailsLambda/vitest.config.ts b/packages/prescriptionDetailsLambda/vitest.config.ts new file mode 100644 index 0000000000..fa6978adda --- /dev/null +++ b/packages/prescriptionDetailsLambda/vitest.config.ts @@ -0,0 +1,38 @@ +import {defineConfig, mergeConfig} from "vitest/config" +import sharedVitestConfig from "../../vitest.shared.config" + +const apigeeHost = "https://dummyApigee" +const CIS2_OIDC_HOST = "https://dummy_cis2_auth.com" +const MOCK_OIDC_HOST = "https://dummy_mock_auth.com" +const viteConfig = defineConfig({ + test: { + env: { + apigeeHost: apigeeHost, + apigeeCIS2TokenEndpoint: `${apigeeHost}/cis2_token`, + apigeeMockTokenEndpoint: `${apigeeHost}/mock_token`, + apigeePrescriptionsEndpoint: `${apigeeHost}/prescriptions`, + apigeePersonalDemographicsEndpoint: `${apigeeHost}/Patient`, + TokenMappingTableName: "dummyTable", + jwtPrivateKeyArn: "dummy_jwtPrivateKeyArn", + jwtKid: "jwt_kid", + roleId: "dummy_role", + MOCK_MODE_ENABLED: "true", + CIS2_OIDC_ISSUER: "valid_cis2_iss", + CIS2_OIDC_CLIENT_ID: "valid_cis2_aud", + CIS2_OIDC_HOST: CIS2_OIDC_HOST, + CIS2_OIDCJWKS_ENDPOINT: `${CIS2_OIDC_HOST}/.well-known/jwks.json`, + CIS2_USER_INFO_ENDPOINT: `${CIS2_OIDC_HOST}/userinfo`, + CIS2_USER_POOL_IDP: "CIS2DummyPoolIdentityProvider", + CIS2_IDP_TOKEN_PATH: `${CIS2_OIDC_HOST}/token`, + MOCK_OIDC_ISSUER: "valid_mock_iss", + MOCK_OIDC_CLIENT_ID: "valid_mock_aud", + MOCK_OIDC_HOST: MOCK_OIDC_HOST, + MOCK_OIDCJWKS_ENDPOINT: `${MOCK_OIDC_HOST}/.well-known/jwks.json`, + MOCK_USER_INFO_ENDPOINT: `${MOCK_OIDC_HOST}/userinfo`, + MOCK_USER_POOL_IDP: "MockDummyPoolIdentityProvider", + MOCK_IDP_TOKEN_PATH: `${MOCK_OIDC_HOST}/token` + } + } +}) + +export default mergeConfig(sharedVitestConfig, viteConfig) diff --git a/packages/prescriptionListLambda/package.json b/packages/prescriptionListLambda/package.json index 0a46dd7904..375cf1612f 100644 --- a/packages/prescriptionListLambda/package.json +++ b/packages/prescriptionListLambda/package.json @@ -14,8 +14,8 @@ }, "dependencies": { "@aws-lambda-powertools/logger": "^2.30.1", - "@aws-sdk/client-dynamodb": "^3.956.0", - "@aws-sdk/lib-dynamodb": "^3.975.0", + "@aws-sdk/client-dynamodb": "^3.980.0", + "@aws-sdk/lib-dynamodb": "^3.980.0", "@cpt-ui-common/authFunctions": "^1.0.0", "@cpt-ui-common/lambdaUtils": "^1.0.0", "@cpt-ui-common/middyErrorHandler": "^1.0.0", diff --git a/packages/selectedRoleLambda/package.json b/packages/selectedRoleLambda/package.json index f86f0a6a8a..b430f77908 100644 --- a/packages/selectedRoleLambda/package.json +++ b/packages/selectedRoleLambda/package.json @@ -14,8 +14,8 @@ }, "dependencies": { "@aws-lambda-powertools/logger": "^2.30.1", - "@aws-sdk/client-dynamodb": "^3.956.0", - "@aws-sdk/lib-dynamodb": "^3.975.0", + "@aws-sdk/client-dynamodb": "^3.980.0", + "@aws-sdk/lib-dynamodb": "^3.980.0", "@cpt-ui-common/authFunctions": "^1.0.0", "@cpt-ui-common/dynamoFunctions": "^1.0.0", "@cpt-ui-common/middyErrorHandler": "^1.0.0", diff --git a/packages/testingSupport/clearActiveSessions/package.json b/packages/testingSupport/clearActiveSessions/package.json index be684eb41b..f0127311c0 100644 --- a/packages/testingSupport/clearActiveSessions/package.json +++ b/packages/testingSupport/clearActiveSessions/package.json @@ -14,8 +14,8 @@ }, "dependencies": { "@aws-lambda-powertools/logger": "^2.30.1", - "@aws-sdk/client-dynamodb": "^3.956.0", - "@aws-sdk/lib-dynamodb": "^3.975.0", + "@aws-sdk/client-dynamodb": "^3.980.0", + "@aws-sdk/lib-dynamodb": "^3.980.0", "@cpt-ui-common/authFunctions": "^1.0.0", "@cpt-ui-common/dynamoFunctions": "^1.0.0", "@cpt-ui-common/middyErrorHandler": "^1.0.0", diff --git a/packages/trackerUserInfoLambda/package.json b/packages/trackerUserInfoLambda/package.json index 4b5b691263..6a1c90feef 100644 --- a/packages/trackerUserInfoLambda/package.json +++ b/packages/trackerUserInfoLambda/package.json @@ -14,8 +14,8 @@ }, "dependencies": { "@aws-lambda-powertools/logger": "^2.30.1", - "@aws-sdk/client-dynamodb": "^3.956.0", - "@aws-sdk/lib-dynamodb": "^3.975.0", + "@aws-sdk/client-dynamodb": "^3.980.0", + "@aws-sdk/lib-dynamodb": "^3.980.0", "@cpt-ui-common/authFunctions": "^1.0.0", "@cpt-ui-common/dynamoFunctions": "^1.0.0", "@cpt-ui-common/middyErrorHandler": "^1.0.0", diff --git a/sonar-project.properties b/sonar-project.properties index daea086451..b9c9a102ba 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -12,14 +12,16 @@ sonar.coverage.exclusions=\ release.config.js, \ packages/cdk/**, \ packages/cpt-ui/next.config.js, \ - packages/common/commonTypes/** + packages/common/commonTypes/**, \ + **/vitest.config.ts sonar.cpd.exclusions=\ packages/cloudfrontFunctions/tests/**, \ packages/cdk/nagSuppressions.ts, \ **/mock*, \ **/__mocks__*, \ - **/*.test.ts* + **/*.test.ts*, \ + **/vitest.config.ts # Define the modules sonar.modules=\ diff --git a/vitest.shared.config.ts b/vitest.shared.config.ts new file mode 100644 index 0000000000..708fcc732f --- /dev/null +++ b/vitest.shared.config.ts @@ -0,0 +1,18 @@ +import {defineConfig} from "vitest/config" + +const sharedVitestConfig = defineConfig({ + test: { + environment: "node", + globals: true, + reporters: "default", + coverage: { + "enabled": true, + provider: "v8", + reporter: ["text", "lcov"], + reportsDirectory: "./coverage" + }, + exclude: ["**/lib/**", "**/node_modules/**", "**/packages/**"] + } +}) + +export default sharedVitestConfig