diff --git a/docs/tests/post_v1_message-batches/happy_path.md b/docs/tests/post_v1_message-batches/happy_path.md
index 72b45a85f..de6fb4456 100644
--- a/docs/tests/post_v1_message-batches/happy_path.md
+++ b/docs/tests/post_v1_message-batches/happy_path.md
@@ -3,6 +3,24 @@
## 201 - Success
+### Scenario: An API consumer creating a message that has a terminal status of FAILED
+
+**Given** the API consumer provides details for an unreachable recipient in their new message
+
+**When** the request is submitted
+
+**Then** the response is a 201 success
+
+
+**Asserts**
+- Response returns a 201 status code
+- Response body matches expected result
+- Response contains correctly formatted link to new message URI
+- Message in NHS Notify reaches terminal status of FAILED
+- Message has expected failure reason and code
+- Channel has expected failure reason and code
+
+
### Scenario: An API consumer creating a batch of messages with an undefined NHS number receives a 201 response
**Given** the API consumer does not provide an NHS number for a recipient in their new message batch and the allowAnonymousPatient flag is set to true
diff --git a/docs/tests/post_v1_single-message/happy_path.md b/docs/tests/post_v1_single-message/happy_path.md
index b9ab4ed7b..97fbffb8b 100644
--- a/docs/tests/post_v1_single-message/happy_path.md
+++ b/docs/tests/post_v1_single-message/happy_path.md
@@ -3,6 +3,24 @@
These tests target the API endpoint POST /v1/messages testing successful responses when valid data is provided.
+## Scenario: An API consumer creating a message that has a terminal status of FAILED
+
+**Given** the API consumer provides details for an unreachable recipient in their new message
+
+**When** the request is submitted
+
+**Then** the response is a 201 success
+
+
+**Asserts**
+- Response returns a 201 status code
+- Response body matches expected result
+- Response contains correctly formatted link to new message URI
+- Message in NHS Notify reaches terminal status of FAILED
+- Message has expected failure reason and code
+- Channel has expected failure reason and code
+
+
## Scenario: An API consumer creating a message with an undefined NHS number receives a 201 response
**Given** the API consumer does not provide an NHS number for the recipient in their new message and the allowAnonymousPatient flag is set to true
diff --git a/proxies/live/apiproxy/targets/target.xml b/proxies/live/apiproxy/targets/target.xml
index c9c0bfdd3..2fe629d5a 100644
--- a/proxies/live/apiproxy/targets/target.xml
+++ b/proxies/live/apiproxy/targets/target.xml
@@ -18,10 +18,14 @@
true
-
-
-
- {requestpath}
+ {% if ENVIRONMENT_TYPE == 'sandbox' %}
+
+
+
+ {requestpath}
+ {% else %}
+ https://comms-apim.de-rake4.communications.national.nhs.uk
+ {% endif %}
29000
diff --git a/proxies/shared/policies/AssignMessage.MessageBatches.Create.Request.xml b/proxies/shared/policies/AssignMessage.MessageBatches.Create.Request.xml
index 3dfbc673b..a269ec338 100644
--- a/proxies/shared/policies/AssignMessage.MessageBatches.Create.Request.xml
+++ b/proxies/shared/policies/AssignMessage.MessageBatches.Create.Request.xml
@@ -16,10 +16,17 @@
target.copy.pathsuffix
false
-
- requestpath
- /api/v1/send
-
+ {% if ENVIRONMENT_TYPE == 'sandbox' %}
+
+ requestpath
+ /api/v1/send
+
+ {% else %}
+
+ target.url
+ https://comms-apim.de-rake4.communications.national.nhs.uk/api/v1/send
+
+ {% endif %}
%data.payload#
diff --git a/proxies/shared/policies/AssignMessage.Messages.Create.Request.xml b/proxies/shared/policies/AssignMessage.Messages.Create.Request.xml
index 2a8e9cd5c..8344269f8 100644
--- a/proxies/shared/policies/AssignMessage.Messages.Create.Request.xml
+++ b/proxies/shared/policies/AssignMessage.Messages.Create.Request.xml
@@ -16,10 +16,17 @@
target.copy.pathsuffix
false
-
- requestpath
- /api/v1/messages
-
+ {% if ENVIRONMENT_TYPE == 'sandbox' %}
+
+ requestpath
+ /api/v1/messages
+
+ {% else %}
+
+ target.url
+ https://comms-apim.de-rake4.communications.national.nhs.uk/api/v1/messages
+
+ {% endif %}
%data.payload#
diff --git a/proxies/shared/policies/AssignMessage.Messages.GetSingle.Request.xml b/proxies/shared/policies/AssignMessage.Messages.GetSingle.Request.xml
index ce54ec959..da2298f8c 100644
--- a/proxies/shared/policies/AssignMessage.Messages.GetSingle.Request.xml
+++ b/proxies/shared/policies/AssignMessage.Messages.GetSingle.Request.xml
@@ -16,10 +16,17 @@
target.copy.pathsuffix
false
-
- requestpath
- /api/v1/messages/{data.messageId}
-
+ {% if ENVIRONMENT_TYPE == 'sandbox' %}
+
+ requestpath
+ /api/v1/messages/{data.messageId}
+
+ {% else %}
+
+ target.url
+ https://comms-apim.de-rake4.communications.national.nhs.uk/api/v1/messages/{data.messageId}
+
+ {% endif %}
diff --git a/proxies/shared/policies/AssignMessage.NhsAppAccounts.Get.Request.xml b/proxies/shared/policies/AssignMessage.NhsAppAccounts.Get.Request.xml
index 6d71f721a..ee362d0b9 100644
--- a/proxies/shared/policies/AssignMessage.NhsAppAccounts.Get.Request.xml
+++ b/proxies/shared/policies/AssignMessage.NhsAppAccounts.Get.Request.xml
@@ -16,10 +16,17 @@
target.copy.pathsuffix
false
-
- requestpath
- /api/channels/nhsapp/accounts
-
+ {% if ENVIRONMENT_TYPE == 'sandbox' %}
+
+ requestpath
+ /api/channels/nhsapp/accounts
+
+ {% else %}
+
+ target.url
+ https://comms-apim.de-rake4.communications.national.nhs.uk/api/channels/nhsapp/accounts
+
+ {% endif %}
diff --git a/sandbox/messages/2WL45YuHOLATvC3GspEu0oSioux.json b/sandbox/messages/2WL45YuHOLATvC3GspEu0oSioux.json
index 982977cee..c7b0a922e 100644
--- a/sandbox/messages/2WL45YuHOLATvC3GspEu0oSioux.json
+++ b/sandbox/messages/2WL45YuHOLATvC3GspEu0oSioux.json
@@ -12,6 +12,7 @@
"messageReference": "d1cc6082-729e-4e31-8842-ac3279d3f72d",
"messageStatus": "failed",
"messageStatusDescription": "Failed reason: NHS number not found",
+ "messageFailureReasonCode": "MFR_PDSV_0004",
"channels": [
{
"type": "nhsapp",
@@ -20,6 +21,7 @@
"cascadeOrder": 1,
"channelStatus": "failed",
"channelStatusDescription": "Failed reason: NHS number not found",
+ "channelFailureReasonCode": "CFR_PDSV_0004",
"timestamps": {
"created": "2023-10-09T10:31:59Z",
"enriched": "2023-10-09T10:31:59Z",
diff --git a/sandbox/messages/2WL4JtCiOe7l2TT4szwPjNJah3z.json b/sandbox/messages/2WL4JtCiOe7l2TT4szwPjNJah3z.json
index 5918c3c47..524dc421a 100644
--- a/sandbox/messages/2WL4JtCiOe7l2TT4szwPjNJah3z.json
+++ b/sandbox/messages/2WL4JtCiOe7l2TT4szwPjNJah3z.json
@@ -12,6 +12,7 @@
"messageReference": "e21cd9c0-75af-4596-9e9a-57fa7581fe59",
"messageStatus": "failed",
"messageStatusDescription": "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.",
+ "messageFailureReasonCode": "MFR_SUPE_0007",
"channels": [
{
"type": "sms",
@@ -20,6 +21,7 @@
"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.",
+ "channelFailureReasonCode": "CFR_SUPE_0007",
"timestamps": {
"created": "2023-10-09T10:31:59Z",
"enriched": "2023-10-09T10:31:59Z",
diff --git a/sandbox/messages/2WL4MOuSeCTODDAScFG7KIq9a5r.json b/sandbox/messages/2WL4MOuSeCTODDAScFG7KIq9a5r.json
index 616392db6..a8ee0d575 100644
--- a/sandbox/messages/2WL4MOuSeCTODDAScFG7KIq9a5r.json
+++ b/sandbox/messages/2WL4MOuSeCTODDAScFG7KIq9a5r.json
@@ -12,6 +12,7 @@
"messageReference": "b9603694-5542-4043-b905-1e570b26d4c2",
"messageStatus": "failed",
"messageStatusDescription": "Failed reason: We had an unexpected error while sending the letter to our printing provider.",
+ "messageFailureReasonCode": "MFR_SUPE_0007",
"channels": [
{
"type": "letter",
@@ -20,6 +21,7 @@
"cascadeOrder": 1,
"channelStatus": "failed",
"channelStatusDescription": "Failed reason: We had an unexpected error while sending the letter to our printing provider.",
+ "channelFailureReasonCode": "CFR_SUPE_0007",
"timestamps": {
"created": "2023-10-09T10:31:59Z",
"enriched": "2023-10-09T10:31:59Z",
diff --git a/sandbox/messages/2WL4QcKGjNHvHFQeKgYbapZJGHK.json b/sandbox/messages/2WL4QcKGjNHvHFQeKgYbapZJGHK.json
index f974985db..7e95f8003 100644
--- a/sandbox/messages/2WL4QcKGjNHvHFQeKgYbapZJGHK.json
+++ b/sandbox/messages/2WL4QcKGjNHvHFQeKgYbapZJGHK.json
@@ -12,6 +12,7 @@
"messageReference": "4fbc5f73-0f7c-4304-b848-5c6ccb781c84",
"messageStatus": "failed",
"messageStatusDescription": "Failed reason: Patient does not exist in PDS",
+ "messageFailureReasonCode": "MFR_PDSV_0006",
"timestamps": {
"created": "2023-10-09T10:31:37Z",
"enriched": "2023-10-09T10:31:40Z",
diff --git a/sandbox/messages/2WL4W9RgbuLLByXdR77H8vjKSDd.json b/sandbox/messages/2WL4W9RgbuLLByXdR77H8vjKSDd.json
index 175de6674..af791ce1a 100644
--- a/sandbox/messages/2WL4W9RgbuLLByXdR77H8vjKSDd.json
+++ b/sandbox/messages/2WL4W9RgbuLLByXdR77H8vjKSDd.json
@@ -12,6 +12,7 @@
"messageReference": "0397b134-e30d-4fb1-9060-0d204b0ac340",
"messageStatus": "failed",
"messageStatusDescription": "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.",
+ "messageFailureReasonCode": "MFR_SUPE_0007",
"channels": [
{
"type": "email",
@@ -20,6 +21,7 @@
"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.",
+ "channelFailureReasonCode": "CFR_SUPE_0007",
"timestamps": {
"created": "2023-10-09T10:31:59Z",
"enriched": "2023-10-09T10:31:59Z",
diff --git a/sandbox/messages/2WL4cPfBRuPKa44JxhyXYf2kr1E.json b/sandbox/messages/2WL4cPfBRuPKa44JxhyXYf2kr1E.json
index 4a3a5f5f2..47dab6ed4 100644
--- a/sandbox/messages/2WL4cPfBRuPKa44JxhyXYf2kr1E.json
+++ b/sandbox/messages/2WL4cPfBRuPKa44JxhyXYf2kr1E.json
@@ -12,6 +12,7 @@
"messageReference": "d85be1f0-1593-41b0-94d2-beae6ee7dcc0",
"messageStatus": "failed",
"messageStatusDescription": "Failed reason: patient record invalidated",
+ "messageFailureReasonCode": "MFR_PDSV_0008",
"timestamps": {
"created": "2023-10-09T10:31:37Z",
"enriched": "2023-10-09T10:31:45Z",
diff --git a/sandbox/messages/2WL4ibGoigirgbH4yQwqbPaJXyQ.json b/sandbox/messages/2WL4ibGoigirgbH4yQwqbPaJXyQ.json
index a6cf99899..ff33d5031 100644
--- a/sandbox/messages/2WL4ibGoigirgbH4yQwqbPaJXyQ.json
+++ b/sandbox/messages/2WL4ibGoigirgbH4yQwqbPaJXyQ.json
@@ -12,6 +12,7 @@
"messageReference": "2f2e071c-9fba-4dde-bf8d-8dd0c8060300",
"messageStatus": "failed",
"messageStatusDescription": "Failed reason: Patient is formally dead",
+ "messageFailureReasonCode": "MFR_PDSV_0112",
"timestamps": {
"created": "2023-10-09T10:31:37Z",
"enriched": "2023-10-09T10:31:59Z",
diff --git a/sandbox/messages/2WL4khXn32dOOj4bB4bi8Tkllrq.json b/sandbox/messages/2WL4khXn32dOOj4bB4bi8Tkllrq.json
index caa3424f1..7c7918a15 100644
--- a/sandbox/messages/2WL4khXn32dOOj4bB4bi8Tkllrq.json
+++ b/sandbox/messages/2WL4khXn32dOOj4bB4bi8Tkllrq.json
@@ -12,6 +12,7 @@
"messageReference": "68cc8e05-c404-405a-a41d-92cdb8afd9e3",
"messageStatus": "failed",
"messageStatusDescription": "Failed reason: Patient is informally dead",
+ "messageFailureReasonCode": "MFR_PDSV_0111",
"timestamps": {
"created": "2023-10-09T10:31:37Z",
"enriched": "2023-10-09T10:31:59Z",
diff --git a/sandbox/messages/2WL4mvx6eBva8dcIK60VEGIfcgZ.json b/sandbox/messages/2WL4mvx6eBva8dcIK60VEGIfcgZ.json
index b91abd6e7..04e3a74a1 100644
--- a/sandbox/messages/2WL4mvx6eBva8dcIK60VEGIfcgZ.json
+++ b/sandbox/messages/2WL4mvx6eBva8dcIK60VEGIfcgZ.json
@@ -12,6 +12,7 @@
"messageReference": "1791d23b-6f5d-4153-ab45-637bd7711d41",
"messageStatus": "failed",
"messageStatusDescription": "Failed reason: Patient has exit code",
+ "messageFailureReasonCode": "MFR_PDSV_0110",
"timestamps": {
"created": "2023-10-09T10:31:37Z",
"enriched": "2023-10-09T10:31:59Z",
diff --git a/sandbox/messages/2WL4pcpmKxYYblm3PSKVEqcmEPX.json b/sandbox/messages/2WL4pcpmKxYYblm3PSKVEqcmEPX.json
index 1a0e5d2a4..57d589cfd 100644
--- a/sandbox/messages/2WL4pcpmKxYYblm3PSKVEqcmEPX.json
+++ b/sandbox/messages/2WL4pcpmKxYYblm3PSKVEqcmEPX.json
@@ -12,6 +12,7 @@
"messageReference": "2c98d875-3843-4919-9c79-2034e957e251",
"messageStatus": "failed",
"messageStatusDescription": "Failed reason: Name response is invalid: Contact detail is malformed",
+ "messageFailureReasonCode": "MFR_PDSV_0001",
"timestamps": {
"created": "2023-10-09T10:31:37Z",
"enriched": "2023-10-09T10:31:59Z",
diff --git a/sandbox/messages/2WL4vHFZzInmaYwq6HRNDqTX8dH.json b/sandbox/messages/2WL4vHFZzInmaYwq6HRNDqTX8dH.json
index c0ce57f50..90564ad92 100644
--- a/sandbox/messages/2WL4vHFZzInmaYwq6HRNDqTX8dH.json
+++ b/sandbox/messages/2WL4vHFZzInmaYwq6HRNDqTX8dH.json
@@ -12,6 +12,7 @@
"messageReference": "d1cc6082-729e-4e31-8842-ac3279d3f72d",
"messageStatus": "failed",
"messageStatusDescription": "Failed reason: Contact detail is missing",
+ "messageFailureReasonCode": "MFR_PDSV_0002",
"channels": [
{
"type": "nhsapp",
@@ -20,6 +21,7 @@
"cascadeOrder": 1,
"channelStatus": "failed",
"channelStatusDescription": "Failed reason: Contact detail is missing",
+ "channelFailureReasonCode": "CFR_PDSV_0002",
"timestamps": {
"created": "2023-10-09T10:31:59Z",
"enriched": "2023-10-09T10:31:59Z",
diff --git a/sandbox/messages/2WL4xcWKvz4F32g0htBEl8DINzn.json b/sandbox/messages/2WL4xcWKvz4F32g0htBEl8DINzn.json
index a68314f68..56fa095b3 100644
--- a/sandbox/messages/2WL4xcWKvz4F32g0htBEl8DINzn.json
+++ b/sandbox/messages/2WL4xcWKvz4F32g0htBEl8DINzn.json
@@ -12,6 +12,7 @@
"messageReference": "0b88296d-b7e3-4697-be86-7424444a205c",
"messageStatus": "failed",
"messageStatusDescription": "Failed reason: Contact detail is missing",
+ "messageFailureReasonCode": "MFR_PDSV_0002",
"channels": [
{
"type": "email",
@@ -20,6 +21,7 @@
"cascadeOrder": 1,
"channelStatus": "failed",
"channelStatusDescription": "Failed reason: Contact detail is missing",
+ "channelFailureReasonCode": "CFR_PDSV_0002",
"timestamps": {
"created": "2023-10-09T10:31:59Z",
"failed": "2023-10-09T10:52:12Z"
diff --git a/sandbox/messages/2WL50w41YaZXcyFCNT346LY8rlz.json b/sandbox/messages/2WL50w41YaZXcyFCNT346LY8rlz.json
index 62989c7e2..4d20d03cb 100644
--- a/sandbox/messages/2WL50w41YaZXcyFCNT346LY8rlz.json
+++ b/sandbox/messages/2WL50w41YaZXcyFCNT346LY8rlz.json
@@ -12,6 +12,7 @@
"messageReference": "7b2be2cf-7e59-4b15-b199-fd4ba0cb9f19",
"messageStatus": "failed",
"messageStatusDescription": "Failed reason: Contact detail is missing",
+ "messageFailureReasonCode": "MFR_PDSV_0002",
"channels": [
{
"type": "sms",
@@ -20,6 +21,7 @@
"cascadeOrder": 1,
"channelStatus": "failed",
"channelStatusDescription": "Failed reason: Contact detail is missing",
+ "channelFailureReasonCode": "CFR_PDSV_0002",
"timestamps": {
"created": "2023-10-09T10:31:59Z",
"enriched": "2023-10-09T10:31:59Z",
diff --git a/sandbox/messages/2WL54x0XQjCbWeE5lN0DKQZcokU.json b/sandbox/messages/2WL54x0XQjCbWeE5lN0DKQZcokU.json
index 130c573ca..eaaae732c 100644
--- a/sandbox/messages/2WL54x0XQjCbWeE5lN0DKQZcokU.json
+++ b/sandbox/messages/2WL54x0XQjCbWeE5lN0DKQZcokU.json
@@ -12,6 +12,7 @@
"messageReference": "1226417c-f683-4d68-afe8-2d52321a6b70",
"messageStatus": "failed",
"messageStatusDescription": "Failed reason: Contact detail is missing",
+ "messageFailureReasonCode": "MFR_PDSV_0002",
"channels": [
{
"type": "letter",
@@ -20,6 +21,7 @@
"cascadeOrder": 1,
"channelStatus": "failed",
"channelStatusDescription": "Failed reason: Contact detail is missing",
+ "channelFailureReasonCode": "CFR_PDSV_0002",
"timestamps": {
"created": "2023-10-09T10:31:59Z",
"enriched": "2023-10-09T10:31:59Z",
diff --git a/sandbox/messages/2WL5eDefrbW31uw1il84WdF8ndH.json b/sandbox/messages/2WL5eDefrbW31uw1il84WdF8ndH.json
index ad23c63ff..212b1f1a8 100644
--- a/sandbox/messages/2WL5eDefrbW31uw1il84WdF8ndH.json
+++ b/sandbox/messages/2WL5eDefrbW31uw1il84WdF8ndH.json
@@ -19,6 +19,7 @@
"cascadeOrder": 1,
"channelStatus": "failed",
"channelStatusDescription": "Failed reason: NHS number not found",
+ "channelFailureReasonCode": "CFR_PDSV_0004",
"timestamps": {
"created": "2023-10-09T10:31:59Z",
"enriched": "2023-10-09T10:39:37Z",
diff --git a/sandbox/messages/2WL5eYSWGzCHlGmzNxuqVusPxDg.json b/sandbox/messages/2WL5eYSWGzCHlGmzNxuqVusPxDg.json
index c47119991..3db4df677 100644
--- a/sandbox/messages/2WL5eYSWGzCHlGmzNxuqVusPxDg.json
+++ b/sandbox/messages/2WL5eYSWGzCHlGmzNxuqVusPxDg.json
@@ -19,6 +19,7 @@
"cascadeOrder": 1,
"channelStatus": "failed",
"channelStatusDescription": "Failed reason: NHS number not found",
+ "channelFailureReasonCode": "CFR_PDSV_0004",
"timestamps": {
"created": "2023-10-09T10:31:59Z",
"enriched": "2023-10-09T10:39:37Z",
@@ -37,6 +38,7 @@
"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.",
+ "channelFailureReasonCode": "CFR_SUPE_0007",
"timestamps": {
"created": "2023-10-09T10:31:59Z",
"enriched": "2023-10-09T10:39:37Z",
diff --git a/sandbox/messages/2WL5f8j4XVxUPgd3OOqXVYvVFIW.json b/sandbox/messages/2WL5f8j4XVxUPgd3OOqXVYvVFIW.json
index 9fb11c5de..126ce0165 100644
--- a/sandbox/messages/2WL5f8j4XVxUPgd3OOqXVYvVFIW.json
+++ b/sandbox/messages/2WL5f8j4XVxUPgd3OOqXVYvVFIW.json
@@ -19,6 +19,7 @@
"cascadeOrder": 1,
"channelStatus": "failed",
"channelStatusDescription": "Failed reason: NHS number not found",
+ "channelFailureReasonCode": "CFR_PDSV_0004",
"timestamps": {
"created": "2023-10-09T10:31:59Z",
"enriched": "2023-10-09T10:39:37Z",
@@ -37,6 +38,7 @@
"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.",
+ "channelFailureReasonCode": "CFR_SUPE_0007",
"timestamps": {
"created": "2023-10-09T10:31:59Z",
"enriched": "2023-10-09T10:39:37Z",
@@ -55,6 +57,7 @@
"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.",
+ "channelFailureReasonCode": "CFR_SUPE_0007",
"timestamps": {
"created": "2023-10-09T10:31:59Z",
"enriched": "2023-10-09T10:39:37Z",
diff --git a/sandbox/messages/2bBBpsiMl2rnQt99qm6JLZ6w1vq.json b/sandbox/messages/2bBBpsiMl2rnQt99qm6JLZ6w1vq.json
index 9887387c4..2bfa6ea3d 100644
--- a/sandbox/messages/2bBBpsiMl2rnQt99qm6JLZ6w1vq.json
+++ b/sandbox/messages/2bBBpsiMl2rnQt99qm6JLZ6w1vq.json
@@ -12,6 +12,7 @@
"messageReference": "1226417c-f683-4d68-afe8-2d52321a6b70",
"messageStatus": "failed",
"messageStatusDescription": "Failed reason: Contact detail is missing",
+ "messageFailureReasonCode": "MFR_PDSV_0002",
"channels": [
{
"type": "sms",
@@ -20,6 +21,7 @@
"cascadeOrder": 1,
"channelStatus": "failed",
"channelStatusDescription": "Failed reason: Contact detail is missing",
+ "channelFailureReasonCode": "CFR_PDSV_0002",
"timestamps": {
"created": "2023-10-09T10:31:59Z",
"failed": "2023-10-09T10:52:12Z"
diff --git a/specification/documentation/GetMessage.md b/specification/documentation/GetMessage.md
index e60a98f3e..c0b97f44d 100644
--- a/specification/documentation/GetMessage.md
+++ b/specification/documentation/GetMessage.md
@@ -22,6 +22,7 @@ Key values that are returned for each of these channels are:
* `type` - the channel type
* `channelStatus` - the status of that channel
* `channelStatusDescription` - the channel status description
+* `channelFailureReasonCode` - the channel failed reason code
* `supplierStatus` - the status provided by the supplier for this channel
* `retryCount` - the number of times we have attempted delivery
* `timestamps` - timestamps of key events
diff --git a/specification/schemas/components/MessageStatus.yaml b/specification/schemas/components/MessageStatus.yaml
index dd9f9891a..9ec05ebe4 100644
--- a/specification/schemas/components/MessageStatus.yaml
+++ b/specification/schemas/components/MessageStatus.yaml
@@ -18,7 +18,11 @@ properties:
messageStatusDescription:
type: string
description: If there is extra information associated with the status of this message, it is provided here.
- example: " "
+ example: "Failed reason: Contact detail is missing"
+ messageFailureReasonCode:
+ type: string
+ description: If there is a failed reason code associated with this message, it is provided here.
+ example: "MFR_PDSV_0002"
channels:
type: array
minItems: 0
diff --git a/specification/schemas/components/SupplierStatus.yaml b/specification/schemas/components/SupplierStatus.yaml
index 3aac5052f..fc804ab7f 100644
--- a/specification/schemas/components/SupplierStatus.yaml
+++ b/specification/schemas/components/SupplierStatus.yaml
@@ -30,7 +30,11 @@ properties:
channelStatusDescription:
type: string
description: If there is extra information associated with the status of this channel, it is provided here.
- example: " "
+ example: "Failed reason: Contact detail is missing"
+ channelFailureReasonCode:
+ type: string
+ description: If there is a failed reason code associated with this channel, it is provided here.
+ example: "CFR_PDSV_0002"
supplierStatus:
$ref: ../enums/SupplierStatusEnum.yaml
timestamp:
diff --git a/specification/schemas/responses/MessageResponse.yaml b/specification/schemas/responses/MessageResponse.yaml
index 56251854a..a3fc84126 100644
--- a/specification/schemas/responses/MessageResponse.yaml
+++ b/specification/schemas/responses/MessageResponse.yaml
@@ -25,7 +25,11 @@ properties:
messageStatusDescription:
type: string
description: If there is extra information associated with the status of this message, it is provided here.
- example: ""
+ example: "Failed reason: Contact detail is missing"
+ messageFailureReasonCode:
+ type: string
+ description: If there is a failed reason code associated with this message, it is provided here.
+ example: "MFR_PDSV_0002"
channels:
type: array
description: |-
@@ -54,7 +58,11 @@ properties:
channelStatusDescription:
type: string
description: If there is extra information associated with the status of this channel, it is provided here.
- example: ""
+ example: "Failed reason: Contact detail is missing"
+ channelFailureReasonCode:
+ type: string
+ description: If there is a failed reason code associated with this channel, it is provided here.
+ example: "CFR_PDSV_0002"
supplierStatus:
description: |-
The current status of this message within the channel at the time this response was generated.
diff --git a/tests/api/message_batches/test_201_success.py b/tests/api/message_batches/test_201_success.py
index 8f738fa09..c76b1dca2 100644
--- a/tests/api/message_batches/test_201_success.py
+++ b/tests/api/message_batches/test_201_success.py
@@ -1,6 +1,6 @@
import requests
import pytest
-from lib import Assertions, Generators
+from lib import Assertions, Helper, Generators
from lib.fixtures import * # NOSONAR
from lib.constants.constants import VALID_ACCEPT_HEADERS, DEFAULT_CONTENT_TYPE, \
VALID_CONTENT_TYPE_HEADERS, VALID_NHS_NUMBER, VALID_SMS_NUMBERS
@@ -125,3 +125,52 @@ def test_201_message_batch_valid_contact_details(
json=data,
)
Assertions.assert_201_response(resp, data)
+
+
+@pytest.mark.test
+@pytest.mark.devtest
+def test_201_message_batch_terminal_failed_status(
+ url,
+ bearer_token
+):
+ """
+ .. include:: ../partials/happy_path/test_201_message_batch_terminal_failed_status.rst
+ """
+ headers = Generators.generate_valid_headers(bearer_token.value)
+ data = Generators.generate_valid_create_message_batch_body("dev")
+ data["data"]["attributes"]["messages"][0]["recipient"].pop("nhsNumber", None)
+ data["data"]["attributes"]["messages"][0]["recipient"][
+ "contactDetails"
+ ] = {
+ "name": {
+ "firstName": "name",
+ "lastName": "last"
+ }
+ }
+
+ resp = requests.post(
+ f"{url}{MESSAGE_BATCHES_ENDPOINT}",
+ headers=headers,
+ json=data,
+ )
+ Assertions.assert_201_response(resp, data)
+
+ message_id = resp.json().get("data").get("attributes").get("messages")[0].get("id")
+
+ Helper.poll_get_message(url=url, headers=headers, message_id=message_id, end_state="failed")
+
+ resp = Helper.get_message(url, headers, message_id)
+
+ Assertions.assert_get_message_status(
+ resp,
+ "failed",
+ "Failed reason: No valid request item plans were generated",
+ "MFR_CFGV_0005"
+ )
+
+ Assertions.assert_get_message_response_channels(
+ resp,
+ "failed",
+ "Failed reason: Not registered with NHS App",
+ "CFR_SUPE_0001"
+ )
diff --git a/tests/api/single_message/test_201_success.py b/tests/api/single_message/test_201_success.py
index 73f539067..dd6826aae 100644
--- a/tests/api/single_message/test_201_success.py
+++ b/tests/api/single_message/test_201_success.py
@@ -1,6 +1,6 @@
import requests
import pytest
-from lib import Assertions, Generators
+from lib import Assertions, Helper, Generators
from lib.fixtures import * # NOSONAR
from lib.constants.messages_paths import MESSAGES_ENDPOINT
from lib.constants.constants import VALID_ACCEPT_HEADERS, DEFAULT_CONTENT_TYPE, \
@@ -113,3 +113,48 @@ def test_201_message_valid_contact_details(url, bearer_token, valid_sms_numbers)
)
Assertions.assert_201_response_messages(resp, url)
+
+
+@pytest.mark.test
+@pytest.mark.devtest
+def test_201_message_terminal_failed_status(url, bearer_token):
+ """
+ .. include:: ../partials/happy_path/test_201_messages_terminal_failed_status.rst
+ """
+ headers = Generators.generate_valid_headers(bearer_token.value)
+ data = Generators.generate_valid_create_message_body("dev")
+ data["data"]["attributes"]["recipient"].pop("nhsNumber", None)
+ data["data"]["attributes"]["recipient"]["contactDetails"] = {
+ "name": {
+ "firstName": "name",
+ "lastName": "last"
+ }
+ }
+
+ resp = requests.post(
+ f"{url}{MESSAGES_ENDPOINT}",
+ headers=headers,
+ json=data
+ )
+
+ Assertions.assert_201_response_messages(resp, url)
+
+ message_id = resp.json().get("data").get("id")
+
+ Helper.poll_get_message(url=url, headers=headers, message_id=message_id, end_state="failed")
+
+ resp = Helper.get_message(url, headers, message_id)
+
+ Assertions.assert_get_message_status(
+ resp,
+ "failed",
+ "Failed reason: No valid request item plans were generated",
+ "MFR_CFGV_0005"
+ )
+
+ Assertions.assert_get_message_response_channels(
+ resp,
+ "failed",
+ "Failed reason: Not registered with NHS App",
+ "CFR_SUPE_0001"
+ )
diff --git a/tests/docs/partials/happy_path/test_201_message_batch_terminal_failed_status.rst b/tests/docs/partials/happy_path/test_201_message_batch_terminal_failed_status.rst
new file mode 100644
index 000000000..afe4bf000
--- /dev/null
+++ b/tests/docs/partials/happy_path/test_201_message_batch_terminal_failed_status.rst
@@ -0,0 +1,14 @@
+Scenario: An API consumer creating a message that has a terminal status of FAILED
+=================================================================================
+
+| **Given** the API consumer provides details for an unreachable recipient in their new message
+| **When** the request is submitted
+| **Then** the response is a 201 success
+
+**Asserts**
+- Response returns a 201 status code
+- Response body matches expected result
+- Response contains correctly formatted link to new message URI
+- Message in NHS Notify reaches terminal status of FAILED
+- Message has expected failure reason and code
+- Channel has expected failure reason and code
diff --git a/tests/docs/partials/happy_path/test_201_messages_terminal_failed_status.rst b/tests/docs/partials/happy_path/test_201_messages_terminal_failed_status.rst
new file mode 100644
index 000000000..afe4bf000
--- /dev/null
+++ b/tests/docs/partials/happy_path/test_201_messages_terminal_failed_status.rst
@@ -0,0 +1,14 @@
+Scenario: An API consumer creating a message that has a terminal status of FAILED
+=================================================================================
+
+| **Given** the API consumer provides details for an unreachable recipient in their new message
+| **When** the request is submitted
+| **Then** the response is a 201 success
+
+**Asserts**
+- Response returns a 201 status code
+- Response body matches expected result
+- Response contains correctly formatted link to new message URI
+- Message in NHS Notify reaches terminal status of FAILED
+- Message has expected failure reason and code
+- Channel has expected failure reason and code
diff --git a/tests/lib/assertions.py b/tests/lib/assertions.py
index 5a36ef5aa..d76cbbafd 100644
--- a/tests/lib/assertions.py
+++ b/tests/lib/assertions.py
@@ -139,24 +139,25 @@ def assert_200_response_message(resp, base_url):
assert response.get("links").get("self").endswith(f"/v1/messages/{response.get('id')}")
@staticmethod
- def assert_get_message_status(resp, status, failure_reason=None):
+ def assert_get_message_status(resp, status, failure_reason=None, failure_reason_code=None):
response = resp.json().get("data")
assert response.get("attributes").get("messageStatus") == status
if status == "failed":
assert response.get("attributes").get("messageStatusDescription") == failure_reason
+ assert response.get("attributes").get("messageFailureReasonCode") == failure_reason_code
@staticmethod
- def assert_get_message_response_channels(resp, channel_type, channel_status):
+ def assert_get_message_response_channels(resp, status, failure_reason=None, failure_reason_code=None):
response = resp.json().get("data")
channels = response.get("attributes").get("channels")
for c in range(len(channels)):
- assert response.get("attributes").get("channels")[c].get("type") in channel_type
- assert response.get("attributes").get("channels")[c].get("retryCount") == 1
- 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
+ assert response.get("attributes").get("channels")[c].get("channelStatus") == status
+ if status == "failed":
+ assert response.get("attributes").get("channels")[c].get("channelStatusDescription") == failure_reason
+ assert (
+ response.get("attributes").get("channels")[c].get("channelFailureReasonCode")
+ == failure_reason_code
+ )
@staticmethod
def assert_201_response_messages(resp, environment):
diff --git a/tests/lib/helper.py b/tests/lib/helper.py
index b958fdfa1..817523a20 100644
--- a/tests/lib/helper.py
+++ b/tests/lib/helper.py
@@ -45,10 +45,6 @@ def poll_get_message(url, headers, message_id, end_state="delivered", poll_time=
message_status = get_message_response.json().get("data").get("attributes").get("messageStatus")
time.sleep(10)
- if message_status == "failed":
- raise ValueError(f"Request ended up in an unexpected state. \
- Message status: {message_status}, Message ID: {message_id}")
-
if message_status != end_state:
raise TimeoutError(f"Request took too long to be processed. \
Message status: {message_status}, Message ID: {message_id}")