Skip to content

Commit

Permalink
Merge pull request #79 from NHSDigital/NPA-2676_Update_Sandbox_And_Po…
Browse files Browse the repository at this point in the history
…stman_Collection

NPA-2676 Add QuestionnaireResponse to Sandbox and Postman Collection
  • Loading branch information
JackPlowman authored May 20, 2024
2 parents 59e9002 + f3e219c commit fc6f31b
Show file tree
Hide file tree
Showing 12 changed files with 225 additions and 97 deletions.
160 changes: 89 additions & 71 deletions poetry.lock

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"info": {
"_postman_id": "a7ded7b6-5327-41f5-bf02-dcfe56d258c8",
"_postman_id": "85ee4695-8528-4e6e-82a6-29c4bea26a6d",
"name": "Validate Relationship Service Sandbox",
"description": "Example usage of the Validate Relationship Service (VRS) sandbox.\n\nFull specification is available at [https://digital.nhs.uk/developer/api-catalogue/validated-relationship-service](https://digital.nhs.uk/developer/api-catalogue/validated-relationship-service)",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
"_exporter_id": "21394218"
"_exporter_id": "34042403"
},
"item": [
{
Expand Down Expand Up @@ -202,6 +202,43 @@
}
],
"description": "Examples of how to utilise the API to list relationships for a given NHS record."
},
{
"name": "Questionnaire Response",
"item": [
{
"name": "Questionnaire Response",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/fhir+json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"resourceType\": \"QuestionnaireResponse\",\n \"status\": \"completed\",\n \"authored\": \"2024-03-24T16:32:12.363Z\",\n \"source\": { \n \"type\": \"RelatedPerson\",\n \"identifier\": \"9000000001\" \n },\n \"item\": [\n {\n \"linkId\": \"proxy_details\",\n \"text\": \"Proxy details\",\n \"item\": [\n {\n \"linkId\": \"nhs_number\",\n \"text\": \"NHS Number\",\n \"answer\": [ \n {\n \"valueString\": \"9000000001\"\n }\n ]\n },\n {\n \"linkId\": \"relationship\",\n \"text\": \"Relationship\",\n \"answer\": [\n {\n \"valueCoding\": {\n \"system\": \"http://terminology.hl7.org/CodeSystem/v3-RoleCode\",\n \"code\": \"PRN\",\n \"display\": \"Parent\"\n }\n }\n ]\n }\n ]\n },\n {\n \"linkId\": \"patient_details\",\n \"text\": \"Patient details\",\n \"item\": [\n {\n \"linkId\": \"nhs_number\",\n \"text\": \"NHS Number\",\n \"answer\": [\n {\n \"valueString\": \"9000000002\"\n }\n ]\n },\n {\n \"linkId\": \"first_name\",\n \"text\": \"First Name\",\n \"answer\": [\n {\n \"valueString\": \"Timmy\"\n }\n ]\n },\n {\n \"linkId\": \"last_name\",\n \"text\": \"Last name\",\n \"answer\": [\n {\n \"valueString\": \"Tenenbaum\"\n }\n ]\n },\n {\n \"linkId\": \"date_of_birth\",\n \"text\": \"Date of Birth\",\n \"answer\": [\n {\n \"valueDate\": \"2020-10-22\"\n }\n ]\n },\n {\n \"linkId\": \"postcode\",\n \"text\": \"Postcode\",\n \"answer\": [\n {\n \"valueString\": \"LS1 4AP\"\n }\n ]\n }\n ]\n },\n {\n \"linkId\": \"requested_services\",\n \"text\": \"Requested services\",\n \"answer\": [\n {\n \"valueCoding\": {\n \"system\": \"http://terminology.hl7.org/CodeSystem/consentaction\",\n \"code\": \"appointments\",\n \"display\": \"manage appointments\"\n }\n },\n {\n \"valueCoding\": {\n \"system\": \"http://terminology.hl7.org/CodeSystem/consentaction\",\n \"code\": \"medicines\",\n \"display\": \"manage medicines\"\n }\n },\n {\n \"valueCoding\": {\n \"system\": \"http://terminology.hl7.org/CodeSystem/consentaction\",\n \"code\": \"records\",\n \"display\": \"access medical records\"\n }\n },\n {\n \"valueCoding\": {\n \"system\": \"http://terminology.hl7.org/CodeSystem/consentaction\",\n \"code\": \"demographics\",\n \"display\": \"manage demographics and contact details\"\n }\n }\n ]\n }\n ]\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{api_base_url}}/QuestionnaireResponse",
"host": [
"{{api_base_url}}"
],
"path": [
"QuestionnaireResponse"
]
},
"description": "Example of a response where the given NHS numbers do not have a relationship."
},
"response": []
}
]
}
],
"event": [
Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ pytest-nhsd-apim = "^3.3.2"
[tool.poetry.dev-dependencies]
flake8 = "^3.7.9"
black = "^24.3.0"
pip-licenses = "^2.0.1"
pip-licenses = "^4.4.0"
jinja2 = "^3.1.3"
pyyaml = "^6.0.1"
docopt = "^0.6.2"
jsonpath-rw = "^1.4.0"
semver = "^2.9.0"
semver = "^3.0.2"
gitpython = "^3.0.5"
pytest = "^8.2.0"
coverage = "^5.5"
coverage = "^7.5"
aiohttp = "^3.7.3"
pytest-asyncio = "^0.14.0"
31 changes: 24 additions & 7 deletions sandbox/api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,26 @@
from flask import Flask, request

from .utils import (
ERROR_RESPONSE,
LIST_RELATIONSHIP,
LIST_RELATIONSHIP_INCLUDE,
QUESTIONNAIRE_RESPONSE_SUCCESS,
VALIDATE_RELATIONSHIP_009,
VALIDATE_RELATIONSHIP_INCLUDE_009,
VALIDATE_RELATIONSHIP_025,
VALIDATE_RELATIONSHIP_INCLUDE_009,
VALIDATE_RELATIONSHIP_INCLUDE_025,
LIST_RELATIONSHIP,
LIST_RELATIONSHIP_INCLUDE,
ERROR_RESPONSE,
check_for_errors,
check_for_empty,
check_for_validate,
check_for_errors,
check_for_list,
check_for_validate,
generate_response,
load_json_file,
)

app = Flask(__name__)
basicConfig(level=INFO, format="%(asctime)s - %(message)s")
logger = getLogger(__name__)
COMMON_PATH = "FHIR/R4"


@app.route("/_status", methods=["GET"])
Expand All @@ -35,7 +37,7 @@ def health() -> dict:
}


