Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ https://nhsd-jira.digital.nhs.uk/browse/NPA-XXXX
<!-- - Any tests added/updated -->
<!-- - Evidence that each acceptance criterion from the Jira ticket is met -->

- [ ] <!-- Add bullet points for testing instructions -->
- [ ] <!-- Add bullet points for testing instructions -->
- [ ] <!-- Add bullet points for testing instructions -->
- <!-- Add bullet points for testing instructions -->
- <!-- Add bullet points for testing instructions -->
- <!-- Add bullet points for testing instructions -->

---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,73 @@
},
"response": []
},
{
"name": "Duplicate relationship",
"event": [
{
"listen": "test",
"script": {
"exec": [
"const expectedResponseBody = {",
" \"issue\": [",
" {",
" \"code\": \"invalid\",",
" \"diagnostics\": \"Proxy role already exists.\",",
" \"details\": {",
" \"coding\": [",
" {",
" \"code\": \"DUPLICATE_RELATIONSHIP\",",
" \"display\": \"Request must be for a new proxy role.\",",
" \"system\": \"https://fhir.nhs.uk/R4/CodeSystem/ValidatedRelationships-ErrorOrWarningCode\",",
" \"version\": \"1\"",
" }",
" ]",
" },",
" \"severity\": \"error\"",
" }",
" ],",
" \"resourceType\": \"OperationOutcome\"",
"}",
"",
"pm.test(\"Status code is 409\", function () {",
" pm.response.to.have.status(409);",
"});",
"",
"pm.test(\"Should have correct response body\", () => {",
" var responseJson = pm.response.json();",
" pm.expect(responseJson).to.eql(expectedResponseBody);",
"});",
""
],
"type": "text/javascript",
"packages": {}
}
}
],
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"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}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{api_base_url}}/QuestionnaireResponse",
"host": [
"{{api_base_url}}"
],
"path": [
"QuestionnaireResponse"
]
}
},
"response": []
},
{
"name": "Adult to adult without ability to consent access request",
"event": [
Expand Down Expand Up @@ -10183,4 +10250,4 @@
"type": "string"
}
]
}
}
3 changes: 3 additions & 0 deletions sandbox/api/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@
# POST QuestionnaireResponse
POST_QUESTIONNAIRE_RESPONSE_DIRECTORY = "./api/examples/POST_QuestionnaireResponse/"
POST_QUESTIONNAIRE_RESPONSE__SUCCESS = f"{POST_QUESTIONNAIRE_RESPONSE_DIRECTORY}success.yaml"
POST_QUESTIONNAIRE_RESPONSE__DUPLICATE_RELATIONSHIP_ERROR = (
f"{POST_QUESTIONNAIRE_RESPONSE_DIRECTORY}errors/duplicate_relationship_error.yaml"
)

# GET QuestionnaireResponse
GET_QUESTIONNAIRE_RESPONSE_DIRECTORY = "./api/examples/GET_QuestionnaireResponse/"
Expand Down
27 changes: 25 additions & 2 deletions sandbox/api/post_questionnaire_response.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
from logging import INFO, basicConfig, getLogger
from typing import Union

from .constants import INTERNAL_SERVER_ERROR_EXAMPLE, POST_QUESTIONNAIRE_RESPONSE__SUCCESS
from flask import request

from .constants import (
INTERNAL_SERVER_ERROR_EXAMPLE,
POST_QUESTIONNAIRE_RESPONSE__SUCCESS,
POST_QUESTIONNAIRE_RESPONSE__DUPLICATE_RELATIONSHIP_ERROR,
)
from .utils import generate_response_from_example

