Skip to content

Commit 2a81bbc

Browse files
authored
Merge pull request #231 from NHSDigital/task/NPA-5191/update-oas-postman-duplicate-relationship
NPA-5191: Update POST QuestionnaireResponse DUPLICATE_RELATIONSHIP
2 parents 6d7ca04 + 93683bb commit 2a81bbc

File tree

8 files changed

+259
-28
lines changed

8 files changed

+259
-28
lines changed

.github/pull_request_template.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ https://nhsd-jira.digital.nhs.uk/browse/NPA-XXXX
2424
<!-- - Any tests added/updated -->
2525
<!-- - Evidence that each acceptance criterion from the Jira ticket is met -->
2626

27-
- [ ] <!-- Add bullet points for testing instructions -->
28-
- [ ] <!-- Add bullet points for testing instructions -->
29-
- [ ] <!-- Add bullet points for testing instructions -->
27+
- <!-- Add bullet points for testing instructions -->
28+
- <!-- Add bullet points for testing instructions -->
29+
- <!-- Add bullet points for testing instructions -->
3030

3131
---
3232

postman/Validate Relationship Service Sandbox.postman_collection.json

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,73 @@
578578
},
579579
"response": []
580580
},
581+
{
582+
"name": "Duplicate relationship",
583+
"event": [
584+
{
585+
"listen": "test",
586+
"script": {
587+
"exec": [
588+
"const expectedResponseBody = {",
589+
" \"issue\": [",
590+
" {",
591+
" \"code\": \"invalid\",",
592+
" \"diagnostics\": \"Proxy role already exists.\",",
593+
" \"details\": {",
594+
" \"coding\": [",
595+
" {",
596+
" \"code\": \"DUPLICATE_RELATIONSHIP\",",
597+
" \"display\": \"Request must be for a new proxy role.\",",
598+
" \"system\": \"https://fhir.nhs.uk/R4/CodeSystem/ValidatedRelationships-ErrorOrWarningCode\",",
599+
" \"version\": \"1\"",
600+
" }",
601+
" ]",
602+
" },",
603+
" \"severity\": \"error\"",
604+
" }",
605+
" ],",
606+
" \"resourceType\": \"OperationOutcome\"",
607+
"}",
608+
"",
609+
"pm.test(\"Status code is 409\", function () {",
610+
" pm.response.to.have.status(409);",
611+
"});",
612+
"",
613+
"pm.test(\"Should have correct response body\", () => {",
614+
" var responseJson = pm.response.json();",
615+
" pm.expect(responseJson).to.eql(expectedResponseBody);",
616+
"});",
617+
""
618+
],
619+
"type": "text/javascript",
620+
"packages": {}
621+
}
622+
}
623+
],
624+
"request": {
625+
"method": "POST",
626+
"header": [],
627+
"body": {
628+
"mode": "raw",
629+
"raw": "{\n \"resourceType\": \"QuestionnaireResponse\",\n \"status\": \"completed\",\n \"authored\": \"2024-07-15T09:43:03.280Z\",\n \"source\": {\n \"type\": \"RelatedPerson\",\n \"identifier\": {\n \"system\": \"https://fhir.nhs.uk/Id/nhs-number\",\n \"value\": \"9000000049\"\n }\n },\n \"subject\": {\n \"type\": \"Patient\",\n \"identifier\": {\n \"system\": \"https://fhir.nhs.uk/Id/nhs-number\",\n \"value\": \"9000000006\"\n }\n },\n \"questionnaire\": \"https://api.service.nhs.uk/validated-relationships/FHIR/R4/Questionnaire/01dc6813-3421-4d14-948d-a4888241add1\",\n \"item\": [\n {\n \"linkId\": \"relatedPerson\",\n \"text\": \"Proxy details\",\n \"item\": [\n {\n \"linkId\": \"relatedPerson_identifier\",\n \"text\": \"NHS number\",\n \"answer\": [\n {\n \"valueString\": \"9000000049\"\n }\n ]\n },\n {\n \"linkId\": \"relatedPerson_basisForAccess\",\n \"text\": \"Basis for Access\",\n \"answer\": [\n {\n \"valueCoding\": {\n \"system\": \"https://fhir.hl7.org.uk/CodeSystem/UKCore-AdditionalPersonRelationshipRole\",\n \"code\": \"Personal\",\n \"display\": \"Personal relationship with the patient\"\n }\n }\n ]\n },\n {\n \"linkId\": \"relatedPerson_relationship\",\n \"text\": \"Relationship\",\n \"answer\": [\n {\n \"valueCoding\": {\n \"system\": \"http://terminology.hl7.org/CodeSystem/v3-RoleCode\",\n \"code\": \"SPS\",\n \"display\": \"Spouse\"\n }\n }\n ]\n }\n ]\n },\n {\n \"linkId\": \"patient\",\n \"text\": \"Patient details\",\n \"item\": [\n {\n \"linkId\": \"patient_identifier\",\n \"text\": \"NHS number\",\n \"answer\": [\n {\n \"valueString\": \"9000000006\"\n }\n ]\n },\n {\n \"linkId\": \"patient_name\",\n \"text\": \"Name\",\n \"item\": [\n {\n \"linkId\": \"patient_name_first\",\n \"text\": \"First name\",\n \"answer\": [\n {\n \"valueString\": \"Jill\"\n }\n ]\n },\n {\n \"linkId\": \"patient_name_family\",\n \"text\": \"Last name\",\n \"answer\": [\n {\n \"valueString\": \"Jones\"\n }\n ]\n }\n ]\n },\n {\n \"linkId\": \"patient_birthDate\",\n \"text\": \"Date of birth\",\n \"answer\": [\n {\n \"valueDate\": \"1965-01-01\"\n }\n ]\n }\n ]\n },\n {\n \"linkId\": \"requestedAccess\",\n \"text\": \"Requested access\",\n \"item\": [\n {\n \"linkId\": \"requestedAccess_accessLevel\",\n \"text\": \"Requested access level\",\n \"answer\": [\n {\n \"valueCoding\": {\n \"system\": \"https://fhir.nhs.uk/CodeSystem/Proxy-Placeholder-RequestedAccess\",\n \"code\": \"APPT\",\n \"display\": \"Appointment Booking\"\n }\n },\n {\n \"valueCoding\": {\n \"system\": \"https://fhir.nhs.uk/CodeSystem/Proxy-Placeholder-RequestedAccess\",\n \"code\": \"VACC\",\n \"display\": \"Vaccination Records\"\n }\n }\n ]\n },\n {\n \"linkId\": \"requestedAccess_reasonsForAccess\",\n \"text\": \"Reason for access\",\n \"answer\": [\n {\n \"valueCoding\": {\n \"system\": \"https://fhir.nhs.uk/CodeSystem/Proxy-Placeholder-ReasonForAccess\",\n \"code\": \"PRAC\",\n \"display\": \"Practical Reasons\"\n }\n }\n ]\n }\n ]\n }\n ]\n}",
630+
"options": {
631+
"raw": {
632+
"language": "json"
633+
}
634+
}
635+
},
636+
"url": {
637+
"raw": "{{api_base_url}}/QuestionnaireResponse",
638+
"host": [
639+
"{{api_base_url}}"
640+
],
641+
"path": [
642+
"QuestionnaireResponse"
643+
]
644+
}
645+
},
646+
"response": []
647+
},
581648
{
582649
"name": "Adult to adult without ability to consent access request",
583650
"event": [
@@ -10183,4 +10250,4 @@
1018310250
"type": "string"
1018410251
}
1018510252
]
10186-
}
10253+
}

