diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 59970be83..adc1af65a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -57,6 +57,10 @@ jobs: run: | 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 - name: Check licenses run: make check-licenses diff --git a/.gitignore b/.gitignore index eb5118c45..6abaa01f3 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ env *.pem sandbox/coverage sandbox/.nyc_output +coverage zap-report/ zap-report.json zap-report.xml diff --git a/sandbox/messages/2WL4pcpmKxYYblm3PSKVEqcmEPX.json b/sandbox/messages/2WL4pcpmKxYYblm3PSKVEqcmEPX.json index 57d589cfd..90aa2fee2 100644 --- a/sandbox/messages/2WL4pcpmKxYYblm3PSKVEqcmEPX.json +++ b/sandbox/messages/2WL4pcpmKxYYblm3PSKVEqcmEPX.json @@ -22,7 +22,7 @@ { "queriedAt": "2023-10-09T10:31:59Z", "source": "pds", - "version": 1, + "version": "1", "labels": [] } ] diff --git a/sandbox/package-lock.json b/sandbox/package-lock.json index 6b8b9939a..5ab1b06b1 100644 --- a/sandbox/package-lock.json +++ b/sandbox/package-lock.json @@ -20,7 +20,6 @@ "license-checker": "^25.0.1", "mocha": "^11.7.2", "mocha-junit-reporter": "^2.2.1", - "mocha-multi": "^1.1.3", "nodemon": "^3.1.10", "supertest": "^7.1.4" } @@ -1453,21 +1452,6 @@ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", @@ -1599,12 +1583,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "dev": true - }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -1947,60 +1925,6 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "node_modules/mocha-multi": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/mocha-multi/-/mocha-multi-1.1.7.tgz", - "integrity": "sha512-SXZRgHy0XiRTASyOp0p6fjOkdj+R62L6cqutnYyQOvIjNznJuUwzykxctypeRiOwPd+gfn4yt3NRulMQyI8Tzg==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "is-string": "^1.0.4", - "lodash.once": "^4.1.1", - "mkdirp": "^1.0.4", - "object-assign": "^4.1.1" - }, - "engines": { - "node": ">=6.0.0" - }, - "peerDependencies": { - "mocha": ">=2.2.0 <7 || >=9" - } - }, - "node_modules/mocha-multi/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/mocha-multi/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha-multi/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "node_modules/mocha/node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -2242,15 +2166,6 @@ "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", "dev": true }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -4561,15 +4476,6 @@ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" }, - "is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, "is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", @@ -4669,12 +4575,6 @@ "p-locate": "^5.0.0" } }, - "lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "dev": true - }, "log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -4958,42 +4858,6 @@ } } }, - "mocha-multi": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/mocha-multi/-/mocha-multi-1.1.7.tgz", - "integrity": "sha512-SXZRgHy0XiRTASyOp0p6fjOkdj+R62L6cqutnYyQOvIjNznJuUwzykxctypeRiOwPd+gfn4yt3NRulMQyI8Tzg==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "is-string": "^1.0.4", - "lodash.once": "^4.1.1", - "mkdirp": "^1.0.4", - "object-assign": "^4.1.1" - }, - "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -5117,12 +4981,6 @@ "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", "dev": true }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true - }, "object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", diff --git a/sandbox/package.json b/sandbox/package.json index f0f10f17e..195b58e00 100644 --- a/sandbox/package.json +++ b/sandbox/package.json @@ -24,7 +24,6 @@ "license-checker": "^25.0.1", "mocha": "^11.7.2", "mocha-junit-reporter": "^2.2.1", - "mocha-multi": "^1.1.3", "nodemon": "^3.1.10", "supertest": "^7.1.4" } diff --git a/scripts/config/sonar-scanner.properties b/scripts/config/sonar-scanner.properties index bd369a713..03b28d168 100644 --- a/scripts/config/sonar-scanner.properties +++ b/scripts/config/sonar-scanner.properties @@ -3,14 +3,18 @@ sonar.organization=nhsdigital sonar.projectKey=NHSDigital_communications-manager-api sonar.sourceEncoding=UTF-8 sonar.language=js,python +sonar.qualitygate.wait=true +sonar.sources=. +sonar.tests=tests/, sandbox/__test__ +sonar.test.inclusions=tests/**.py, sandbox/__test__/** sonar.exclusions=.venv/**,proxies/utils/performance/* -#exclude everything from test coverage, this is covered by other tools -sonar.coverage.exclusions=**/* -#sonar.nodejs.executable=/usr/bin/node sonar.python.version=3.10.8 +# Configure test coverage reporting +sonar.javascript.lcov.reportPaths=coverage/lcov.info + #exclusion rules for our proxy JS - rhinojs 1.7.12 -https://mozilla.github.io/rhino/compat/engines.html sonar.issue.ignore.multicriteria=optChain,useLet diff --git a/tests/docs/conf.py b/tests/docs/conf.py index ed486ddab..5e7733313 100644 --- a/tests/docs/conf.py +++ b/tests/docs/conf.py @@ -16,3 +16,7 @@ '.txt': 'markdown', '.md': 'markdown', } + +autodoc_mock_imports = [ + "tests" +] diff --git a/tests/lib/__init__.py b/tests/lib/__init__.py index cbc87da1b..07c98be0c 100644 --- a/tests/lib/__init__.py +++ b/tests/lib/__init__.py @@ -1,4 +1,4 @@ -from .assertions import Assertions +from .assertions.assertions import Assertions from .permutations import Permutations from .generators import Generators from .authentication import AuthenticationCache diff --git a/tests/lib/assertions.py b/tests/lib/assertions.py deleted file mode 100644 index d76cbbafd..000000000 --- a/tests/lib/assertions.py +++ /dev/null @@ -1,332 +0,0 @@ -from .constants.constants import CORS_METHODS, CORS_MAX_AGE, CORS_ALLOW_HEADERS, CORS_EXPOSE_HEADERS, CORS_POLICY -from .error_handler import error_handler -import json -from urllib.parse import urlparse, parse_qs - - -class Assertions(): - @staticmethod - def assert_201_response(resp, data): - error_handler.handle_retry(resp) - - assert resp.status_code == 201, f"Response: {resp.status_code}: {resp.text}" - - message_batch_reference = data["data"]["attributes"]["messageBatchReference"] - routing_plan_id = data["data"]["attributes"]["routingPlanId"] - messages = data["data"]["attributes"]["messages"] - - response = resp.json().get("data") - assert response.get("type") == "MessageBatch" - assert response.get("id") is not None - assert response.get("id") != "" - assert response.get("attributes").get("messageBatchReference") is not None - assert response.get("attributes").get("messageBatchReference") == message_batch_reference - assert response.get("attributes").get("routingPlan").get("id") is not None - assert response.get("attributes").get("routingPlan").get("id") == routing_plan_id - assert response.get("attributes").get("routingPlan").get("version") is not None - assert response.get("attributes").get("routingPlan").get("name") is not None - assert response.get("attributes").get("routingPlan").get("createdDate") is not None - assert response.get("attributes").get("messages") is not None - assert len(response.get("attributes").get("messages")) > 0 - expected_messages = sorted(messages, key=lambda x: x["messageReference"]) - actual_messages = sorted(response.get("attributes").get("messages"), key=lambda x: x["messageReference"]) - for i in range(len(actual_messages)): - assert actual_messages[i].get("messageReference") is not None - assert actual_messages[i].get("messageReference") == expected_messages[i].get("messageReference") - assert actual_messages[i].get("id") is not None - assert actual_messages[i].get("id") != "" - - # ensure we have our x-content-type-options set correctly - assert resp.headers.get("X-Content-Type-Options") == "nosniff" - - # ensure we have our cache-control set correctly - assert resp.headers.get("Cache-Control") == "no-cache, no-store, must-revalidate" - - @staticmethod - def assert_200_response_nhsapp_accounts(resp, base_url, ods_code, page): - error_handler.handle_retry(resp) - - assert resp.status_code == 200, f"Response: {resp.status_code}: {resp.text}" - - response = resp.json() - data = response.get("data") - - assert data.get("id") is not None - assert data.get("id") == ods_code - assert data.get("type") == "NhsAppAccounts" - assert data.get("attributes").get("accounts") is not None - assert len(data.get("attributes").get("accounts")) > 0 - for i in range(len(data.get("attributes").get("accounts"))): - assert data.get("attributes").get("accounts")[i].get("nhsNumber") is not None - assert data.get("attributes").get("accounts")[i].get("nhsNumber") != "" - assert data.get("attributes").get("accounts")[i].get("notificationsEnabled") is not None - assert response.get("links").get("self").startswith(base_url) - assert response.get("links").get("self") \ - .endswith(f"/channels/nhsapp/accounts?ods-organisation-code={ods_code}&page={page}") - - last_link = response.get("links").get("last") - parsed_last_link = urlparse(last_link) - last_link_query_params = parse_qs(parsed_last_link.query) - last_page_number = int(last_link_query_params["page"][0]) - - assert last_link.startswith(base_url) - assert response.get("links").get("last") \ - .endswith(f"/channels/nhsapp/accounts?ods-organisation-code={ods_code}&page={last_page_number}") - - self_link = response.get("links").get("self") - parsed_self_link = urlparse(self_link) - self_link_query_params = parse_qs(parsed_self_link.query) - self_page_number = int(self_link_query_params["page"][0]) - - if self_page_number == last_page_number: - assert response.get("links").get("next") is None - else: - next_page_number = self_page_number + 1 - assert response.get("links").get("next").startswith(base_url) - assert response.get("links").get("next") \ - .endswith(f"/channels/nhsapp/accounts?ods-organisation-code={ods_code}&page={next_page_number}") - - @staticmethod - def assert_200_response_message(resp, base_url): - error_handler.handle_retry(resp) - - assert resp.status_code == 200, f"Response: {resp.status_code}: {resp.text}" - - response = resp.json().get("data") - message_status = response.get("attributes").get("messageStatus") - - assert response.get("type") == "Message" - assert response.get("id") is not None - assert response.get("id") != "" - assert response.get("attributes").get("messageStatus") is not None - assert response.get("attributes").get("messageStatus") != "" - assert response.get("attributes").get("messageReference") is not None - assert response.get("attributes").get("messageReference") != "" - assert response.get("attributes").get("routingPlan") is not None - assert response.get("attributes").get("routingPlan") != "" - assert response.get("attributes").get("routingPlan").get("id") is not None - assert response.get("attributes").get("routingPlan").get("id") != "" - assert response.get("attributes").get("routingPlan").get("version") is not None - assert response.get("attributes").get("routingPlan").get("version") != "" - assert response.get("attributes").get("routingPlan").get("name") is not None - assert response.get("attributes").get("routingPlan").get("createdDate") is not None - assert response.get("attributes").get("timestamps").get("created") - assert response.get("attributes").get("timestamps").get("created") is not None - assert response.get("attributes").get("timestamps").get("created") != "" - if message_status != "pending_enrichment": - assert response.get("attributes").get("metadata") is not None - assert response.get("attributes").get("metadata") != "" - assert response.get("attributes").get("metadata")[0].get("queriedAt") is not None - assert response.get("attributes").get("metadata")[0].get("queriedAt") != "" - assert response.get("attributes").get("metadata")[0].get("source") is not None - assert response.get("attributes").get("metadata")[0].get("source") != "" - assert response.get("attributes").get("metadata")[0].get("version") is not None - assert response.get("attributes").get("metadata")[0].get("version") != "" - assert response.get("attributes").get("metadata")[0].get("labels") != "" - if message_status == "sending" or message_status == "delivered": - assert response.get("attributes").get("channels") is not None - assert response.get("attributes").get("channels")[0].get("type") is not None - assert response.get("attributes").get("channels")[0].get("type") != "" - assert response.get("attributes").get("channels")[0].get("retryCount") is not None - assert response.get("attributes").get("channels")[0].get("retryCount") != "" - assert response.get("attributes").get("channels")[0].get("channelStatus") is not None - assert response.get("attributes").get("channels")[0].get("channelStatus") != "" - assert response.get("attributes").get("channels")[0].get("timestamps") is not None - assert response.get("attributes").get("channels")[0].get("timestamps") != "" - assert response.get("attributes").get("channels")[0].get("routingPlan") is not None - assert response.get("attributes").get("channels")[0].get("routingPlan") != "" - assert response.get("links").get("self").startswith(base_url) - assert response.get("links").get("self").endswith(f"/v1/messages/{response.get('id')}") - - @staticmethod - def assert_get_message_status(resp, status, failure_reason=None, failure_reason_code=None): - response = resp.json().get("data") - assert response.get("attributes").get("messageStatus") == status - if status == "failed": - assert response.get("attributes").get("messageStatusDescription") == failure_reason - assert response.get("attributes").get("messageFailureReasonCode") == failure_reason_code - - @staticmethod - def assert_get_message_response_channels(resp, status, failure_reason=None, failure_reason_code=None): - response = resp.json().get("data") - channels = response.get("attributes").get("channels") - for c in range(len(channels)): - assert response.get("attributes").get("channels")[c].get("channelStatus") == status - if status == "failed": - assert response.get("attributes").get("channels")[c].get("channelStatusDescription") == failure_reason - assert ( - response.get("attributes").get("channels")[c].get("channelFailureReasonCode") - == failure_reason_code - ) - - @staticmethod - def assert_201_response_messages(resp, environment): - error_handler.handle_retry(resp) - - assert resp.status_code == 201, f"Response: {resp.status_code}: {resp.text}" - - response = resp.json().get("data") - assert response.get("type") == "Message" - assert response.get("id") is not None - assert response.get("id") != "" - assert response.get("attributes").get("messageStatus") == "created" - assert response.get("attributes").get("timestamps").get("created") - assert response.get("attributes").get("timestamps").get("created") is not None - assert response.get("attributes").get("timestamps").get("created") != "" - assert response.get("attributes").get("routingPlan") is not None - assert response.get("attributes").get("routingPlan").get("id") != "" - assert response.get("attributes").get("routingPlan").get("version") != "" - assert response.get("attributes").get("routingPlan").get("name") is not None - assert response.get("attributes").get("routingPlan").get("createdDate") is not None - - assert response.get("links").get("self").startswith(environment) - assert response.get("links").get("self").endswith(f"/v1/messages/{response.get('id')}") - assert resp.headers.get("Location") == f"/v1/messages/{response.get('id')}" - - @staticmethod - def assert_200_valid_message_id_response_body(resp, message_id, url): - error_handler.handle_retry(resp) - - assert resp.status_code == 200, f"Response: {resp.status_code}: {resp.text}" - - expected_response_file = open(f"sandbox/messages/{message_id}.json") - expected = json.load(expected_response_file).get("data") - expected["links"]["self"] = url - actual = resp.json().get("data") - - assert actual == expected - - @staticmethod - def assert_email_gov_uk(response, message_id): - for message in response: - if message_id in message.get("reference"): - assert message.get("status") == "delivered" - assert message.get("type") == "email" - assert message.get("body") is not None - assert message.get("subject") is not None - assert message.get("email_address") is not None - break - - @staticmethod - def assert_sms_gov_uk(response, message_id): - for message in response: - if message_id in message.get("reference"): - assert message.get("status") == "delivered" - assert message.get("type") == "sms" - assert message.get("body") is not None - assert message.get("phone_number") is not None - break - - @staticmethod - def assert_letter_gov_uk(response, message_id): - for message in response: - if message_id in message.get("reference"): - assert message.get("status") == "received" - assert message.get("type") == "letter" - assert message.get("subject") is not None - assert message.get("body") is not None - break - - @staticmethod - def assert_message_batches_idempotency(resp_one, resp_two): - error_handler.handle_retry(resp_one) - error_handler.handle_retry(resp_two) - - assert resp_one.status_code == 201 - assert resp_two.status_code == 201 - - response_one = resp_one.json().get("data") - response_two = resp_two.json().get("data") - - assert response_one.get("id") == response_two.get("id") - - @staticmethod - def assert_messages_idempotency(resp_one, resp_two): - error_handler.handle_retry(resp_one) - error_handler.handle_retry(resp_two) - - assert resp_one.status_code == 201 - assert resp_two.status_code == 201 - - response_one = resp_one.json().get("data") - response_two = resp_two.json().get("data") - - assert response_one.get("id") == response_two.get("id") - assert (response_one.get("attributes").get("messageStatus") == - response_one.get("attributes").get("messageStatus")) - assert (response_one.get("attributes").get("timestamps").get("created") == - response_two.get("attributes").get("timestamps").get("created")) - assert (response_one.get("attributes").get("routingPlan").get("id") == - response_two.get("attributes").get("routingPlan").get("id")) - assert (response_one.get("attributes").get("routingPlan").get("version") == - response_two.get("attributes").get("routingPlan").get("version")) - - @staticmethod - def assert_error_with_optional_correlation_id(resp, code, error, correlation_id): - if resp.status_code != code: - # Unexpected status code, check if the test needs to be retried - error_handler.handle_retry(resp) - - assert resp.status_code == code, f"Response: {resp.status_code}: {resp.text}" - - if error is not None: - # ensure that all errors contain an identifier - response_errors = resp.json().get("errors") - current_identifier_num = 0 - for e in response_errors: - assert e.get("id") is not None - - # extract the identifier num - assert int(e.get("id").split(".")[-1]) == current_identifier_num - - # then remove it as its a unique value that we do not know ahead of time - e.pop("id") - current_identifier_num += 1 - - # validate the error is present - if len(response_errors) == 1: - # this case is for making debugging easier where possible - assert response_errors[0] == error - else: - assert error in response_errors - - Assertions.assert_correlation_id(resp.headers.get("X-Correlation-Id"), correlation_id) - - # ensure we have our x-content-type-options set correctly - assert resp.headers.get("X-Content-Type-Options") == "nosniff" - - # ensure we have our cache-control set correctly - assert resp.headers.get("Cache-Control") == "no-cache, no-store, must-revalidate" - - @staticmethod - def assert_correlation_id(res_correlation_id, correlation_id): - # apigee generates this value if not present with rrt prefix - if correlation_id: - assert res_correlation_id == correlation_id - else: - assert res_correlation_id.startswith('rrt') - - @staticmethod - def assert_cors_response(resp, website): - error_handler.handle_retry(resp) - - assert resp.status_code == 200, f"Response: {resp.status_code}: {resp.text}" - assert resp.headers.get("Access-Control-Allow-Origin") == website - assert resp.headers.get("Access-Control-Allow-Methods") == CORS_METHODS - assert resp.headers.get("Access-Control-Max-Age") == CORS_MAX_AGE - assert resp.headers.get("Access-Control-Allow-Headers") == CORS_ALLOW_HEADERS - assert resp.headers.get("Cross-Origin-Resource-Policy") == CORS_POLICY - - @staticmethod - def assert_cors_headers(resp, website): - error_handler.handle_retry(resp) - - assert resp.headers.get("Access-Control-Allow-Origin") == website - assert resp.headers.get("Access-Control-Expose-Headers") == CORS_EXPOSE_HEADERS - assert resp.headers.get("Cross-Origin-Resource-Policy") == CORS_POLICY - - @staticmethod - def assert_no_aws_headers(resp): - assert "X-Amzn-Trace-Id" not in resp.headers - assert "x-amzn-RequestId" not in resp.headers - assert "x-amz-apigw-id" not in resp.headers diff --git a/tests/lib/assertions/__init__.py b/tests/lib/assertions/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lib/assertions/assertions.py b/tests/lib/assertions/assertions.py new file mode 100644 index 000000000..a23d3a0ad --- /dev/null +++ b/tests/lib/assertions/assertions.py @@ -0,0 +1,237 @@ +import json +from tests.lib.assertions import get_message +from lib.constants.constants import CORS_METHODS, CORS_MAX_AGE, CORS_ALLOW_HEADERS, CORS_EXPOSE_HEADERS, CORS_POLICY +from lib.error_handler import error_handler +from lib.assertions import common, message_batches, messages, headers, nhsapp_accounts + + +class Assertions(): + @staticmethod + def assert_cors_response(resp, website): + error_handler.handle_retry(resp) + assert resp.status_code == 200, f"Response: {resp.status_code}: {resp.text}" + + headers.assert_access_control_allow_origin(resp, website) + headers.assert_access_control_allow_methods(resp, CORS_METHODS) + headers.assert_access_control_max_age(resp, CORS_MAX_AGE) + headers.assert_access_control_allow_headers(resp, CORS_ALLOW_HEADERS) + headers.assert_access_control_resource_policy(resp, CORS_POLICY) + + @staticmethod + def assert_cors_headers(resp, website): + error_handler.handle_retry(resp) + + headers.assert_access_control_allow_origin(resp, website) + headers.assert_access_control_expose_headers(resp, CORS_EXPOSE_HEADERS) + headers.assert_access_control_resource_policy(resp, CORS_POLICY) + + @staticmethod + def assert_no_aws_headers(resp): + headers.assert_no_aws_headers(resp) + + @staticmethod + def assert_201_response(resp, data): + error_handler.handle_retry(resp) + assert resp.status_code == 201, f"Response: {resp.status_code}: {resp.text}" + + common.assert_message_type(resp, "MessageBatch") + common.assert_request_id(resp) + common.assert_routing_plan_id(resp, data["data"]["attributes"]["routingPlanId"]) + common.assert_routing_plan_version(resp) + common.assert_routing_plan_name(resp) + common.assert_routing_plan_created_date(resp) + message_batches.assert_message_batch_reference(resp, data["data"]["attributes"]["messageBatchReference"]) + message_batches.assert_messages(resp, data["data"]["attributes"]["messages"]) + headers.assert_x_content_type_options(resp, "nosniff") + headers.assert_cache_control(resp, "no-cache, no-store, must-revalidate") + + @staticmethod + def assert_200_response_nhsapp_accounts(resp, base_url, ods_code, page): + error_handler.handle_retry(resp) + assert resp.status_code == 200, f"Response: {resp.status_code}: {resp.text}" + last_page_number = nhsapp_accounts.get_page_number_from_url(resp.json()["links"]["last"]) + self_page_number = nhsapp_accounts.get_page_number_from_url(resp.json()["links"]["self"]) + + common.assert_message_type(resp, "NhsAppAccounts") + nhsapp_accounts.assert_ods_code(resp, ods_code) + nhsapp_accounts.assert_accounts(resp) + nhsapp_accounts.assert_self_link(resp, base_url, ods_code, page) + nhsapp_accounts.assert_last_link(resp, base_url, ods_code, last_page_number) + nhsapp_accounts.assert_next_link(resp, base_url, ods_code, self_page_number, last_page_number) + + @staticmethod + def assert_200_response_message(resp, base_url): + error_handler.handle_retry(resp) + assert resp.status_code == 200, f"Response: {resp.status_code}: {resp.text}" + message_status = resp.json().get("data").get("attributes").get("messageStatus") + + common.assert_message_type(resp, "Message") + common.assert_request_id(resp) + common.assert_routing_plan_id(resp) + common.assert_routing_plan_version(resp) + common.assert_routing_plan_name(resp) + common.assert_routing_plan_created_date(resp) + get_message.assert_message_status(resp) + get_message.assert_message_reference(resp) + get_message.assert_created_timestamp(resp) + get_message.assert_self_link(resp, base_url) + if message_status != "pending_enrichment": + get_message.assert_metadata(resp) + if message_status == "sending" or message_status == "delivered": + get_message.assert_channels(resp) + + @staticmethod + def assert_get_message_status(resp, status, failure_reason=None, failure_reason_code=None): + get_message.assert_message_status(resp, status) + if status == "failed": + get_message.assert_message_status_description(resp, failure_reason) + get_message.assert_failure_reason_code(resp, failure_reason_code) + + @staticmethod + def assert_get_message_response_channels(resp, status, failure_reason=None, failure_reason_code=None): + response = resp.json().get("data") + channels = response.get("attributes").get("channels") + for channel in channels: + get_message.assert_channel_status(channel.get("channelStatus"), status) + if status == "failed": + get_message.assert_channel_status_description( + channel.get("channelStatusDescription"), failure_reason + ) + get_message.assert_channel_failure_reason_code( + channel.get("channelFailureReasonCode"), failure_reason_code + ) + + @staticmethod + def assert_201_response_messages(resp, base_url): + error_handler.handle_retry(resp) + assert resp.status_code == 201, f"Response: {resp.status_code}: {resp.text}" + + common.assert_message_type(resp, "Message") + common.assert_request_id(resp) + common.assert_routing_plan_id(resp) + common.assert_routing_plan_version(resp) + common.assert_routing_plan_name(resp) + common.assert_routing_plan_created_date(resp) + messages.assert_message_status(resp, "created") + messages.assert_created_timestamp(resp) + messages.assert_self_link(resp, base_url) + headers.assert_location_header(resp) + + @staticmethod + def assert_200_valid_message_id_response_body(resp, message_id, url): + error_handler.handle_retry(resp) + + assert resp.status_code == 200, f"Response: {resp.status_code}: {resp.text}" + + expected_response_file = open(f"sandbox/messages/{message_id}.json") + expected = json.load(expected_response_file).get("data") + expected["links"]["self"] = url + actual = resp.json().get("data") + + assert actual == expected + + @staticmethod + def assert_email_gov_uk(response, message_id): + for message in response: + if message_id in message.get("reference"): + assert message.get("status") == "delivered" + assert message.get("type") == "email" + assert message.get("body") is not None + assert message.get("subject") is not None + assert message.get("email_address") is not None + break + + @staticmethod + def assert_sms_gov_uk(response, message_id): + for message in response: + if message_id in message.get("reference"): + assert message.get("status") == "delivered" + assert message.get("type") == "sms" + assert message.get("body") is not None + assert message.get("phone_number") is not None + break + + @staticmethod + def assert_letter_gov_uk(response, message_id): + for message in response: + if message_id in message.get("reference"): + assert message.get("status") == "received" + assert message.get("type") == "letter" + assert message.get("subject") is not None + assert message.get("body") is not None + break + + @staticmethod + def assert_message_batches_idempotency(resp_one, resp_two): + error_handler.handle_retry(resp_one) + error_handler.handle_retry(resp_two) + + assert resp_one.status_code == 201 + assert resp_two.status_code == 201 + + response_one = resp_one.json().get("data") + response_two = resp_two.json().get("data") + + assert response_one.get("id") == response_two.get("id") + + @staticmethod + def assert_messages_idempotency(resp_one, resp_two): + error_handler.handle_retry(resp_one) + error_handler.handle_retry(resp_two) + + assert resp_one.status_code == 201 + assert resp_two.status_code == 201 + + response_one = resp_one.json().get("data") + response_two = resp_two.json().get("data") + + assert response_one.get("id") == response_two.get("id") + assert (response_one.get("attributes").get("messageStatus") == + response_one.get("attributes").get("messageStatus")) + assert (response_one.get("attributes").get("timestamps").get("created") == + response_two.get("attributes").get("timestamps").get("created")) + assert (response_one.get("attributes").get("routingPlan").get("id") == + response_two.get("attributes").get("routingPlan").get("id")) + assert (response_one.get("attributes").get("routingPlan").get("version") == + response_two.get("attributes").get("routingPlan").get("version")) + + @staticmethod + def assert_error_with_optional_correlation_id(resp, code, error, correlation_id): + if resp.status_code != code: + # Unexpected status code, check if the test needs to be retried + error_handler.handle_retry(resp) + + assert resp.status_code == code, f"Response: {resp.status_code}: {resp.text}" + + if error is not None: + # ensure that all errors contain an identifier + response_errors = resp.json().get("errors") + current_identifier_num = 0 + for e in response_errors: + assert e.get("id") is not None + + # extract the identifier num + assert int(e.get("id").split(".")[-1]) == current_identifier_num + + # then remove it as its a unique value that we do not know ahead of time + e.pop("id") + current_identifier_num += 1 + + # validate the error is present + if len(response_errors) == 1: + # this case is for making debugging easier where possible + assert response_errors[0] == error + else: + assert error in response_errors + + Assertions.assert_correlation_id(resp.headers.get("X-Correlation-Id"), correlation_id) + headers.assert_x_content_type_options(resp, "nosniff") + headers.assert_cache_control(resp, "no-cache, no-store, must-revalidate") + + @staticmethod + def assert_correlation_id(res_correlation_id, correlation_id): + # apigee generates this value if not present with rrt prefix + if correlation_id: + assert res_correlation_id == correlation_id + else: + assert res_correlation_id.startswith('rrt') diff --git a/tests/lib/assertions/common.py b/tests/lib/assertions/common.py new file mode 100644 index 000000000..82bb1c653 --- /dev/null +++ b/tests/lib/assertions/common.py @@ -0,0 +1,63 @@ +from datetime import datetime +from lib.constants.constants import MESSAGE_TYPES +import re + + +def assert_valid_string(string): + assert string is not None + assert string != "" + assert isinstance(string, str) + + +def assert_valid_list(lst): + assert lst is not None + assert isinstance(lst, list) + + +def assert_valid_int(integer): + assert integer is not None + assert isinstance(integer, int) + + +def assert_valid_bool(value): + assert value is not None + assert isinstance(value, bool) + + +def assert_message_type(resp, type): + message_type = resp.json().get("data").get("type") + assert_valid_string(message_type) + assert message_type in MESSAGE_TYPES + assert message_type == type + + +def assert_request_id(resp): + request_id = resp.json().get("data").get("id") + assert_valid_string(request_id) + assert re.match(r"^[a-zA-Z0-9]{27}$", request_id) + + +def assert_routing_plan_id(resp, expected_routing_plan_id=None): + actual_routing_plan_id = resp.json().get("data").get("attributes").get("routingPlan").get("id") + assert_valid_string(actual_routing_plan_id) + if expected_routing_plan_id is not None: + assert expected_routing_plan_id == actual_routing_plan_id + + +def assert_routing_plan_version(resp): + actual_routing_plan_version = resp.json().get("data").get("attributes").get("routingPlan").get("version") + assert_valid_string(actual_routing_plan_version) + + +def assert_routing_plan_name(resp): + actual_routing_plan_name = resp.json().get("data").get("attributes").get("routingPlan").get("name") + # If routing plan name is not present on the routing plan, it will return an empty string + assert actual_routing_plan_name is not None + assert isinstance(actual_routing_plan_name, str) + + +def assert_routing_plan_created_date(resp): + actual_routing_plan_created_date = resp.json().get("data").get("attributes").get("routingPlan").get("createdDate") + assert_valid_string(actual_routing_plan_created_date) + formatted_date = datetime.fromisoformat(actual_routing_plan_created_date.replace("Z", "+00:00")) + assert formatted_date is not None diff --git a/tests/lib/assertions/get_message.py b/tests/lib/assertions/get_message.py new file mode 100644 index 000000000..c57974a4f --- /dev/null +++ b/tests/lib/assertions/get_message.py @@ -0,0 +1,130 @@ +from lib.constants.constants import MESSAGE_STATUS, CHANNEL_STATUS, CHANNEL_TYPE, CASCADE_TYPE +from lib.assertions.common import assert_valid_string, assert_valid_list, assert_valid_int +from datetime import datetime + + +def assert_message_status(resp, expected_status=None): + message_status = resp.json().get("data").get("attributes").get("messageStatus") + assert_valid_string(message_status) + assert message_status in MESSAGE_STATUS + if expected_status is not None: + assert expected_status == message_status + + +def assert_message_reference(resp): + message_reference = resp.json().get("data").get("attributes").get("messageReference") + assert_valid_string(message_reference) + + +def assert_created_timestamp(resp): + created_timestamp = resp.json().get("data").get("attributes").get("timestamps").get("created") + assert_valid_string(created_timestamp) + formatted_date = datetime.fromisoformat(created_timestamp.replace("Z", "+00:00")) + assert isinstance(formatted_date, datetime) + + +def assert_metadata(resp): + metadata = resp.json().get("data").get("attributes").get("metadata") + assert_valid_list(metadata) + assert len(metadata) > 0 + assert_metadata_queried(metadata[0].get("queriedAt")) + assert_metadata_source(metadata[0].get("source")) + assert_metadata_version(metadata[0].get("version")) + assert_metadata_labels(metadata[0].get("labels")) + + +def assert_channels(resp): + channels = resp.json().get("data").get("attributes").get("channels") + assert_valid_list(channels) + assert len(channels) > 0 + for channel in channels: + assert_channel_type(channel.get("type")) + assert_cascade_type(channel.get("cascadeType")) + assert_cascade_order(channel.get("cascadeOrder")) + assert_channel_status(channel.get("channelStatus"), None) + assert_channel_created_timestamp(channel.get("timestamps").get("created")) + assert_routing_plan(channel.get("routingPlan")) + + +def assert_self_link(resp, base_url): + self_link = resp.json().get("data").get("links").get("self") + request_id = resp.json().get("data").get("id") + assert_valid_string(self_link) + assert self_link.startswith(base_url) + assert self_link.endswith(f"/v1/messages/{request_id}") + + +def assert_message_status_description(resp, expected_description): + actual_description = resp.json().get("data").get("attributes").get("messageStatusDescription") + assert_valid_string(actual_description) + assert expected_description == actual_description + + +def assert_failure_reason_code(resp, expected_code): + failure_reason_code = resp.json().get("data").get("attributes").get("messageFailureReasonCode") + assert_valid_string(failure_reason_code) + assert expected_code == failure_reason_code + + +def assert_channel_status(channel_status, expected_status): + assert_valid_string(channel_status) + assert channel_status in CHANNEL_STATUS + if expected_status is not None: + assert expected_status == channel_status + + +def assert_channel_status_description(channel_status_description, expected_description): + assert_valid_string(channel_status_description) + assert expected_description == channel_status_description + + +def assert_channel_failure_reason_code(channel_failure_reason_code, expected_code): + assert_valid_string(channel_failure_reason_code) + assert expected_code == channel_failure_reason_code + + +def assert_metadata_queried(queried_at): + assert_valid_string(queried_at) + formatted_date = datetime.fromisoformat(queried_at.replace("Z", "+00:00")) + assert isinstance(formatted_date, datetime) + + +def assert_metadata_source(source): + assert_valid_string(source) + + +def assert_metadata_version(version): + assert_valid_string(version) + + +def assert_metadata_labels(labels): + assert_valid_list(labels) + for label in labels: + assert_valid_string(label) + + +def assert_channel_type(channel_type): + assert_valid_string(channel_type) + assert channel_type in CHANNEL_TYPE + + +def assert_cascade_type(cascade_type): + assert_valid_string(cascade_type) + assert cascade_type in CASCADE_TYPE + + +def assert_cascade_order(cascade_order): + assert_valid_int(cascade_order) + assert cascade_order > 0 + + +def assert_channel_created_timestamp(created_timestamp): + assert_valid_string(created_timestamp) + formatted_date = datetime.fromisoformat(created_timestamp.replace("Z", "+00:00")) + assert isinstance(formatted_date, datetime) + + +def assert_routing_plan(routing_plan): + assert_valid_string(routing_plan.get("id")) + assert_valid_string(routing_plan.get("type")) + assert_valid_string(routing_plan.get("version")) diff --git a/tests/lib/assertions/headers.py b/tests/lib/assertions/headers.py new file mode 100644 index 000000000..0593b4caf --- /dev/null +++ b/tests/lib/assertions/headers.py @@ -0,0 +1,59 @@ +def assert_x_content_type_options(resp, expected_options): + # ensure we have our x-content-type-options set correctly + content_type_options = resp.headers.get("X-Content-Type-Options") + assert content_type_options == expected_options + + +def assert_cache_control(resp, expected_cache_control): + # ensure we have our cache-control set correctly + cache_control = resp.headers.get("Cache-Control") + assert cache_control == expected_cache_control + + +def assert_access_control_allow_origin(resp, expected_origin): + # ensure we have our access-control-allow-origin set correctly + allow_origin = resp.headers.get("Access-Control-Allow-Origin") + assert allow_origin == expected_origin + + +def assert_access_control_allow_methods(resp, expected_methods): + # ensure we have our access-control-allow-methods set correctly + allow_methods = resp.headers.get("Access-Control-Allow-Methods") + assert allow_methods == expected_methods + + +def assert_access_control_max_age(resp, expected_max_age): + # ensure we have our access-control-max-age set correctly + max_age = resp.headers.get("Access-Control-Max-Age") + assert max_age == expected_max_age + + +def assert_access_control_allow_headers(resp, expected_headers): + # ensure we have our access-control-allow-headers set correctly + allow_headers = resp.headers.get("Access-Control-Allow-Headers") + assert allow_headers == expected_headers + + +def assert_access_control_expose_headers(resp, expected_expose_headers): + # ensure we have our access-control-expose-headers set correctly + expose_headers = resp.headers.get("Access-Control-Expose-Headers") + assert expose_headers == expected_expose_headers + + +def assert_access_control_resource_policy(resp, expected_policy): + # ensure we have our access-control-resource-policy set correctly + resource_policy = resp.headers.get("Cross-Origin-Resource-Policy") + assert resource_policy == expected_policy + + +def assert_no_aws_headers(resp): + # ensure we do not have any AWS headers leaking through + assert "X-Amzn-Trace-Id" not in resp.headers + assert "x-amzn-RequestId" not in resp.headers + assert "x-amz-apigw-id" not in resp.headers + + +def assert_location_header(resp): + location_header = resp.headers.get("Location") + expected_value = f"/v1/messages/{resp.json().get('data').get('id')}" + assert location_header == expected_value diff --git a/tests/lib/assertions/message_batches.py b/tests/lib/assertions/message_batches.py new file mode 100644 index 000000000..73a34ae8f --- /dev/null +++ b/tests/lib/assertions/message_batches.py @@ -0,0 +1,31 @@ +from lib.assertions.common import assert_valid_string, assert_valid_list +import re + + +def assert_message_batch_reference(resp, expected_message_batch_reference): + actual_message_batch_reference = resp.json().get("data").get("attributes").get("messageBatchReference") + assert_valid_string(actual_message_batch_reference) + assert expected_message_batch_reference == actual_message_batch_reference + + +def assert_messages(resp, expected_messages): + actual_messages = resp.json().get("data").get("attributes").get("messages") + assert_valid_list(actual_messages) + expected_messages = sorted(expected_messages, key=lambda x: x["messageReference"]) + actual_messages = sorted(actual_messages, key=lambda x: x["messageReference"]) + for i in range(len(actual_messages)): + assert_message_reference( + actual_messages[i].get("messageReference"), expected_messages[i].get("messageReference") + ) + assert_message_id(actual_messages[i].get("id")) + + +def assert_message_reference(actual_message_reference, expected_message_reference=None): + assert_valid_string(actual_message_reference) + if expected_message_reference is not None: + assert actual_message_reference == expected_message_reference + + +def assert_message_id(actual_message_id): + assert_valid_string(actual_message_id) + assert re.match(r"^[a-zA-Z0-9]{27}$", actual_message_id) diff --git a/tests/lib/assertions/messages.py b/tests/lib/assertions/messages.py new file mode 100644 index 000000000..d3aa9db30 --- /dev/null +++ b/tests/lib/assertions/messages.py @@ -0,0 +1,24 @@ +from datetime import datetime +from lib.assertions.common import assert_valid_string + + +def assert_created_timestamp(resp): + created_timestamp = resp.json().get("data").get("attributes").get("timestamps").get("created") + assert_valid_string(created_timestamp) + formatted_date = datetime.fromisoformat(created_timestamp.replace("Z", "+00:00")) + assert isinstance(formatted_date, datetime) + + +def assert_message_status(resp, expected_status=None): + message_status = resp.json().get("data").get("attributes").get("messageStatus") + assert_valid_string(message_status) + if expected_status is not None: + assert expected_status == message_status + + +def assert_self_link(resp, base_url): + self_link = resp.json().get("data").get("links").get("self") + request_id = resp.json().get("data").get("id") + assert_valid_string(self_link) + assert self_link.startswith(base_url) + assert self_link.endswith(f"/v1/messages/{request_id}") diff --git a/tests/lib/assertions/nhsapp_accounts.py b/tests/lib/assertions/nhsapp_accounts.py new file mode 100644 index 000000000..1ab829616 --- /dev/null +++ b/tests/lib/assertions/nhsapp_accounts.py @@ -0,0 +1,61 @@ +from lib.assertions.common import assert_valid_string, assert_valid_list, assert_valid_bool +from urllib.parse import urlparse, parse_qs + + +def get_page_number_from_url(url: str) -> int: + """Extract the 'page' query parameter as an integer from a URL.""" + parsed = urlparse(url) + query_params = parse_qs(parsed.query) + return int(query_params["page"][0]) + + +def assert_ods_code(resp, expected_ods_code): + actual_ods_code = resp.json().get("data").get("id") + assert_valid_string(actual_ods_code) + assert actual_ods_code == expected_ods_code + + +def assert_accounts(resp): + accounts = resp.json().get("data").get("attributes").get("accounts") + assert_valid_list(accounts) + assert len(accounts) > 0 + for i in range(len(accounts)): + assert_nhs_number(accounts[i].get("nhsNumber")) + assert_notifications_enabled(accounts[i].get("notificationsEnabled")) + + +def assert_self_link(resp, base_url, ods_code, page): + self_link = resp.json().get("links").get("self") + assert self_link.startswith(base_url) + assert self_link.endswith( + f"/channels/nhsapp/accounts?ods-organisation-code={ods_code}&page={page}" + ) + + +def assert_last_link(resp, base_url, ods_code, last_page_number): + last_link = resp.json().get("links").get("last") + assert last_link.startswith(base_url) + assert last_link.endswith( + f"/channels/nhsapp/accounts?ods-organisation-code={ods_code}&page={last_page_number}" + ) + + +def assert_next_link(resp, base_url, ods_code, self_page_number, last_page_number): + next_link = resp.json().get("links").get("next") + if self_page_number == last_page_number: + assert next_link is None + else: + next_page_number = self_page_number + 1 + assert next_link.startswith(base_url) + assert next_link.endswith( + f"/channels/nhsapp/accounts?ods-organisation-code={ods_code}&page={next_page_number}" + ) + + +def assert_nhs_number(nhs_number): + assert_valid_string(nhs_number) + assert len(nhs_number) == 10 + + +def assert_notifications_enabled(notifications_enabled): + assert_valid_bool(notifications_enabled) diff --git a/tests/lib/constants/constants.py b/tests/lib/constants/constants.py index 1ad914d0b..484cb86b3 100644 --- a/tests/lib/constants/constants.py +++ b/tests/lib/constants/constants.py @@ -63,6 +63,30 @@ INVALID_PERSONALISATION_VALUES = [5, "", "some-string", []] NULL_VALUES = [None] +MESSAGE_TYPES = ["MessageBatch", "Message", "NhsAppAccounts"] +MESSAGE_STATUS = ["created", "pending_enrichment", "enriched", "sending", "delivered", "failed"] +CHANNEL_STATUS = ["created", "sending", "delivered", "failed", "skipped"] +CHANNEL_TYPE = ["nhsapp", "email", "sms", "letter"] +CASCADE_TYPE = ["primary", "secondary"] +SUPPLIER_STATUS = [ + "delivered", + "read", + "notification_attempted", + "unnotified", + "rejected", + "notified", + "received", + "permanent_failure", + "temporary_failure", + "technical_failure", + "accepted", + "cancelled", + "pending_virus_check", + "validation_failed", + "unknown" +] +ROUTING_PLAN_TYPE = ["original", "override"] + class Error(): def __init__(self, code, status, title, detail, links={}):