diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 37ba088f4..9774b40d4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,15 +28,16 @@ jobs: sudo apt-get update sudo apt-get install -y ca-certificates curl gnupg sudo mkdir -p /etc/apt/keyrings - curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg + curl --proto '=https' --tlsv1.2 -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg NODE_MAJOR=20 echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list - name: Upgrade python packaging tools - run: python -m pip install --upgrade pip setuptools wheel + run: python -m pip install pip==26.1.2 setuptools==83.0.0 wheel==0.47.0 - name: Install poetry - run: pip install poetry + run: | + pip install --only-binary :all: poetry==2.2.1 - name: Cache poetry packages uses: actions/cache@v4 @@ -58,9 +59,7 @@ jobs: cd sandbox npm run test cd - - mkdir coverage coverage-temp - TMPDIR="./coverage-temp" npx lcov-result-merger sandbox/coverage/lcov.info coverage/lcov.info --prepend-source-files - rm -r coverage-temp + make merge-coverage-reports - name: Check licenses run: make check-licenses diff --git a/Makefile b/Makefile index 8a6a7c2d1..02c66a0b8 100644 --- a/Makefile +++ b/Makefile @@ -24,6 +24,12 @@ install-hooks: .git/hooks/pre-commit lint: .check-licenses .ensure-test-documentation-validity .lint-js .lint-python npm run lint +merge-coverage-reports: + mkdir -p coverage coverage-temp + TMPDIR="./coverage-temp" npm run merge-coverage-reports + rm -r coverage-temp + rm -rf sandbox/coverage/tmp + static-analysis: npm run static-analysis diff --git a/azure/azure-release-pipeline.yml b/azure/azure-release-pipeline.yml index e7b10a18e..9687628c9 100644 --- a/azure/azure-release-pipeline.yml +++ b/azure/azure-release-pipeline.yml @@ -37,6 +37,8 @@ extends: prod_producer_approval: true apigee_deployments: - environment: internal-dev + enable_monitoring: false + enable_status_monitoring: false jinja_templates: ENVIRONMENT_TYPE: 'internal' ERROR_ABOUT_LINK: ${{ variables.error_about_link }} @@ -48,6 +50,8 @@ extends: - environment: internal-dev-sandbox proxy_path: sandbox + enable_monitoring: false + enable_status_monitoring: false jinja_templates: ENVIRONMENT_TYPE: 'sandbox' ERROR_ABOUT_LINK: ${{ variables.error_about_link }} @@ -66,6 +70,8 @@ extends: - environment: ref depends_on: - manual_approval_ref + enable_monitoring: false + enable_status_monitoring: false jinja_templates: ENVIRONMENT_TYPE: 'internal' ERROR_ABOUT_LINK: ${{ variables.error_about_link }} @@ -83,6 +89,8 @@ extends: - environment: internal-qa depends_on: - manual_approval_internal_qa + enable_monitoring: false + enable_status_monitoring: false jinja_templates: ENVIRONMENT_TYPE: 'internal' ERROR_ABOUT_LINK: ${{ variables.error_about_link }} @@ -101,6 +109,8 @@ extends: proxy_path: sandbox depends_on: - manual_approval_internal_qa_sandbox + enable_monitoring: false + enable_status_monitoring: false jinja_templates: ENVIRONMENT_TYPE: 'sandbox' ERROR_ABOUT_LINK: ${{ variables.error_about_link }} @@ -120,6 +130,8 @@ extends: depends_on: - manual_approval_sandbox proxy_path: sandbox + enable_monitoring: false + enable_status_monitoring: false jinja_templates: ENVIRONMENT_TYPE: 'sandbox' ERROR_ABOUT_LINK: ${{ variables.error_about_link }} diff --git a/manifest_template.yml b/manifest_template.yml index 7bbabb04b..e39a4e325 100644 --- a/manifest_template.yml +++ b/manifest_template.yml @@ -15,6 +15,8 @@ APIGEE_ENVIRONMENTS: has_mock_auth: true variants: - name: internal-qa + global_ratelimit: '18000pm' + global_quota: '6000' display_name: Internal QA - name: ref has_mock_auth: true @@ -29,18 +31,26 @@ APIGEE_ENVIRONMENTS: variants: - name: internal-dev-sandbox display_name: Internal Development Sandbox + global_ratelimit: '18000pm' + global_quota: '6000' - name: internal-qa-sandbox variants: - name: internal-qa-sandbox display_name: Internal QA Sandbox + global_ratelimit: '18000pm' + global_quota: '6000' - name: sandbox variants: - name: sandbox display_name: Sandbox + global_ratelimit: '18000pm' + global_quota: '6000' - name: int variants: - name: int display_name: Integration Testing + global_ratelimit: '18000pm' + global_quota: '6000' - name: prod variants: - name: prod @@ -73,11 +83,11 @@ apigee: quota: enabled: true interval: 1 - limit: {{ VARIANT.global_quota | default(6000)}} + limit: {{ VARIANT.global_quota | default(12000)}} timeunit: minute spikeArrest: enabled: true - ratelimit: {{ VARIANT.global_ratelimit | default('18000pm') }} + ratelimit: {{ VARIANT.global_ratelimit | default('36000pm') }} app: quota: enabled: true diff --git a/package-lock.json b/package-lock.json index c2a1b1e1e..9732fb539 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "eslint-plugin-workspaces": "^0.11.0", "eslint-plugin-yml": "^1.2.0", "handlebars": "^4.7.9", + "lcov-result-merger": "^6.0.0", "license-checker": "^25.0.1", "minimist": "^1.2.2", "newman": "^6.2.2", @@ -4012,6 +4013,19 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -5389,6 +5403,151 @@ "node": ">=0.10" } }, + "node_modules/lcov-result-merger": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lcov-result-merger/-/lcov-result-merger-6.0.0.tgz", + "integrity": "sha512-RsXi/N4J9b37mu83d3L8WdCGU0jsftN6GlXaJySK6fu+zD48joHC579hpA3K4brPvwWOhbNX5NoT7mghxDi5qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "^3.3.3", + "yargs": "^18.0.0" + }, + "bin": { + "lcov-result-merger": "bin/lcov-result-merger.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/lcov-result-merger/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/lcov-result-merger/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/lcov-result-merger/node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/lcov-result-merger/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/lcov-result-merger/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lcov-result-merger/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/lcov-result-merger/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/lcov-result-merger/node_modules/yargs": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", + "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^9.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "string-width": "^7.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^22.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/lcov-result-merger/node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", diff --git a/package.json b/package.json index ba5fddaab..b079de7d9 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "sandbox-postman-collection": "newman run postman/NhsNotify.Sandbox.postman_collection.json", "integration-postman-collection": "poetry run python scripts/build_postman_environment.py && newman run postman/NhsNotify.Integration.postman_collection.json -e postman/Integration.test.postman_environment.json", "static-analysis": "./scripts/perform-static-analysis.sh", - "zap-security-scan": "./scripts/run_zap.sh" + "zap-security-scan": "./scripts/run_zap.sh", + "merge-coverage-reports": "lcov-result-merger sandbox/coverage/lcov.info coverage/lcov.info --prepend-source-files" }, "author": "NHS Digital", "license": "MIT", @@ -41,6 +42,7 @@ "eslint-plugin-workspaces": "^0.11.0", "eslint-plugin-yml": "^1.2.0", "handlebars": "^4.7.9", + "lcov-result-merger": "^6.0.0", "license-checker": "^25.0.1", "minimist": "^1.2.2", "newman": "^6.2.2", diff --git a/scripts/publish_zap_compatible.py b/scripts/publish_zap_compatible.py index f9bd467da..075ab987c 100644 --- a/scripts/publish_zap_compatible.py +++ b/scripts/publish_zap_compatible.py @@ -42,8 +42,9 @@ def scan_and_remove(obj, mappings): ( ("format", "date"), ("personalisation", None), - ("/", None), - ("/", None), + ("/{client-provided-message-status-URI}", None), + ("/{client-provided-channel-status-URI}", None), + ("/{client-provided-recipient-response-URI}", None), ) ), f diff --git a/specification/communications-manager.yaml b/specification/communications-manager.yaml index 5c2979ad8..4ab713263 100644 --- a/specification/communications-manager.yaml +++ b/specification/communications-manager.yaml @@ -35,15 +35,15 @@ paths: /channels/nhsapp/accounts: get: $ref: endpoints/get_nhsapp_account_details.yaml - /: + /{client-provided-message-status-URI}: post: $ref: callbacks/message_status.yaml tags: ['Callbacks'] - /: + /{client-provided-channel-status-URI}: post: $ref: callbacks/channel_status.yaml tags: ['Callbacks'] - /: + /{client-provided-recipient-response-URI}: post: $ref: callbacks/recipient_response.yaml tags: ['Callbacks'] diff --git a/specification/documentation/APIDescription.md b/specification/documentation/APIDescription.md index b103572a0..c4a24db72 100644 --- a/specification/documentation/APIDescription.md +++ b/specification/documentation/APIDescription.md @@ -253,7 +253,7 @@ Answer options: [Yes, No] In order to present the recipient with answers, include the `answerOptions` field. -If you subscribe to recipient response callbacks, NHS Notify will send you a callback when a recipient responds to a message (currently only NHS App supports this). See [the recipient response callback](#post-/) for more details. +If you subscribe to recipient response callbacks, NHS Notify will send you a callback when a recipient responds to a message (currently only NHS App supports this). See [the recipient response callback](#post-/-client-provided-recipient-response-URI-) for more details. ## Message character limits Different character limits apply to each of the communication channels as listed below. NHS Notify will validate that any personalisation fields submitted in the send message request do not exceed these limits but it is the client's responsibility to ensure that when personalisation is combined with any templated text, the channel character limit is not exceeded. diff --git a/specification/schemas/components/RecipientResponse.yaml b/specification/schemas/components/RecipientResponse.yaml index ea1ea49ed..a8e42cac8 100644 --- a/specification/schemas/components/RecipientResponse.yaml +++ b/specification/schemas/components/RecipientResponse.yaml @@ -69,13 +69,13 @@ properties: links: type: object properties: - recipientResponse: + messageResponses: type: string description: URL to retrieve the recipient response details. format: uri - example: "https://api.service.nhs.uk/comms/v1/messages/2WL3qFTEFM0qMY8xjRbt1LIKCzM/recipient-response/33333333-3333-4333-8333-333333333333" + example: "https://api.service.nhs.uk/comms/v1/message-responses/2WL3qFTEFM0qMY8xjRbt1LIKCzM" required: - - recipientResponse + - messageResponses meta: type: object properties: