From 4f19b8d8e9a151e11a1f2b0143d56b5f6469d6f7 Mon Sep 17 00:00:00 2001 From: Tom D'Roza <83403414+tdroza-nhs@users.noreply.github.com> Date: Wed, 16 Apr 2025 10:56:46 +0100 Subject: [PATCH 1/5] CCM-8392: [API Docs] Add name override fields to msg send endpoints (#927) --- specification/documentation/APIDescription.md | 2 +- .../schemas/components/Recipient.yaml | 28 +++++++++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/specification/documentation/APIDescription.md b/specification/documentation/APIDescription.md index 52b99dd0a..3283e868a 100644 --- a/specification/documentation/APIDescription.md +++ b/specification/documentation/APIDescription.md @@ -9,7 +9,7 @@ Use this API to send messages to citizens via NHS App, email, text message or le * enrichment of recipient details * support for accessible formats and multiple languages -Learn more about [NHS Notify's features](https://digital.nhs.uk/services/nhs-notify/features). +Learn more about [NHS Notify's features](https://notify.nhs.uk/features/). ## Who can use this API diff --git a/specification/schemas/components/Recipient.yaml b/specification/schemas/components/Recipient.yaml index e7f6da8ae..3e1c29b55 100644 --- a/specification/schemas/components/Recipient.yaml +++ b/specification/schemas/components/Recipient.yaml @@ -8,7 +8,7 @@ properties: minLength: 10 maxLength: 10 example: "9990548609" - description: The [NHS number](https://digital.nhs.uk/services/nhs-number) of the recipient. Only [valid NHS Numbers](https://www.datadictionary.nhs.uk/attributes/nhs_number.html) will be accepted. This will be used to lookup the recipients details with the [Personal Demographics Service](https://digital.nhs.uk/developer/api-catalogue/personal-demographics-service-fhir). + description: The [NHS number](https://digital.nhs.uk/services/nhs-number) of the recipient. Only [valid NHS Numbers](https://www.datadictionary.nhs.uk/attributes/nhs_number.html) will be accepted. This will be used to lookup the recipients details with the [Personal Demographics Service](https://digital.nhs.uk/developer/api-catalogue/personal-demographics-service-fhir). Normally a required field, unless prior agreement with the Onboarding team is in place. contactDetails: type: object description: "Overriding contact details is a sensitive action and requires explicit approval from the onboarding team." @@ -47,5 +47,27 @@ properties: type: string description: Postcode of overriding address. A required field when address is specified. Must be a valid UK postcode format. example: "LS1 4AP" -required: - - nhsNumber + name: + type: object + description: Overriding name fields for the recipient. + properties: + prefix: + type: string + description: Prefix of overriding name. + example: Dr. + firstName: + type: string + description: First name of overriding name. + example: John + middleNames: + type: string + description: Middle names of overriding name. + example: Andrew Robert + lastName: + type: string + description: Last name of overriding name. A required field when name is specified. + example: Smith + suffix: + type: string + description: Suffix of overriding name. + example: Jr. From 379a2c314c6d5a63d06fef69dba2666d5d5932b9 Mon Sep 17 00:00:00 2001 From: Rachel Kennedy Date: Thu, 17 Apr 2025 09:33:49 +0100 Subject: [PATCH 2/5] CCM-9690: Fix apim e2e auth (#925) * CCM-9690: Fix apim e2e * CCM-9690: Fix apim e2e --- tests/end_to_end/test_email.py | 23 +++++++++++++---------- tests/end_to_end/test_letter.py | 24 ++++++++++++++---------- tests/end_to_end/test_nhsapp.py | 31 +++++++++++++++++-------------- tests/end_to_end/test_sms.py | 24 ++++++++++++++---------- tests/lib/helper.py | 14 ++++---------- 5 files changed, 62 insertions(+), 54 deletions(-) diff --git a/tests/end_to_end/test_email.py b/tests/end_to_end/test_email.py index 3c29dde15..bd2f52c56 100644 --- a/tests/end_to_end/test_email.py +++ b/tests/end_to_end/test_email.py @@ -7,21 +7,22 @@ @pytest.mark.e2e @pytest.mark.devtest -def test_email_end_to_end_internal_dev(nhsd_apim_proxy_url, bearer_token_internal_dev): +def test_email_end_to_end_internal_dev(url, bearer_token): """ .. include:: ../../partials/happy_path/test_email_end_to_end_internal_dev.rst """ + headers = Generators.generate_valid_headers(bearer_token.value) resp = Helper.send_single_message( - nhsd_apim_proxy_url, - {"Authorization": bearer_token_internal_dev.value}, + url, + headers, Generators.generate_send_message_body("email", "internal-dev") ) message_id = resp.json().get("data").get("id") Helper.poll_get_message( - url=nhsd_apim_proxy_url, - auth={"Authorization": bearer_token_internal_dev.value}, + url=url, + headers=headers, message_id=message_id ) @@ -33,21 +34,23 @@ def test_email_end_to_end_internal_dev(nhsd_apim_proxy_url, bearer_token_interna @pytest.mark.e2e @pytest.mark.uattest -def test_email_end_to_end_uat(nhsd_apim_proxy_url, bearer_token_internal_dev): +def test_email_end_to_end_uat(url, bearer_token): """ .. include:: ../../partials/happy_path/test_email_end_to_end_uat.rst """ + headers = Generators.generate_valid_headers(bearer_token.value) + resp = Helper.send_single_message( - nhsd_apim_proxy_url, - {"Authorization": bearer_token_internal_dev.value}, + url, + headers, Generators.generate_send_message_body("email", "internal-qa") ) message_id = resp.json().get("data").get("id") Helper.poll_get_message( - url=nhsd_apim_proxy_url, - auth={"Authorization": bearer_token_internal_dev.value}, + url=url, + headers=headers, message_id=message_id ) diff --git a/tests/end_to_end/test_letter.py b/tests/end_to_end/test_letter.py index 909ec3f97..2d2f00a86 100644 --- a/tests/end_to_end/test_letter.py +++ b/tests/end_to_end/test_letter.py @@ -7,21 +7,23 @@ @pytest.mark.e2e @pytest.mark.devtest -def test_letter_end_to_end_internal_dev(nhsd_apim_proxy_url, bearer_token_internal_dev): +def test_letter_end_to_end_internal_dev(url, bearer_token): """ .. include:: ../../partials/happy_path/test_letter_end_to_end_internal_dev.rst """ + headers = Generators.generate_valid_headers(bearer_token.value) + resp = Helper.send_single_message( - nhsd_apim_proxy_url, - {"Authorization": bearer_token_internal_dev.value}, + url, + headers, Generators.generate_send_message_body("letter", "internal-dev"), ) message_id = resp.json().get("data").get("id") Helper.poll_get_message( - url=nhsd_apim_proxy_url, - auth={"Authorization": bearer_token_internal_dev.value}, + url=url, + headers=headers, message_id=message_id, end_state="sending", poll_time=595, @@ -37,21 +39,23 @@ def test_letter_end_to_end_internal_dev(nhsd_apim_proxy_url, bearer_token_intern @pytest.mark.e2e @pytest.mark.uattest -def test_letter_end_to_end_uat(nhsd_apim_proxy_url, bearer_token_internal_dev): +def test_letter_end_to_end_uat(url, bearer_token): """ .. include:: ../../partials/happy_path/test_letter_end_to_end_uat.rst """ + headers = Generators.generate_valid_headers(bearer_token.value) + resp = Helper.send_single_message( - nhsd_apim_proxy_url, - {"Authorization": bearer_token_internal_dev.value}, + url, + headers, Generators.generate_send_message_body("letter", "internal-qa"), ) message_id = resp.json().get("data").get("id") Helper.poll_get_message( - url=nhsd_apim_proxy_url, - auth={"Authorization": bearer_token_internal_dev.value}, + url=url, + headers=headers, message_id=message_id, end_state="sending", poll_time=595, diff --git a/tests/end_to_end/test_nhsapp.py b/tests/end_to_end/test_nhsapp.py index 6a4a89bcf..8eed80424 100644 --- a/tests/end_to_end/test_nhsapp.py +++ b/tests/end_to_end/test_nhsapp.py @@ -6,28 +6,30 @@ @pytest.mark.e2e @pytest.mark.devtest -def test_nhsapp_end_to_end(nhsd_apim_proxy_url, bearer_token_internal_dev): +def test_nhsapp_end_to_end(url, bearer_token): """ .. include:: ../../partials/happy_path/test_nhsapp_end_to_end_internal_dev.rst """ + headers = Generators.generate_valid_headers(bearer_token.value) + resp = Helper.send_single_message( - nhsd_apim_proxy_url, - {"Authorization": bearer_token_internal_dev.value}, + url, + headers, Generators.generate_send_message_body("nhsapp", "internal-dev") ) message_id = resp.json().get("data").get("id") Helper.poll_get_message( - url=nhsd_apim_proxy_url, - auth={"Authorization": bearer_token_internal_dev.value}, + url=url, + headers=headers, message_id=message_id ) Assertions.assert_get_message_status( Helper.get_message( - nhsd_apim_proxy_url, - {"Authorization": bearer_token_internal_dev.value}, + url, + headers, message_id ), "delivered" @@ -36,31 +38,32 @@ def test_nhsapp_end_to_end(nhsd_apim_proxy_url, bearer_token_internal_dev): @pytest.mark.e2e @pytest.mark.uattest -def test_nhsapp_end_to_end_uat(nhsd_apim_proxy_url, bearer_token_internal_dev): +def test_nhsapp_end_to_end_uat(url, bearer_token): """ .. include:: ../../partials/happy_path/test_nhsapp_end_to_end_uat.rst """ + headers = Generators.generate_valid_headers(bearer_token.value) personalisation = str(uuid.uuid1()) resp = Helper.send_single_message( - nhsd_apim_proxy_url, - {"Authorization": bearer_token_internal_dev.value}, + url, + headers, Generators.generate_send_message_body("nhsapp", "internal-qa", personalisation) ) message_id = resp.json().get("data").get("id") Helper.poll_get_message( - url=nhsd_apim_proxy_url, - auth={"Authorization": bearer_token_internal_dev.value}, + url=url, + headers=headers, message_id=message_id, end_state="sending" ) Assertions.assert_get_message_status( Helper.get_message( - nhsd_apim_proxy_url, - {"Authorization": bearer_token_internal_dev.value}, + url, + headers, message_id ), "sending" diff --git a/tests/end_to_end/test_sms.py b/tests/end_to_end/test_sms.py index 44eb71c7f..7b304cd86 100644 --- a/tests/end_to_end/test_sms.py +++ b/tests/end_to_end/test_sms.py @@ -7,21 +7,23 @@ @pytest.mark.e2e @pytest.mark.devtest -def test_sms_end_to_end_internal_dev(nhsd_apim_proxy_url, bearer_token_internal_dev): +def test_sms_end_to_end_internal_dev(url, bearer_token): """ .. include:: ../../partials/happy_path/test_sms_end_to_end_internal_dev.rst """ + headers = Generators.generate_valid_headers(bearer_token.value) + resp = Helper.send_single_message( - nhsd_apim_proxy_url, - {"Authorization": bearer_token_internal_dev.value}, + url, + headers, Generators.generate_send_message_body("sms", "internal-dev") ) message_id = resp.json().get("data").get("id") Helper.poll_get_message( - url=nhsd_apim_proxy_url, - auth={"Authorization": bearer_token_internal_dev.value}, + url=url, + headers=headers, message_id=message_id ) @@ -33,21 +35,23 @@ def test_sms_end_to_end_internal_dev(nhsd_apim_proxy_url, bearer_token_internal_ @pytest.mark.e2e @pytest.mark.uattest -def test_sms_end_to_end_uat(nhsd_apim_proxy_url, bearer_token_internal_dev): +def test_sms_end_to_end_uat(url, bearer_token): """ .. include:: ../../partials/happy_path/test_sms_end_to_end_uat.rst """ + headers = Generators.generate_valid_headers(bearer_token.value) + resp = Helper.send_single_message( - nhsd_apim_proxy_url, - {"Authorization": bearer_token_internal_dev.value}, + url, + headers, Generators.generate_send_message_body("sms", "internal-qa") ) message_id = resp.json().get("data").get("id") Helper.poll_get_message( - url=nhsd_apim_proxy_url, - auth={"Authorization": bearer_token_internal_dev.value}, + url=url, + headers=headers, message_id=message_id ) diff --git a/tests/lib/helper.py b/tests/lib/helper.py index f605e16d8..b958fdfa1 100644 --- a/tests/lib/helper.py +++ b/tests/lib/helper.py @@ -24,27 +24,21 @@ def send_single_message(url, auth, body): return resp @staticmethod - def get_message(url, auth, message_id): - resp = requests.get(f"{url}{MESSAGES_ENDPOINT}/{message_id}", headers={ - **auth, - "Accept": DEFAULT_CONTENT_TYPE - }) + def get_message(url, headers, message_id): + resp = requests.get(f"{url}{MESSAGES_ENDPOINT}/{message_id}", headers=headers) error_handler.handle_retry(resp) assert resp.status_code == 200 return resp @staticmethod - def poll_get_message(url, auth, message_id, end_state="delivered", poll_time=300): + def poll_get_message(url, headers, message_id, end_state="delivered", poll_time=300): message_status = None end_time = int(time.time()) + poll_time while message_status != end_state and int(time.time()) < end_time: get_message_response = requests.get( f"{url}{MESSAGES_ENDPOINT}/{message_id}", - headers={ - **auth, - "Accept": DEFAULT_CONTENT_TYPE - }, + headers=headers, ) if get_message_response.status_code == 200: From 307c2929bd0fdd5415584346b7fa16a0256953f7 Mon Sep 17 00:00:00 2001 From: "mark.slowey1" Date: Tue, 22 Apr 2025 11:42:51 +0100 Subject: [PATCH 3/5] CCM-9156: Update readme for envar setup --- README.md | 34 +++++++++++++++++++++++++++++----- example.env | 2 +- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index d14f4a2b2..17cf1eb62 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,8 @@ To use your local `.env` file make sure to source it: $ source .env ``` +As a reminder: environment variable changes from the `.env` file take place only when the workspace is reloaded (e.g. through a new cli or `direnv allow`) + ### Make commands There are `make` commands that alias some of this functionality: @@ -126,7 +128,11 @@ Our API integration tests support two authentication methods: #### Bearer Token Authentication -To be able to generate bearer token authentication for tests you need to declare the `API_ENVIRONMENT` environment variable: +>**Notice:** This section contains the handling of secrets. As a reminder, secrets should only be shared via secure methods (e.g. NHS Mail) and MUST NOT be committed to source control + +##### Set the Environment + +To be able to generate bearer token authentication for tests you need to declare the `API_ENVIRONMENT` environment variable, e.g.: ``` export API_ENVIRONMENT=internal-dev @@ -139,7 +145,13 @@ Available values for `API_ENVIRONMENT` include: * `int` * `prod` -The authentication process uses `API_ENVIRONMENT` to generate authentication for a given client by using an api and private key pairing suitable for a client on its given environment. +This defines the scope of tests that will be executed and the Apigee App/Product that will be used to service requests. + +The authentication process uses an API and private key pairing for the application defined by the `API_ENVIRONMENT` + +##### API Key + +To define the API Key, the following envars are used: |Environment|API Key Variable|Private Key Variable| |-----------|----------------|--------------------| @@ -148,7 +160,20 @@ The authentication process uses `API_ENVIRONMENT` to generate authentication for |int|`INTEGRATION_API_KEY`|`INTEGRATION_PRIVATE_KEY`| |prod|`PRODUCTION_API_KEY`|`PRODUCTION_PRIVATE_KEY`| -__Ensure these variables are set and sourced in your .env file before running tests.__ +To find the values for the `*_API_KEY` values: +* Identify the correct envar for your `API_ENVIRONMENT` from the table above +* Navigate to the Apigee App that will serve that environment e.g. `Comms-manager-local` +* Copy the value of the `Key` found in the credentials section to your envar in `.env` + +In addition the `API_KEY` envar is also used and should use the same value. + +##### Private Key + +The value of the `*_PRIVATE_KEY` envars are a *file path* to the location of a private key file. + +The keys are held securely within the Management Account - talk to existing team members for more information on sourcing and configuring these values + +>__Ensure these variables are set and sourced in your .env file before running tests.__ #### Generate An Apigee Access Token @@ -409,5 +434,4 @@ The collections must be kept in sync manually, this is done by setting the `INTE ## Releasing -Our release process is [documented here](https://nhsd-confluence.digital.nhs.uk/pages/viewpage.action?pageId=789753975). - +Our release process is [documented here](https://nhsd-confluence.digital.nhs.uk/pages/viewpage.action?pageId=789753975). \ No newline at end of file diff --git a/example.env b/example.env index 789b25293..51ccd6b60 100644 --- a/example.env +++ b/example.env @@ -15,7 +15,7 @@ export PROXY_NAME=xxx # In order to find out the value of an environments given API key, follow these steps # 1. Log in to Non-Prod/ Prod APIGEE # 2. Navigate to 'Publish' > 'Apps' and search for the app linked to authentication -# 3. Copy the secret key related to the app +# 3. Copy the "key" from the Credentials related to the app # # api key used to manually generate authentication for a given environment, used in generate_bearer_token.py and for postman authentication export API_KEY=xxx From f788fafa89e8d32c4ae6b612651bcc2e2ee20ef3 Mon Sep 17 00:00:00 2001 From: lapenna-bjss <160487283+lapenna-bjss@users.noreply.github.com> Date: Tue, 22 Apr 2025 14:41:34 +0100 Subject: [PATCH 4/5] CCM-8894: Update sandbox to include patientName scenarios (#926) --- sandbox/__test__/batch_send.spec.js | 416 +++++++++++++++- sandbox/__test__/messages.spec.js | 447 +++++++++++++++++- .../override_contact_details.js | 136 +++++- 3 files changed, 960 insertions(+), 39 deletions(-) diff --git a/sandbox/__test__/batch_send.spec.js b/sandbox/__test__/batch_send.spec.js index 25b04c6ce..d5a1c2941 100644 --- a/sandbox/__test__/batch_send.spec.js +++ b/sandbox/__test__/batch_send.spec.js @@ -1047,12 +1047,12 @@ describe("/api/v1/send", () => { }, }) .expect(400, { - message: "Invalid recipient contact details. Field 'sms': Invalid", + message: "Invalid recipient contact details. Field 'sms': Input is not a string", errors: [ { code: 'CM_INVALID_VALUE', field: "/data/attributes/messages/0/recipient/contactDetails/sms", - message: "Invalid", + message: "Input is not a string", title: "Invalid value", statusCode: 400, }, @@ -1156,12 +1156,12 @@ describe("/api/v1/send", () => { }, }) .expect(400, { - message: "Invalid recipient contact details. Field 'email': Invalid", + message: "Invalid recipient contact details. Field 'email': Input is not a string", errors: [ { code: 'CM_INVALID_VALUE', field: "/data/attributes/messages/0/recipient/contactDetails/email", - message: "Invalid", + message: "Input is not a string", title: "Invalid value", statusCode: 400, }, @@ -1308,7 +1308,7 @@ describe("/api/v1/send", () => { }) .expect(400, { message: - "Invalid recipient contact details. Field 'lines': 'lines' is missing", + "Invalid recipient contact details. Field 'address': 'lines' is missing", errors: [ { code: 'CM_MISSING_VALUE', @@ -1351,7 +1351,7 @@ describe("/api/v1/send", () => { }) .expect(400, { message: - "Invalid recipient contact details. Field 'lines': Too few address lines were provided", + "Invalid recipient contact details. Field 'address': Too few address lines were provided", errors: [ { code: 'CM_TOO_FEW_ITEMS', @@ -1393,7 +1393,7 @@ describe("/api/v1/send", () => { }, }) .expect(400, { - message: "Invalid recipient contact details. Field 'lines': Too many address lines were provided", + message: "Invalid recipient contact details. Field 'address': Too many address lines were provided", errors: [ { code: 'CM_INVALID_VALUE', @@ -1435,12 +1435,12 @@ describe("/api/v1/send", () => { }, }) .expect(400, { - message: "Invalid recipient contact details. Field 'lines': Invalid", + message: "Invalid recipient contact details. Field 'address': Lines contain non-string or empty line", errors: [ { code: 'CM_INVALID_VALUE', field: "/data/attributes/messages/0/recipient/contactDetails/address", - message: "Invalid", + message: "Lines contain non-string or empty line", title: "Invalid value", statusCode: 400, }, @@ -1477,7 +1477,7 @@ describe("/api/v1/send", () => { }) .expect(400, { message: - "Invalid recipient contact details. Field 'postcode': 'postcode' is missing", + "Invalid recipient contact details. Field 'address': 'postcode' is missing", errors: [ { code: 'CM_MISSING_VALUE', @@ -1519,11 +1519,84 @@ describe("/api/v1/send", () => { }, }) .expect(400, { - message: "Invalid recipient contact details. Field 'postcode': Invalid", + message: "Invalid recipient contact details. Field 'address': 'postcode' is not a string", errors: [ { code: 'CM_INVALID_VALUE', - field: "/data/attributes/messages/0/recipient/contactDetails/address/postcode", + field: "/data/attributes/messages/0/recipient/contactDetails/address", + message: "'postcode' is not a string", + title: "Invalid value", + statusCode: 400, + }, + ], + }) + .expect("Content-Type", /json/, done); + }); + + it("returns a 200 when name alternate contact detail is provided", (done) => { + request(server) + .post("/api/v1/send") + .set({ Authorization: "allowedContactDetailOverride" }) + .send({ + data: { + type: "MessageBatch", + attributes: { + routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", + messageBatchReference: "request-id", + messages: [ + { + messageReference: "1", + recipient: { + nhsNumber: "1", + contactDetails: { + name: { + prefix: "Mr", + firstName: "John", + middleNames: "Little", + lastName: "Smith", + suffix: "Jr" + }, + }, + }, + }, + ], + }, + }, + }) + .expect(200) + .expect("Content-Type", /json/, done); + }); + + it("returns a 400 when string for name alternate contact detail is provided", (done) => { + request(server) + .post("/api/v1/send") + .set({ Authorization: "allowedContactDetailOverride" }) + .send({ + data: { + type: "MessageBatch", + attributes: { + routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", + messageBatchReference: "request-id", + messages: [ + { + messageReference: "1", + recipient: { + nhsNumber: "1", + contactDetails: { + name: "hello", + }, + }, + }, + ], + }, + }, + }) + .expect(400, { + message: "Invalid recipient contact details. Field 'name': Invalid", + errors: [ + { + code: 'CM_INVALID_VALUE', + field: "/data/attributes/messages/0/recipient/contactDetails/name", message: "Invalid", title: "Invalid value", statusCode: 400, @@ -1532,6 +1605,316 @@ describe("/api/v1/send", () => { }) .expect("Content-Type", /json/, done); }); + + it("returns a 400 when array for name alternate contact detail is provided", (done) => { + request(server) + .post("/api/v1/send") + .set({ Authorization: "allowedContactDetailOverride" }) + .send({ + data: { + type: "MessageBatch", + attributes: { + routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", + messageBatchReference: "request-id", + messages: [ + { + messageReference: "1", + recipient: { + nhsNumber: "1", + contactDetails: { + name: [], + }, + }, + }, + ], + }, + }, + }) + .expect(400, { + message: "Invalid recipient contact details. Field 'name': Invalid", + errors: [ + { + code: 'CM_INVALID_VALUE', + field: "/data/attributes/messages/0/recipient/contactDetails/name", + message: "Invalid", + title: "Invalid value", + statusCode: 400, + }, + ], + }) + .expect("Content-Type", /json/, done); + }); + + it("returns a 400 when prefix contains non-string value in name alternate contact detail is provided", (done) => { + request(server) + .post("/api/v1/send") + .set({ Authorization: "allowedContactDetailOverride" }) + .send({ + data: { + type: "MessageBatch", + attributes: { + routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", + messageBatchReference: "request-id", + messages: [ + { + messageReference: "1", + recipient: { + nhsNumber: "1", + contactDetails: { + name: { + prefix: 0, + firstName: "John", + middleNames: "Little", + lastName: "Smith", + suffix: "Jr" + }, + }, + }, + }, + ], + }, + }, + }) + .expect(400, { + message: "Invalid recipient contact details. Field 'name': 'prefix' is not a string", + errors: [ + { + code: 'CM_INVALID_VALUE', + field: "/data/attributes/messages/0/recipient/contactDetails/name", + message: "'prefix' is not a string", + title: "Invalid value", + statusCode: 400, + }, + ], + }) + .expect("Content-Type", /json/, done); + }); + + it("returns a 400 when firstName contains non-string value in name alternate contact detail is provided", (done) => { + request(server) + .post("/api/v1/send") + .set({ Authorization: "allowedContactDetailOverride" }) + .send({ + data: { + type: "MessageBatch", + attributes: { + routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", + messageBatchReference: "request-id", + messages: [ + { + messageReference: "1", + recipient: { + nhsNumber: "1", + contactDetails: { + name: { + prefix: "Mr", + firstName: 0, + middleNames: "Little", + lastName: "Smith", + suffix: "Jr" + }, + }, + }, + }, + ], + }, + }, + }) + .expect(400, { + message: "Invalid recipient contact details. Field 'name': 'firstName' is not a string", + errors: [ + { + code: 'CM_INVALID_VALUE', + field: "/data/attributes/messages/0/recipient/contactDetails/name", + message: "'firstName' is not a string", + title: "Invalid value", + statusCode: 400, + }, + ], + }) + .expect("Content-Type", /json/, done); + }); + + it("returns a 400 when middleNames contains non-string value in name alternate contact detail is provided", (done) => { + request(server) + .post("/api/v1/send") + .set({ Authorization: "allowedContactDetailOverride" }) + .send({ + data: { + type: "MessageBatch", + attributes: { + routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", + messageBatchReference: "request-id", + messages: [ + { + messageReference: "1", + recipient: { + nhsNumber: "1", + contactDetails: { + name: { + prefix: "Mr", + firstName: "John", + middleNames: 0, + lastName: "Smith", + suffix: "Jr" + }, + }, + }, + }, + ], + }, + }, + }) + .expect(400, { + message: "Invalid recipient contact details. Field 'name': 'middleNames' is not a string", + errors: [ + { + code: 'CM_INVALID_VALUE', + field: "/data/attributes/messages/0/recipient/contactDetails/name", + message: "'middleNames' is not a string", + title: "Invalid value", + statusCode: 400, + }, + ], + }) + .expect("Content-Type", /json/, done); + }); + + it("returns a 400 when no lastName is provided in name alternate contact detail is provided", (done) => { + request(server) + .post("/api/v1/send") + .set({ Authorization: "allowedContactDetailOverride" }) + .send({ + data: { + type: "MessageBatch", + attributes: { + routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", + messageBatchReference: "request-id", + messages: [ + { + messageReference: "1", + recipient: { + nhsNumber: "1", + contactDetails: { + name: { + prefix: "Mr", + firstName: "John", + middleNames: "Little", + suffix: "Jr" + }, + }, + }, + }, + ], + }, + }, + }) + .expect(400, { + message: + "Invalid recipient contact details. Field 'name': 'lastName' is missing", + errors: [ + { + code: 'CM_MISSING_VALUE', + field: "/data/attributes/messages/0/recipient/contactDetails/name", + message: "`lastName` is missing", + title: "Missing value", + statusCode: 400, + }, + ], + }) + .expect("Content-Type", /json/, done); + }); + + it("returns a 400 when lastName contains non-string value in name alternate contact detail is provided", (done) => { + request(server) + .post("/api/v1/send") + .set({ Authorization: "allowedContactDetailOverride" }) + .send({ + data: { + type: "MessageBatch", + attributes: { + routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", + messageBatchReference: "request-id", + messages: [ + { + messageReference: "1", + recipient: { + nhsNumber: "1", + contactDetails: { + name: { + prefix: "Mr", + firstName: "John", + middleNames: "Little", + lastName: 0, + suffix: "Jr" + }, + }, + }, + }, + ], + }, + }, + }) + .expect(400, { + message: "Invalid recipient contact details. Field 'name': 'lastName' must be a non-empty string", + errors: [ + { + code: 'CM_INVALID_VALUE', + field: "/data/attributes/messages/0/recipient/contactDetails/name", + message: "'lastName' must be a non-empty string", + title: "Invalid value", + statusCode: 400, + }, + ], + }) + .expect("Content-Type", /json/, done); + }); + + it("returns a 400 when suffix contains non-string value in name alternate contact detail is provided", (done) => { + request(server) + .post("/api/v1/send") + .set({ Authorization: "allowedContactDetailOverride" }) + .send({ + data: { + type: "MessageBatch", + attributes: { + routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", + messageBatchReference: "request-id", + messages: [ + { + messageReference: "1", + recipient: { + nhsNumber: "1", + contactDetails: { + name: { + prefix: "Mr", + firstName: "John", + middleNames: "Little", + lastName: "Smith", + suffix: 0 + }, + }, + }, + }, + ], + }, + }, + }) + .expect(400, { + message: "Invalid recipient contact details. Field 'name': 'suffix' is not a string", + errors: [ + { + code: 'CM_INVALID_VALUE', + field: "/data/attributes/messages/0/recipient/contactDetails/name", + message: "'suffix' is not a string", + title: "Invalid value", + statusCode: 400, + }, + ], + }) + .expect("Content-Type", /json/, done); + }); + it("returns a 400 and multiple errors when there are multiple issues in contact details provided", (done) => { request(server) .post("/api/v1/send") @@ -1562,7 +1945,7 @@ describe("/api/v1/send", () => { }) .expect(400, { message: - "Invalid recipient contact details. Field 'email': Input failed format check. Field 'lines': Too few address lines were provided", + "Invalid recipient contact details. Field 'email': Input failed format check. Field 'address': Too few address lines were provided", errors: [ { code: 'CM_INVALID_VALUE', @@ -1605,6 +1988,13 @@ describe("/api/v1/send", () => { lines: ["1", "2"], postcode: "hello", }, + name: { + prefix: "Mr", + firstName: "John", + middleNames: "Little", + lastName: "Smith", + suffix: "Jr" + }, }, }, }, diff --git a/sandbox/__test__/messages.spec.js b/sandbox/__test__/messages.spec.js index a489569d3..d47d18d0e 100644 --- a/sandbox/__test__/messages.spec.js +++ b/sandbox/__test__/messages.spec.js @@ -740,12 +740,12 @@ describe("/api/v1/messages", () => { }, }) .expect(400, { - message: "Invalid recipient contact details. Field 'sms': Invalid", + message: "Invalid recipient contact details. Field 'sms': Input is not a string", errors: [ { code: 'CM_INVALID_VALUE', field: "/data/attributes/recipient/contactDetails/sms", - message: "Invalid", + message: "Input is not a string", title: "Invalid value", statusCode: 400, }, @@ -837,12 +837,12 @@ describe("/api/v1/messages", () => { }, }) .expect(400, { - message: "Invalid recipient contact details. Field 'email': Invalid", + message: "Invalid recipient contact details. Field 'email': Input is not a string", errors: [ { code: 'CM_INVALID_VALUE', field: "/data/attributes/recipient/contactDetails/email", - message: "Invalid", + message: "Input is not a string", title: "Invalid value", statusCode: 400, }, @@ -973,7 +973,7 @@ describe("/api/v1/messages", () => { }) .expect(400, { message: - "Invalid recipient contact details. Field 'lines': 'lines' is missing", + "Invalid recipient contact details. Field 'address': 'lines' is missing", errors: [ { code: 'CM_MISSING_VALUE', @@ -1012,7 +1012,7 @@ describe("/api/v1/messages", () => { }) .expect(400, { message: - "Invalid recipient contact details. Field 'lines': Too few address lines were provided", + "Invalid recipient contact details. Field 'address': Too few address lines were provided", errors: [ { code: 'CM_TOO_FEW_ITEMS', @@ -1050,7 +1050,7 @@ describe("/api/v1/messages", () => { }, }) .expect(400, { - message: "Invalid recipient contact details. Field 'lines': Too many address lines were provided", + message: "Invalid recipient contact details. Field 'address': Too many address lines were provided", errors: [ { code: 'CM_INVALID_VALUE', @@ -1088,12 +1088,12 @@ describe("/api/v1/messages", () => { }, }) .expect(400, { - message: "Invalid recipient contact details. Field 'lines': Invalid", + message: "Invalid recipient contact details. Field 'address': Lines contain non-string or empty line", errors: [ { code: 'CM_INVALID_VALUE', field: "/data/attributes/recipient/contactDetails/address", - message: "Invalid", + message: "Lines contain non-string or empty line", title: "Invalid value", statusCode: 400, }, @@ -1126,7 +1126,7 @@ describe("/api/v1/messages", () => { }) .expect(400, { message: - "Invalid recipient contact details. Field 'postcode': 'postcode' is missing", + "Invalid recipient contact details. Field 'address': 'postcode' is missing", errors: [ { code: 'CM_MISSING_VALUE', @@ -1164,11 +1164,137 @@ describe("/api/v1/messages", () => { }, }) .expect(400, { - message: "Invalid recipient contact details. Field 'postcode': Invalid", + message: "Invalid recipient contact details. Field 'address': 'postcode' is not a string", + errors: [ + { + code: 'CM_INVALID_VALUE', + field: "/data/attributes/recipient/contactDetails/address", + message: "'postcode' is not a string", + title: "Invalid value", + statusCode: 400, + }, + ], + }) + .expect("Content-Type", /json/, done); + }); + + it("returns a 201 when valid value for name alternate contact detail is provided", (done) => { + request(server) + .post("/api/v1/messages") + .set({ Authorization: "allowedContactDetailOverride" }) + .send({ + data: { + type: "Message", + attributes: { + routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", + messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", + recipient: { + nhsNumber: "1", + contactDetails: { + name: { + prefix: "Mr", + firstName: "John", + middleNames: "Little", + lastName: "Smith", + suffix: "Jr" + }, + }, + }, + personalisation: {}, + }, + }, + }) + .expect(201) + .expect("Content-Type", /json/, done); + }); + + it("returns a 201 when only lastName is provided for name alternate contact details", (done) => { + request(server) + .post("/api/v1/messages") + .set({ Authorization: "allowedContactDetailOverride" }) + .send({ + data: { + type: "Message", + attributes: { + routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", + messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", + recipient: { + nhsNumber: "1", + contactDetails: { + name: { + lastName: "Smith" + }, + }, + }, + personalisation: {}, + }, + }, + }) + .expect(201) + .expect("Content-Type", /json/, done); + }); + + it("returns a 400 when string for name alternate contact detail is provided", (done) => { + request(server) + .post("/api/v1/messages") + .set({ Authorization: "allowedContactDetailOverride" }) + .send({ + data: { + type: "Message", + attributes: { + routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", + messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", + recipient: { + nhsNumber: "1", + contactDetails: { + name: "hello", + }, + }, + personalisation: {}, + }, + }, + }) + .expect(400, { + message: "Invalid recipient contact details. Field 'name': Invalid", + errors: [ + { + code: 'CM_INVALID_VALUE', + field: "/data/attributes/recipient/contactDetails/name", + message: "Invalid", + title: "Invalid value", + statusCode: 400, + }, + ], + }) + .expect("Content-Type", /json/, done); + }); + + it("returns a 400 when array for name alternate contact detail is provided", (done) => { + request(server) + .post("/api/v1/messages") + .set({ Authorization: "allowedContactDetailOverride" }) + .send({ + data: { + type: "Message", + attributes: { + routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", + messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", + recipient: { + nhsNumber: "1", + contactDetails: { + name: [], + }, + }, + personalisation: {}, + }, + }, + }) + .expect(400, { + message: "Invalid recipient contact details. Field 'name': Invalid", errors: [ { code: 'CM_INVALID_VALUE', - field: "/data/attributes/recipient/contactDetails/address/postcode", + field: "/data/attributes/recipient/contactDetails/name", message: "Invalid", title: "Invalid value", statusCode: 400, @@ -1177,6 +1303,294 @@ describe("/api/v1/messages", () => { }) .expect("Content-Type", /json/, done); }); + + it("returns a 400 when prefix contains non-string value in name alternate contact detail is provided", (done) => { + request(server) + .post("/api/v1/messages") + .set({ Authorization: "allowedContactDetailOverride" }) + .send({ + data: { + type: "Message", + attributes: { + routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", + messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", + recipient: { + nhsNumber: "1", + contactDetails: { + name: { + prefix: 0, + firstName: "John", + middleNames: "Little", + lastName: "Smith", + suffix: "Jr" + }, + }, + }, + personalisation: {}, + }, + }, + }) + .expect(400, { + message: "Invalid recipient contact details. Field 'name': 'prefix' is not a string", + errors: [ + { + code: 'CM_INVALID_VALUE', + field: "/data/attributes/recipient/contactDetails/name", + message: "'prefix' is not a string", + title: "Invalid value", + statusCode: 400, + }, + ], + }) + .expect("Content-Type", /json/, done); + }); + + it("returns a 400 when firstName contains non-string value in name alternate contact detail is provided", (done) => { + request(server) + .post("/api/v1/messages") + .set({ Authorization: "allowedContactDetailOverride" }) + .send({ + data: { + type: "Message", + attributes: { + routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", + messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", + recipient: { + nhsNumber: "1", + contactDetails: { + name: { + prefix: "Mr", + firstName: 0, + middleNames: "Little", + lastName: "Smith", + suffix: "Jr" + }, + }, + }, + personalisation: {}, + }, + }, + }) + .expect(400, { + message: "Invalid recipient contact details. Field 'name': 'firstName' is not a string", + errors: [ + { + code: 'CM_INVALID_VALUE', + field: "/data/attributes/recipient/contactDetails/name", + message: "'firstName' is not a string", + title: "Invalid value", + statusCode: 400, + }, + ], + }) + .expect("Content-Type", /json/, done); + }); + + it("returns a 400 when middleNames contains non-string value in name alternate contact detail is provided", (done) => { + request(server) + .post("/api/v1/messages") + .set({ Authorization: "allowedContactDetailOverride" }) + .send({ + data: { + type: "Message", + attributes: { + routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", + messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", + recipient: { + nhsNumber: "1", + contactDetails: { + name: { + prefix: "Mr", + firstName: "John", + middleNames: 0, + lastName: "Smith", + suffix: "Jr" + }, + }, + }, + personalisation: {}, + }, + }, + }) + .expect(400, { + message: "Invalid recipient contact details. Field 'name': 'middleNames' is not a string", + errors: [ + { + code: 'CM_INVALID_VALUE', + field: "/data/attributes/recipient/contactDetails/name", + message: "'middleNames' is not a string", + title: "Invalid value", + statusCode: 400, + }, + ], + }) + .expect("Content-Type", /json/, done); + }); + + it("returns a 400 when no lastName is provided in name alternate contact detail is provided", (done) => { + request(server) + .post("/api/v1/messages") + .set({ Authorization: "allowedContactDetailOverride" }) + .send({ + data: { + type: "Message", + attributes: { + routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", + messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", + recipient: { + nhsNumber: "1", + contactDetails: { + name: { + prefix: "Mr", + firstName: "John", + middleNames: "Little", + suffix: "Jr" + }, + }, + }, + personalisation: {}, + }, + }, + }) + .expect(400, { + message: + "Invalid recipient contact details. Field 'name': 'lastName' is missing", + errors: [ + { + code: 'CM_MISSING_VALUE', + field: "/data/attributes/recipient/contactDetails/name", + message: "`lastName` is missing", + title: "Missing value", + statusCode: 400, + }, + ], + }) + .expect("Content-Type", /json/, done); + }); + + it("returns a 400 when lastName contains non-string value in name alternate contact detail is provided", (done) => { + request(server) + .post("/api/v1/messages") + .set({ Authorization: "allowedContactDetailOverride" }) + .send({ + data: { + type: "Message", + attributes: { + routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", + messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", + recipient: { + nhsNumber: "1", + contactDetails: { + name: { + prefix: "Mr", + firstName: "John", + middleNames: "Little", + lastName: [], + suffix: "Jr" + }, + }, + }, + personalisation: {}, + }, + }, + }) + .expect(400, { + message: "Invalid recipient contact details. Field 'name': 'lastName' must be a non-empty string", + errors: [ + { + code: 'CM_INVALID_VALUE', + field: "/data/attributes/recipient/contactDetails/name", + message: "'lastName' must be a non-empty string", + title: "Invalid value", + statusCode: 400, + }, + ], + }) + .expect("Content-Type", /json/, done); + }); + + it("returns a 400 when lastName contains empty-string value in name alternate contact detail is provided", (done) => { + request(server) + .post("/api/v1/messages") + .set({ Authorization: "allowedContactDetailOverride" }) + .send({ + data: { + type: "Message", + attributes: { + routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", + messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", + recipient: { + nhsNumber: "1", + contactDetails: { + name: { + prefix: "Mr", + firstName: "John", + middleNames: "Little", + lastName: "", + suffix: "Jr" + }, + }, + }, + personalisation: {}, + }, + }, + }) + .expect(400, { + message: "Invalid recipient contact details. Field 'name': 'lastName' must be a non-empty string", + errors: [ + { + code: 'CM_INVALID_VALUE', + field: "/data/attributes/recipient/contactDetails/name", + message: "'lastName' must be a non-empty string", + title: "Invalid value", + statusCode: 400, + }, + ], + }) + .expect("Content-Type", /json/, done); + }); + + it("returns a 400 when suffix contains non-string value in name alternate contact detail is provided", (done) => { + request(server) + .post("/api/v1/messages") + .set({ Authorization: "allowedContactDetailOverride" }) + .send({ + data: { + type: "Message", + attributes: { + routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", + messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", + recipient: { + nhsNumber: "1", + contactDetails: { + name: { + prefix: "Mr", + firstName: "John", + middleNames: "Little", + lastName: "Smith", + suffix: 0 + }, + }, + }, + personalisation: {}, + }, + }, + }) + .expect(400, { + message: "Invalid recipient contact details. Field 'name': 'suffix' is not a string", + errors: [ + { + code: 'CM_INVALID_VALUE', + field: "/data/attributes/recipient/contactDetails/name", + message: "'suffix' is not a string", + title: "Invalid value", + statusCode: 400, + }, + ], + }) + .expect("Content-Type", /json/, done); + }); + it("returns a 400 and multiple errors when there are multiple issues in contact details provided", (done) => { request(server) .post("/api/v1/messages") @@ -1203,7 +1617,7 @@ describe("/api/v1/messages", () => { }) .expect(400, { message: - "Invalid recipient contact details. Field 'email': Input failed format check. Field 'lines': Too few address lines were provided", + "Invalid recipient contact details. Field 'email': Input failed format check. Field 'address': Too few address lines were provided", errors: [ { code: 'CM_INVALID_VALUE', @@ -1244,6 +1658,13 @@ describe("/api/v1/messages", () => { lines: ["1", "2"], postcode: "hello", }, + name: { + prefix: "Mr", + firstName: "John", + middleNames: "Little", + lastName: "Smith", + suffix: "Jr" + }, }, }, }, diff --git a/sandbox/handlers/error_scenarios/override_contact_details.js b/sandbox/handlers/error_scenarios/override_contact_details.js index 5fbf4b742..b9189dc5c 100644 --- a/sandbox/handlers/error_scenarios/override_contact_details.js +++ b/sandbox/handlers/error_scenarios/override_contact_details.js @@ -38,10 +38,10 @@ function smsValidation(sms, path) { title: "Invalid value", code: 'CM_INVALID_VALUE', field: `${path}/recipient/contactDetails/sms`, - message: "Invalid", + message: "Input is not a string", statusCode: 400, }, - `Field 'sms': Invalid`, + `Field 'sms': Input is not a string`, ]); } return validationSuccess(); @@ -69,10 +69,10 @@ function emailValidation(email, path) { title: "Invalid value", code: 'CM_INVALID_VALUE', field: `${path}/recipient/contactDetails/email`, - message: "Invalid", + message: "Input is not a string", statusCode: 400, }, - `Field 'email': Invalid`, + `Field 'email': Input is not a string`, ]); } return validationSuccess(); @@ -105,7 +105,7 @@ function addressValidation(address, path) { message: "`lines` is missing", statusCode: 400, }, - `Field 'lines': 'lines' is missing`, + `Field 'address': 'lines' is missing`, ]); } if (!Array.isArray(address.lines)) { @@ -117,7 +117,7 @@ function addressValidation(address, path) { message: "`lines` is missing", statusCode: 400, }, - `Field 'lines': 'lines' is missing`, + `Field 'address': 'lines' is missing`, ]); } @@ -127,10 +127,10 @@ function addressValidation(address, path) { title: "Invalid value", code: 'CM_INVALID_VALUE', field: `${path}/recipient/contactDetails/address`, - message: "Invalid", + message: "Lines contain non-string or empty line", statusCode: 400, }, - `Field 'lines': Invalid`, + `Field 'address': Lines contain non-string or empty line`, ]); } @@ -143,7 +143,7 @@ function addressValidation(address, path) { message: "Too few address lines were provided", statusCode: 400, }, - `Field 'lines': Too few address lines were provided`, + `Field 'address': Too few address lines were provided`, ]); } if (address.lines.length > 5) { @@ -155,7 +155,7 @@ function addressValidation(address, path) { message: "Too many address lines were provided", statusCode: 400, }, - `Field 'lines': Too many address lines were provided`, + `Field 'address': Too many address lines were provided`, ]); } @@ -168,7 +168,7 @@ function addressValidation(address, path) { message: "`postcode` is missing", statusCode: 400, }, - `Field 'postcode': 'postcode' is missing`, + `Field 'address': 'postcode' is missing`, ]); } @@ -177,11 +177,110 @@ function addressValidation(address, path) { { title: "Invalid value", code: 'CM_INVALID_VALUE', - field: `${path}/recipient/contactDetails/address/postcode`, + field: `${path}/recipient/contactDetails/address`, + message: "'postcode' is not a string", + statusCode: 400, + }, + `Field 'address': 'postcode' is not a string`, + ]); + } + return validationSuccess(); +} + +function nameValidation(name, path) { + + if (!name) { + return validationSuccess(); + } + + if (typeof name !== "object" || Array.isArray(name)) { + return validationFailure([ + { + title: "Invalid value", + code: 'CM_INVALID_VALUE', + field: `${path}/recipient/contactDetails/name`, message: "Invalid", statusCode: 400, }, - `Field 'postcode': Invalid`, + `Field 'name': Invalid`, + ]); + } + + if ("prefix" in name && typeof name.prefix !== "string") { + return validationFailure([ + { + title: "Invalid value", + code: 'CM_INVALID_VALUE', + field: `${path}/recipient/contactDetails/name`, + message: "'prefix' is not a string", + statusCode: 400, + }, + `Field 'name': 'prefix' is not a string`, + ]); + } + + if ("firstName" in name && typeof name.firstName !== "string") { + return validationFailure([ + { + title: "Invalid value", + code: 'CM_INVALID_VALUE', + field: `${path}/recipient/contactDetails/name`, + message: "'firstName' is not a string", + statusCode: 400, + }, + `Field 'name': 'firstName' is not a string`, + ]); + } + + if ("middleNames" in name && typeof name.middleNames !== "string") { + return validationFailure([ + { + title: "Invalid value", + code: 'CM_INVALID_VALUE', + field: `${path}/recipient/contactDetails/name`, + message: "'middleNames' is not a string", + statusCode: 400, + }, + `Field 'name': 'middleNames' is not a string`, + ]); + } + + if (typeof name.lastName === "undefined") { + return validationFailure([ + { + title: "Missing value", + code: 'CM_MISSING_VALUE', + field: `${path}/recipient/contactDetails/name`, + message: "`lastName` is missing", + statusCode: 400, + }, + `Field 'name': 'lastName' is missing`, + ]); + } + + if (typeof name.lastName !== "string" || name.lastName.trim() === "") { + return validationFailure([ + { + title: "Invalid value", + code: 'CM_INVALID_VALUE', + field: `${path}/recipient/contactDetails/name`, + message: "'lastName' must be a non-empty string", + statusCode: 400, + }, + `Field 'name': 'lastName' must be a non-empty string`, + ]); + } + + if ("suffix" in name && typeof name.suffix !== "string") { + return validationFailure([ + { + title: "Invalid value", + code: 'CM_INVALID_VALUE', + field: `${path}/recipient/contactDetails/name`, + message: "'suffix' is not a string", + statusCode: 400, + }, + `Field 'name': 'suffix' is not a string`, ]); } return validationSuccess(); @@ -248,6 +347,17 @@ export function getAlternateContactDetailsError( validationErrorMessages.push(errorMessage); } + const { valid: nameValid, result: nameErrors } = nameValidation( + contactDetails.name, + path + ); + + if (!nameValid) { + const [errors, errorMessage] = nameErrors; + validationErrors.push(errors); + validationErrorMessages.push(errorMessage); + } + if (validationErrors.length) { return [400, validationErrorMessages.join(". "), validationErrors]; } From f37b9c09a9a18dc6bc8b92eaf46a8134af78395e Mon Sep 17 00:00:00 2001 From: Ian Hodges Date: Thu, 24 Apr 2025 14:40:25 +0100 Subject: [PATCH 5/5] +minor