basicConfig(level=INFO, format="%(asctime)s - %(message)s")
Expand All @@ -15,7 +21,24 @@ def post_questionnaire_response_response() -> Union[dict, tuple]:
Union[dict, tuple]: Response for POST /QuestionnaireResponse
"""
try:
return generate_response_from_example(POST_QUESTIONNAIRE_RESPONSE__SUCCESS, 200)
logger.debug("Received request to POST questionnaire response")
# Validate body - beyond the scope of sandbox - assume body is valid for scenario
json = request.get_json()
source_identifier = json.get("source", {}).get("identifier", {}).get("value")
response = None

# Successful questionnaire response
if source_identifier in ["9000000009", "9000000017"]:
response = generate_response_from_example(POST_QUESTIONNAIRE_RESPONSE__SUCCESS, 200)
# Duplicate relationship
elif source_identifier == "9000000049":
response = generate_response_from_example(POST_QUESTIONNAIRE_RESPONSE__DUPLICATE_RELATIONSHIP_ERROR, 409)
else:
# Out of scope errors
raise ValueError("Invalid Request")

return response

except Exception:
logger.exception("POST questionnaire response failed")
return generate_response_from_example(INTERNAL_SERVER_ERROR_EXAMPLE, 500)
36 changes: 29 additions & 7 deletions sandbox/api/tests/test_post_questionnaire_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,58 @@
import pytest
from flask import Response

from sandbox.api.constants import (
POST_QUESTIONNAIRE_RESPONSE__SUCCESS,
POST_QUESTIONNAIRE_RESPONSE__DUPLICATE_RELATIONSHIP_ERROR,
INTERNAL_SERVER_ERROR_EXAMPLE,
)

QUESTIONNAIRE_RESPONSE_API_ENDPOINT = "/FHIR/R4/QuestionnaireResponse"


@pytest.mark.parametrize(
("url_path", "response_file_name", "status_code"),
("nhs_num", "response_file_name", "status_code"),
[
(
QUESTIONNAIRE_RESPONSE_API_ENDPOINT,
"./api/examples/POST_QuestionnaireResponse/success.yaml",
"9000000009",
POST_QUESTIONNAIRE_RESPONSE__SUCCESS,
200,
),
(
Comment thread
LeoKHoward marked this conversation as resolved.
"9000000017",
POST_QUESTIONNAIRE_RESPONSE__SUCCESS,
200,
),
(
"9000000049",
POST_QUESTIONNAIRE_RESPONSE__DUPLICATE_RELATIONSHIP_ERROR,
409,
),
(
"INVALID_NHS_NUMBER",
INTERNAL_SERVER_ERROR_EXAMPLE,
500,
),
],
)
@patch("sandbox.api.post_questionnaire_response.generate_response_from_example")
def test_post_questionnaire_response(
mock_generate_response_from_example: MagicMock,
url_path: str,
nhs_num: str,
response_file_name: str,
client: object,
status_code: int,
client: object,
) -> None:
"""Test related_persons endpoint with identifier only."""
"""Test POST QuestionnaireResponse endpoint with different scenarios."""
# Arrange
mock_generate_response_from_example.return_value = mocked_response = Response(
dumps({"data": "mocked"}),
status=status_code,
content_type="application/json",
)
json = {"resourceType": "QuestionnaireResponse", "source": {"identifier": {"value": nhs_num}}}
# Act
response = client.post(url_path, json={"data": "mocked"})
response = client.post(QUESTIONNAIRE_RESPONSE_API_ENDPOINT, json=json)
# Assert
mock_generate_response_from_example.assert_called_once_with(response_file_name, status_code)
assert response.status_code == status_code
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
QuestionnaireResponseDuplicateRelationship:
summary: Duplicate relationship request
description: |
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`.

Significant details to point out:

- `source.type` should be `RelatedPerson` when a proxy is applying
- `source.identifier.value` should be the NHS number of the user completing the form - this should correlate with the Identity token in the request
- `subject.type` should be `Patient` since it is the patient that is the subject of the application
- `subject.identifier.value` should be the NHS Number of the patient to which the application relates
- `patient` demographics are present in the request as a result of being provided by the applicant
value:
resourceType: "QuestionnaireResponse"
status: "completed"
authored: "2024-07-15T09:43:03.280Z"
source:
type: "RelatedPerson"
identifier:
system: "https://fhir.nhs.uk/Id/nhs-number"
value: "9000000049"
subject:
type: "Patient"
identifier:
system: "https://fhir.nhs.uk/Id/nhs-number"
value: "9000000006"
questionnaire: "https://api.service.nhs.uk/validated-relationships/FHIR/R4/Questionnaire/01dc6813-3421-4d14-948d-a4888241add1"
item:
- linkId: "relatedPerson"
text: "Proxy details"
item:
- linkId: "relatedPerson_identifier"
text: "NHS number"
answer:
- valueString: "9000000049"
- linkId: "relatedPerson_basisForAccess"
text: "Basis for Access"
answer:
- valueCoding:
system: "https://fhir.hl7.org.uk/CodeSystem/UKCore-AdditionalPersonRelationshipRole"
code: "Personal"
display: "Personal relationship with the patient"
- linkId: "relatedPerson_relationship"
text: "Relationship"
answer:
- valueCoding:
system: "http://terminology.hl7.org/CodeSystem/v3-RoleCode"
code: "SPS"
display: "Spouse"
- linkId: "patient"
text: "Patient details"
item:
- linkId: "patient_identifier"
text: "NHS number"
answer:
- valueString: "9000000006"
- linkId: "patient_name"
text: "Name"
item:
- linkId: "patient_name_first"
text: "First name"
answer:
- valueString: "Jill"
- linkId: "patient_name_family"
text: "Last name"
answer:
- valueString: "Jones"
- linkId: "patient_birthDate"
text: "Date of birth"
answer:
- valueDate: "1965-01-01"
- linkId: "requestedAccess"
text: "Requested access"
item:
- linkId: "requestedAccess_accessLevel"
text: "Requested access level"
answer:
- valueCoding:
system: "https://fhir.nhs.uk/CodeSystem/Proxy-Placeholder-RequestedAccess"
code: "APPT"
display: "Appointment Booking"
- valueCoding:
system: "https://fhir.nhs.uk/CodeSystem/Proxy-Placeholder-RequestedAccess"
code: "VACC"
display: "Vaccination Records"
- linkId: "requestedAccess_reasonsForAccess"
text: "Reason for access"
answer:
- valueCoding:
system: "https://fhir.nhs.uk/CodeSystem/Proxy-Placeholder-ReasonForAccess"
code: "PRAC"
display: "Practical Reasons"
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
PostQuestionnaireResponseDuplicateRelationshipError:
summary: Duplicate request for proxy role that already exists
description: Error response for a duplicate proxy role
value:
issue:
- code: invalid
diagnostics: "Proxy role already exists."
details:
coding:
- code: "DUPLICATE_RELATIONSHIP"
display: "Request must be for a new proxy role."
system: "https://fhir.nhs.uk/R4/CodeSystem/ValidatedRelationships-ErrorOrWarningCode"
version: '1'
severity: error
resourceType: "OperationOutcome"
Loading