diff --git a/docs/tests/development/post_v1_message-batches/performance.md b/docs/tests/development/post_v1_message-batches/performance.md index 76f4d4d18..ae4459828 100644 --- a/docs/tests/development/post_v1_message-batches/performance.md +++ b/docs/tests/development/post_v1_message-batches/performance.md @@ -1,9 +1,9 @@ # Performance Tests -## Scenario: An API consumer submitting a request with a request body containing 50,000 messages receives a 201 response +## Scenario: An API consumer submitting a request with a request body containing 40,000 messages receives a 201 response -**Given** the API consumer provides a message body of around 50k messages +**Given** the API consumer provides a message body of around 40k messages
**When** the request is submitted
@@ -16,9 +16,9 @@ - Response returns a 201 status code -## Scenario: An API consumer submitting a request with a large request body containing 50,000 duplicate messages receives a 400 response +## Scenario: An API consumer submitting a request with a large request body containing 40,000 duplicate messages receives a 400 response -**Given** the API consumer provides a message body of 50,000 duplicate messages +**Given** the API consumer provides a message body of 40,000 duplicate messages
**When** the request is submitted
diff --git a/docs/tests/development/post_v1_message-batches/validation.md b/docs/tests/development/post_v1_message-batches/validation.md index 8f0af6691..227584c88 100644 --- a/docs/tests/development/post_v1_message-batches/validation.md +++ b/docs/tests/development/post_v1_message-batches/validation.md @@ -214,7 +214,7 @@ A valid contact detail must be structured in this format: { sms: value, email: v | LS1 6AECD | Used to ensure only valid postcode is accepted | -## Scenario: An API consumer submitting a request with invalid address lines (too few) receives a 400 ‘Missing Value’ response +## Scenario: An API consumer submitting a request with invalid address lines (too few) receives a 400 ‘Too few items’ response A valid contact detail must be structured in this format: { address: { lines: [ Value1, Value2 ], postcode: value } } @@ -222,11 +222,11 @@ A valid contact detail must be structured in this format: { address: { lines: [
**When** the request is submitted
-**Then** the response returns a 400 invalid value error +**Then** the response returns a 400 too few items error
**Asserts** -- Response returns a 400 ‘Missing value’ error +- Response returns a 400 ‘Too few items’ error - Response returns the expected error message body with references to the invalid attribute - Response returns the ‘X-Correlation-Id’ header if provided diff --git a/docs/tests/development/post_v1_messages/validation.md b/docs/tests/development/post_v1_messages/validation.md index e9f1756e1..778e228bd 100644 --- a/docs/tests/development/post_v1_messages/validation.md +++ b/docs/tests/development/post_v1_messages/validation.md @@ -102,7 +102,7 @@ A valid contact detail must be structured in this format: { sms: value, email: v | LS1 6AECD | Used to ensure only valid postcode is accepted | -## Scenario: An API consumer submitting a request with invalid address lines (too few) receives a 400 ‘Missing Value’ response +## Scenario: An API consumer submitting a request with invalid address lines (too few) receives a 400 ‘Too few items’ response A valid contact detail must be structured in this format: { address: { lines: [ Value1, Value2 ], postcode: value } } @@ -110,11 +110,11 @@ A valid contact detail must be structured in this format: { address: { lines: [
**When** the request is submitted
-**Then** the response returns a 400 invalid value error +**Then** the response returns a 400 too few items error
**Asserts** -- Response returns a 400 ‘Missing value’ error +- Response returns a 400 ‘Too few items’ error - Response returns the expected error message body with references to the invalid attribute - Response returns the ‘X-Correlation-Id’ header if provided diff --git a/docs/tests/integration/post_v1_message-batches/performance.md b/docs/tests/integration/post_v1_message-batches/performance.md index 76f4d4d18..ae4459828 100644 --- a/docs/tests/integration/post_v1_message-batches/performance.md +++ b/docs/tests/integration/post_v1_message-batches/performance.md @@ -1,9 +1,9 @@ # Performance Tests -## Scenario: An API consumer submitting a request with a request body containing 50,000 messages receives a 201 response +## Scenario: An API consumer submitting a request with a request body containing 40,000 messages receives a 201 response -**Given** the API consumer provides a message body of around 50k messages +**Given** the API consumer provides a message body of around 40k messages
**When** the request is submitted
@@ -16,9 +16,9 @@ - Response returns a 201 status code -## Scenario: An API consumer submitting a request with a large request body containing 50,000 duplicate messages receives a 400 response +## Scenario: An API consumer submitting a request with a large request body containing 40,000 duplicate messages receives a 400 response -**Given** the API consumer provides a message body of 50,000 duplicate messages +**Given** the API consumer provides a message body of 40,000 duplicate messages
**When** the request is submitted
diff --git a/docs/tests/integration/post_v1_message-batches/validation.md b/docs/tests/integration/post_v1_message-batches/validation.md index b0e3eb3b3..3507109a5 100644 --- a/docs/tests/integration/post_v1_message-batches/validation.md +++ b/docs/tests/integration/post_v1_message-batches/validation.md @@ -214,7 +214,7 @@ A valid contact detail must be structured in this format: { sms: value, email: v | LS1 6AECD | Used to ensure only valid postcode is accepted | -## Scenario: An API consumer submitting a request with invalid address lines (too few) receives a 400 ‘Missing Value’ response +## Scenario: An API consumer submitting a request with invalid address lines (too few) receives a 400 ‘Too few items’ response A valid contact detail must be structured in this format: { address: { lines: [ Value1, Value2 ], postcode: value } } @@ -222,11 +222,11 @@ A valid contact detail must be structured in this format: { address: { lines: [
**When** the request is submitted
-**Then** the response returns a 400 invalid value error +**Then** the response returns a 400 too few items error
**Asserts** -- Response returns a 400 ‘Missing value’ error +- Response returns a 400 ‘Too few items’ error - Response returns the expected error message body with references to the invalid attribute - Response returns the ‘X-Correlation-Id’ header if provided diff --git a/docs/tests/integration/post_v1_messages/validation.md b/docs/tests/integration/post_v1_messages/validation.md index e9f1756e1..778e228bd 100644 --- a/docs/tests/integration/post_v1_messages/validation.md +++ b/docs/tests/integration/post_v1_messages/validation.md @@ -102,7 +102,7 @@ A valid contact detail must be structured in this format: { sms: value, email: v | LS1 6AECD | Used to ensure only valid postcode is accepted | -## Scenario: An API consumer submitting a request with invalid address lines (too few) receives a 400 ‘Missing Value’ response +## Scenario: An API consumer submitting a request with invalid address lines (too few) receives a 400 ‘Too few items’ response A valid contact detail must be structured in this format: { address: { lines: [ Value1, Value2 ], postcode: value } } @@ -110,11 +110,11 @@ A valid contact detail must be structured in this format: { address: { lines: [
**When** the request is submitted
-**Then** the response returns a 400 invalid value error +**Then** the response returns a 400 too few items error
**Asserts** -- Response returns a 400 ‘Missing value’ error +- Response returns a 400 ‘Too few items’ error - Response returns the expected error message body with references to the invalid attribute - Response returns the ‘X-Correlation-Id’ header if provided diff --git a/docs/tests/production/post_v1_message-batches/performance.md b/docs/tests/production/post_v1_message-batches/performance.md index 76f4d4d18..ae4459828 100644 --- a/docs/tests/production/post_v1_message-batches/performance.md +++ b/docs/tests/production/post_v1_message-batches/performance.md @@ -1,9 +1,9 @@ # Performance Tests -## Scenario: An API consumer submitting a request with a request body containing 50,000 messages receives a 201 response +## Scenario: An API consumer submitting a request with a request body containing 40,000 messages receives a 201 response -**Given** the API consumer provides a message body of around 50k messages +**Given** the API consumer provides a message body of around 40k messages
**When** the request is submitted
@@ -16,9 +16,9 @@ - Response returns a 201 status code -## Scenario: An API consumer submitting a request with a large request body containing 50,000 duplicate messages receives a 400 response +## Scenario: An API consumer submitting a request with a large request body containing 40,000 duplicate messages receives a 400 response -**Given** the API consumer provides a message body of 50,000 duplicate messages +**Given** the API consumer provides a message body of 40,000 duplicate messages
**When** the request is submitted
diff --git a/docs/tests/production/post_v1_message-batches/validation.md b/docs/tests/production/post_v1_message-batches/validation.md index b0e3eb3b3..3507109a5 100644 --- a/docs/tests/production/post_v1_message-batches/validation.md +++ b/docs/tests/production/post_v1_message-batches/validation.md @@ -214,7 +214,7 @@ A valid contact detail must be structured in this format: { sms: value, email: v | LS1 6AECD | Used to ensure only valid postcode is accepted | -## Scenario: An API consumer submitting a request with invalid address lines (too few) receives a 400 ‘Missing Value’ response +## Scenario: An API consumer submitting a request with invalid address lines (too few) receives a 400 ‘Too few items’ response A valid contact detail must be structured in this format: { address: { lines: [ Value1, Value2 ], postcode: value } } @@ -222,11 +222,11 @@ A valid contact detail must be structured in this format: { address: { lines: [
**When** the request is submitted
-**Then** the response returns a 400 invalid value error +**Then** the response returns a 400 too few items error
**Asserts** -- Response returns a 400 ‘Missing value’ error +- Response returns a 400 ‘Too few items’ error - Response returns the expected error message body with references to the invalid attribute - Response returns the ‘X-Correlation-Id’ header if provided diff --git a/docs/tests/production/post_v1_messages/validation.md b/docs/tests/production/post_v1_messages/validation.md index 5207a4ff5..1d21c51b5 100644 --- a/docs/tests/production/post_v1_messages/validation.md +++ b/docs/tests/production/post_v1_messages/validation.md @@ -102,7 +102,7 @@ A valid contact detail must be structured in this format: { sms: value, email: v | LS1 6AECD | Used to ensure only valid postcode is accepted | -## Scenario: An API consumer submitting a request with invalid address lines (too few) receives a 400 ‘Missing Value’ response +## Scenario: An API consumer submitting a request with invalid address lines (too few) receives a 400 ‘Too few items’ response A valid contact detail must be structured in this format: { address: { lines: [ Value1, Value2 ], postcode: value } } @@ -110,11 +110,11 @@ A valid contact detail must be structured in this format: { address: { lines: [
**When** the request is submitted
-**Then** the response returns a 400 invalid value error +**Then** the response returns a 400 too few items error
**Asserts** -- Response returns a 400 ‘Missing value’ error +- Response returns a 400 ‘Too few items’ error - Response returns the expected error message body with references to the invalid attribute - Response returns the ‘X-Correlation-Id’ header if provided diff --git a/docs/tests/sandbox/post_v1_message-batches/performance.md b/docs/tests/sandbox/post_v1_message-batches/performance.md index ea5b252dd..df99ad0f9 100644 --- a/docs/tests/sandbox/post_v1_message-batches/performance.md +++ b/docs/tests/sandbox/post_v1_message-batches/performance.md @@ -1,9 +1,9 @@ # Performance Tests -## Scenario: An API consumer submitting a request with a request body containing 50,000 messages receives a 201 response +## Scenario: An API consumer submitting a request with a request body containing 40,000 messages receives a 201 response -**Given** the API consumer provides a message body of around 50k messages +**Given** the API consumer provides a message body of around 40k messages
**When** the request is submitted
@@ -16,9 +16,9 @@ - Response returns a 201 status code -## Scenario: An API consumer submitting a request with a large request body containing 50,000 duplicate messages receives a 400 response +## Scenario: An API consumer submitting a request with a large request body containing 40,000 duplicate messages receives a 400 response -**Given** the API consumer provides a message body of 50,000 duplicate messages +**Given** the API consumer provides a message body of 40,000 duplicate messages
**When** the request is submitted
@@ -34,9 +34,9 @@ - Response returns 100 error message blocks -## Scenario: An API consumer submitting a request with a request body containing 50,000 messages receives a 201 response +## Scenario: An API consumer submitting a request with a request body containing 40,000 messages receives a 201 response -**Given** the API consumer provides a message body of around 50k messages +**Given** the API consumer provides a message body of around 40k messages
**When** the request is submitted
diff --git a/docs/tests/sandbox/post_v1_message-batches/validation.md b/docs/tests/sandbox/post_v1_message-batches/validation.md index 6ef50852c..e9fde0315 100644 --- a/docs/tests/sandbox/post_v1_message-batches/validation.md +++ b/docs/tests/sandbox/post_v1_message-batches/validation.md @@ -193,7 +193,7 @@ This test uses the ‘X-Correlation-Id’ header, when provided in a request it | 76491414-d0cf-4655-ae20-a4d1368472f3 | Is tested to ensure that when a correlation identifier is sent, we respond with the same value. | -## Scenario: An API consumer submitting a request with invalid address lines (too few) receives a 400 ‘Missing Value’ response +## Scenario: An API consumer submitting a request with invalid address lines (too few) receives a 400 ‘Too few items’ response A valid contact detail must be structured in this format: { address: { lines: [ Value1, Value2 ], postcode: value } } @@ -201,11 +201,11 @@ A valid contact detail must be structured in this format: { address: { lines: [
**When** the request is submitted
-**Then** the response returns a 400 invalid value error +**Then** the response returns a 400 too few items error
**Asserts** -- Response returns a 400 ‘Missing value’ error +- Response returns a 400 ‘Too few items’ error - Response returns the expected error message body with references to the invalid attribute - Response returns the ‘X-Correlation-Id’ header if provided @@ -451,6 +451,21 @@ A valid sms contact detail must be structured in this format: { sms: value } | 077009000021 | Used to ensure invalid phone number is not accepted | +## Scenario: An API consumer submitting a request with an contact details when not allowed receives a 400 ‘Cannot set contact details’ response + +**Given** the API consumer provides a message body with contact details +
+**When** the request is submitted +
+**Then** the response returns a 400 Cannot set contact details error +
+ +**Asserts** +- Response returns a 400 ‘Cannot set contact details’ error +- Response returns the expected error message body with references to the invalid attribute +- Response returns the ‘X-Correlation-Id’ header if provided + + ## Scenario: An API consumer submitting a request with an invalid personalisation receives a 400 ‘Invalid Value’ response A valid personalisation must be structured in this format: { parameter: value } diff --git a/docs/tests/sandbox/post_v1_messages/validation.md b/docs/tests/sandbox/post_v1_messages/validation.md index 2a62c7046..eaf215bb8 100644 --- a/docs/tests/sandbox/post_v1_messages/validation.md +++ b/docs/tests/sandbox/post_v1_messages/validation.md @@ -81,7 +81,7 @@ This test uses the ‘X-Correlation-Id’ header, when provided in a request it | 76491414-d0cf-4655-ae20-a4d1368472f3 | Is tested to ensure that when a correlation identifier is sent, we respond with the same value. | -## Scenario: An API consumer submitting a request with invalid address lines (too few) receives a 400 ‘Missing Value’ response +## Scenario: An API consumer submitting a request with invalid address lines (too few) receives a 400 ‘Too few items’ response A valid contact detail must be structured in this format: { address: { lines: [ Value1, Value2 ], postcode: value } } @@ -89,11 +89,11 @@ A valid contact detail must be structured in this format: { address: { lines: [
**When** the request is submitted
-**Then** the response returns a 400 invalid value error +**Then** the response returns a 400 too few items error
**Asserts** -- Response returns a 400 ‘Missing value’ error +- Response returns a 400 ‘Too few items’ error - Response returns the expected error message body with references to the invalid attribute - Response returns the ‘X-Correlation-Id’ header if provided @@ -313,6 +313,21 @@ A valid sms contact detail must be structured in this format: { sms: value } | 077009000021 | Used to ensure invalid phone number is not accepted | +## Scenario: An API consumer submitting a request with an contact details when not allowed receives a 400 ‘Cannot set contact details’ response + +**Given** the API consumer provides a message body with contact details +
+**When** the request is submitted +
+**Then** the response returns a 400 Cannot set contact details error +
+ +**Asserts** +- Response returns a 400 ‘Cannot set contact details’ error +- Response returns the expected error message body with references to the invalid attribute +- Response returns the ‘X-Correlation-Id’ header if provided + + ## Scenario: An API consumer submitting a request with an invalid personalisation receives a 400 ‘Invalid Value’ response A valid personalisation must be structured in this format: { parameter: value } diff --git a/proxies/shared/partials/Partial.Flows.CreateMessageBatchEndpoint.xml b/proxies/shared/partials/Partial.Flows.CreateMessageBatchEndpoint.xml index 4c3b4e125..a58f91547 100644 --- a/proxies/shared/partials/Partial.Flows.CreateMessageBatchEndpoint.xml +++ b/proxies/shared/partials/Partial.Flows.CreateMessageBatchEndpoint.xml @@ -5,7 +5,7 @@ JavaScript.MessageBatches.Create.Validate - RaiseFault.400BadRequest + RaiseFault.4xxGeneric errors != null @@ -31,4 +31,4 @@ (proxy.pathsuffix MatchesPath "/v1/message-batches") and (request.verb = "POST") - \ No newline at end of file + diff --git a/proxies/shared/partials/Partial.Flows.CreateMessageEndpoint.xml b/proxies/shared/partials/Partial.Flows.CreateMessageEndpoint.xml index 2dfe28a62..bed53cb59 100644 --- a/proxies/shared/partials/Partial.Flows.CreateMessageEndpoint.xml +++ b/proxies/shared/partials/Partial.Flows.CreateMessageEndpoint.xml @@ -5,7 +5,7 @@ JavaScript.Messages.Create.Validate - RaiseFault.400BadRequest + RaiseFault.4xxGeneric errors != null @@ -31,4 +31,4 @@ (proxy.pathsuffix MatchesPath "/v1/messages") and (request.verb = "POST") - \ No newline at end of file + diff --git a/proxies/shared/partials/Partial.Target.FaultRules.xml b/proxies/shared/partials/Partial.Target.FaultRules.xml index b57f0852f..5fbb37976 100644 --- a/proxies/shared/partials/Partial.Target.FaultRules.xml +++ b/proxies/shared/partials/Partial.Target.FaultRules.xml @@ -72,6 +72,12 @@ RaiseFault.404NotFound response.status.code = 404 + + RaiseFault.413RequestTooLarge + + response.status.code = 413 and response.content Like "*Request Too Long*" + + RaiseFault.400BackendException.OdsCodeRequired response.status.code = 400 and response.content Like "*odsCode must be provided*" diff --git a/proxies/shared/policies/RaiseFault.413RequestTooLarge.xml b/proxies/shared/policies/RaiseFault.413RequestTooLarge.xml new file mode 100644 index 000000000..792db28a5 --- /dev/null +++ b/proxies/shared/policies/RaiseFault.413RequestTooLarge.xml @@ -0,0 +1,42 @@ + + + + RaiseFault.413RequestTooLarge + + + + + 413 + + { + "errors" : [ + { + "id" : "@messageid#.0", + "code" : "CM_TOO_LARGE", + "links" : { + "about" : "{{ ERROR_ABOUT_LINK }}" + }, + "status" : "413", + "title" : "Request too large", + "detail" : "Request message was larger than the service limit", + "source": {"pointer": "/"} + } + ] + } + + + + true + diff --git a/proxies/shared/policies/RaiseFault.400BadRequest.xml b/proxies/shared/policies/RaiseFault.4xxGeneric.xml similarity index 79% rename from proxies/shared/policies/RaiseFault.400BadRequest.xml rename to proxies/shared/policies/RaiseFault.4xxGeneric.xml index 66daaf323..0f678cf84 100644 --- a/proxies/shared/policies/RaiseFault.400BadRequest.xml +++ b/proxies/shared/policies/RaiseFault.4xxGeneric.xml @@ -1,6 +1,6 @@ - - RaiseFault.400BadRequest + + RaiseFault.4xxGeneric @@ -20,8 +20,7 @@ "errors" : %errors# } - 400 - Bad request + {generic_status_code} true diff --git a/proxies/shared/resources/jsc/EnhanceErrorDetails.js b/proxies/shared/resources/jsc/EnhanceErrorDetails.js index db99e2a86..832d546f6 100644 --- a/proxies/shared/resources/jsc/EnhanceErrorDetails.js +++ b/proxies/shared/resources/jsc/EnhanceErrorDetails.js @@ -2,20 +2,15 @@ const errors = context.getVariable('data.errors'); const messageId = context.getVariable('messageid'); const statusCode = context.getVariable('response.status.code'); const links = { - about : "{{ ERROR_ABOUT_LINK }}" + about: "{{ ERROR_ABOUT_LINK }}" }; const enhancedErrors = []; JSON.parse(errors).forEach((error, index) => { - var code = 'CM_INVALID_VALUE'; - if (error.title === 'Missing value') { - code = 'CM_MISSING_VALUE'; - } - enhancedErrors.push({ id: messageId + '.' + index, - code: code, + code: error.code, links: links, status: String(statusCode), title: error.title, @@ -26,4 +21,4 @@ JSON.parse(errors).forEach((error, index) => { }); }); -context.setVariable("error.content", JSON.stringify({errors: enhancedErrors})); +context.setVariable("error.content", JSON.stringify({ errors: enhancedErrors })); diff --git a/proxies/shared/resources/jsc/MessageBatches.Create.Validate.js b/proxies/shared/resources/jsc/MessageBatches.Create.Validate.js index 6499a5169..9e8942a96 100644 --- a/proxies/shared/resources/jsc/MessageBatches.Create.Validate.js +++ b/proxies/shared/resources/jsc/MessageBatches.Create.Validate.js @@ -41,6 +41,10 @@ const validate = () => { // $.data.attributes.messages const validArray = validateArray(errors, data.attributes.messages, "/data/attributes/messages", 1) if (validArray) { + if (data.attributes.messages.length > 45000) { + errors.push(tooManyItemsError("/data/attributes/messages")); + return null; + } // $.data.attributes.messages.x data.attributes.messages.forEach((message, index) => { var pointer = "/data/attributes/messages/" + index; @@ -106,7 +110,9 @@ const validate = () => { validate(); if (errors.length > 0) { + context.setVariable("generic_status_code", errors[0].status); context.setVariable("errors", JSON.stringify(errors)); } else { + context.setVariable("generic_status_code", null); context.setVariable("errors", null); } diff --git a/proxies/shared/resources/jsc/Messages.Create.Validate.js b/proxies/shared/resources/jsc/Messages.Create.Validate.js index 5e48d124a..9bfb42a36 100644 --- a/proxies/shared/resources/jsc/Messages.Create.Validate.js +++ b/proxies/shared/resources/jsc/Messages.Create.Validate.js @@ -72,7 +72,9 @@ const validate = () => { validate(); if (errors.length > 0) { + context.setVariable("generic_status_code", errors[0].status); context.setVariable("errors", JSON.stringify(errors)); } else { + context.setVariable("generic_status_code", null); context.setVariable("errors", null); } diff --git a/proxies/shared/resources/jsc/helpers/validationErrors.js b/proxies/shared/resources/jsc/helpers/validationErrors.js index ad2867d0b..8f5944600 100644 --- a/proxies/shared/resources/jsc/helpers/validationErrors.js +++ b/proxies/shared/resources/jsc/helpers/validationErrors.js @@ -1,9 +1,9 @@ -function createErrorObject(code, title, detail, pointer, links) { +function createErrorObject(code, status_code, title, detail, pointer, links) { return { "id": messageId + "." + errors.length, "code": code, "links": Object.assign({}, { "about": "https://digital.nhs.uk/developer/api-catalogue/nhs-notify" }, links), // NOSONAR - "status": "400", + "status": status_code, "title": title, "detail": detail, "source": { @@ -15,6 +15,7 @@ function createErrorObject(code, title, detail, pointer, links) { function missingError(pointer) { return createErrorObject( "CM_MISSING_VALUE", + "400", "Missing property", "The property at the specified location is required, but was not present in the request.", pointer, @@ -25,6 +26,7 @@ function missingError(pointer) { function nullError(pointer) { return createErrorObject( "CM_NULL_VALUE", + "400", "Property cannot be null", "The property at the specified location is required, but a null value was passed in the request.", pointer, @@ -35,6 +37,7 @@ function nullError(pointer) { function invalidError(pointer) { return createErrorObject( "CM_INVALID_VALUE", + "400", "Invalid value", "The property at the specified location does not allow this value.", pointer, @@ -45,6 +48,7 @@ function invalidError(pointer) { function duplicateError(pointer) { return createErrorObject( "CM_DUPLICATE_VALUE", + "400", "Duplicate value", "The property at the specified location is a duplicate, duplicated values are not allowed.", pointer, @@ -55,6 +59,7 @@ function duplicateError(pointer) { function tooFewItemsError(pointer) { return createErrorObject( "CM_TOO_FEW_ITEMS", + "400", "Too few items", "The property at the specified location contains too few items.", pointer, @@ -62,13 +67,24 @@ function tooFewItemsError(pointer) { ) } +function tooManyItemsError(pointer) { + return createErrorObject( + "CM_TOO_MANY_ITEMS", + "413", + "Too many items", + "The property at the specified location contains too many items.", + pointer, + {} + ) +} + function invalidNhsNumberError(pointer) { return createErrorObject( "CM_INVALID_NHS_NUMBER", + "400", "Invalid nhs number", "The value provided in this nhsNumber field is not a valid NHS number.", pointer, { "nhsNumbers": "https://www.datadictionary.nhs.uk/attributes/nhs_number.html" } ); } - diff --git a/sandbox/__test__/batch_send.spec.js b/sandbox/__test__/batch_send.spec.js index 226863f34..7a4f9857e 100644 --- a/sandbox/__test__/batch_send.spec.js +++ b/sandbox/__test__/batch_send.spec.js @@ -1,8 +1,7 @@ -import request from "supertest" +import request from "supertest"; import { assert } from "chai"; -import * as uuid from 'uuid'; -import { setup } from './helpers.js' - +import * as uuid from "uuid"; +import { setup } from "./helpers.js"; describe("/api/v1/send", () => { let env; @@ -466,7 +465,7 @@ describe("/api/v1/send", () => { const correlationId = uuid.v4(); request(server) .post("/api/v1/send") - .set('X-Correlation-Id', correlationId) + .set("X-Correlation-Id", correlationId) .send({ data: { type: "MessageBatch", @@ -580,7 +579,7 @@ describe("/api/v1/send", () => { }, }) .expect(400, { - message: "Expect single personalisation field of 'body'", + message: "Some personalisation fields are missing: body", }) .expect("Content-Type", /json/, done); }); @@ -620,12 +619,12 @@ describe("/api/v1/send", () => { }, }) .expect(400, { - message: "Expect single personalisation field of 'body'", + message: "Some personalisation fields are missing: body", }) .expect("Content-Type", /json/, done); }); - it("responds with a 400 for redundant personalisation for global NHS app routing plan", (done) => { + it("responds with a 200 for redundant personalisation for global NHS app routing plan", (done) => { request(server) .post("/api/v1/send") .send({ @@ -660,12 +659,169 @@ describe("/api/v1/send", () => { }, }, }) + .expect(200) + .expect("Content-Type", /json/, done); + }); + + it("responds with a 400 for missing personalisation fields for global email routing plan", (done) => { + request(server) + .post("/api/v1/send") + .send({ + data: { + type: "MessageBatch", + attributes: { + routingPlanId: "00000000-0000-0000-0000-000000000002", + messageBatchReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", + messages: [ + { + messageReference: "1", + recipient: { + nhsNumber: "1", + dateOfBirth: "1", + }, + originator: { + odsCode: "X123", + }, + personalisation: {}, + }, + ], + }, + }, + }) .expect(400, { - message: "Expect single personalisation field of 'body'", + message: + "Some personalisation fields are missing: email_subject,email_body", }) .expect("Content-Type", /json/, done); }); + it("responds with a 400 for missing personalisation field for global email routing plan", (done) => { + request(server) + .post("/api/v1/send") + .send({ + data: { + type: "MessageBatch", + attributes: { + routingPlanId: "00000000-0000-0000-0000-000000000002", + messageBatchReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", + messages: [ + { + messageReference: "1", + recipient: { + nhsNumber: "1", + dateOfBirth: "1", + }, + originator: { + odsCode: "X123", + }, + personalisation: { + email_subject: "test", + }, + }, + ], + }, + }, + }) + .expect(400, { + message: "Some personalisation fields are missing: email_body", + }) + .expect("Content-Type", /json/, done); + }); + + it("responds with a 200 when required fields provided for global email routing plan", (done) => { + request(server) + .post("/api/v1/send") + .send({ + data: { + type: "MessageBatch", + attributes: { + routingPlanId: "00000000-0000-0000-0000-000000000002", + messageBatchReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", + messages: [ + { + messageReference: "1", + recipient: { + nhsNumber: "1", + dateOfBirth: "1", + }, + originator: { + odsCode: "X123", + }, + personalisation: { + email_subject: "test", + email_body: "test", + }, + }, + ], + }, + }, + }) + .expect(200) + .expect("Content-Type", /json/, done); + }); + + it("responds with a 400 for missing personalisation for global sms routing plan", (done) => { + request(server) + .post("/api/v1/send") + .send({ + data: { + type: "MessageBatch", + attributes: { + routingPlanId: "00000000-0000-0000-0000-000000000003", + messageBatchReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", + messages: [ + { + messageReference: "1", + recipient: { + nhsNumber: "1", + dateOfBirth: "1", + }, + originator: { + odsCode: "X123", + }, + personalisation: {}, + }, + ], + }, + }, + }) + .expect(400, { + message: "Some personalisation fields are missing: sms_body", + }) + .expect("Content-Type", /json/, done); + }); + + it("responds with a 200 when required fields provided for global sms routing plan", (done) => { + request(server) + .post("/api/v1/send") + .send({ + data: { + type: "MessageBatch", + attributes: { + routingPlanId: "00000000-0000-0000-0000-000000000003", + messageBatchReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", + messages: [ + { + messageReference: "1", + recipient: { + nhsNumber: "1", + dateOfBirth: "1", + }, + originator: { + odsCode: "X123", + }, + personalisation: { + sms_body: "test", + }, + }, + ], + }, + }, + }) + .expect(200) + .expect("Content-Type", /json/, done); + }); + it("returns a 400 when a message has no odsCode and the client has no default", (done) => { request(server) .post("/api/v1/send") @@ -683,8 +839,8 @@ describe("/api/v1/send", () => { { messageReference: "2", originator: { - odsCode: "X26" - } + odsCode: "X26", + }, }, ], }, @@ -713,15 +869,16 @@ describe("/api/v1/send", () => { { messageReference: "2", originator: { - odsCode: "X26" - } + odsCode: "X26", + }, }, ], }, }, }) .expect(400, { - message: "odsCode was provided but ODS code override is not enabled for the client", + message: + "odsCode was provided but ODS code override is not enabled for the client", }) .expect("Content-Type", /json/, done); }); @@ -730,29 +887,27 @@ describe("/api/v1/send", () => { 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", - dateOfBirth: "1", - contactDetails: { - email: 'hello' - }, + .send({ + data: { + type: "MessageBatch", + attributes: { + routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", + messageBatchReference: "request-id", + messages: [ + { + messageReference: "1", + recipient: { + nhsNumber: "1", + dateOfBirth: "1", + contactDetails: { + email: "hello", }, }, - ], - }, + }, + ], }, - } - ) + }, + }) .expect(200) .expect("Content-Type", /json/, done); }); @@ -761,31 +916,38 @@ describe("/api/v1/send", () => { request(server) .post("/api/v1/send") .set({ Authorization: "notAllowedContactDetailOverride" }) - .send( - { - data: { - type: "MessageBatch", - attributes: { - routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", - messageBatchReference: "request-id", - messages: [ - { - messageReference: "1", - recipient: { - nhsNumber: "1", - dateOfBirth: "1", - contactDetails: { - sms: 'hello' - }, + .send({ + data: { + type: "MessageBatch", + attributes: { + routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", + messageBatchReference: "request-id", + messages: [ + { + messageReference: "1", + recipient: { + nhsNumber: "1", + dateOfBirth: "1", + contactDetails: { + sms: "hello", }, }, - ], - }, + }, + ], }, - } - ) + }, + }) .expect(400, { message: "Client is not allowed to provide alternative contact details", + errors: [ + { + code: "CM_CANNOT_SET_CONTACT_DETAILS", + title: 'Cannot set contact details', + field: `/data/attributes/messages/0/recipient/contactDetails`, + message: + 'Client is not allowed to provide alternative contact details.', + }, + ], }) .expect("Content-Type", /json/, done); }); @@ -794,28 +956,27 @@ describe("/api/v1/send", () => { 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", - dateOfBirth: "1", - contactDetails: { - sms: 'hello' - }, + .send({ + data: { + type: "MessageBatch", + attributes: { + routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", + messageBatchReference: "request-id", + messages: [ + { + messageReference: "1", + recipient: { + nhsNumber: "1", + dateOfBirth: "1", + contactDetails: { + sms: "hello", }, }, - ], - }, + }, + ], }, - }) + }, + }) .expect(200) .expect("Content-Type", /json/, done); }); @@ -824,37 +985,38 @@ describe("/api/v1/send", () => { 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", - dateOfBirth: "1", - contactDetails: { - sms: '07700900002' - }, + .send({ + data: { + type: "MessageBatch", + attributes: { + routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", + messageBatchReference: "request-id", + messages: [ + { + messageReference: "1", + recipient: { + nhsNumber: "1", + dateOfBirth: "1", + contactDetails: { + sms: "07700900002", }, }, - ], - }, + }, + ], }, - }) + }, + }) .expect(400, { - message: "Invalid recipient contact details. Field 'sms': Input failed format check", + message: + "Invalid recipient contact details. Field 'sms': Input failed format check", errors: [ { + code: 'CM_INVALID_VALUE', field: "/data/attributes/messages/0/recipient/contactDetails/sms", message: "Input failed format check", - title: "Invalid value" - } - ] + title: "Invalid value", + }, + ], }) .expect("Content-Type", /json/, done); }); @@ -863,39 +1025,39 @@ describe("/api/v1/send", () => { 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", - dateOfBirth: "1", - contactDetails: { - sms: { - hello: 1 - } + .send({ + data: { + type: "MessageBatch", + attributes: { + routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", + messageBatchReference: "request-id", + messages: [ + { + messageReference: "1", + recipient: { + nhsNumber: "1", + dateOfBirth: "1", + contactDetails: { + sms: { + hello: 1, }, }, }, - ], - }, + }, + ], }, - }) + }, + }) .expect(400, { message: "Invalid recipient contact details. Field 'sms': Invalid", errors: [ { + code: 'CM_INVALID_VALUE', field: "/data/attributes/messages/0/recipient/contactDetails/sms", message: "Invalid", - title: "Invalid value" - } - ] + title: "Invalid value", + }, + ], }) .expect("Content-Type", /json/, done); }); @@ -904,28 +1066,27 @@ describe("/api/v1/send", () => { 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", - dateOfBirth: "1", - contactDetails: { - email: 'hello' - }, + .send({ + data: { + type: "MessageBatch", + attributes: { + routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", + messageBatchReference: "request-id", + messages: [ + { + messageReference: "1", + recipient: { + nhsNumber: "1", + dateOfBirth: "1", + contactDetails: { + email: "hello", }, }, - ], - }, + }, + ], }, - }) + }, + }) .expect(200) .expect("Content-Type", /json/, done); }); @@ -934,37 +1095,38 @@ describe("/api/v1/send", () => { 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", - dateOfBirth: "1", - contactDetails: { - email: 'invalidEmailAddress' - }, + .send({ + data: { + type: "MessageBatch", + attributes: { + routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", + messageBatchReference: "request-id", + messages: [ + { + messageReference: "1", + recipient: { + nhsNumber: "1", + dateOfBirth: "1", + contactDetails: { + email: "invalidEmailAddress", }, }, - ], - }, + }, + ], }, - }) + }, + }) .expect(400, { - message: "Invalid recipient contact details. Field 'email': Input failed format check", + message: + "Invalid recipient contact details. Field 'email': Input failed format check", errors: [ { + code: 'CM_INVALID_VALUE', field: "/data/attributes/messages/0/recipient/contactDetails/email", message: "Input failed format check", - title: "Invalid value" - } - ] + title: "Invalid value", + }, + ], }) .expect("Content-Type", /json/, done); }); @@ -987,8 +1149,8 @@ describe("/api/v1/send", () => { dateOfBirth: "1", contactDetails: { email: { - hello: 1 - } + hello: 1, + }, }, }, }, @@ -1000,11 +1162,12 @@ describe("/api/v1/send", () => { message: "Invalid recipient contact details. Field 'email': Invalid", errors: [ { + code: 'CM_INVALID_VALUE', field: "/data/attributes/messages/0/recipient/contactDetails/email", message: "Invalid", - title: "Invalid value" - } - ] + title: "Invalid value", + }, + ], }) .expect("Content-Type", /json/, done); }); @@ -1013,31 +1176,30 @@ describe("/api/v1/send", () => { 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", - dateOfBirth: "1", - contactDetails: { - address: { - lines: ['1', '2'], - postcode: 'hello' - } + .send({ + data: { + type: "MessageBatch", + attributes: { + routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", + messageBatchReference: "request-id", + messages: [ + { + messageReference: "1", + recipient: { + nhsNumber: "1", + dateOfBirth: "1", + contactDetails: { + address: { + lines: ["1", "2"], + postcode: "hello", }, }, }, - ], - }, + }, + ], }, - }) + }, + }) .expect(200) .expect("Content-Type", /json/, done); }); @@ -1059,7 +1221,7 @@ describe("/api/v1/send", () => { nhsNumber: "1", dateOfBirth: "1", contactDetails: { - address: 'hello' + address: "hello", }, }, }, @@ -1071,11 +1233,12 @@ describe("/api/v1/send", () => { message: "Invalid recipient contact details. Field 'address': Invalid", errors: [ { + code: 'CM_INVALID_VALUE', field: "/data/attributes/messages/0/recipient/contactDetails/address", message: "Invalid", - title: "Invalid value" - } - ] + title: "Invalid value", + }, + ], }) .expect("Content-Type", /json/, done); }); @@ -1097,7 +1260,7 @@ describe("/api/v1/send", () => { nhsNumber: "1", dateOfBirth: "1", contactDetails: { - address: [] + address: [], }, }, }, @@ -1109,11 +1272,12 @@ describe("/api/v1/send", () => { message: "Invalid recipient contact details. Field 'address': Invalid", errors: [ { + code: 'CM_INVALID_VALUE', field: "/data/attributes/messages/0/recipient/contactDetails/address", message: "Invalid", - title: "Invalid value" - } - ] + title: "Invalid value", + }, + ], }) .expect("Content-Type", /json/, done); }); @@ -1136,9 +1300,9 @@ describe("/api/v1/send", () => { dateOfBirth: "1", contactDetails: { address: { - lines: 'test', - postcode: 'test' - } + lines: "test", + postcode: "test", + }, }, }, }, @@ -1147,14 +1311,16 @@ describe("/api/v1/send", () => { }, }) .expect(400, { - message: "Invalid recipient contact details. Field 'lines': 'lines' is missing", + message: + "Invalid recipient contact details. Field 'lines': 'lines' is missing", errors: [ { + code: 'CM_MISSING_VALUE', field: "/data/attributes/messages/0/recipient/contactDetails/address", message: "`lines` is missing", - title: "Missing value" - } - ] + title: "Missing value", + }, + ], }) .expect("Content-Type", /json/, done); }); @@ -1177,9 +1343,9 @@ describe("/api/v1/send", () => { dateOfBirth: "1", contactDetails: { address: { - lines: ['1'], - postcode: 'hello' - } + lines: ["1"], + postcode: "hello", + }, }, }, }, @@ -1188,12 +1354,14 @@ describe("/api/v1/send", () => { }, }) .expect(400, { - message: "Invalid recipient contact details. Field 'lines': Too few address lines were provided", + message: + "Invalid recipient contact details. Field 'lines': Too few address lines were provided", errors: [ { + code: 'CM_TOO_FEW_ITEMS', field: "/data/attributes/messages/0/recipient/contactDetails/address", message: "Too few address lines were provided", - title: "Missing value" + title: "Too few items" } ] }) @@ -1218,9 +1386,9 @@ describe("/api/v1/send", () => { dateOfBirth: "1", contactDetails: { address: { - lines: ['1', '2', '3', '4', '5', '6'], - postcode: 'hello' - } + lines: ["1", "2", "3", "4", "5", "6"], + postcode: "hello", + }, }, }, }, @@ -1232,11 +1400,12 @@ describe("/api/v1/send", () => { message: "Invalid recipient contact details. Field 'lines': Invalid", errors: [ { + code: 'CM_INVALID_VALUE', field: "/data/attributes/messages/0/recipient/contactDetails/address", message: "Invalid", - title: "Invalid value" - } - ] + title: "Invalid value", + }, + ], }) .expect("Content-Type", /json/, done); }); @@ -1259,9 +1428,9 @@ describe("/api/v1/send", () => { dateOfBirth: "1", contactDetails: { address: { - lines: ['1', '2', 3, '4', '5'], - postcode: 'hello' - } + lines: ["1", "2", 3, "4", "5"], + postcode: "hello", + }, }, }, }, @@ -1273,11 +1442,12 @@ describe("/api/v1/send", () => { message: "Invalid recipient contact details. Field 'lines': Invalid", errors: [ { + code: 'CM_INVALID_VALUE', field: "/data/attributes/messages/0/recipient/contactDetails/address", message: "Invalid", - title: "Invalid value" - } - ] + title: "Invalid value", + }, + ], }) .expect("Content-Type", /json/, done); }); @@ -1300,8 +1470,8 @@ describe("/api/v1/send", () => { dateOfBirth: "1", contactDetails: { address: { - lines: ['1', '2'], - } + lines: ["1", "2"], + }, }, }, }, @@ -1310,14 +1480,16 @@ describe("/api/v1/send", () => { }, }) .expect(400, { - message: "Invalid recipient contact details. Field 'postcode': 'postcode' is missing", + message: + "Invalid recipient contact details. Field 'postcode': 'postcode' is missing", errors: [ { + code: 'CM_MISSING_VALUE', field: "/data/attributes/messages/0/recipient/contactDetails/address", message: "`postcode` is missing", - title: "Missing value" - } - ] + title: "Missing value", + }, + ], }) .expect("Content-Type", /json/, done); }); @@ -1340,9 +1512,9 @@ describe("/api/v1/send", () => { dateOfBirth: "1", contactDetails: { address: { - lines: ['1', '2'], - postcode: [] - } + lines: ["1", "2"], + postcode: [], + }, }, }, }, @@ -1354,15 +1526,16 @@ describe("/api/v1/send", () => { message: "Invalid recipient contact details. Field 'postcode': Invalid", errors: [ { + code: 'CM_INVALID_VALUE', field: "/data/attributes/messages/0/recipient/contactDetails/address/postcode", message: "Invalid", - title: "Invalid value" - } - ] + title: "Invalid value", + }, + ], }) .expect("Content-Type", /json/, done); }); - it('returns a 400 and multiple errors when there are multiple issues in contact details provided', (done) => { + it("returns a 400 and multiple errors when there are multiple issues in contact details provided", (done) => { request(server) .post("/api/v1/send") .set({ Authorization: "allowedContactDetailOverride" }) @@ -1380,10 +1553,10 @@ describe("/api/v1/send", () => { dateOfBirth: "1", contactDetails: { address: { - lines: ['1'], - postcode: [] + lines: ["1"], + postcode: [], }, - email: 'invalidEmailAddress' + email: "invalidEmailAddress", }, }, }, @@ -1392,54 +1565,56 @@ 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", + message: + "Invalid recipient contact details. Field 'email': Input failed format check. Field 'lines': Too few address lines were provided", errors: [ { + code: 'CM_INVALID_VALUE', field: "/data/attributes/messages/0/recipient/contactDetails/email", message: "Input failed format check", - title: "Invalid value" + title: "Invalid value", }, { + code: 'CM_TOO_FEW_ITEMS', field: "/data/attributes/messages/0/recipient/contactDetails/address", message: "Too few address lines were provided", - title: "Missing value" + title: "Too few items" }, - ] + ], }) .expect("Content-Type", /json/, done); - }) + }); it("returns a 200 when all alternate contact details are 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", - dateOfBirth: "1", - contactDetails: { - email: 'hello', - sms: 'hello', - address: { - lines: ['1', '2'], - postcode: 'hello' - } + .send({ + data: { + type: "MessageBatch", + attributes: { + routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", + messageBatchReference: "request-id", + messages: [ + { + messageReference: "1", + recipient: { + nhsNumber: "1", + dateOfBirth: "1", + contactDetails: { + email: "hello", + sms: "hello", + address: { + lines: ["1", "2"], + postcode: "hello", }, }, }, - ], - }, + }, + ], }, - }) + }, + }) .expect(200) .expect("Content-Type", /json/, done); }); diff --git a/sandbox/__test__/messages.spec.js b/sandbox/__test__/messages.spec.js index a732e6a9b..304cca1b5 100644 --- a/sandbox/__test__/messages.spec.js +++ b/sandbox/__test__/messages.spec.js @@ -1,7 +1,7 @@ -import request from "supertest" +import request from "supertest"; import { assert } from "chai"; -import * as uuid from 'uuid'; -import { setup } from './helpers.js' +import * as uuid from "uuid"; +import { setup } from "./helpers.js"; describe("/api/v1/messages", () => { let env; @@ -86,29 +86,29 @@ describe("/api/v1/messages", () => { .expect("Content-Type", /json/, done); }); - it("returns a 400 when the routingPlanId is null", (done) => { + it("returns a 400 when the routingPlanId is null", (done) => { request(server) - .post("/api/v1/messages") - .send({ - data: { - type: "Message", - attributes: { - routingPlanId: null, + .post("/api/v1/messages") + .send({ + data: { + type: "Message", + attributes: { + routingPlanId: null, + }, }, - }, - }) - .expect(400, { - message: "Missing routingPlanId", - }) - .expect("Content-Type", /json/, done); - }) + }) + .expect(400, { + message: "Missing routingPlanId", + }) + .expect("Content-Type", /json/, done); + }); it("responds with a 201 when the request is correctly formatted", (done) => { request(server) .post("/api/v1/messages") .send({ data: { - type: 'Message', + type: "Message", attributes: { routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", @@ -117,7 +117,7 @@ describe("/api/v1/messages", () => { dateOfBirth: "1", }, originator: { - odsCode: "X123" + odsCode: "X123", }, personalisation: {}, }, @@ -149,7 +149,7 @@ describe("/api/v1/messages", () => { .post("/api/v1/messages") .send({ data: { - type: 'Message', + type: "Message", attributes: { routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", @@ -158,13 +158,13 @@ describe("/api/v1/messages", () => { dateOfBirth: "1", }, originator: { - odsCode: "X123" + odsCode: "X123", }, personalisation: {}, }, }, }) - .set('X-Correlation-Id', correlationId) + .set("X-Correlation-Id", correlationId) .expect(201) .expect("X-Correlation-Id", correlationId, done); }); @@ -174,7 +174,7 @@ describe("/api/v1/messages", () => { .post("/api/v1/messages") .send({ data: { - type: 'Message', + type: "Message", attributes: { routingPlanId: "00000000-0000-0000-0000-000000000001", messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", @@ -183,7 +183,7 @@ describe("/api/v1/messages", () => { dateOfBirth: "1", }, originator: { - odsCode: "X123" + odsCode: "X123", }, personalisation: { body: "Free text message", @@ -216,7 +216,7 @@ describe("/api/v1/messages", () => { .post("/api/v1/messages") .send({ data: { - type: 'Message', + type: "Message", attributes: { routingPlanId: "00000000-0000-0000-0000-000000000001", messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", @@ -225,13 +225,13 @@ describe("/api/v1/messages", () => { dateOfBirth: "1", }, originator: { - odsCode: "X123" + odsCode: "X123", }, }, }, }) .expect(400, { - message: "Expect single personalisation field of 'body'", + message: "Some personalisation fields are missing: body", }) .expect("Content-Type", /json/, done); }); @@ -241,7 +241,7 @@ describe("/api/v1/messages", () => { .post("/api/v1/messages") .send({ data: { - type: 'Message', + type: "Message", attributes: { routingPlanId: "00000000-0000-0000-0000-000000000001", messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", @@ -250,7 +250,7 @@ describe("/api/v1/messages", () => { dateOfBirth: "1", }, originator: { - odsCode: "X123" + odsCode: "X123", }, }, personalisation: { @@ -259,7 +259,7 @@ describe("/api/v1/messages", () => { }, }) .expect(400, { - message: "Expect single personalisation field of 'body'", + message: "Some personalisation fields are missing: body", }) .expect("Content-Type", /json/, done); }); @@ -269,7 +269,7 @@ describe("/api/v1/messages", () => { .post("/api/v1/messages") .send({ data: { - type: 'Message', + type: "Message", attributes: { routingPlanId: "00000000-0000-0000-0000-000000000001", messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", @@ -278,7 +278,7 @@ describe("/api/v1/messages", () => { dateOfBirth: "1", }, originator: { - odsCode: "X123" + odsCode: "X123", }, }, personalisation: { @@ -288,8 +288,142 @@ describe("/api/v1/messages", () => { }, }) .expect(400, { - message: "Expect single personalisation field of 'body'", + message: "Some personalisation fields are missing: body", + }) + .expect("Content-Type", /json/, done); + }); + + it("responds with a 400 for missing personalisation fields for global email routing plan", (done) => { + request(server) + .post("/api/v1/messages") + .send({ + data: { + type: "Message", + attributes: { + routingPlanId: "00000000-0000-0000-0000-000000000002", + messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", + recipient: { + nhsNumber: "1", + dateOfBirth: "1", + }, + originator: { + odsCode: "X123", + }, + personalisation: {}, + }, + }, + }) + .expect(400, { + message: + "Some personalisation fields are missing: email_subject,email_body", + }) + .expect("Content-Type", /json/, done); + }); + + it("responds with a 400 for missing personalisation field for global email routing plan", (done) => { + request(server) + .post("/api/v1/messages") + .send({ + data: { + type: "Message", + attributes: { + routingPlanId: "00000000-0000-0000-0000-000000000002", + messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", + recipient: { + nhsNumber: "1", + dateOfBirth: "1", + }, + originator: { + odsCode: "X123", + }, + personalisation: { + email_subject: "test", + }, + }, + }, + }) + .expect(400, { + message: "Some personalisation fields are missing: email_body", + }) + .expect("Content-Type", /json/, done); + }); + + it("responds with a 200 when required fields provided for global email routing plan", (done) => { + request(server) + .post("/api/v1/messages") + .send({ + data: { + type: "Message", + attributes: { + routingPlanId: "00000000-0000-0000-0000-000000000002", + messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", + recipient: { + nhsNumber: "1", + dateOfBirth: "1", + }, + originator: { + odsCode: "X123", + }, + personalisation: { + email_subject: "test", + email_body: "test", + }, + }, + }, + }) + .expect(201) + .expect("Content-Type", /json/, done); + }); + + it("responds with a 400 for missing personalisation for global sms routing plan", (done) => { + request(server) + .post("/api/v1/messages") + .send({ + data: { + type: "Message", + attributes: { + routingPlanId: "00000000-0000-0000-0000-000000000003", + messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", + recipient: { + nhsNumber: "1", + dateOfBirth: "1", + }, + originator: { + odsCode: "X123", + }, + personalisation: {}, + }, + }, + }) + .expect(400, { + message: "Some personalisation fields are missing: sms_body", + }) + .expect("Content-Type", /json/, done); + }); + + it("responds with a 200 when required fields provided for global sms routing plan", (done) => { + request(server) + .post("/api/v1/messages") + .send({ + data: { + type: "Message", + attributes: { + routingPlanId: "00000000-0000-0000-0000-000000000003", + messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", + recipient: { + nhsNumber: "1", + dateOfBirth: "1", + }, + originator: { + odsCode: "X123", + }, + personalisation: { + sms_body: "test", + }, + }, + }, }) + .expect(201) .expect("Content-Type", /json/, done); }); @@ -307,7 +441,7 @@ describe("/api/v1/messages", () => { dateOfBirth: "2000-01-01", }, originator: { - odsCode: "X123" + odsCode: "X123", }, personalisation: {}, }, @@ -334,7 +468,7 @@ describe("/api/v1/messages", () => { dateOfBirth: "2000-01-01", }, originator: { - odsCode: "X123" + odsCode: "X123", }, personalisation: {}, }, @@ -361,7 +495,7 @@ describe("/api/v1/messages", () => { dateOfBirth: "2000-01-01", }, originator: { - odsCode: "X123" + odsCode: "X123", }, personalisation: {}, }, @@ -388,7 +522,7 @@ describe("/api/v1/messages", () => { dateOfBirth: "2000-01-01", }, originator: { - odsCode: "X123" + odsCode: "X123", }, personalisation: {}, }, @@ -415,7 +549,7 @@ describe("/api/v1/messages", () => { dateOfBirth: "2000-01-01", }, originator: { - odsCode: "X123" + odsCode: "X123", }, personalisation: {}, }, @@ -433,7 +567,7 @@ describe("/api/v1/messages", () => { .set({ Authorization: "noDefaultOds" }) .send({ data: { - type: 'Message', + type: "Message", attributes: { routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", @@ -457,7 +591,7 @@ describe("/api/v1/messages", () => { .set({ Authorization: "noOdsChange" }) .send({ data: { - type: 'Message', + type: "Message", attributes: { routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", @@ -466,14 +600,15 @@ describe("/api/v1/messages", () => { dateOfBirth: "1", }, originator: { - odsCode: "X123" + odsCode: "X123", }, personalisation: {}, }, }, }) .expect(400, { - message: "odsCode was provided but ODS code override is not enabled for the client", + message: + "odsCode was provided but ODS code override is not enabled for the client", }) .expect("Content-Type", /json/, done); }); @@ -484,7 +619,7 @@ describe("/api/v1/messages", () => { .set({ Authorization: "allowedContactDetailOverride" }) .send({ data: { - type: 'Message', + type: "Message", attributes: { routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", @@ -492,7 +627,7 @@ describe("/api/v1/messages", () => { nhsNumber: "1", dateOfBirth: "1", contactDetails: { - email: 'hello' + email: "hello", }, }, personalisation: {}, @@ -509,7 +644,7 @@ describe("/api/v1/messages", () => { .set({ Authorization: "notAllowedContactDetailOverride" }) .send({ data: { - type: 'Message', + type: "Message", attributes: { routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", @@ -517,7 +652,7 @@ describe("/api/v1/messages", () => { nhsNumber: "1", dateOfBirth: "1", contactDetails: { - sms: 'hello' + sms: "hello", }, }, personalisation: {}, @@ -526,6 +661,15 @@ describe("/api/v1/messages", () => { }) .expect(400, { message: "Client is not allowed to provide alternative contact details", + errors: [ + { + code: "CM_CANNOT_SET_CONTACT_DETAILS", + title: 'Cannot set contact details', + field: `/data/attributes/recipient/contactDetails`, + message: + 'Client is not allowed to provide alternative contact details.', + }, + ], }) .expect("Content-Type", /json/, done); }); @@ -536,7 +680,7 @@ describe("/api/v1/messages", () => { .set({ Authorization: "allowedContactDetailOverride" }) .send({ data: { - type: 'Message', + type: "Message", attributes: { routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", @@ -544,7 +688,7 @@ describe("/api/v1/messages", () => { nhsNumber: "1", dateOfBirth: "1", contactDetails: { - sms: 'hello' + sms: "hello", }, }, personalisation: {}, @@ -561,7 +705,7 @@ describe("/api/v1/messages", () => { .set({ Authorization: "allowedContactDetailOverride" }) .send({ data: { - type: 'Message', + type: "Message", attributes: { routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", @@ -569,7 +713,7 @@ describe("/api/v1/messages", () => { nhsNumber: "1", dateOfBirth: "1", contactDetails: { - sms: '07700900002' + sms: "07700900002", }, }, personalisation: {}, @@ -577,14 +721,16 @@ describe("/api/v1/messages", () => { }, }) .expect(400, { - message: "Invalid recipient contact details. Field 'sms': Input failed format check", + message: + "Invalid recipient contact details. Field 'sms': Input failed format check", errors: [ { + code: 'CM_INVALID_VALUE', field: "/data/attributes/recipient/contactDetails/sms", message: "Input failed format check", - title: "Invalid value" - } - ] + title: "Invalid value", + }, + ], }) .expect("Content-Type", /json/, done); }); @@ -595,7 +741,7 @@ describe("/api/v1/messages", () => { .set({ Authorization: "allowedContactDetailOverride" }) .send({ data: { - type: 'Message', + type: "Message", attributes: { routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", @@ -604,8 +750,8 @@ describe("/api/v1/messages", () => { dateOfBirth: "1", contactDetails: { sms: { - hello: 1 - } + hello: 1, + }, }, }, personalisation: {}, @@ -616,11 +762,12 @@ describe("/api/v1/messages", () => { message: "Invalid recipient contact details. Field 'sms': Invalid", errors: [ { + code: 'CM_INVALID_VALUE', field: "/data/attributes/recipient/contactDetails/sms", message: "Invalid", - title: "Invalid value" - } - ] + title: "Invalid value", + }, + ], }) .expect("Content-Type", /json/, done); }); @@ -631,7 +778,7 @@ describe("/api/v1/messages", () => { .set({ Authorization: "allowedContactDetailOverride" }) .send({ data: { - type: 'Message', + type: "Message", attributes: { routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", @@ -639,7 +786,7 @@ describe("/api/v1/messages", () => { nhsNumber: "1", dateOfBirth: "1", contactDetails: { - email: 'hello' + email: "hello", }, }, personalisation: {}, @@ -648,7 +795,7 @@ describe("/api/v1/messages", () => { }) .expect(201) .expect("Content-Type", /json/, done); - }) + }); it("returns a 400 when invalid email alternate contact detail is provided", (done) => { request(server) @@ -656,7 +803,7 @@ describe("/api/v1/messages", () => { .set({ Authorization: "allowedContactDetailOverride" }) .send({ data: { - type: 'Message', + type: "Message", attributes: { routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", @@ -664,7 +811,7 @@ describe("/api/v1/messages", () => { nhsNumber: "1", dateOfBirth: "1", contactDetails: { - email: 'invalidEmailAddress' + email: "invalidEmailAddress", }, }, personalisation: {}, @@ -672,14 +819,16 @@ describe("/api/v1/messages", () => { }, }) .expect(400, { - message: "Invalid recipient contact details. Field 'email': Input failed format check", + message: + "Invalid recipient contact details. Field 'email': Input failed format check", errors: [ { + code: 'CM_INVALID_VALUE', field: "/data/attributes/recipient/contactDetails/email", message: "Input failed format check", - title: "Invalid value" - } - ] + title: "Invalid value", + }, + ], }) .expect("Content-Type", /json/, done); }); @@ -690,7 +839,7 @@ describe("/api/v1/messages", () => { .set({ Authorization: "allowedContactDetailOverride" }) .send({ data: { - type: 'Message', + type: "Message", attributes: { routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", @@ -699,8 +848,8 @@ describe("/api/v1/messages", () => { dateOfBirth: "1", contactDetails: { email: { - hello: 1 - } + hello: 1, + }, }, }, personalisation: {}, @@ -711,11 +860,12 @@ describe("/api/v1/messages", () => { message: "Invalid recipient contact details. Field 'email': Invalid", errors: [ { + code: 'CM_INVALID_VALUE', field: "/data/attributes/recipient/contactDetails/email", message: "Invalid", - title: "Invalid value" - } - ] + title: "Invalid value", + }, + ], }) .expect("Content-Type", /json/, done); }); @@ -726,7 +876,7 @@ describe("/api/v1/messages", () => { .set({ Authorization: "allowedContactDetailOverride" }) .send({ data: { - type: 'Message', + type: "Message", attributes: { routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", @@ -735,9 +885,9 @@ describe("/api/v1/messages", () => { dateOfBirth: "1", contactDetails: { address: { - lines: ['1', '2'], - postcode: 'hello' - } + lines: ["1", "2"], + postcode: "hello", + }, }, }, personalisation: {}, @@ -754,7 +904,7 @@ describe("/api/v1/messages", () => { .set({ Authorization: "allowedContactDetailOverride" }) .send({ data: { - type: 'Message', + type: "Message", attributes: { routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", @@ -762,7 +912,7 @@ describe("/api/v1/messages", () => { nhsNumber: "1", dateOfBirth: "1", contactDetails: { - address: 'hello' + address: "hello", }, }, personalisation: {}, @@ -773,11 +923,12 @@ describe("/api/v1/messages", () => { message: "Invalid recipient contact details. Field 'address': Invalid", errors: [ { + code: 'CM_INVALID_VALUE', field: "/data/attributes/recipient/contactDetails/address", message: "Invalid", - title: "Invalid value" - } - ] + title: "Invalid value", + }, + ], }) .expect("Content-Type", /json/, done); }); @@ -788,7 +939,7 @@ describe("/api/v1/messages", () => { .set({ Authorization: "allowedContactDetailOverride" }) .send({ data: { - type: 'Message', + type: "Message", attributes: { routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", @@ -796,7 +947,7 @@ describe("/api/v1/messages", () => { nhsNumber: "1", dateOfBirth: "1", contactDetails: { - address: [] + address: [], }, }, personalisation: {}, @@ -807,11 +958,12 @@ describe("/api/v1/messages", () => { message: "Invalid recipient contact details. Field 'address': Invalid", errors: [ { + code: 'CM_INVALID_VALUE', field: "/data/attributes/recipient/contactDetails/address", message: "Invalid", - title: "Invalid value" - } - ] + title: "Invalid value", + }, + ], }) .expect("Content-Type", /json/, done); }); @@ -822,7 +974,7 @@ describe("/api/v1/messages", () => { .set({ Authorization: "allowedContactDetailOverride" }) .send({ data: { - type: 'Message', + type: "Message", attributes: { routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", @@ -831,9 +983,9 @@ describe("/api/v1/messages", () => { dateOfBirth: "1", contactDetails: { address: { - lines: 'test', - postcode: 'test' - } + lines: "test", + postcode: "test", + }, }, }, personalisation: {}, @@ -841,14 +993,16 @@ describe("/api/v1/messages", () => { }, }) .expect(400, { - message: "Invalid recipient contact details. Field 'lines': 'lines' is missing", + message: + "Invalid recipient contact details. Field 'lines': 'lines' is missing", errors: [ { + code: 'CM_MISSING_VALUE', field: "/data/attributes/recipient/contactDetails/address", message: "`lines` is missing", - title: "Missing value" - } - ] + title: "Missing value", + }, + ], }) .expect("Content-Type", /json/, done); }); @@ -859,7 +1013,7 @@ describe("/api/v1/messages", () => { .set({ Authorization: "allowedContactDetailOverride" }) .send({ data: { - type: 'Message', + type: "Message", attributes: { routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", @@ -868,9 +1022,9 @@ describe("/api/v1/messages", () => { dateOfBirth: "1", contactDetails: { address: { - lines: ['1'], - postcode: 'hello' - } + lines: ["1"], + postcode: "hello", + }, }, }, personalisation: {}, @@ -878,12 +1032,14 @@ describe("/api/v1/messages", () => { }, }) .expect(400, { - message: "Invalid recipient contact details. Field 'lines': Too few address lines were provided", + message: + "Invalid recipient contact details. Field 'lines': Too few address lines were provided", errors: [ { + code: 'CM_TOO_FEW_ITEMS', field: "/data/attributes/recipient/contactDetails/address", message: "Too few address lines were provided", - title: "Missing value" + title: "Too few items" } ] }) @@ -896,7 +1052,7 @@ describe("/api/v1/messages", () => { .set({ Authorization: "allowedContactDetailOverride" }) .send({ data: { - type: 'Message', + type: "Message", attributes: { routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", @@ -905,9 +1061,9 @@ describe("/api/v1/messages", () => { dateOfBirth: "1", contactDetails: { address: { - lines: ['1', '2', '3', '4', '5', '6'], - postcode: 'hello' - } + lines: ["1", "2", "3", "4", "5", "6"], + postcode: "hello", + }, }, }, personalisation: {}, @@ -918,11 +1074,12 @@ describe("/api/v1/messages", () => { message: "Invalid recipient contact details. Field 'lines': Invalid", errors: [ { + code: 'CM_INVALID_VALUE', field: "/data/attributes/recipient/contactDetails/address", message: "Invalid", - title: "Invalid value" - } - ] + title: "Invalid value", + }, + ], }) .expect("Content-Type", /json/, done); }); @@ -933,7 +1090,7 @@ describe("/api/v1/messages", () => { .set({ Authorization: "allowedContactDetailOverride" }) .send({ data: { - type: 'Message', + type: "Message", attributes: { routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", @@ -942,9 +1099,9 @@ describe("/api/v1/messages", () => { dateOfBirth: "1", contactDetails: { address: { - lines: ['1', '2', 3, '4', '5'], - postcode: 'hello' - } + lines: ["1", "2", 3, "4", "5"], + postcode: "hello", + }, }, }, personalisation: {}, @@ -955,11 +1112,12 @@ describe("/api/v1/messages", () => { message: "Invalid recipient contact details. Field 'lines': Invalid", errors: [ { + code: 'CM_INVALID_VALUE', field: "/data/attributes/recipient/contactDetails/address", message: "Invalid", - title: "Invalid value" - } - ] + title: "Invalid value", + }, + ], }) .expect("Content-Type", /json/, done); }); @@ -970,7 +1128,7 @@ describe("/api/v1/messages", () => { .set({ Authorization: "allowedContactDetailOverride" }) .send({ data: { - type: 'Message', + type: "Message", attributes: { routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", @@ -979,8 +1137,8 @@ describe("/api/v1/messages", () => { dateOfBirth: "1", contactDetails: { address: { - lines: ['1', '2'], - } + lines: ["1", "2"], + }, }, }, personalisation: {}, @@ -988,14 +1146,16 @@ describe("/api/v1/messages", () => { }, }) .expect(400, { - message: "Invalid recipient contact details. Field 'postcode': 'postcode' is missing", + message: + "Invalid recipient contact details. Field 'postcode': 'postcode' is missing", errors: [ { + code: 'CM_MISSING_VALUE', field: "/data/attributes/recipient/contactDetails/address", message: "`postcode` is missing", - title: "Missing value" - } - ] + title: "Missing value", + }, + ], }) .expect("Content-Type", /json/, done); }); @@ -1006,7 +1166,7 @@ describe("/api/v1/messages", () => { .set({ Authorization: "allowedContactDetailOverride" }) .send({ data: { - type: 'Message', + type: "Message", attributes: { routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", @@ -1015,9 +1175,9 @@ describe("/api/v1/messages", () => { dateOfBirth: "1", contactDetails: { address: { - lines: ['1', '2'], - postcode: [] - } + lines: ["1", "2"], + postcode: [], + }, }, }, personalisation: {}, @@ -1028,21 +1188,22 @@ describe("/api/v1/messages", () => { message: "Invalid recipient contact details. Field 'postcode': Invalid", errors: [ { + code: 'CM_INVALID_VALUE', field: "/data/attributes/recipient/contactDetails/address/postcode", message: "Invalid", - title: "Invalid value" - } - ] + title: "Invalid value", + }, + ], }) .expect("Content-Type", /json/, done); }); - it('returns a 400 and multiple errors when there are multiple issues in contact details provided', (done) => { + it("returns a 400 and multiple errors when there are multiple issues in contact details provided", (done) => { request(server) .post("/api/v1/messages") .set({ Authorization: "allowedContactDetailOverride" }) .send({ data: { - type: 'Message', + type: "Message", attributes: { routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", messageReference: "b5bb84b9-a522-41e9-aa8b-ad1b6a454243", @@ -1051,10 +1212,10 @@ describe("/api/v1/messages", () => { dateOfBirth: "1", contactDetails: { address: { - lines: ['1'], - postcode: [] + lines: ["1"], + postcode: [], }, - email: 'invalidEmailAddress' + email: "invalidEmailAddress", }, }, personalisation: {}, @@ -1062,51 +1223,53 @@ 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", + message: + "Invalid recipient contact details. Field 'email': Input failed format check. Field 'lines': Too few address lines were provided", errors: [ { + code: 'CM_INVALID_VALUE', field: "/data/attributes/recipient/contactDetails/email", message: "Input failed format check", - title: "Invalid value" + title: "Invalid value", }, { + code: 'CM_TOO_FEW_ITEMS', field: "/data/attributes/recipient/contactDetails/address", message: "Too few address lines were provided", - title: "Missing value" + title: "Too few items" }, - ] + ], }) .expect("Content-Type", /json/, done); - }) + }); it("returns a 201 when all alternate contact details are provided", (done) => { request(server) .post("/api/v1/messages") .set({ Authorization: "allowedContactDetailOverride" }) - .send( - { - data: { - type: "Message", - attributes: { - routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", - messageBatchReference: "request-id", - messageReference: "1", - recipient: { - nhsNumber: "1", - dateOfBirth: "1", - contactDetails: { - email: 'hello', - sms: 'hello', - address: { - lines: ['1', '2'], - postcode: 'hello' - } - } + .send({ + data: { + type: "Message", + attributes: { + routingPlanId: "b838b13c-f98c-4def-93f0-515d4e4f4ee1", + messageBatchReference: "request-id", + messageReference: "1", + recipient: { + nhsNumber: "1", + dateOfBirth: "1", + contactDetails: { + email: "hello", + sms: "hello", + address: { + lines: ["1", "2"], + postcode: "hello", + }, }, }, }, - }) + }, + }) .expect(201) .expect("Content-Type", /json/, done); }); -}) \ No newline at end of file +}); diff --git a/sandbox/handlers/batch_send.js b/sandbox/handlers/batch_send.js index e94b62618..268658f81 100644 --- a/sandbox/handlers/batch_send.js +++ b/sandbox/handlers/batch_send.js @@ -1,16 +1,10 @@ -import KSUID from "ksuid" -import { - sendError, - writeLog, - hasValidGlobalTemplatePersonalisation, -} from "./utils.js" -import { - globalFreeTextNhsAppSendingGroupId, -} from "./config.js" -import { getSendingGroupIdError } from "./error_scenarios/sending_group_id.js" -import { getOdsCodeError } from "./error_scenarios/ods_code.js" -import { mandatoryBatchMessageFieldValidation } from "./validation/mandatory_batch_message_fields.js" -import { getAlternateContactDetailsError } from "./error_scenarios/override_contact_details.js" +import KSUID from "ksuid"; +import { sendError, writeLog } from "./utils.js"; +import { getGlobalFreeTextError } from "./error_scenarios/global_free_text.js"; +import { getSendingGroupIdError } from "./error_scenarios/sending_group_id.js"; +import { getOdsCodeError } from "./error_scenarios/ods_code.js"; +import { mandatoryBatchMessageFieldValidation } from "./validation/mandatory_batch_message_fields.js"; +import { getAlternateContactDetailsError } from "./error_scenarios/override_contact_details.js"; export async function batchSend(req, res, next) { const { headers, body } = req; @@ -24,68 +18,72 @@ export async function batchSend(req, res, next) { return; } - const mandatoryFieldError = mandatoryBatchMessageFieldValidation(body) + const mandatoryFieldError = mandatoryBatchMessageFieldValidation(body); if (mandatoryFieldError !== null) { - const [errorCode, errorMessage] = mandatoryFieldError - sendError(res, errorCode, errorMessage) - next() + const [errorCode, errorMessage] = mandatoryFieldError; + sendError(res, errorCode, errorMessage); + next(); return; } - const { routingPlanId, messages } = body.data.attributes + const { routingPlanId, messages } = body.data.attributes; - if ( - routingPlanId === globalFreeTextNhsAppSendingGroupId && - messages.findIndex( - (message) => - !hasValidGlobalTemplatePersonalisation(message.personalisation) - ) > -1 - ) { - sendError(res, 400, "Expect single personalisation field of 'body'"); - next(); - return; + const messagePersonsalisations = messages.map( + (message) => message.personalisation + ); + for (const personalisation of messagePersonsalisations) { + const globalFreeTextError = getGlobalFreeTextError( + personalisation, + routingPlanId + ); + + if (globalFreeTextError !== null) { + const [errorCode, errorMessage] = globalFreeTextError; + sendError(res, errorCode, errorMessage); + next(); + return; + } } - const odsCodes = messages.map((message) => message && message.originator?.odsCode) + const odsCodes = messages.map( + (message) => message && message.originator?.odsCode + ); for (const odsCode of odsCodes) { - const odsCodeError = getOdsCodeError(odsCode, req.headers.authorization) + const odsCodeError = getOdsCodeError(odsCode, req.headers.authorization); if (odsCodeError !== null) { - const [errorCode, errorMessage] = odsCodeError - sendError(res, errorCode, errorMessage) - next() + const [errorCode, errorMessage] = odsCodeError; + sendError(res, errorCode, errorMessage); + next(); return; } } - const sendingGroupIdError = getSendingGroupIdError(routingPlanId) + const sendingGroupIdError = getSendingGroupIdError(routingPlanId); if (sendingGroupIdError !== null) { - const [errorCode, errorMessage] = sendingGroupIdError - sendError( - res, - errorCode, - errorMessage - ) - next() + const [errorCode, errorMessage] = sendingGroupIdError; + sendError(res, errorCode, errorMessage); + next(); return; } - const alternateContactDetails = messages.map((message) => message.recipient?.contactDetails) + const alternateContactDetails = messages.map( + (message) => message.recipient?.contactDetails + ); let messageIndex = 0; for (const contactDetail of alternateContactDetails) { - const alternateContactDetailsError = getAlternateContactDetailsError(contactDetail, req.headers.authorization, `/data/attributes/messages/${messageIndex}`) + const alternateContactDetailsError = getAlternateContactDetailsError( + contactDetail, + req.headers.authorization, + `/data/attributes/messages/${messageIndex}` + ); if (alternateContactDetailsError !== null) { - const [errorCode, errorMessage, errors] = alternateContactDetailsError - sendError( - res, - errorCode, - errorMessage, - errors - ) - next() + const [errorCode, errorMessage, errors] = alternateContactDetailsError; + sendError(res, errorCode, errorMessage, errors); + next(); return; } - messageIndex += 1; + messageIndex += 1; } writeLog(res, "warn", { diff --git a/sandbox/handlers/config.js b/sandbox/handlers/config.js index fd9141a6f..cb3e2f3e5 100644 --- a/sandbox/handlers/config.js +++ b/sandbox/handlers/config.js @@ -5,20 +5,26 @@ const sendingGroupIdWithMissingTemplates = "c8857ccf-06ec-483f-9b3a-7fc732d9ad48 const sendingGroupIdWithDuplicateTemplates = "a3a4e55d-7a21-45a6-9286-8eb595c872a8"; const sendingGroupIdWithMissingNHSTemplates = "aeb16ab8-cb9c-4d23-92e9-87c78119175c"; const globalFreeTextNhsAppSendingGroupId = "00000000-0000-0000-0000-000000000001"; +const globalFreeTextEmailSendingGroupId = "00000000-0000-0000-0000-000000000002"; +const globalFreeTextSmsSendingGroupId = "00000000-0000-0000-0000-000000000003"; + const validSendingGroupIds = { "b838b13c-f98c-4def-93f0-515d4e4f4ee1": "ztoe2qRAM8M8vS0bqajhyEBcvXacrGPp", "49e43b98-70cb-47a9-a55e-fe70c9a6f77c": "G.uwELAFAGMsKEBk2iIeRCBOB6kj6OkE", "b402cd20-b62a-4357-8e02-2952959531c8": "J7ZPQIf1yyUB4CiBpUBoy.1ahfOTCCQ7", "936e9d45-15de-4a95-bb36-ae163c33ae53": "riOAKoN4ajoVyUf9U2xwHVNRHc5V52A.", "9ba00d23-cd6f-4aca-8688-00abc85a7980": "nkz2osS_oc8IZ5GqeN_1yXKSXe9VEUjV", + [invalidRoutingPlanId]: "c889ViFFlqtDdgV9E2KH.w58T5I71_x_", + [sendingGroupIdWithMissingTemplates]: "EaSC3Wy4BC8Kuk.hZjI95F1f9mZIkP9l", + [sendingGroupIdWithDuplicateTemplates]: "3t.Ofz4i.NO83.tBDLhRpyClu.sGCxTU", + [trigger500SendingGroupId]: "Jc96S9y4AKjncXndUXiS7M7yIZ3jNtY1", + [trigger425SendingGroupId]: "_oivtOz8fHRXhtZAyFxJmT2j_xpWzq9s", + [sendingGroupIdWithMissingNHSTemplates]: "DjAAu455M2TMq0VKEaD_1WZfwjkspJDL", + [globalFreeTextNhsAppSendingGroupId]: "odbGpMQvYRM7sp7jueU2lRHQiueKujVO", + [globalFreeTextEmailSendingGroupId]: "HXdzlIX2Rv2qy7j9MR3FxbPHhJM7Jjzt", + [globalFreeTextSmsSendingGroupId]: "xoERWoahq12cq8WMXya4yed9hZtZbMxJ" }; -validSendingGroupIds[invalidRoutingPlanId] = "c889ViFFlqtDdgV9E2KH.w58T5I71_x_"; -validSendingGroupIds[sendingGroupIdWithMissingTemplates] = "EaSC3Wy4BC8Kuk.hZjI95F1f9mZIkP9l"; -validSendingGroupIds[sendingGroupIdWithDuplicateTemplates] = "3t.Ofz4i.NO83.tBDLhRpyClu.sGCxTU"; -validSendingGroupIds[trigger500SendingGroupId] = "Jc96S9y4AKjncXndUXiS7M7yIZ3jNtY1"; -validSendingGroupIds[trigger425SendingGroupId] = "_oivtOz8fHRXhtZAyFxJmT2j_xpWzq9s"; -validSendingGroupIds[sendingGroupIdWithMissingNHSTemplates] = "DjAAu455M2TMq0VKEaD_1WZfwjkspJDL"; -validSendingGroupIds[globalFreeTextNhsAppSendingGroupId] = "odbGpMQvYRM7sp7jueU2lRHQiueKujVO"; + const invalidEmailAddress = "invalidEmailAddress"; const duplicateTemplates = [ @@ -58,6 +64,8 @@ export { validSendingGroupIds, duplicateTemplates, globalFreeTextNhsAppSendingGroupId, + globalFreeTextEmailSendingGroupId, + globalFreeTextSmsSendingGroupId, noDefaultOdsClientAuth, noOdsChangeClientAuth, notAllowedContactDetailOverride, diff --git a/sandbox/handlers/error_scenarios/global_free_text.js b/sandbox/handlers/error_scenarios/global_free_text.js new file mode 100644 index 000000000..17bd33362 --- /dev/null +++ b/sandbox/handlers/error_scenarios/global_free_text.js @@ -0,0 +1,50 @@ +import { + globalFreeTextEmailSendingGroupId, + globalFreeTextNhsAppSendingGroupId, + globalFreeTextSmsSendingGroupId, +} from "../config.js"; + +const globalFreeTextPersonalisationFields = { + [globalFreeTextNhsAppSendingGroupId]: ["body"], + [globalFreeTextEmailSendingGroupId]: ["email_subject", "email_body"], + [globalFreeTextSmsSendingGroupId]: ["sms_body"], +}; + +export function getGlobalFreeTextError(personalisation, sendingGroupId) { + const requiredPersonalisationFields = + globalFreeTextPersonalisationFields[sendingGroupId]; + + if (!requiredPersonalisationFields) { + return null; + } + + if (!personalisation) { + return [ + 400, + `Some personalisation fields are missing: ${requiredPersonalisationFields.join( + "," + )}`, + ]; + } + + const personalisationFields = Object.keys(personalisation); + + const missingPersonalisationFields = []; + + for (const requiredField of requiredPersonalisationFields) { + if (!personalisationFields.includes(requiredField)) { + missingPersonalisationFields.push(requiredField); + } + } + + if (missingPersonalisationFields.length) { + return [ + 400, + `Some personalisation fields are missing: ${missingPersonalisationFields.join( + "," + )}`, + ]; + } + + return null; +} diff --git a/sandbox/handlers/error_scenarios/override_contact_details.js b/sandbox/handlers/error_scenarios/override_contact_details.js index b16284044..402f00313 100644 --- a/sandbox/handlers/error_scenarios/override_contact_details.js +++ b/sandbox/handlers/error_scenarios/override_contact_details.js @@ -24,6 +24,7 @@ function smsValidation(sms, path) { return validationFailure([ { title: "Invalid value", + code: 'CM_INVALID_VALUE', field: `${path}/recipient/contactDetails/sms`, message: "Input failed format check", }, @@ -34,6 +35,7 @@ function smsValidation(sms, path) { return validationFailure([ { title: "Invalid value", + code: 'CM_INVALID_VALUE', field: `${path}/recipient/contactDetails/sms`, message: "Invalid", }, @@ -51,6 +53,7 @@ function emailValidation(email, path) { return validationFailure([ { title: "Invalid value", + code: 'CM_INVALID_VALUE', field: `${path}/recipient/contactDetails/email`, message: "Input failed format check", }, @@ -61,6 +64,7 @@ function emailValidation(email, path) { return validationFailure([ { title: "Invalid value", + code: 'CM_INVALID_VALUE', field: `${path}/recipient/contactDetails/email`, message: "Invalid", }, @@ -79,6 +83,7 @@ function addressValidation(address, path) { return validationFailure([ { title: "Invalid value", + code: 'CM_INVALID_VALUE', field: `${path}/recipient/contactDetails/address`, message: "Invalid", }, @@ -90,6 +95,7 @@ function addressValidation(address, path) { return validationFailure([ { title: "Missing value", + code: 'CM_MISSING_VALUE', field: `${path}/recipient/contactDetails/address`, message: "`lines` is missing", }, @@ -100,6 +106,7 @@ function addressValidation(address, path) { return validationFailure([ { title: "Missing value", + code: 'CM_MISSING_VALUE', field: `${path}/recipient/contactDetails/address`, message: "`lines` is missing", }, @@ -111,6 +118,7 @@ function addressValidation(address, path) { return validationFailure([ { title: "Invalid value", + code: 'CM_INVALID_VALUE', field: `${path}/recipient/contactDetails/address`, message: "Invalid", }, @@ -121,7 +129,8 @@ function addressValidation(address, path) { if (address.lines.length < 2) { return validationFailure([ { - title: "Missing value", + code: 'CM_TOO_FEW_ITEMS', + title: "Too few items", field: `${path}/recipient/contactDetails/address`, message: "Too few address lines were provided", }, @@ -132,6 +141,7 @@ function addressValidation(address, path) { return validationFailure([ { title: "Invalid value", + code: 'CM_INVALID_VALUE', field: `${path}/recipient/contactDetails/address`, message: "Invalid", }, @@ -143,6 +153,7 @@ function addressValidation(address, path) { return validationFailure([ { title: "Missing value", + code: 'CM_MISSING_VALUE', field: `${path}/recipient/contactDetails/address`, message: "`postcode` is missing", }, @@ -154,6 +165,7 @@ function addressValidation(address, path) { return validationFailure([ { title: "Invalid value", + code: 'CM_INVALID_VALUE', field: `${path}/recipient/contactDetails/address/postcode`, message: "Invalid", }, @@ -176,6 +188,14 @@ export function getAlternateContactDetailsError( return [ 400, "Client is not allowed to provide alternative contact details", + [ + { + code: "CM_CANNOT_SET_CONTACT_DETAILS", + title: "Cannot set contact details", + field: `${path}/recipient/contactDetails`, + message: "Client is not allowed to provide alternative contact details.", + }, + ] ]; } diff --git a/sandbox/handlers/messages.js b/sandbox/handlers/messages.js index 105599568..46bac1cba 100644 --- a/sandbox/handlers/messages.js +++ b/sandbox/handlers/messages.js @@ -1,13 +1,11 @@ import KSUID from "ksuid"; -import { sendError, writeLog, hasValidGlobalTemplatePersonalisation } from "./utils.js"; -import { - validSendingGroupIds, - globalFreeTextNhsAppSendingGroupId, -} from "./config.js" +import { sendError, writeLog } from "./utils.js"; +import { validSendingGroupIds } from "./config.js"; import { getSendingGroupIdError } from "./error_scenarios/sending_group_id.js"; import { getOdsCodeError } from "./error_scenarios/ods_code.js"; import { mandatorySingleMessageFieldValidation } from "./validation/mandatory_single_message_fields.js"; import { getAlternateContactDetailsError } from "./error_scenarios/override_contact_details.js"; +import { getGlobalFreeTextError } from "./error_scenarios/global_free_text.js"; export async function messages(req, res, next) { if (req.headers.authorization === "banned") { @@ -20,57 +18,56 @@ export async function messages(req, res, next) { return; } - const mandatoryFieldError = mandatorySingleMessageFieldValidation(req.body) + const mandatoryFieldError = mandatorySingleMessageFieldValidation(req.body); if (mandatoryFieldError !== null) { - const [errorCode, errorMessage] = mandatoryFieldError - sendError(res, errorCode, errorMessage) - next() + const [errorCode, errorMessage] = mandatoryFieldError; + sendError(res, errorCode, errorMessage); + next(); return; } const { routingPlanId } = req.body.data.attributes; - const sendingGroupIdError = getSendingGroupIdError(routingPlanId) + const sendingGroupIdError = getSendingGroupIdError(routingPlanId); if (sendingGroupIdError !== null) { - const [errorCode, errorMessage] = sendingGroupIdError - sendError( - res, - errorCode, - errorMessage - ) - next() + const [errorCode, errorMessage] = sendingGroupIdError; + sendError(res, errorCode, errorMessage); + next(); return; } - if ( - routingPlanId === globalFreeTextNhsAppSendingGroupId && - !hasValidGlobalTemplatePersonalisation(req.body.data.attributes.personalisation) - ) { - sendError(res, 400, "Expect single personalisation field of 'body'"); + const personalisation = req.body.data?.attributes?.personalisation; + const globalFreeTextError = getGlobalFreeTextError( + personalisation, + routingPlanId + ); + if (globalFreeTextError !== null) { + const [errorCode, errorMessage] = globalFreeTextError; + sendError(res, errorCode, errorMessage); next(); return; } const odsCode = req.body.data?.attributes?.originator?.odsCode; - const odsCodeError = getOdsCodeError(odsCode, req.headers.authorization) + const odsCodeError = getOdsCodeError(odsCode, req.headers.authorization); if (odsCodeError !== null) { - const [odsCodeErrorCode, odsCodeErrorMessage] = odsCodeError - sendError(res, odsCodeErrorCode, odsCodeErrorMessage) - next() + const [errorCode, errorMessage] = odsCodeError; + sendError(res, errorCode, errorMessage); + next(); return; } - const alternateContactDetails = req.body.data?.attributes?.recipient?.contactDetails - const alternateContactDetailsError = getAlternateContactDetailsError(alternateContactDetails, req.headers.authorization, '/data/attributes') + const alternateContactDetails = + req.body.data?.attributes?.recipient?.contactDetails; + const alternateContactDetailsError = getAlternateContactDetailsError( + alternateContactDetails, + req.headers.authorization, + "/data/attributes" + ); if (alternateContactDetailsError !== null) { - const [errorCode, errorMessage, errors] = alternateContactDetailsError - sendError( - res, - errorCode, - errorMessage, - errors - ) - next() + const [errorCode, errorMessage, errors] = alternateContactDetailsError; + sendError(res, errorCode, errorMessage, errors); + next(); return; } diff --git a/sandbox/handlers/utils.js b/sandbox/handlers/utils.js index ccf566ee1..4cd90173c 100644 --- a/sandbox/handlers/utils.js +++ b/sandbox/handlers/utils.js @@ -44,18 +44,3 @@ export function sendError(res, code, message, errors) { }); } -export function hasValidGlobalTemplatePersonalisation(personalisation) { - if (!personalisation) { - return false; - } - - const personalisationFields = Object.keys(personalisation); - if (personalisationFields.length !== 1) { - return false; - } - - if (personalisationFields[0] !== "body") { - return false; - } - return true; -} diff --git a/sandbox/messages/2WL3qFTEFM0qMY8xjRbt1LIKCzM.json b/sandbox/messages/2WL3qFTEFM0qMY8xjRbt1LIKCzM.json index 60b1d9ff5..e48f8eaad 100644 --- a/sandbox/messages/2WL3qFTEFM0qMY8xjRbt1LIKCzM.json +++ b/sandbox/messages/2WL3qFTEFM0qMY8xjRbt1LIKCzM.json @@ -13,6 +13,8 @@ { "type": "nhsapp", "retryCount": 0, + "cascadeType": "primary", + "cascadeOrder": 1, "channelStatus": "delivered", "timestamps": { "created": "2023-10-09T10:31:59Z", diff --git a/sandbox/messages/2WL3wwFhpZ6blJNIoh747bDEFNv.json b/sandbox/messages/2WL3wwFhpZ6blJNIoh747bDEFNv.json index ddf753a5d..9ce83b3d3 100644 --- a/sandbox/messages/2WL3wwFhpZ6blJNIoh747bDEFNv.json +++ b/sandbox/messages/2WL3wwFhpZ6blJNIoh747bDEFNv.json @@ -13,6 +13,8 @@ { "type": "email", "retryCount": 0, + "cascadeType": "primary", + "cascadeOrder": 1, "channelStatus": "delivered", "timestamps": { "created": "2023-10-09T10:31:59Z", diff --git a/sandbox/messages/2WL3ydEEk37IzREoWRhuAdolFCE.json b/sandbox/messages/2WL3ydEEk37IzREoWRhuAdolFCE.json index 934971f08..cb573fb0a 100644 --- a/sandbox/messages/2WL3ydEEk37IzREoWRhuAdolFCE.json +++ b/sandbox/messages/2WL3ydEEk37IzREoWRhuAdolFCE.json @@ -13,6 +13,8 @@ { "type": "sms", "retryCount": 0, + "cascadeType": "primary", + "cascadeOrder": 1, "channelStatus": "delivered", "timestamps": { "created": "2023-10-09T10:31:59Z", diff --git a/sandbox/messages/2WL3zxCY9e5vm2VP1ZfYMb53WPF.json b/sandbox/messages/2WL3zxCY9e5vm2VP1ZfYMb53WPF.json index cf5d9e72e..ec30c1429 100644 --- a/sandbox/messages/2WL3zxCY9e5vm2VP1ZfYMb53WPF.json +++ b/sandbox/messages/2WL3zxCY9e5vm2VP1ZfYMb53WPF.json @@ -13,6 +13,8 @@ { "type": "letter", "retryCount": 0, + "cascadeType": "primary", + "cascadeOrder": 1, "channelStatus": "delivered", "timestamps": { "created": "2023-10-09T10:31:59Z", diff --git a/sandbox/messages/2WL44QP7vrKxKKBZdTtUQoB2bWl.json b/sandbox/messages/2WL44QP7vrKxKKBZdTtUQoB2bWl.json index 87a0edcd9..466aa8954 100644 --- a/sandbox/messages/2WL44QP7vrKxKKBZdTtUQoB2bWl.json +++ b/sandbox/messages/2WL44QP7vrKxKKBZdTtUQoB2bWl.json @@ -13,6 +13,8 @@ { "type": "nhsapp", "retryCount": 0, + "cascadeType": "primary", + "cascadeOrder": 1, "channelStatus": "sending", "timestamps": { "created": "2023-10-09T10:31:59Z", diff --git a/sandbox/messages/2WL45YuHOLATvC3GspEu0oSioux.json b/sandbox/messages/2WL45YuHOLATvC3GspEu0oSioux.json index 837155d7c..4808e541f 100644 --- a/sandbox/messages/2WL45YuHOLATvC3GspEu0oSioux.json +++ b/sandbox/messages/2WL45YuHOLATvC3GspEu0oSioux.json @@ -14,6 +14,8 @@ { "type": "nhsapp", "retryCount": 0, + "cascadeType": "primary", + "cascadeOrder": 1, "channelStatus": "failed", "channelStatusDescription": "Failed reason: NHS number not found", "timestamps": { diff --git a/sandbox/messages/2WL4GEeFVxXG9S57nRlefBwwKxp.json b/sandbox/messages/2WL4GEeFVxXG9S57nRlefBwwKxp.json index 8b672b991..b9dde8931 100644 --- a/sandbox/messages/2WL4GEeFVxXG9S57nRlefBwwKxp.json +++ b/sandbox/messages/2WL4GEeFVxXG9S57nRlefBwwKxp.json @@ -13,6 +13,8 @@ { "type": "email", "retryCount": 0, + "cascadeType": "primary", + "cascadeOrder": 1, "channelStatus": "sending", "timestamps": { "created": "2023-10-09T10:31:59Z", diff --git a/sandbox/messages/2WL4JXrfauCaQnSFbAujoImSKwo.json b/sandbox/messages/2WL4JXrfauCaQnSFbAujoImSKwo.json index d482d8d34..f1065f8e9 100644 --- a/sandbox/messages/2WL4JXrfauCaQnSFbAujoImSKwo.json +++ b/sandbox/messages/2WL4JXrfauCaQnSFbAujoImSKwo.json @@ -13,6 +13,8 @@ { "type": "sms", "retryCount": 0, + "cascadeType": "primary", + "cascadeOrder": 1, "channelStatus": "sending", "timestamps": { "created": "2023-10-09T10:31:59Z", diff --git a/sandbox/messages/2WL4JtCiOe7l2TT4szwPjNJah3z.json b/sandbox/messages/2WL4JtCiOe7l2TT4szwPjNJah3z.json index 32b5989a9..375353580 100644 --- a/sandbox/messages/2WL4JtCiOe7l2TT4szwPjNJah3z.json +++ b/sandbox/messages/2WL4JtCiOe7l2TT4szwPjNJah3z.json @@ -14,6 +14,8 @@ { "type": "sms", "retryCount": 0, + "cascadeType": "primary", + "cascadeOrder": 1, "channelStatus": "failed", "channelStatusDescription": "Failed reason: The provider could not deliver the message. This can happen when the recipient’s phone is off, has no signal, or their text message inbox is full. You can try to send the message again. You’ll still be charged for text messages to phones that are not accepting messages.", "timestamps": { diff --git a/sandbox/messages/2WL4LuyNMtoGAsJQIpTxZLl8e3e.json b/sandbox/messages/2WL4LuyNMtoGAsJQIpTxZLl8e3e.json index fc0aec5d3..0cc2b8c23 100644 --- a/sandbox/messages/2WL4LuyNMtoGAsJQIpTxZLl8e3e.json +++ b/sandbox/messages/2WL4LuyNMtoGAsJQIpTxZLl8e3e.json @@ -13,6 +13,8 @@ { "type": "letter", "retryCount": 0, + "cascadeType": "primary", + "cascadeOrder": 1, "channelStatus": "sending", "timestamps": { "created": "2023-10-09T10:31:59Z", diff --git a/sandbox/messages/2WL4MOuSeCTODDAScFG7KIq9a5r.json b/sandbox/messages/2WL4MOuSeCTODDAScFG7KIq9a5r.json index 602c05384..2bbf66d67 100644 --- a/sandbox/messages/2WL4MOuSeCTODDAScFG7KIq9a5r.json +++ b/sandbox/messages/2WL4MOuSeCTODDAScFG7KIq9a5r.json @@ -14,6 +14,8 @@ { "type": "letter", "retryCount": 0, + "cascadeType": "primary", + "cascadeOrder": 1, "channelStatus": "failed", "channelStatusDescription": "Failed reason: We had an unexpected error while sending the letter to our printing provider.", "timestamps": { diff --git a/sandbox/messages/2WL4W9RgbuLLByXdR77H8vjKSDd.json b/sandbox/messages/2WL4W9RgbuLLByXdR77H8vjKSDd.json index bedbbb955..b64128eca 100644 --- a/sandbox/messages/2WL4W9RgbuLLByXdR77H8vjKSDd.json +++ b/sandbox/messages/2WL4W9RgbuLLByXdR77H8vjKSDd.json @@ -14,6 +14,8 @@ { "type": "email", "retryCount": 0, + "cascadeType": "primary", + "cascadeOrder": 1, "channelStatus": "failed", "channelStatusDescription": "Failed reason: The provider could not deliver the message. This can happen when the recipient’s inbox is full or their anti-spam filter rejects your email. Check your content does not look like spam before you try to send the message again.", "timestamps": { diff --git a/sandbox/messages/2WL4vHFZzInmaYwq6HRNDqTX8dH.json b/sandbox/messages/2WL4vHFZzInmaYwq6HRNDqTX8dH.json index 8b923f566..1395176af 100644 --- a/sandbox/messages/2WL4vHFZzInmaYwq6HRNDqTX8dH.json +++ b/sandbox/messages/2WL4vHFZzInmaYwq6HRNDqTX8dH.json @@ -14,6 +14,8 @@ { "type": "nhsapp", "retryCount": 0, + "cascadeType": "primary", + "cascadeOrder": 1, "channelStatus": "failed", "channelStatusDescription": "Failed reason: Contact detail is missing", "timestamps": { diff --git a/sandbox/messages/2WL4xcWKvz4F32g0htBEl8DINzn.json b/sandbox/messages/2WL4xcWKvz4F32g0htBEl8DINzn.json index 22749bf45..be54f4601 100644 --- a/sandbox/messages/2WL4xcWKvz4F32g0htBEl8DINzn.json +++ b/sandbox/messages/2WL4xcWKvz4F32g0htBEl8DINzn.json @@ -14,6 +14,8 @@ { "type": "email", "retryCount": 0, + "cascadeType": "primary", + "cascadeOrder": 1, "channelStatus": "failed", "channelStatusDescription": "Failed reason: Contact detail is missing", "timestamps": { diff --git a/sandbox/messages/2WL50w41YaZXcyFCNT346LY8rlz.json b/sandbox/messages/2WL50w41YaZXcyFCNT346LY8rlz.json index 32fc8aec9..02682a07d 100644 --- a/sandbox/messages/2WL50w41YaZXcyFCNT346LY8rlz.json +++ b/sandbox/messages/2WL50w41YaZXcyFCNT346LY8rlz.json @@ -14,6 +14,8 @@ { "type": "sms", "retryCount": 0, + "cascadeType": "primary", + "cascadeOrder": 1, "channelStatus": "failed", "channelStatusDescription": "Failed reason: Contact detail is missing", "timestamps": { diff --git a/sandbox/messages/2WL54x0XQjCbWeE5lN0DKQZcokU.json b/sandbox/messages/2WL54x0XQjCbWeE5lN0DKQZcokU.json index 1dc28c5a7..611981fe5 100644 --- a/sandbox/messages/2WL54x0XQjCbWeE5lN0DKQZcokU.json +++ b/sandbox/messages/2WL54x0XQjCbWeE5lN0DKQZcokU.json @@ -14,6 +14,8 @@ { "type": "letter", "retryCount": 0, + "cascadeType": "primary", + "cascadeOrder": 1, "channelStatus": "failed", "channelStatusDescription": "Failed reason: Contact detail is missing", "timestamps": { diff --git a/sandbox/messages/2WL5TWl7F7PondWbZ1vctlEtOZ3.json b/sandbox/messages/2WL5TWl7F7PondWbZ1vctlEtOZ3.json index 092c6f0c3..c2bda62e4 100644 --- a/sandbox/messages/2WL5TWl7F7PondWbZ1vctlEtOZ3.json +++ b/sandbox/messages/2WL5TWl7F7PondWbZ1vctlEtOZ3.json @@ -13,6 +13,8 @@ { "type": "nhsapp", "retryCount": 0, + "cascadeType": "primary", + "cascadeOrder": 1, "channelStatus": "delivered", "timestamps": { "created": "2023-10-09T10:31:59Z", @@ -28,6 +30,8 @@ { "type": "email", "retryCount": 0, + "cascadeType": "primary", + "cascadeOrder": 2, "channelStatus": "created", "timestamps": { "created": "2023-10-09T10:31:59Z" @@ -41,6 +45,8 @@ { "type": "sms", "retryCount": 0, + "cascadeType": "primary", + "cascadeOrder": 3, "channelStatus": "created", "timestamps": { "created": "2023-10-09T10:31:59Z" @@ -54,6 +60,8 @@ { "type": "letter", "retryCount": 0, + "cascadeType": "primary", + "cascadeOrder": 4, "channelStatus": "created", "timestamps": { "created": "2023-10-09T10:31:59Z" diff --git a/sandbox/messages/2WL5eDefrbW31uw1il84WdF8ndH.json b/sandbox/messages/2WL5eDefrbW31uw1il84WdF8ndH.json index 1d527a6f3..3104ffd1a 100644 --- a/sandbox/messages/2WL5eDefrbW31uw1il84WdF8ndH.json +++ b/sandbox/messages/2WL5eDefrbW31uw1il84WdF8ndH.json @@ -13,6 +13,8 @@ { "type": "nhsapp", "retryCount": 0, + "cascadeType": "primary", + "cascadeOrder": 1, "channelStatus": "failed", "channelStatusDescription": "Failed reason: NHS number not found", "timestamps": { @@ -29,6 +31,8 @@ { "type": "email", "retryCount": 0, + "cascadeType": "primary", + "cascadeOrder": 2, "channelStatus": "delivered", "timestamps": { "created": "2023-10-09T10:31:59Z", @@ -44,6 +48,8 @@ { "type": "sms", "retryCount": 0, + "cascadeType": "primary", + "cascadeOrder": 3, "channelStatus": "created", "timestamps": { "created": "2023-10-09T10:31:59Z" @@ -57,6 +63,8 @@ { "type": "letter", "retryCount": 0, + "cascadeType": "primary", + "cascadeOrder": 4, "channelStatus": "created", "timestamps": { "created": "2023-10-09T10:31:59Z" diff --git a/sandbox/messages/2WL5eYSWGzCHlGmzNxuqVusPxDg.json b/sandbox/messages/2WL5eYSWGzCHlGmzNxuqVusPxDg.json index a6d601c7a..d0d6bd846 100644 --- a/sandbox/messages/2WL5eYSWGzCHlGmzNxuqVusPxDg.json +++ b/sandbox/messages/2WL5eYSWGzCHlGmzNxuqVusPxDg.json @@ -13,6 +13,8 @@ { "type": "nhsapp", "retryCount": 0, + "cascadeType": "primary", + "cascadeOrder": 1, "channelStatus": "failed", "channelStatusDescription": "Failed reason: NHS number not found", "timestamps": { @@ -29,6 +31,8 @@ { "type": "email", "retryCount": 0, + "cascadeType": "primary", + "cascadeOrder": 2, "channelStatus": "failed", "channelStatusDescription": "Failed reason: The provider could not deliver the message. This can happen when the recipient’s inbox is full or their anti-spam filter rejects your email. Check your content does not look like spam before you try to send the message again.", "timestamps": { @@ -45,6 +49,8 @@ { "type": "sms", "retryCount": 0, + "cascadeType": "primary", + "cascadeOrder": 3, "channelStatus": "delivered", "timestamps": { "created": "2023-10-09T10:31:59Z", @@ -60,6 +66,8 @@ { "type": "letter", "retryCount": 0, + "cascadeType": "primary", + "cascadeOrder": 4, "channelStatus": "created", "timestamps": { "created": "2023-10-09T10:31:59Z" diff --git a/sandbox/messages/2WL5f8j4XVxUPgd3OOqXVYvVFIW.json b/sandbox/messages/2WL5f8j4XVxUPgd3OOqXVYvVFIW.json index a07ac3aa9..0fd9f80ee 100644 --- a/sandbox/messages/2WL5f8j4XVxUPgd3OOqXVYvVFIW.json +++ b/sandbox/messages/2WL5f8j4XVxUPgd3OOqXVYvVFIW.json @@ -13,6 +13,8 @@ { "type": "nhsapp", "retryCount": 0, + "cascadeType": "primary", + "cascadeOrder": 1, "channelStatus": "failed", "channelStatusDescription": "Failed reason: NHS number not found", "timestamps": { @@ -29,6 +31,8 @@ { "type": "email", "retryCount": 0, + "cascadeType": "primary", + "cascadeOrder": 2, "channelStatus": "failed", "channelStatusDescription": "Failed reason: The provider could not deliver the message. This can happen when the recipient’s inbox is full or their anti-spam filter rejects your email. Check your content does not look like spam before you try to send the message again.", "timestamps": { @@ -45,6 +49,8 @@ { "type": "sms", "retryCount": 0, + "cascadeType": "primary", + "cascadeOrder": 3, "channelStatus": "failed", "channelStatusDescription": "Failed reason: The provider could not deliver the message. This can happen when the recipient’s phone is off, has no signal, or their text message inbox is full. You can try to send the message again. You’ll still be charged for text messages to phones that are not accepting messages.", "timestamps": { @@ -61,6 +67,8 @@ { "type": "letter", "retryCount": 0, + "cascadeType": "primary", + "cascadeOrder": 4, "channelStatus": "delivered", "timestamps": { "created": "2023-10-09T10:31:59Z", diff --git a/sandbox/messages/2WL5qbEa7TzSWZXU2IAOCCrLXVL.json b/sandbox/messages/2WL5qbEa7TzSWZXU2IAOCCrLXVL.json index df970ab4f..c96f413d7 100644 --- a/sandbox/messages/2WL5qbEa7TzSWZXU2IAOCCrLXVL.json +++ b/sandbox/messages/2WL5qbEa7TzSWZXU2IAOCCrLXVL.json @@ -13,6 +13,8 @@ { "type": "nhsapp", "retryCount": 0, + "cascadeType": "primary", + "cascadeOrder": 1, "channelStatus": "delivered", "timestamps": { "created": "2023-10-09T10:31:59Z", diff --git a/sandbox/messages/2bBBpsiMl2rnQt99qm6JLZ6w1vq.json b/sandbox/messages/2bBBpsiMl2rnQt99qm6JLZ6w1vq.json index c7151ffc2..71633cb7f 100644 --- a/sandbox/messages/2bBBpsiMl2rnQt99qm6JLZ6w1vq.json +++ b/sandbox/messages/2bBBpsiMl2rnQt99qm6JLZ6w1vq.json @@ -14,6 +14,8 @@ { "type": "sms", "retryCount": 0, + "cascadeType": "primary", + "cascadeOrder": 1, "channelStatus": "failed", "channelStatusDescription": "Failed reason: Contact detail is missing", "timestamps": { diff --git a/sandbox/messages/2n7C5wdJ9JSOSeCJTyd50GvtZeR.json b/sandbox/messages/2n7C5wdJ9JSOSeCJTyd50GvtZeR.json new file mode 100644 index 000000000..22b9351bb --- /dev/null +++ b/sandbox/messages/2n7C5wdJ9JSOSeCJTyd50GvtZeR.json @@ -0,0 +1,88 @@ +{ + "data": { + "type": "Message", + "id": "2n7C5wdJ9JSOSeCJTyd50GvtZeR", + "attributes": { + "routingPlan": { + "id": "b838b13c-f98c-4def-93f0-515d4e4f4ee1", + "version": "1" + }, + "messageReference": "d5469ebf-7e26-4dfc-9818-7c74d500097f", + "messageStatus": "delivered", + "channels": [ + { + "type": "nhsapp", + "retryCount": 0, + "cascadeType": "primary", + "cascadeOrder": 1, + "channelStatus": "failed", + "channelStatusDescription": "Message not read in specified time frame", + "timestamps": { + "created": "2023-10-09T10:31:59Z", + "enriched": "2023-10-09T10:31:59Z", + "failed": "2023-10-09T10:31:59Z" + }, + "routingPlan": { + "id": "b838b13c-f98c-4def-93f0-515d4e4f4ee1", + "version": "1", + "type": "original" + } + }, + { + "type": "sms", + "retryCount": 0, + "cascadeType": "secondary", + "cascadeOrder": 1, + "channelStatus": "delivered", + "timestamps": { + "created": "2023-10-09T10:31:59Z", + "enriched": "2023-10-09T10:32:37Z", + "delivered": "2023-10-09T11:48:52Z" + }, + "routingPlan": { + "id": "b838b13c-f98c-4def-93f0-515d4e4f4ee1", + "version": "1", + "type": "original" + } + }, + { + "type": "letter", + "retryCount": 0, + "cascadeType": "primary", + "cascadeOrder": 2, + "channelStatus": "delivered", + "timestamps": { + "created": "2023-10-09T10:31:59Z", + "enriched": "2023-10-09T10:32:37Z", + "delivered": "2023-10-09T11:48:52Z" + }, + "routingPlan": { + "id": "b838b13c-f98c-4def-93f0-515d4e4f4ee1", + "version": "1", + "type": "original" + } + } + ], + "timestamps": { + "created": "2023-10-09T10:31:37Z", + "enriched": "2023-10-09T10:31:59Z", + "delivered": "2023-10-09T11:48:52Z" + }, + "metadata": [ + { + "queriedAt": "2023-10-09T10:31:59Z", + "version": "23", + "source": "pds", + "labels": [ + "nhsapp", + "sms", + "letter" + ] + } + ] + }, + "links": { + "self": "%PATH_ROOT%" + } + } +} \ No newline at end of file diff --git a/specification/documentation/APIDescription.md b/specification/documentation/APIDescription.md index 786624b8e..66e15f6d1 100644 --- a/specification/documentation/APIDescription.md +++ b/specification/documentation/APIDescription.md @@ -143,6 +143,8 @@ Free-text communications (as opposed to fixed format communications) are possibl | Global Routing Plan ID | Channel/Supplier | Read wait time (before failing channel) | Personalisation field name | |--------------------------------------|------------------|-----------------------------------------|----------------------------| | 00000000-0000-0000-0000-000000000001 | NHS App | 24 hours | body | +| 00000000-0000-0000-0000-000000000002 | Email | | email_body, email_subject | +| 00000000-0000-0000-0000-000000000003 | SMS | | sms_body | Please see the Postman collections in the [environments and testing section](#section/Environments-and-testing) for examples. diff --git a/specification/endpoints/create_message_batch.yaml b/specification/endpoints/create_message_batch.yaml index 692ced560..ccbfc2704 100644 --- a/specification/endpoints/create_message_batch.yaml +++ b/specification/endpoints/create_message_batch.yaml @@ -31,6 +31,8 @@ responses: $ref: ../responses/4xx/406_NotAcceptable.yaml '408': $ref: ../responses/4xx/408_RequestTimeout.yaml + '413': + $ref: ../responses/4xx/413_RequestEntityTooLarge.yaml '415': $ref: ../responses/4xx/415_UnsupportedMedia.yaml '425': diff --git a/specification/responses/4xx/413_RequestEntityTooLarge.yaml b/specification/responses/4xx/413_RequestEntityTooLarge.yaml new file mode 100644 index 000000000..ae776fff5 --- /dev/null +++ b/specification/responses/4xx/413_RequestEntityTooLarge.yaml @@ -0,0 +1,12 @@ +description: |+ + Either the whole of or some part of the request was too large for the host side to handle and was rejected. + +content: + application/vnd.api+json: + schema: + $ref: ../../schemas/responses/errors/RequestEntityTooLarge.yaml + application/json: + schema: + $ref: ../../schemas/responses/errors/RequestEntityTooLarge.yaml +headers: + $ref: ../../snippets/StandardResponseHeaders.yaml diff --git a/specification/schemas/enums/ErrorRequestEntityTooLarge.yaml b/specification/schemas/enums/ErrorRequestEntityTooLarge.yaml new file mode 100644 index 000000000..942e71183 --- /dev/null +++ b/specification/schemas/enums/ErrorRequestEntityTooLarge.yaml @@ -0,0 +1,6 @@ +title: Enum_Error_RequestEntityTooLarge +type: string +enum: + - CM_TOO_LARGE + - CM_TOO_MANY_ITEMS +example: CM_TOO_LARGE diff --git a/specification/schemas/responses/errors/RequestEntityTooLarge.yaml b/specification/schemas/responses/errors/RequestEntityTooLarge.yaml new file mode 100644 index 000000000..73251dd7a --- /dev/null +++ b/specification/schemas/responses/errors/RequestEntityTooLarge.yaml @@ -0,0 +1,43 @@ +type: object +title: Request Entity Too Large +additionalProperties: false +properties: + errors: + type: array + minItems: 1 + maxItems: 1 + uniqueItems: true + items: + type: object + additionalProperties: false + properties: + id: + $ref: ../../types/ErrorIdentifier.yaml + code: + $ref: ../../enums/ErrorRequestEntityTooLarge.yaml + links: + $ref: ../../types/LinksError.yaml + status: + type: string + enum: + - '413' + example: '413' + title: + type: string + enum: + - Request too large + - Too many items + example: Request too large + detail: + type: string + enum: + - Request message was larger than the service limit + - The property at the specified location contains too many items. + example: Request message was larger than the service limit + source: + type: object + additionalProperties: false + properties: + pointer: + type: string + example: / diff --git a/tests/development/message_batches/create_message_batches/test_field_validation.py b/tests/development/message_batches/create_message_batches/test_field_validation.py index b27f8ec37..12630999a 100644 --- a/tests/development/message_batches/create_message_batches/test_field_validation.py +++ b/tests/development/message_batches/create_message_batches/test_field_validation.py @@ -633,9 +633,10 @@ def test_invalid_address_contact_details_too_few_lines(nhsd_apim_proxy_url, bear Assertions.assert_error_with_optional_correlation_id( resp, 400, - Generators.generate_error(constants.ERROR_TOO_FEW_ADDRESS_LINES, source={ - "pointer": "/data/attributes/messages/0/recipient/contactDetails/address" - }), + Generators.generate_too_few_items_error_custom_detail( + "/data/attributes/messages/0/recipient/contactDetails/address", + "Too few address lines were provided" + ), correlation_id ) diff --git a/tests/development/message_batches/create_message_batches/test_performance.py b/tests/development/message_batches/create_message_batches/test_performance.py index d427882a1..3b5ca9bc8 100644 --- a/tests/development/message_batches/create_message_batches/test_performance.py +++ b/tests/development/message_batches/create_message_batches/test_performance.py @@ -6,7 +6,7 @@ from lib.constants.constants import NUM_MAX_ERRORS from lib.constants.message_batches_paths import MESSAGE_BATCHES_ENDPOINT -NUM_MESSAGES = 50000 +NUM_MESSAGES = 40000 CONTENT_TYPE = "application/json" @@ -17,7 +17,7 @@ def test_create_messages_large_invalid_payload(nhsd_apim_proxy_url, bearer_token """ data = Generators.generate_valid_create_message_batch_body("dev") - # around 50k messages gives us close to our max body size + # around 40k messages gives us close to our max body size data["data"]["attributes"]["messages"] = [] for _ in range(0, NUM_MESSAGES): data["data"]["attributes"]["messages"].append({ @@ -45,7 +45,7 @@ def test_create_messages_large_not_unique_payload(nhsd_apim_proxy_url, bearer_to """ data = Generators.generate_valid_create_message_batch_body("dev") - # around 50k messages gives us close to our max body size + # around 40k messages gives us close to our max body size data["data"]["attributes"]["messages"] = [] reference = str(uuid.uuid1()) for _ in range(0, NUM_MESSAGES): diff --git a/tests/development/message_batches/create_message_batches/test_too_large.py b/tests/development/message_batches/create_message_batches/test_too_large.py new file mode 100644 index 000000000..1efdf7dec --- /dev/null +++ b/tests/development/message_batches/create_message_batches/test_too_large.py @@ -0,0 +1,92 @@ +import requests +import pytest +import uuid +import json +import lib.constants.constants as constants +from lib import Assertions, Generators +from lib.fixtures import * # NOSONAR +from lib.constants.message_batches_paths import MESSAGE_BATCHES_ENDPOINT + +MESSAGE_LIMIT = 45000 +CONTENT_TYPE = "application/json" + + +@pytest.mark.devtest +def test_too_many_messages(nhsd_apim_proxy_url, bearer_token_internal_dev): + """ + .. include:: ../../partials/too_large/test_too_many_messages.rst + """ + + data = Generators.generate_valid_create_message_batch_body("dev") + messages = [] + data["data"]["attributes"]["messages"] = messages + + for i in range(MESSAGE_LIMIT+1): + messages.append({ + "messageReference": str(uuid.uuid1()), + "recipient": { + "nhsNumber": "x" + } + }) + # make sure it's less than 6MB to be a fair test + assert len(json.dumps(data)) < 6000000 + resp = requests.post(f"{nhsd_apim_proxy_url}{MESSAGE_BATCHES_ENDPOINT}", headers={ + "Authorization": bearer_token_internal_dev.value, + "Accept": CONTENT_TYPE, + "Content-Type": CONTENT_TYPE + }, + json=data + ) + + Assertions.assert_error_with_optional_correlation_id( + resp, + 413, + Generators.generate_error( + constants.ERROR_TOO_MANY_ITEMS, + source={"pointer": "/data/attributes/messages"} + ), + None + ) + + +@pytest.mark.devtest +def test_payload_too_large(nhsd_apim_proxy_url, bearer_token_internal_dev): + """ + .. include:: ../../partials/too_large/test_payload_too_large.rst + """ + + data = Generators.generate_valid_create_message_batch_body("dev") + valid_recipient = data["data"]["attributes"]["messages"][0]["recipient"] + large_personalisation = {'body': 'x'*32} + messages = [] + data["data"]["attributes"]["messages"] = messages + + for i in range(MESSAGE_LIMIT): + messages.append({ + "messageReference": str(uuid.uuid1()), + "recipient": valid_recipient, + "personalisation": large_personalisation + }) + # make sure it's more than 6MB to be a fair test + payload_length = len(json.dumps(data)) + assert payload_length > 6*(1024**2) + # but less than 10MB to make sure it doesn't fail because apigee didn't accept it + assert payload_length < 10000000 + + resp = requests.post(f"{nhsd_apim_proxy_url}{MESSAGE_BATCHES_ENDPOINT}", headers={ + "Authorization": bearer_token_internal_dev.value, + "Accept": CONTENT_TYPE, + "Content-Type": CONTENT_TYPE + }, + json=data + ) + + Assertions.assert_error_with_optional_correlation_id( + resp, + 413, + Generators.generate_error( + constants.ERROR_TOO_LARGE, + source={"pointer": "/"} + ), + None + ) diff --git a/tests/development/messages/create_messages/test_field_validation.py b/tests/development/messages/create_messages/test_field_validation.py index 0c2c8c5fa..1e3b16019 100644 --- a/tests/development/messages/create_messages/test_field_validation.py +++ b/tests/development/messages/create_messages/test_field_validation.py @@ -376,9 +376,10 @@ def test_invalid_address_contact_details_too_few_lines(nhsd_apim_proxy_url, bear Assertions.assert_error_with_optional_correlation_id( resp, 400, - Generators.generate_error(constants.ERROR_TOO_FEW_ADDRESS_LINES, source={ - "pointer": "/data/attributes/recipient/contactDetails/address" - }), + Generators.generate_too_few_items_error_custom_detail( + "/data/attributes/recipient/contactDetails/address", + "Too few address lines were provided" + ), correlation_id ) diff --git a/tests/docs/partials/performance/test_create_messages_large_invalid_payload.rst b/tests/docs/partials/performance/test_create_messages_large_invalid_payload.rst index c6b68a7fd..e3de3f796 100644 --- a/tests/docs/partials/performance/test_create_messages_large_invalid_payload.rst +++ b/tests/docs/partials/performance/test_create_messages_large_invalid_payload.rst @@ -1,7 +1,7 @@ -Scenario: An API consumer submitting a request with a request body containing 50,000 invalid messages receives a 400 status code and response body containing the first 100 instances of errors +Scenario: An API consumer submitting a request with a request body containing 40,000 invalid messages receives a 400 status code and response body containing the first 100 instances of errors =============================================================================================================================================================================================== -| **Given** the API consumer provides a message body of 50,000 invalid messages +| **Given** the API consumer provides a message body of 40,000 invalid messages | **When** the request is submitted | **Then** the response is a 400 invalid value error | **And** the response body contains 100 errors @@ -10,4 +10,4 @@ Scenario: An API consumer submitting a request with a request body containing 50 **Asserts** - Response returns a 400 'Invalid Value' status code -- Response returns 100 error message blocks \ No newline at end of file +- Response returns 100 error message blocks diff --git a/tests/docs/partials/performance/test_create_messages_large_not_unique_payload.rst b/tests/docs/partials/performance/test_create_messages_large_not_unique_payload.rst index 680649297..dbaa7fd2b 100644 --- a/tests/docs/partials/performance/test_create_messages_large_not_unique_payload.rst +++ b/tests/docs/partials/performance/test_create_messages_large_not_unique_payload.rst @@ -1,7 +1,7 @@ -Scenario: An API consumer submitting a request with a large request body containing 50,000 duplicate messages receives a 400 response +Scenario: An API consumer submitting a request with a large request body containing 40,000 duplicate messages receives a 400 response ===================================================================================================================================== -| **Given** the API consumer provides a message body of 50,000 duplicate messages +| **Given** the API consumer provides a message body of 40,000 duplicate messages | **When** the request is submitted | **Then** the response is a 400 invalid value error | **And** the response body contains 100 errors @@ -9,4 +9,4 @@ Scenario: An API consumer submitting a request with a large request body contain **Asserts** - Response returns a 400 'Invalid Value' status code -- Response returns 100 error message blocks \ No newline at end of file +- Response returns 100 error message blocks diff --git a/tests/docs/partials/performance/test_create_messages_large_valid_payload.rst b/tests/docs/partials/performance/test_create_messages_large_valid_payload.rst index 38b41c01c..7ab99adf1 100644 --- a/tests/docs/partials/performance/test_create_messages_large_valid_payload.rst +++ b/tests/docs/partials/performance/test_create_messages_large_valid_payload.rst @@ -1,10 +1,10 @@ -Scenario: An API consumer submitting a request with a request body containing 50,000 messages receives a 201 response +Scenario: An API consumer submitting a request with a request body containing 40,000 messages receives a 201 response ===================================================================================================================== -| **Given** the API consumer provides a message body of around 50k messages +| **Given** the API consumer provides a message body of around 40k messages | **When** the request is submitted | **Then** the response is a 201 success | **And** the response takes less than 29 seconds **Asserts** -- Response returns a 201 status code \ No newline at end of file +- Response returns a 201 status code diff --git a/tests/docs/partials/too_large/test_payload_too_large.rst b/tests/docs/partials/too_large/test_payload_too_large.rst new file mode 100644 index 000000000..820ff4008 --- /dev/null +++ b/tests/docs/partials/too_large/test_payload_too_large.rst @@ -0,0 +1,11 @@ +Scenario: An API consumer submitting a request with a request body containing 45,000 messages between 6MB and 10MB receives a 413 status code and response body containing an instance of that error +==================================================================================================================================================================================================== + +| **Given** the API consumer provides a message body of 45,000 messages between 6MB and 10MB +| **When** the request is submitted +| **Then** the response is a 413 Request Entity Too Large +| **And** the response body contains 1 error + +**Asserts** +- Response returns a 413 'Request Entity Too Large' status code +- Response returns 1 error message block diff --git a/tests/docs/partials/too_large/test_too_many_messages.rst b/tests/docs/partials/too_large/test_too_many_messages.rst new file mode 100644 index 000000000..624e33d4a --- /dev/null +++ b/tests/docs/partials/too_large/test_too_many_messages.rst @@ -0,0 +1,11 @@ +Scenario: An API consumer submitting a request with a request body containing 45,001 messages receives a 413 status code and response body containing an instance of that error +=============================================================================================================================================================================== + +| **Given** the API consumer provides a message body of 45,001 messages +| **When** the request is submitted +| **Then** the response is a 413 Request Entity Too Large +| **And** the response body contains 1 error + +**Asserts** +- Response returns a 413 'Request Entity Too Large' status code +- Response returns 1 error message block diff --git a/tests/docs/partials/validation/test_invalid_contact_details_address_lines_too_few.rst b/tests/docs/partials/validation/test_invalid_contact_details_address_lines_too_few.rst index 632c475fa..17090856f 100644 --- a/tests/docs/partials/validation/test_invalid_contact_details_address_lines_too_few.rst +++ b/tests/docs/partials/validation/test_invalid_contact_details_address_lines_too_few.rst @@ -1,14 +1,14 @@ -Scenario: An API consumer submitting a request with invalid address lines (too few) receives a 400 'Missing Value' response +Scenario: An API consumer submitting a request with invalid address lines (too few) receives a 400 'Too few items' response ============================================================================================================================ A valid contact detail must be structured in this format: { address: { lines: [ Value1, Value2 ], postcode: value } } | **Given** the API consumer provides an message body with with too few address lines | **When** the request is submitted -| **Then** the response returns a 400 invalid value error +| **Then** the response returns a 400 too few items error **Asserts** -- Response returns a 400 'Missing value' error +- Response returns a 400 'Too few items' error - Response returns the expected error message body with references to the invalid attribute - Response returns the 'X-Correlation-Id' header if provided diff --git a/tests/docs/partials/validation/test_not_permitted_to_use_contact_details.rst b/tests/docs/partials/validation/test_not_permitted_to_use_contact_details.rst new file mode 100644 index 000000000..6ca6cf235 --- /dev/null +++ b/tests/docs/partials/validation/test_not_permitted_to_use_contact_details.rst @@ -0,0 +1,12 @@ +Scenario: An API consumer submitting a request with an contact details when not allowed receives a 400 'Cannot set contact details' response +====================================================================================================================== + + +| **Given** the API consumer provides a message body with contact details +| **When** the request is submitted +| **Then** the response returns a 400 Cannot set contact details error + +**Asserts** +- Response returns a 400 'Cannot set contact details' error +- Response returns the expected error message body with references to the invalid attribute +- Response returns the 'X-Correlation-Id' header if provided \ No newline at end of file diff --git a/tests/integration/message_batches/create_message_batches/test_field_validation.py b/tests/integration/message_batches/create_message_batches/test_field_validation.py index a5c284a9d..88b12f130 100644 --- a/tests/integration/message_batches/create_message_batches/test_field_validation.py +++ b/tests/integration/message_batches/create_message_batches/test_field_validation.py @@ -519,9 +519,10 @@ def test_invalid_address_contact_details_too_few_lines(bearer_token_int, correla Assertions.assert_error_with_optional_correlation_id( resp, 400, - Generators.generate_error(constants.ERROR_TOO_FEW_ADDRESS_LINES, source={ - "pointer": "/data/attributes/messages/0/recipient/contactDetails/address" - }), + Generators.generate_too_few_items_error_custom_detail( + "/data/attributes/messages/0/recipient/contactDetails/address", + "Too few address lines were provided" + ), correlation_id ) diff --git a/tests/integration/message_batches/create_message_batches/test_performance.py b/tests/integration/message_batches/create_message_batches/test_performance.py index 7549290f8..a76ba2975 100644 --- a/tests/integration/message_batches/create_message_batches/test_performance.py +++ b/tests/integration/message_batches/create_message_batches/test_performance.py @@ -6,7 +6,7 @@ from lib.constants.message_batches_paths import MESSAGE_BATCHES_ENDPOINT from lib.fixtures import * # NOSONAR -NUM_MESSAGES = 50000 +NUM_MESSAGES = 40000 CONTENT_TYPE = "application/json" @@ -17,7 +17,7 @@ def test_create_messages_large_invalid_payload(bearer_token_int): """ data = Generators.generate_valid_create_message_batch_body("int") - # around 50k messages gives us close to our max body size + # around 40k messages gives us close to our max body size data["data"]["attributes"]["messages"] = [] for _ in range(0, NUM_MESSAGES): data["data"]["attributes"]["messages"].append({ @@ -45,7 +45,7 @@ def test_create_messages_large_not_unique_payload(bearer_token_int): """ data = Generators.generate_valid_create_message_batch_body("int") - # around 50k messages gives us close to our max body size + # around 40k messages gives us close to our max body size data["data"]["attributes"]["messages"] = [] reference = str(uuid.uuid1()) for _ in range(0, NUM_MESSAGES): diff --git a/tests/integration/message_batches/create_message_batches/test_too_large.py b/tests/integration/message_batches/create_message_batches/test_too_large.py new file mode 100644 index 000000000..5da133574 --- /dev/null +++ b/tests/integration/message_batches/create_message_batches/test_too_large.py @@ -0,0 +1,92 @@ +import requests +import pytest +import uuid +import json +import lib.constants.constants as constants +from lib import Assertions, Generators +from lib.fixtures import * # NOSONAR +from lib.constants.message_batches_paths import MESSAGE_BATCHES_ENDPOINT + +MESSAGE_LIMIT = 45000 +CONTENT_TYPE = "application/json" + + +@pytest.mark.inttest +def test_too_many_messages(bearer_token_int): + """ + .. include:: ../../partials/too_large/test_too_many_messages.rst + """ + + data = Generators.generate_valid_create_message_batch_body("int") + messages = [] + data["data"]["attributes"]["messages"] = messages + + for i in range(MESSAGE_LIMIT+1): + messages.append({ + "messageReference": str(uuid.uuid1()), + "recipient": { + "nhsNumber": "x" + } + }) + # make sure it's less than 6MB to be a fair test + assert len(json.dumps(data)) < 6000000 + resp = requests.post(f"{constants.INT_URL}{MESSAGE_BATCHES_ENDPOINT}", headers={ + "Authorization": bearer_token_int.value, + "Accept": CONTENT_TYPE, + "Content-Type": CONTENT_TYPE + }, + json=data + ) + + Assertions.assert_error_with_optional_correlation_id( + resp, + 413, + Generators.generate_error( + constants.ERROR_TOO_MANY_ITEMS, + source={"pointer": "/data/attributes/messages"} + ), + None + ) + + +@pytest.mark.inttest +def test_payload_too_large(bearer_token_int): + """ + .. include:: ../../partials/too_large/test_payload_too_large.rst + """ + + data = Generators.generate_valid_create_message_batch_body("int") + valid_recipient = data["data"]["attributes"]["messages"][0]["recipient"] + large_personalisation = {'body': 'x'*32} + messages = [] + data["data"]["attributes"]["messages"] = messages + + for i in range(MESSAGE_LIMIT): + messages.append({ + "messageReference": str(uuid.uuid1()), + "recipient": valid_recipient, + "personalisation": large_personalisation + }) + # make sure it's more than 6MB to be a fair test + payload_length = len(json.dumps(data)) + assert payload_length > 6*(1024**2) + # but less than 10MB to make sure it doesn't fail because apigee didn't accept it + assert payload_length < 10000000 + + resp = requests.post(f"{constants.INT_URL}{MESSAGE_BATCHES_ENDPOINT}", headers={ + "Authorization": bearer_token_int.value, + "Accept": CONTENT_TYPE, + "Content-Type": CONTENT_TYPE + }, + json=data + ) + + Assertions.assert_error_with_optional_correlation_id( + resp, + 413, + Generators.generate_error( + constants.ERROR_TOO_LARGE, + source={"pointer": "/"} + ), + None + ) diff --git a/tests/integration/messages/create_messages/test_field_validation.py b/tests/integration/messages/create_messages/test_field_validation.py index c0aa6f808..320dc33d3 100644 --- a/tests/integration/messages/create_messages/test_field_validation.py +++ b/tests/integration/messages/create_messages/test_field_validation.py @@ -377,9 +377,10 @@ def test_invalid_address_contact_details_too_few_lines(bearer_token_int, correla Assertions.assert_error_with_optional_correlation_id( resp, 400, - Generators.generate_error(ERROR_TOO_FEW_ADDRESS_LINES, source={ - "pointer": "/data/attributes/recipient/contactDetails/address" - }), + Generators.generate_too_few_items_error_custom_detail( + "/data/attributes/recipient/contactDetails/address", + "Too few address lines were provided" + ), correlation_id ) diff --git a/tests/lib/assertions.py b/tests/lib/assertions.py index 6770ddf2b..6c545c8a5 100644 --- a/tests/lib/assertions.py +++ b/tests/lib/assertions.py @@ -138,6 +138,8 @@ def assert_get_message_response_channels(resp, channel_type, channel_status): assert response.get("attributes").get("channels")[c].get("channelStatus") in channel_status assert response.get("attributes").get("channels")[c].get("timestamps") is not None assert response.get("attributes").get("channels")[c].get("routingPlan") is not None + assert response.get("attributes").get("channels")[c].get("cascadeType") in ["primary", "secondary"] + assert response.get("attributes").get("channels")[c].get("cascadeOrder") is not None @staticmethod def assert_201_response_messages(resp, environment): diff --git a/tests/lib/constants/constants.py b/tests/lib/constants/constants.py index ce2b24f27..b88efdb35 100644 --- a/tests/lib/constants/constants.py +++ b/tests/lib/constants/constants.py @@ -171,6 +171,21 @@ def __init__(self, code, status, title, detail, links={}): "This service can only generate application/vnd.api+json or application/json." ) +# request too large +ERROR_TOO_LARGE = Error( + "CM_TOO_LARGE", + "413", + "Request too large", + "Request message was larger than the service limit" +) + +ERROR_TOO_MANY_ITEMS = Error( + "CM_TOO_MANY_ITEMS", + "413", + "Too many items", + "The property at the specified location contains too many items." +) + # unsupported media ERROR_UNSUPPORTED_MEDIA = Error( "CM_UNSUPPORTED_MEDIA", @@ -310,3 +325,10 @@ def __init__(self, code, status, title, detail, links={}): "Too many requests", "This endpoint is currently receiving a high volume of requests and is being rate limited." ) + +ERROR_CANNOT_SET_CONTACT_DETAILS = Error( + "CM_CANNOT_SET_CONTACT_DETAILS", + "400", + "Cannot set contact details", + "Client is not allowed to provide alternative contact details." +) diff --git a/tests/lib/generators.py b/tests/lib/generators.py index 3344a99c3..2aceb1058 100644 --- a/tests/lib/generators.py +++ b/tests/lib/generators.py @@ -158,6 +158,12 @@ def generate_too_few_items_error(pointer): "pointer": pointer }) + @staticmethod + def generate_too_few_items_error_custom_detail(pointer, detail): + return Generators.generate_error_with_custom_detail(constants.ERROR_TOO_FEW_ITEMS, detail, source={ + "pointer": pointer + }) + @staticmethod def generate_access_denied_error(): return Generators.generate_error(constants.ERROR_ACCESS_DENIED, source={ diff --git a/tests/lib/helper.py b/tests/lib/helper.py index 2f3160ba0..39b4bf286 100644 --- a/tests/lib/helper.py +++ b/tests/lib/helper.py @@ -69,6 +69,7 @@ def nhs_app_login_and_view_message(personalisation): browser = playwright.chromium.launch() page = browser.new_page() page.set_default_timeout(15000) + expect.set_options(timeout=15000) page.goto("https://www-onboardingaos.nhsapp.service.nhs.uk/login") diff --git a/tests/production/message_batches/create_message_batches/test_field_validation.py b/tests/production/message_batches/create_message_batches/test_field_validation.py index 40f96db96..9b7c69adf 100644 --- a/tests/production/message_batches/create_message_batches/test_field_validation.py +++ b/tests/production/message_batches/create_message_batches/test_field_validation.py @@ -477,9 +477,10 @@ def test_invalid_address_contact_details_too_few_lines(bearer_token_prod, correl Assertions.assert_error_with_optional_correlation_id( resp, 400, - Generators.generate_error(constants.ERROR_TOO_FEW_ADDRESS_LINES, source={ - "pointer": "/data/attributes/messages/0/recipient/contactDetails/address" - }), + Generators.generate_too_few_items_error_custom_detail( + "/data/attributes/messages/0/recipient/contactDetails/address", + "Too few address lines were provided" + ), correlation_id ) diff --git a/tests/production/message_batches/create_message_batches/test_performance.py b/tests/production/message_batches/create_message_batches/test_performance.py index f2f8bc43e..f434bd438 100644 --- a/tests/production/message_batches/create_message_batches/test_performance.py +++ b/tests/production/message_batches/create_message_batches/test_performance.py @@ -6,7 +6,7 @@ from lib.constants.message_batches_paths import MESSAGE_BATCHES_ENDPOINT from lib.fixtures import * # NOSONAR -NUM_MESSAGES = 50000 +NUM_MESSAGES = 40000 @pytest.mark.prodtest @@ -33,7 +33,7 @@ def test_create_messages_large_invalid_payload(bearer_token_prod): "Authorization": bearer_token_prod.value }, json=data ) - Assertions.assert_error_with_optional_correlation_id(resp, 400, None, None) + Assertions.assert_error_with_optional_correlation_id(resp, 400, None, None) assert len(resp.json().get("errors")) == NUM_MAX_ERRORS diff --git a/tests/production/message_batches/create_message_batches/test_too_large.py b/tests/production/message_batches/create_message_batches/test_too_large.py new file mode 100644 index 000000000..9bc1661b1 --- /dev/null +++ b/tests/production/message_batches/create_message_batches/test_too_large.py @@ -0,0 +1,92 @@ +import requests +import pytest +import uuid +import json +import constants.constants as constants +from lib import Assertions, Generators +from lib.fixtures import * # NOSONAR +from lib.constants.message_batches_paths import MESSAGE_BATCHES_ENDPOINT + +MESSAGE_LIMIT = 45000 +CONTENT_TYPE = "application/json" + + +@pytest.mark.prodtest +def test_too_many_messages(bearer_token_prod): + """ + .. include:: ../../partials/too_large/test_too_many_messages.rst + """ + + data = Generators.generate_valid_create_message_batch_body("prod") + messages = [] + data["data"]["attributes"]["messages"] = messages + + for i in range(MESSAGE_LIMIT+1): + messages.append({ + "messageReference": str(uuid.uuid1()), + "recipient": { + "nhsNumber": "x" + } + }) + # make sure it's less than 6MB to be a fair test + assert len(json.dumps(data)) < 6000000 + resp = requests.post(f"{constants.PROD_URL}{MESSAGE_BATCHES_ENDPOINT}", headers={ + "Authorization": bearer_token_prod.value, + "Accept": CONTENT_TYPE, + "Content-Type": CONTENT_TYPE + }, + json=data + ) + + Assertions.assert_error_with_optional_correlation_id( + resp, + 413, + Generators.generate_error( + constants.ERROR_TOO_MANY_ITEMS, + source={"pointer": "/data/attributes/messages"} + ), + None + ) + + +@pytest.mark.prodtest +def test_payload_too_large(bearer_token_prod): + """ + .. include:: ../../partials/too_large/test_payload_too_large.rst + """ + + data = Generators.generate_valid_create_message_batch_body("prod") + valid_recipient = data["data"]["attributes"]["messages"][0]["recipient"] + large_personalisation = {'body': 'x'*32} + messages = [] + data["data"]["attributes"]["messages"] = messages + + for i in range(MESSAGE_LIMIT): + messages.append({ + "messageReference": str(uuid.uuid1()), + "recipient": valid_recipient, + "personalisation": large_personalisation + }) + # make sure it's more than 6MB to be a fair test + payload_length = len(json.dumps(data)) + assert payload_length > 6*(1024**2) + # but less than 10MB to make sure it doesn't fail because apigee didn't accept it + assert payload_length < 10000000 + + resp = requests.post(f"{constants.PROD_URL}{MESSAGE_BATCHES_ENDPOINT}", headers={ + "Authorization": bearer_token_prod.value, + "Accept": CONTENT_TYPE, + "Content-Type": CONTENT_TYPE + }, + json=data + ) + + Assertions.assert_error_with_optional_correlation_id( + resp, + 413, + Generators.generate_error( + constants.ERROR_TOO_LARGE, + source={"pointer": "/"} + ), + None + ) diff --git a/tests/production/messages/create_messages/test_field_validation.py b/tests/production/messages/create_messages/test_field_validation.py index 241eaea3f..720a60abc 100644 --- a/tests/production/messages/create_messages/test_field_validation.py +++ b/tests/production/messages/create_messages/test_field_validation.py @@ -366,9 +366,10 @@ def test_invalid_address_contact_details_too_few_lines(bearer_token_prod, correl Assertions.assert_error_with_optional_correlation_id( resp, 400, - Generators.generate_error(constants.ERROR_TOO_FEW_ADDRESS_LINES, source={ - "pointer": "/data/attributes/recipient/contactDetails/address" - }), + Generators.generate_too_few_items_error_custom_detail( + "/data/attributes/recipient/contactDetails/address", + "Too few address lines were provided" + ), correlation_id ) diff --git a/tests/sandbox/message_batches/create_message_batches/test_field_validation.py b/tests/sandbox/message_batches/create_message_batches/test_field_validation.py index 83e12d167..30108e56c 100644 --- a/tests/sandbox/message_batches/create_message_batches/test_field_validation.py +++ b/tests/sandbox/message_batches/create_message_batches/test_field_validation.py @@ -549,6 +549,34 @@ def test_null_personalisation(nhsd_apim_proxy_url, correlation_id, personalisati ) +@pytest.mark.sandboxtest +@pytest.mark.parametrize("correlation_id", constants.CORRELATION_ID) +def test_not_permitted_to_use_contact_details(nhsd_apim_proxy_url, correlation_id): + """ + .. include:: ../../partials/validation/test_not_permitted_to_use_contact_details.rst + """ + data = Generators.generate_valid_create_message_batch_body() + data["data"]["attributes"]["messages"][0]["recipient"]["contactDetails"] = {"sms": "07700900002"} + resp = requests.post(f"{nhsd_apim_proxy_url}{MESSAGE_BATCHES_ENDPOINT}", headers={ + **headers, + "X-Correlation-Id": correlation_id, + "Authorization": "notAllowedContactDetailOverride" + }, json=data + ) + + Assertions.assert_error_with_optional_correlation_id( + resp, + 400, + Generators.generate_error( + constants.ERROR_CANNOT_SET_CONTACT_DETAILS, + source={ + "pointer": "/data/attributes/messages/0/recipient/contactDetails" + } + ), + correlation_id + ) + + @pytest.mark.sandboxtest @pytest.mark.parametrize("correlation_id", constants.CORRELATION_ID) def test_invalid_sms_contact_details(nhsd_apim_proxy_url, correlation_id): @@ -669,19 +697,13 @@ def test_invalid_address_contact_details_too_few_lines(nhsd_apim_proxy_url, corr } }) - error = constants.Error( - "CM_MISSING_VALUE", - "400", - "Missing value", - "Too few address lines were provided" - ) - Assertions.assert_error_with_optional_correlation_id( resp, 400, - Generators.generate_error(error, source={ - "pointer": "/data/attributes/messages/0/recipient/contactDetails/address" - }), + Generators.generate_too_few_items_error_custom_detail( + "/data/attributes/messages/0/recipient/contactDetails/address", + "Too few address lines were provided" + ), correlation_id ) diff --git a/tests/sandbox/message_batches/create_message_batches/test_performance.py b/tests/sandbox/message_batches/create_message_batches/test_performance.py index fa6180fd6..70ef1b60f 100644 --- a/tests/sandbox/message_batches/create_message_batches/test_performance.py +++ b/tests/sandbox/message_batches/create_message_batches/test_performance.py @@ -5,7 +5,7 @@ from lib.constants.constants import NUM_MAX_ERRORS from lib.constants.message_batches_paths import MESSAGE_BATCHES_ENDPOINT -NUM_MESSAGES = 50000 +NUM_MESSAGES = 40000 @pytest.mark.sandboxtest @@ -15,7 +15,7 @@ def test_create_messages_large_valid_payload(nhsd_apim_proxy_url): """ data = Generators.generate_valid_create_message_batch_body("sandbox") - # around 50k messages gives us close to our max body size + # around 40k messages gives us close to our max body size data["data"]["attributes"]["messages"] = [] for _ in range(0, NUM_MESSAGES): data["data"]["attributes"]["messages"].append( @@ -45,7 +45,7 @@ def test_create_messages_large_invalid_payload(nhsd_apim_proxy_url): """ data = Generators.generate_valid_create_message_batch_body("sandbox") - # around 50k messages gives us close to our max body size + # around 40k messages gives us close to our max body size data["data"]["attributes"]["messages"] = [] for _ in range(0, NUM_MESSAGES): data["data"]["attributes"]["messages"].append( @@ -74,7 +74,7 @@ def test_create_messages_large_not_unique_payload(nhsd_apim_proxy_url): """ data = Generators.generate_valid_create_message_batch_body("sandbox") - # around 50k messages gives us close to our max body size + # around 40k messages gives us close to our max body size data["data"]["attributes"]["messages"] = [] reference = str(uuid.uuid1()) for _ in range(0, NUM_MESSAGES): diff --git a/tests/sandbox/message_batches/create_message_batches/test_too_large.py b/tests/sandbox/message_batches/create_message_batches/test_too_large.py new file mode 100644 index 000000000..1e654c236 --- /dev/null +++ b/tests/sandbox/message_batches/create_message_batches/test_too_large.py @@ -0,0 +1,48 @@ +import requests +import pytest +import uuid +import json +import lib.constants.constants as constants +from lib import Assertions, Generators +from lib.fixtures import * # NOSONAR +from lib.constants.message_batches_paths import MESSAGE_BATCHES_ENDPOINT + +MESSAGE_LIMIT = 45000 +CONTENT_TYPE = "application/json" + + +@pytest.mark.sandboxtest +def test_too_many_messages(nhsd_apim_proxy_url): + """ + .. include:: ../../partials/too_large/test_too_many_messages.rst + """ + + data = Generators.generate_valid_create_message_batch_body("sandbox") + messages = [] + data["data"]["attributes"]["messages"] = messages + + for i in range(MESSAGE_LIMIT+1): + messages.append({ + "messageReference": str(uuid.uuid1()), + "recipient": { + "nhsNumber": "x" + } + }) + # make sure it's less than 6MB to be a fair test + assert len(json.dumps(data)) < 6000000 + resp = requests.post(f"{nhsd_apim_proxy_url}{MESSAGE_BATCHES_ENDPOINT}", headers={ + "Accept": CONTENT_TYPE, + "Content-Type": CONTENT_TYPE + }, + json=data + ) + + Assertions.assert_error_with_optional_correlation_id( + resp, + 413, + Generators.generate_error( + constants.ERROR_TOO_MANY_ITEMS, + source={"pointer": "/data/attributes/messages"} + ), + None + ) diff --git a/tests/sandbox/messages/create_messages/test_field_validation.py b/tests/sandbox/messages/create_messages/test_field_validation.py index d0b615ebf..4d22e2287 100644 --- a/tests/sandbox/messages/create_messages/test_field_validation.py +++ b/tests/sandbox/messages/create_messages/test_field_validation.py @@ -328,6 +328,33 @@ def test_null_personalisation(nhsd_apim_proxy_url, correlation_id, personalisati ) +@pytest.mark.sandboxtest +@pytest.mark.parametrize("correlation_id", constants.CORRELATION_ID) +def test_not_permitted_to_use_contact_details(nhsd_apim_proxy_url, correlation_id): + """ + .. include:: ../../partials/validation/test_not_permitted_to_use_contact_details.rst + """ + data = Generators.generate_valid_create_message_body() + data["data"]["attributes"]["recipient"]["contactDetails"] = {"sms": "07700900002"} + resp = requests.post(f"{nhsd_apim_proxy_url}{MESSAGES_ENDPOINT}", headers={ + **headers, + "X-Correlation-Id": correlation_id, + "Authorization": "notAllowedContactDetailOverride" + }, json=data + ) + + Assertions.assert_error_with_optional_correlation_id( + resp, + 400, + Generators.generate_error( + constants.ERROR_CANNOT_SET_CONTACT_DETAILS, + source={ + "pointer": "/data/attributes/recipient/contactDetails" + }), + correlation_id + ) + + @pytest.mark.sandboxtest @pytest.mark.parametrize("correlation_id", constants.CORRELATION_ID) def test_invalid_sms_contact_details(nhsd_apim_proxy_url, correlation_id): @@ -433,19 +460,13 @@ def test_invalid_address_contact_details_too_few_lines(nhsd_apim_proxy_url, corr } }) - error = constants.Error( - "CM_MISSING_VALUE", - "400", - "Missing value", - "Too few address lines were provided" - ) - Assertions.assert_error_with_optional_correlation_id( resp, 400, - Generators.generate_error(error, source={ - "pointer": "/data/attributes/recipient/contactDetails/address" - }), + Generators.generate_too_few_items_error_custom_detail( + "/data/attributes/recipient/contactDetails/address", + "Too few address lines were provided" + ), correlation_id )