sandbox/api/constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@
9393
# POST QuestionnaireResponse
9494
POST_QUESTIONNAIRE_RESPONSE_DIRECTORY = "./api/examples/POST_QuestionnaireResponse/"
9595
POST_QUESTIONNAIRE_RESPONSE__SUCCESS = f"{POST_QUESTIONNAIRE_RESPONSE_DIRECTORY}success.yaml"
96+
POST_QUESTIONNAIRE_RESPONSE__DUPLICATE_RELATIONSHIP_ERROR = (
97+
f"{POST_QUESTIONNAIRE_RESPONSE_DIRECTORY}errors/duplicate_relationship_error.yaml"
98+
)
9699

97100
# GET QuestionnaireResponse
98101
GET_QUESTIONNAIRE_RESPONSE_DIRECTORY = "./api/examples/GET_QuestionnaireResponse/"

sandbox/api/post_questionnaire_response.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
from logging import INFO, basicConfig, getLogger
22
from typing import Union
33

4-
from .constants import INTERNAL_SERVER_ERROR_EXAMPLE, POST_QUESTIONNAIRE_RESPONSE__SUCCESS
4+
from flask import request
5+
6+
from .constants import (
7+
INTERNAL_SERVER_ERROR_EXAMPLE,
8+
POST_QUESTIONNAIRE_RESPONSE__SUCCESS,
9+
POST_QUESTIONNAIRE_RESPONSE__DUPLICATE_RELATIONSHIP_ERROR,
10+
)
511
from .utils import generate_response_from_example
612