@app.route("/FHIR/R4/RelatedPerson", methods=["GET"])
@app.route(f"/{COMMON_PATH}/RelatedPerson", methods=["GET"])
def get_related_persons() -> Union[dict, tuple]:
"""Sandbox API for GET /RelatedPerson
Expand Down Expand Up @@ -90,3 +92,18 @@ def get_related_persons() -> Union[dict, tuple]:
except Exception as e:
logger.error(e)
return generate_response(load_json_file(ERROR_RESPONSE), 500)


@app.route(f"/{COMMON_PATH}/QuestionnaireResponse", methods=["POST"])
def post_questionnaire_response() -> Union[dict, tuple]:
"""Sandbox API for POST /QuestionnaireResponse
Returns:
Union[dict, tuple]: Response for POST /QuestionnaireResponse
"""

try:
return generate_response(load_json_file(QUESTIONNAIRE_RESPONSE_SUCCESS), 200)
except Exception as e:
logger.error(e)
return generate_response(load_json_file(ERROR_RESPONSE), 500)
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@
]
},
"diagnostics": "The 'identifier' parameter is required",
"severity": "error",
"expression": ["RelatedPerson.identifier"]
"severity": "error"
}
],
"resourceType": "OperationOutcome"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@
]
},
"diagnostics": "The identifier system is not valid.",
"severity": "error",
"expression": ["identifier"]
"severity": "error"
}
],
"resourceType": "OperationOutcome"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "information",
"code": "informational",
"details": {
"coding": [
{
"code": "HDJ2123F",
"display": "HDJ2123F"
}
]
}
}
]
}
File renamed without changes.
4 changes: 3 additions & 1 deletion sandbox/api/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

from ..app import app

RELATED_PERSON_API_ENDPOINT = "/FHIR/R4/RelatedPerson"
FHIR_PATH = "/FHIR/R4"
RELATED_PERSON_API_ENDPOINT = f"{FHIR_PATH}/RelatedPerson"
QUESTIONNAIRE_RESPONSE_API_ENDPOINT = f"{FHIR_PATH}/QuestionnaireResponse"


@pytest.fixture()
Expand Down
46 changes: 38 additions & 8 deletions sandbox/api/tests/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

import pytest

from .conftest import RELATED_PERSON_API_ENDPOINT
from .conftest import RELATED_PERSON_API_ENDPOINT, QUESTIONNAIRE_RESPONSE_API_ENDPOINT

FILE_PATH = "sandbox.api.utils"
UTILS_FILE_PATH = "sandbox.api.utils"
APP_FILE_PATH = "sandbox.api.app"


@pytest.mark.parametrize("endpoint", ["/_status", "/_ping", "/health"])
Expand All @@ -25,12 +26,12 @@ def test_health_check(client: object, endpoint: str) -> None:
[
(
"identifier=9000000041",
"./api/responses/GET_RelatedPerson/not_found.json",
"./api/responses/not_found.json",
404,
),
(
"identifier=9000000017&patient:identifier=9000000041",
"./api/responses/GET_RelatedPerson/not_found.json",
"./api/responses/not_found.json",
404,
),
(
Expand Down Expand Up @@ -85,20 +86,49 @@ def test_health_check(client: object, endpoint: str) -> None:
),
],
)
@patch(f"{FILE_PATH}.load_json_file")
@patch(f"{UTILS_FILE_PATH}.load_json_file")
def test_related_person(
mock_get_response: MagicMock,
mock_load_json_file: MagicMock,
request_args: str,
response_file_name: str,
client: object,
status_code: int,
) -> None:
"""Test related_persons endpoint with identifier only."""
# Arrange
mock_get_response.return_value = expected_body = {"data": "mocked"}
mock_load_json_file.return_value = expected_body = {"data": "mocked"}
# Act
response = client.get(f"{RELATED_PERSON_API_ENDPOINT}?{request_args}")
# Assert
mock_get_response.assert_called_once_with(response_file_name)
mock_load_json_file.assert_called_once_with(response_file_name)
assert response.status_code == status_code
assert response.json == expected_body


@pytest.mark.parametrize(
"url_path,response_file_name,status_code",
[
(
QUESTIONNAIRE_RESPONSE_API_ENDPOINT,
"./api/responses/POST_QuestionnaireResponse/questionnaire_response_success.json",
200,
),
],
)
@patch(f"{APP_FILE_PATH}.load_json_file")
def test_questionnaire_response(
mock_load_json_file: MagicMock,
url_path: str,
response_file_name: str,
client: object,
status_code: int,
) -> None:
"""Test related_persons endpoint with identifier only."""
# Arrange
mock_load_json_file.return_value = expected_body = {"data": "mocked"}
# Act
response = client.post(url_path, json={"data": "mocked"})
# Assert
mock_load_json_file.assert_called_once_with(response_file_name)
assert response.status_code == status_code
assert response.json == expected_body
6 changes: 5 additions & 1 deletion sandbox/api/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from flask import request, Response

NOT_FOUND = "./api/responses/GET_RelatedPerson/not_found.json"
NOT_FOUND = "./api/responses/not_found.json"
EMPTY_RESPONSE = "./api/responses/GET_RelatedPerson/empty_response_9000000033.json"
LIST_RELATIONSHIP = (
"./api/responses/GET_RelatedPerson/list_relationship_9000000017.json"
Expand All @@ -26,6 +26,10 @@
ERROR_RESPONSE = "./api/responses/internal_server_error.json"
INCLUDE_FLAG = "RelatedPerson:patient"

QUESTIONNAIRE_RESPONSE_SUCCESS = (
"./api/responses/POST_QuestionnaireResponse/questionnaire_response_success.json"
)

PATIENT_IDENTIFIERS = ["9000000017", "9000000033"]
RELATED_IDENTIFIERS = ["9000000009", "9000000025"]

Expand Down
5 changes: 5 additions & 0 deletions specification/validated-relationships-service-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,11 @@ paths:
Proxy Access Service and submitted as a QuestionaireResponse.
For the most part demographics information doesn't need to be provided in the access request since it can be pulled from PDS.
## Sandbox test scenarios
For details of sandbox test scenarios, or to try out the sandbox using our 'Try it out' feature, see the documentation for each endpoint.
operationId: new-access-request
parameters:
- $ref: "#/components/parameters/BearerAuthorization"
Expand Down

0 comments on commit fc6f31b

Please sign in to comment.