713
basicConfig(level=INFO, format="%(asctime)s - %(message)s")
@@ -15,7 +21,24 @@ def post_questionnaire_response_response() -> Union[dict, tuple]:
1521
Union[dict, tuple]: Response for POST /QuestionnaireResponse
1622
"""
1723
try:
18-
return generate_response_from_example(POST_QUESTIONNAIRE_RESPONSE__SUCCESS, 200)
24+
logger.debug("Received request to POST questionnaire response")
25+
# Validate body - beyond the scope of sandbox - assume body is valid for scenario
26+
json = request.get_json()
27+
source_identifier = json.get("source", {}).get("identifier", {}).get("value")
28+
response = None
29+
30+
# Successful questionnaire response
31+
if source_identifier in ["9000000009", "9000000017"]:
32+
response = generate_response_from_example(POST_QUESTIONNAIRE_RESPONSE__SUCCESS, 200)
33+
# Duplicate relationship
34+
elif source_identifier == "9000000049":
35+
response = generate_response_from_example(POST_QUESTIONNAIRE_RESPONSE__DUPLICATE_RELATIONSHIP_ERROR, 409)
36+
else:
37+
# Out of scope errors
38+
raise ValueError("Invalid Request")
39+
40+
return response
41+
1942
except Exception:
2043
logger.exception("POST questionnaire response failed")
2144
return generate_response_from_example(INTERNAL_SERVER_ERROR_EXAMPLE, 500)

sandbox/api/tests/test_post_questionnaire_response.py

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,36 +4,58 @@
44
import pytest
55
from flask import Response
66

7+
from sandbox.api.constants import (
8+
POST_QUESTIONNAIRE_RESPONSE__SUCCESS,
9+
POST_QUESTIONNAIRE_RESPONSE__DUPLICATE_RELATIONSHIP_ERROR,
10+
INTERNAL_SERVER_ERROR_EXAMPLE,
11+
)
12+
713
QUESTIONNAIRE_RESPONSE_API_ENDPOINT = "/FHIR/R4/QuestionnaireResponse"
814

915

1016
@pytest.mark.parametrize(
11-
("url_path", "response_file_name", "status_code"),
17+
("nhs_num", "response_file_name", "status_code"),
1218
[
1319
(
14-
QUESTIONNAIRE_RESPONSE_API_ENDPOINT,
15-
"./api/examples/POST_QuestionnaireResponse/success.yaml",
20+
"9000000009",
21+
POST_QUESTIONNAIRE_RESPONSE__SUCCESS,
22+
200,
23+
),
24+
(
25+
"9000000017",
26+
POST_QUESTIONNAIRE_RESPONSE__SUCCESS,
1627
200,
1728
),
29+
(
30+
"9000000049",
31+
POST_QUESTIONNAIRE_RESPONSE__DUPLICATE_RELATIONSHIP_ERROR,
32+
409,
33+
),
34+
(
35+
"INVALID_NHS_NUMBER",
36+
INTERNAL_SERVER_ERROR_EXAMPLE,
37+
500,
38+
),
1839
],
1940
)
2041
@patch("sandbox.api.post_questionnaire_response.generate_response_from_example")
2142
def test_post_questionnaire_response(
2243
mock_generate_response_from_example: MagicMock,
23-
url_path: str,
44+
nhs_num: str,
2445
response_file_name: str,
25-
client: object,
2646
status_code: int,
47+
client: object,
2748
) -> None:
28-
"""Test related_persons endpoint with identifier only."""
49+
"""Test POST QuestionnaireResponse endpoint with different scenarios."""
2950
# Arrange
3051
mock_generate_response_from_example.return_value = mocked_response = Response(
3152
dumps({"data": "mocked"}),
3253
status=status_code,
3354
content_type="application/json",
3455
)
56+
json = {"resourceType": "QuestionnaireResponse", "source": {"identifier": {"value": nhs_num}}}
3557
# Act
36-
response = client.post(url_path, json={"data": "mocked"})
58+
response = client.post(QUESTIONNAIRE_RESPONSE_API_ENDPOINT, json=json)
3759
# Assert
3860
mock_generate_response_from_example.assert_called_once_with(response_file_name, status_code)
3961
assert response.status_code == status_code
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
QuestionnaireResponseDuplicateRelationship:
2+
summary: Duplicate relationship request
3+
description: |
4+
Example proxy access request that triggers a 409 DUPLICATE_RELATIONSHIP error due to an existing proxy role with NHS number `9000000049` requesting access to act on behalf of a patient (Jill Jones) with NHS number `9000000006`.
5+
6+
Significant details to point out:
7+
8+
- `source.type` should be `RelatedPerson` when a proxy is applying
9+
- `source.identifier.value` should be the NHS number of the user completing the form - this should correlate with the Identity token in the request
10+
- `subject.type` should be `Patient` since it is the patient that is the subject of the application
11+
- `subject.identifier.value` should be the NHS Number of the patient to which the application relates
12+
- `patient` demographics are present in the request as a result of being provided by the applicant
13+
value:
14+
resourceType: "QuestionnaireResponse"
15+
status: "completed"
16+
authored: "2024-07-15T09:43:03.280Z"
17+
source:
18+
type: "RelatedPerson"
19+
identifier:
20+
system: "https://fhir.nhs.uk/Id/nhs-number"
21+
value: "9000000049"
22+
subject:
23+
type: "Patient"
24+
identifier:
25+
system: "https://fhir.nhs.uk/Id/nhs-number"
26+
value: "9000000006"
27+
questionnaire: "https://api.service.nhs.uk/validated-relationships/FHIR/R4/Questionnaire/01dc6813-3421-4d14-948d-a4888241add1"
28+
item:
29+
- linkId: "relatedPerson"
30+
text: "Proxy details"
31+
item:
32+
- linkId: "relatedPerson_identifier"
33+
text: "NHS number"
34+
answer:
35+
- valueString: "9000000049"
36+
- linkId: "relatedPerson_basisForAccess"
37+
text: "Basis for Access"
38+
answer:
39+
- valueCoding:
40+
system: "https://fhir.hl7.org.uk/CodeSystem/UKCore-AdditionalPersonRelationshipRole"
41+
code: "Personal"
42+
display: "Personal relationship with the patient"
43+
- linkId: "relatedPerson_relationship"
44+
text: "Relationship"
45+
answer:
46+
- valueCoding:
47+
system: "http://terminology.hl7.org/CodeSystem/v3-RoleCode"
48+
code: "SPS"
49+
display: "Spouse"
50+
- linkId: "patient"
51+
text: "Patient details"
52+
item:
53+
- linkId: "patient_identifier"
54+
text: "NHS number"
55+
answer:
56+
- valueString: "9000000006"
57+
- linkId: "patient_name"
58+
text: "Name"
59+
item:
60+
- linkId: "patient_name_first"
61+
text: "First name"
62+
answer:
63+
- valueString: "Jill"
64+
- linkId: "patient_name_family"
65+
text: "Last name"
66+
answer:
67+
- valueString: "Jones"
68+
- linkId: "patient_birthDate"
69+
text: "Date of birth"
70+
answer:
71+
- valueDate: "1965-01-01"
72+
- linkId: "requestedAccess"
73+
text: "Requested access"
74+
item:
75+
- linkId: "requestedAccess_accessLevel"
76+
text: "Requested access level"
77+
answer:
78+
- valueCoding:
79+
system: "https://fhir.nhs.uk/CodeSystem/Proxy-Placeholder-RequestedAccess"
80+
code: "APPT"
81+
display: "Appointment Booking"
82+
- valueCoding:
83+
system: "https://fhir.nhs.uk/CodeSystem/Proxy-Placeholder-RequestedAccess"
84+
code: "VACC"
85+
display: "Vaccination Records"
86+
- linkId: "requestedAccess_reasonsForAccess"
87+
text: "Reason for access"
88+
answer:
89+
- valueCoding:
90+
system: "https://fhir.nhs.uk/CodeSystem/Proxy-Placeholder-ReasonForAccess"
91+
code: "PRAC"
92+
display: "Practical Reasons"
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
PostQuestionnaireResponseDuplicateRelationshipError:
2+
summary: Duplicate request for proxy role that already exists
3+
description: Error response for a duplicate proxy role
4+
value:
5+
issue:
6+
- code: invalid
7+
diagnostics: "Proxy role already exists."
8+
details:
9+
coding:
10+
- code: "DUPLICATE_RELATIONSHIP"
11+
display: "Request must be for a new proxy role."
12+
system: "https://fhir.nhs.uk/R4/CodeSystem/ValidatedRelationships-ErrorOrWarningCode"
13+
version: '1'
14+
severity: error
15+
resourceType: "OperationOutcome"

0 commit comments

Comments
 (0)