diff --git a/.github/workflows/daily-build.yml b/.github/workflows/daily-build.yml new file mode 100644 index 000000000..0fc15523c --- /dev/null +++ b/.github/workflows/daily-build.yml @@ -0,0 +1,85 @@ +name: Build NRL Project on Environment +run-name: Build NRL Project on ${{ inputs.environment || 'dev' }} +permissions: + id-token: write + contents: read + actions: write + +on: + schedule: + - cron: "0 1 * * *" + workflow_dispatch: + inputs: + environment: + type: environment + description: "The environment to deploy changes to" + default: "dev" + required: true + +jobs: + build: + name: Build - develop + runs-on: [self-hosted, ci] + + steps: + - name: Git clone - develop + uses: actions/checkout@v4 + with: + ref: develop + + - name: Setup asdf cache + uses: actions/cache@v4 + with: + path: ~/.asdf + key: ${{ runner.os }}-asdf-${{ hashFiles('**/.tool-versions') }} + restore-keys: | + ${{ runner.os }}-asdf- + + - name: Install asdf + uses: asdf-vm/actions/install@v3.0.2 + with: + asdf_branch: v0.13.1 + + - name: Install zip + run: sudo apt-get install zip + + - name: Setup Python environment + run: | + poetry install --no-root + source $(poetry env info --path)/bin/activate + + - name: Run Linting + run: make lint + + - name: Run Unit Tests + run: make test + + - name: Build Project + run: make build + + - name: Configure Management Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-region: eu-west-2 + role-to-assume: ${{ secrets.MGMT_ROLE_ARN }} + role-session-name: github-actions-ci-${{ inputs.environment || 'dev' }}-${{ github.run_id }} + + - name: Add S3 Permissions to Lambda + run: | + account=$(echo '${{ inputs.environment || 'dev' }}' | cut -d '-' -f1) + inactive_stack=$(poetry run python ./scripts/get_env_config.py inactive-stack ${{ inputs.environment || 'dev' }}) + make get-s3-perms ENV=${account} TF_WORKSPACE_NAME=${inactive_stack} + + - name: Save Build Artifacts + uses: actions/upload-artifact@v4 + with: + name: build-artifacts + path: | + dist/*.zip + !dist/nrlf_permissions.zip + + - name: Save NRLF Permissions cache + uses: actions/cache/save@v4 + with: + key: ${{ github.run_id }}-nrlf-permissions + path: dist/nrlf_permissions.zip diff --git a/api/consumer/swagger.yaml b/api/consumer/swagger.yaml index 8d6947d7f..5d016c4e7 100644 --- a/api/consumer/swagger.yaml +++ b/api/consumer/swagger.yaml @@ -788,7 +788,7 @@ components: description: The status of this document reference. docStatus: type: string - pattern: "[^\\s]+(\\s[^\\s]+)*" + enum: ["entered-in-error", "amended", "preliminary", "final"] description: The status of the underlying document. type: $ref: "#/components/schemas/CodeableConcept" diff --git a/api/producer/createDocumentReference/tests/test_create_document_reference.py b/api/producer/createDocumentReference/tests/test_create_document_reference.py index 19e29569b..56daacfc6 100644 --- a/api/producer/createDocumentReference/tests/test_create_document_reference.py +++ b/api/producer/createDocumentReference/tests/test_create_document_reference.py @@ -411,6 +411,47 @@ def test_create_document_reference_with_no_practiceSetting(): } +def test_create_document_reference_with_invalid_docStatus(): + doc_ref = load_document_reference("Y05868-736253002-Valid") + doc_ref.docStatus = "invalid" + + event = create_test_api_gateway_event( + headers=create_headers(), + body=doc_ref.model_dump_json(exclude_none=True), + ) + + result = handler(event, create_mock_context()) + body = result.pop("body") + + assert result == { + "statusCode": "400", + "headers": default_response_headers(), + "isBase64Encoded": False, + } + + parsed_body = json.loads(body) + assert parsed_body == { + "resourceType": "OperationOutcome", + "issue": [ + { + "severity": "error", + "code": "invalid", + "details": { + "coding": [ + { + "code": "MESSAGE_NOT_WELL_FORMED", + "display": "Message not well formed", + "system": "https://fhir.nhs.uk/ValueSet/Spine-ErrorOrWarningCode-1", + } + ], + }, + "diagnostics": "Request body could not be parsed (docStatus: Input should be 'entered-in-error', 'amended', 'preliminary' or 'final')", + "expression": ["docStatus"], + }, + ], + } + + def test_create_document_reference_invalid_custodian_id(): doc_ref = load_document_reference("Y05868-736253002-Valid") diff --git a/api/producer/swagger.yaml b/api/producer/swagger.yaml index d1b7801fa..c927f4871 100644 --- a/api/producer/swagger.yaml +++ b/api/producer/swagger.yaml @@ -1351,7 +1351,7 @@ components: description: The status of this document reference. docStatus: type: string - pattern: "[^\\s]+(\\s[^\\s]+)*" + enum: ["entered-in-error", "amended", "preliminary", "final"] description: The status of the underlying document. type: $ref: "#/components/schemas/CodeableConcept" diff --git a/api/producer/upsertDocumentReference/tests/test_upsert_document_reference.py b/api/producer/upsertDocumentReference/tests/test_upsert_document_reference.py index 6f3878cfd..090542dae 100644 --- a/api/producer/upsertDocumentReference/tests/test_upsert_document_reference.py +++ b/api/producer/upsertDocumentReference/tests/test_upsert_document_reference.py @@ -455,6 +455,47 @@ def test_upsert_document_reference_with_no_practiceSetting(): } +def test_upsert_document_reference_with_invalid_docStatus(): + doc_ref = load_document_reference("Y05868-736253002-Valid") + doc_ref.docStatus = "invalid" + + event = create_test_api_gateway_event( + headers=create_headers(), + body=doc_ref.model_dump_json(exclude_none=True), + ) + + result = handler(event, create_mock_context()) + body = result.pop("body") + + assert result == { + "statusCode": "400", + "headers": default_response_headers(), + "isBase64Encoded": False, + } + + parsed_body = json.loads(body) + assert parsed_body == { + "resourceType": "OperationOutcome", + "issue": [ + { + "severity": "error", + "code": "invalid", + "details": { + "coding": [ + { + "code": "MESSAGE_NOT_WELL_FORMED", + "display": "Message not well formed", + "system": "https://fhir.nhs.uk/ValueSet/Spine-ErrorOrWarningCode-1", + } + ], + }, + "diagnostics": "Request body could not be parsed (docStatus: Input should be 'entered-in-error', 'amended', 'preliminary' or 'final')", + "expression": ["docStatus"], + }, + ], + } + + def test_upsert_document_reference_invalid_producer_id(): doc_ref = load_document_reference("Y05868-736253002-Valid") doc_ref.id = "X26-99999-99999-999999" diff --git a/layer/nrlf/consumer/fhir/r4/model.py b/layer/nrlf/consumer/fhir/r4/model.py index d8e069ddc..2fd597b42 100644 --- a/layer/nrlf/consumer/fhir/r4/model.py +++ b/layer/nrlf/consumer/fhir/r4/model.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: swagger.yaml -# timestamp: 2024-12-12T13:19:56+00:00 +# timestamp: 2024-12-13T11:19:30+00:00 from __future__ import annotations @@ -669,11 +669,8 @@ class DocumentReference(BaseModel): ), ] docStatus: Annotated[ - Optional[str], - Field( - description="The status of the underlying document.", - pattern="[^\\s]+(\\s[^\\s]+)*", - ), + Optional[Literal["entered-in-error", "amended", "preliminary", "final"]], + Field(description="The status of the underlying document."), ] = None type: Annotated[ Optional[CodeableConcept], diff --git a/layer/nrlf/core/tests/test_validators.py b/layer/nrlf/core/tests/test_validators.py index 9041a3c6b..4b13e5d56 100644 --- a/layer/nrlf/core/tests/test_validators.py +++ b/layer/nrlf/core/tests/test_validators.py @@ -1303,21 +1303,14 @@ def test_validate_ssp_content_with_multiple_asids(): } -def test_validate_content_extension_invalid_code_and_display_mismatch(): +def test_validate_content_format_invalid_code_for_unstructured_document(): validator = DocumentReferenceValidator() document_ref_data = load_document_reference_json("Y05868-736253002-Valid") - document_ref_data["content"][0]["extension"][0] = { - "url": "https://fhir.nhs.uk/England/StructureDefinition/Extension-England-ContentStability", - "valueCodeableConcept": { - "coding": [ - { - "system": "https://fhir.nhs.uk/England/CodeSystem/England-NRLContentStability", - "code": "static", - "display": "Dynamic", - } - ] - }, + document_ref_data["content"][0]["format"] = { + "system": "https://fhir.nhs.uk/England/CodeSystem/England-NRLFormatCode", + "code": "urn:nhs-ic:record-contact", + "display": "Contact details (HTTP Unsecured)", } result = validator.validate(document_ref_data) @@ -1337,92 +1330,21 @@ def test_validate_content_extension_invalid_code_and_display_mismatch(): } ] }, - "diagnostics": "Invalid content extension display: Dynamic Extension display must be the same as code either 'Static' or 'Dynamic'", - "expression": [ - "content[0].extension[0].valueCodeableConcept.coding[0].display" - ], - } - - -def test_validate_content_invalid_content_type(): - validator = DocumentReferenceValidator() - document_ref_data = load_document_reference_json("Y05868-736253002-Valid") - - document_ref_data["content"][0]["attachment"]["contentType"] = "invalid/type" - - result = validator.validate(document_ref_data) - - assert result.is_valid is False - assert len(result.issues) == 1 - assert result.issues[0].model_dump(exclude_none=True) == { - "severity": "error", - "code": "value", - "details": { - "coding": [ - { - "system": "https://fhir.nhs.uk/ValueSet/Spine-ErrorOrWarningCode-1", - "code": "INVALID_RESOURCE", - "display": "Invalid validation of resource", - } - ] - }, - "diagnostics": "Invalid contentType: invalid/type. Must be 'application/pdf' or 'text/html'", - "expression": ["content[0].attachment.contentType"], - } - - -@pytest.mark.parametrize( - "format_code, format_display", - [ - ("urn:nhs-ic:record-contact", "Contact details (HTTP Unsecured)"), - ("urn:nhs-ic:unstructured", "Unstructured Document"), - ], -) -def test_validate_nrl_format_code_valid_match(format_code, format_display): - validator = DocumentReferenceValidator() - document_ref_data = load_document_reference_json("Y05868-736253002-Valid") - - document_ref_data["content"][0]["format"] = { - "system": "https://fhir.nhs.uk/England/CodeSystem/England-NRLFormatCode", - "code": format_code, - "display": format_display, + "diagnostics": "Invalid content format code: urn:nhs-ic:record-contact format code must be 'urn:nhs-ic:unstructured' for Unstructured Document attachments.", + "expression": ["content[0].format.code"], } - result = validator.validate(document_ref_data) - - assert result.is_valid is True - -@pytest.mark.parametrize( - "format_code, format_display, expected_display", - [ - ( - "urn:nhs-ic:unstructured", - "Contact details (HTTP Unsecured)", - "Unstructured Document", - ), - ( - "urn:nhs-ic:record-contact", - "Unstructured Document", - "Contact details (HTTP Unsecured)", - ), - ], -) -def test_validate_nrl_format_code_display_mismatch( - format_code, format_display, expected_display -): +def test_validate_content_format_invalid_code_for_contact_details(): validator = DocumentReferenceValidator() document_ref_data = load_document_reference_json("Y05868-736253002-Valid") - document_ref_data["content"][0]["format"] = { - "system": "https://fhir.nhs.uk/England/CodeSystem/England-NRLFormatCode", - "code": format_code, - "display": format_display, - } + document_ref_data["content"][0]["attachment"]["contentType"] = "text/html" result = validator.validate(document_ref_data) assert result.is_valid is False + assert result.resource.id == "Y05868-99999-99999-999999" assert len(result.issues) == 1 assert result.issues[0].model_dump(exclude_none=True) == { "severity": "error", @@ -1436,8 +1358,8 @@ def test_validate_nrl_format_code_display_mismatch( } ] }, - "diagnostics": f"Invalid display for format code '{format_code}'. Expected '{expected_display}'", - "expression": ["content[0].format.display"], + "diagnostics": "Invalid content format code: urn:nhs-ic:unstructured format code must be 'urn:nhs-ic:record-contact' for Contact details attachments.", + "expression": ["content[0].format.code"], } @@ -1641,3 +1563,145 @@ def test_validate_practiceSetting_coding_mismatch_code_and_display(): "diagnostics": "Invalid practice setting coding: display Nephrology service does not match the expected display for 788002001 Practice Setting coding is bound to value set https://fhir.nhs.uk/England/ValueSet/England-PracticeSetting", "expression": ["context.practiceSetting.coding[0]"], } + + +def test_validate_content_extension_invalid_code_and_display_mismatch(): + validator = DocumentReferenceValidator() + document_ref_data = load_document_reference_json("Y05868-736253002-Valid") + + document_ref_data["content"][0]["extension"][0] = { + "url": "https://fhir.nhs.uk/England/StructureDefinition/Extension-England-ContentStability", + "valueCodeableConcept": { + "coding": [ + { + "system": "https://fhir.nhs.uk/England/CodeSystem/England-NRLContentStability", + "code": "static", + "display": "Dynamic", + } + ] + }, + } + + result = validator.validate(document_ref_data) + + assert result.is_valid is False + assert result.resource.id == "Y05868-99999-99999-999999" + assert len(result.issues) == 1 + assert result.issues[0].model_dump(exclude_none=True) == { + "severity": "error", + "code": "value", + "details": { + "coding": [ + { + "system": "https://fhir.nhs.uk/ValueSet/Spine-ErrorOrWarningCode-1", + "code": "INVALID_RESOURCE", + "display": "Invalid validation of resource", + } + ] + }, + "diagnostics": "Invalid content extension display: Dynamic Extension display must be the same as code either 'Static' or 'Dynamic'", + "expression": [ + "content[0].extension[0].valueCodeableConcept.coding[0].display" + ], + } + + +def test_validate_content_invalid_content_type(): + validator = DocumentReferenceValidator() + document_ref_data = load_document_reference_json("Y05868-736253002-Valid") + + document_ref_data["content"][0]["attachment"]["contentType"] = "invalid/type" + + result = validator.validate(document_ref_data) + + assert result.is_valid is False + assert len(result.issues) == 1 + assert result.issues[0].model_dump(exclude_none=True) == { + "severity": "error", + "code": "value", + "details": { + "coding": [ + { + "system": "https://fhir.nhs.uk/ValueSet/Spine-ErrorOrWarningCode-1", + "code": "INVALID_RESOURCE", + "display": "Invalid validation of resource", + } + ] + }, + "diagnostics": "Invalid contentType: invalid/type. Must be 'application/pdf' or 'text/html'", + "expression": ["content[0].attachment.contentType"], + } + + +@pytest.mark.parametrize( + "format_code, format_display", + [ + ("urn:nhs-ic:record-contact", "Contact details (HTTP Unsecured)"), + ("urn:nhs-ic:unstructured", "Unstructured Document"), + ], +) +def test_validate_nrl_format_code_valid_match(format_code, format_display): + validator = DocumentReferenceValidator() + document_ref_data = load_document_reference_json("Y05868-736253002-Valid") + if format_code == "urn:nhs-ic:record-contact": + document_ref_data["content"][0]["attachment"]["contentType"] = "text/html" + + document_ref_data["content"][0]["format"] = { + "system": "https://fhir.nhs.uk/England/CodeSystem/England-NRLFormatCode", + "code": format_code, + "display": format_display, + } + + result = validator.validate(document_ref_data) + + assert result.is_valid is True + + +@pytest.mark.parametrize( + "format_code, format_display, expected_display", + [ + ( + "urn:nhs-ic:unstructured", + "Contact details (HTTP Unsecured)", + "Unstructured Document", + ), + ( + "urn:nhs-ic:record-contact", + "Unstructured Document", + "Contact details (HTTP Unsecured)", + ), + ], +) +def test_validate_nrl_format_code_display_mismatch( + format_code, format_display, expected_display +): + validator = DocumentReferenceValidator() + document_ref_data = load_document_reference_json("Y05868-736253002-Valid") + if format_code == "urn:nhs-ic:record-contact": + document_ref_data["content"][0]["attachment"]["contentType"] = "text/html" + + document_ref_data["content"][0]["format"] = { + "system": "https://fhir.nhs.uk/England/CodeSystem/England-NRLFormatCode", + "code": format_code, + "display": format_display, + } + + result = validator.validate(document_ref_data) + + assert result.is_valid is False + assert len(result.issues) == 1 + assert result.issues[0].model_dump(exclude_none=True) == { + "severity": "error", + "code": "value", + "details": { + "coding": [ + { + "system": "https://fhir.nhs.uk/ValueSet/Spine-ErrorOrWarningCode-1", + "code": "INVALID_RESOURCE", + "display": "Invalid validation of resource", + } + ] + }, + "diagnostics": f"Invalid display for format code '{format_code}'. Expected '{expected_display}'", + "expression": ["content[0].format.display"], + } diff --git a/layer/nrlf/core/validators.py b/layer/nrlf/core/validators.py index 57f685609..cb80b0e7d 100644 --- a/layer/nrlf/core/validators.py +++ b/layer/nrlf/core/validators.py @@ -138,6 +138,7 @@ def validate(self, data: Dict[str, Any] | DocumentReference): self._validate_author(resource) self._validate_type_category_mapping(resource) self._validate_content(resource) + self._validate_content_format(resource) self._validate_content_extension(resource) self._validate_practiceSetting(resource) @@ -478,6 +479,35 @@ def _validate_type_category_mapping(self, model: DocumentReference): field="category.coding[0].code", ) + def _validate_content_format(self, model: DocumentReference): + """ + Validate the content.format field contains an appropriate coding. + """ + logger.log(LogReference.VALIDATOR001, step="content_format") + + logger.debug("Validating format") + for i, content in enumerate(model.content): + if ( + content.attachment.contentType == "text/html" + and content.format.code != "urn:nhs-ic:record-contact" + ): + self.result.add_error( + issue_code="value", + error_code="INVALID_RESOURCE", + diagnostics=f"Invalid content format code: {content.format.code} format code must be 'urn:nhs-ic:record-contact' for Contact details attachments.", + field=f"content[{i}].format.code", + ) + elif ( + content.attachment.contentType == "application/pdf" + and content.format.code != "urn:nhs-ic:unstructured" + ): + self.result.add_error( + issue_code="value", + error_code="INVALID_RESOURCE", + diagnostics=f"Invalid content format code: {content.format.code} format code must be 'urn:nhs-ic:unstructured' for Unstructured Document attachments.", + field=f"content[{i}].format.code", + ) + def _validate_content_extension(self, model: DocumentReference): """ Validate the content.extension field contains an appropriate coding. diff --git a/layer/nrlf/producer/fhir/r4/model.py b/layer/nrlf/producer/fhir/r4/model.py index 538659e9d..945b220ec 100644 --- a/layer/nrlf/producer/fhir/r4/model.py +++ b/layer/nrlf/producer/fhir/r4/model.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: swagger.yaml -# timestamp: 2024-12-12T13:19:54+00:00 +# timestamp: 2024-12-13T11:19:26+00:00 from __future__ import annotations @@ -653,11 +653,8 @@ class DocumentReference(BaseModel): ), ] docStatus: Annotated[ - Optional[str], - Field( - description="The status of the underlying document.", - pattern="[^\\s]+(\\s[^\\s]+)*", - ), + Optional[Literal["entered-in-error", "amended", "preliminary", "final"]], + Field(description="The status of the underlying document."), ] = None type: Annotated[ Optional[CodeableConcept], diff --git a/layer/nrlf/producer/fhir/r4/strict_model.py b/layer/nrlf/producer/fhir/r4/strict_model.py index cfa6f3243..0344821fc 100644 --- a/layer/nrlf/producer/fhir/r4/strict_model.py +++ b/layer/nrlf/producer/fhir/r4/strict_model.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: swagger.yaml -# timestamp: 2024-12-12T13:19:55+00:00 +# timestamp: 2024-12-13T11:19:28+00:00 from __future__ import annotations @@ -578,7 +578,8 @@ class DocumentReference(BaseModel): StrictStr, Field(description="The status of this document reference.") ] docStatus: Annotated[ - Optional[StrictStr], Field(description="The status of the underlying document.") + Optional[Literal["entered-in-error", "amended", "preliminary", "final"]], + Field(description="The status of the underlying document."), ] = None type: Annotated[ Optional[CodeableConcept], diff --git a/tests/features/producer/createDocumentReference-failure.feature b/tests/features/producer/createDocumentReference-failure.feature index 17c79b3a1..af22ae1bc 100644 --- a/tests/features/producer/createDocumentReference-failure.feature +++ b/tests/features/producer/createDocumentReference-failure.feature @@ -405,22 +405,42 @@ Feature: Producer - createDocumentReference - Failure Scenarios } """ - Scenario: Invalid practice setting (not in value set) + Scenario: Invalid format code for attachment type contact details Given the application 'DataShare' (ID 'z00z-y11y-x22x') is registered to access the API - And the organisation 'X26' is authorised to access pointer types: + And the organisation 'TSTCUS' is authorised to access pointer types: | system | value | | http://snomed.info/sct | 1363501000000100 | | http://snomed.info/sct | 736253002 | - When producer 'X26' creates a DocumentReference with values: - | property | value | - | subject | 9999999999 | - | status | current | - | type | 736253002 | - | category | 734163000 | - | custodian | X26 | - | author | HAR1 | - | url | https://example.org/my-doc.pdf | - | practiceSetting | 12345 | + When producer 'TSTCUS' requests creation of a DocumentReference with default test values except 'content' is: + """ + "content": [ + { + "attachment": { + "contentType": "text/html", + "url": "someContact.co.uk" + }, + "format": { + "system": "https://fhir.nhs.uk/England/CodeSystem/England-NRLFormatCode", + "code": "urn:nhs-ic:unstructured", + "display": "Unstructured Document" + }, + "extension": [ + { + "url": "https://fhir.nhs.uk/England/StructureDefinition/Extension-England-ContentStability", + "valueCodeableConcept": { + "coding": [ + { + "system": "https://fhir.nhs.uk/England/CodeSystem/England-NRLContentStability", + "code": "static", + "display": "Static" + } + ] + } + } + ] + } + ] + """ Then the response status code is 400 And the response is an OperationOutcome with 1 issue And the OperationOutcome contains the issue: @@ -429,38 +449,60 @@ Feature: Producer - createDocumentReference - Failure Scenarios "severity": "error", "code": "value", "details": { - "coding": [ - { - "system": "https://fhir.nhs.uk/ValueSet/Spine-ErrorOrWarningCode-1", - "code": "INVALID_RESOURCE", - "display": "Invalid validation of resource" - } - ] + "coding": [ + { + "system": "https://fhir.nhs.uk/ValueSet/Spine-ErrorOrWarningCode-1", + "code": "INVALID_RESOURCE", + "display": "Invalid validation of resource" + } + ] }, - "diagnostics": "Invalid practice setting code: 12345 Practice Setting coding must be a member of value set https://fhir.nhs.uk/England/ValueSet/England-PracticeSetting", - "expression": ["context.practiceSetting.coding[0].code"] + "diagnostics": "Invalid content format code: urn:nhs-ic:unstructured format code must be 'urn:nhs-ic:record-contact' for Contact details attachments.", + "expression": [ + "content[0].format.code" + ] } """ - Scenario: Invalid practice setting (valid code but wrong display value) + Scenario: Invalid format code for attachment type pdf Given the application 'DataShare' (ID 'z00z-y11y-x22x') is registered to access the API And the organisation 'TSTCUS' is authorised to access pointer types: | system | value | | http://snomed.info/sct | 1363501000000100 | | http://snomed.info/sct | 736253002 | - When producer 'TSTCUS' requests creation of a DocumentReference with default test values except 'context' is: + When producer 'TSTCUS' requests creation of a DocumentReference with default test values except 'content' is: """ - "context": { - "practiceSetting": { - "coding": [ - { - "system": "http://snomed.info/sct", - "code": "788002001", - "display": "Ophthalmology service" - } + "content": [ + { + "attachment": { + "contentType": "application/pdf", + "language": "en-UK", + "url": "https://spine-proxy.national.ncrs.nhs.uk/https%3A%2F%2Fp1.nhs.uk%2FMentalhealthCrisisPlanReport.pdf", + "hash": "2jmj7l5rSw0yVb/vlWAYkK/YBwk=", + "title": "Mental health crisis plan report", + "creation": "2022-12-21T10:45:41+11:00" + }, + "format": { + "system": "https://fhir.nhs.uk/England/CodeSystem/England-NRLFormatCode", + "code": "urn:nhs-ic:record-contact", + "display": "Contact details (HTTP Unsecured)" + }, + "extension": [ + { + "url": "https://fhir.nhs.uk/England/StructureDefinition/Extension-England-ContentStability", + "valueCodeableConcept": { + "coding": [ + { + "system": "https://fhir.nhs.uk/England/CodeSystem/England-NRLContentStability", + "code": "static", + "display": "Static" + } + ] + } + } + ] + } ] - } - } """ Then the response status code is 400 And the response is an OperationOutcome with 1 issue @@ -470,17 +512,17 @@ Feature: Producer - createDocumentReference - Failure Scenarios "severity": "error", "code": "value", "details": { - "coding": [ - { - "system": "https://fhir.nhs.uk/ValueSet/Spine-ErrorOrWarningCode-1", - "code": "INVALID_RESOURCE", - "display": "Invalid validation of resource" - } - ] + "coding": [ + { + "system": "https://fhir.nhs.uk/ValueSet/Spine-ErrorOrWarningCode-1", + "code": "INVALID_RESOURCE", + "display": "Invalid validation of resource" + } + ] }, - "diagnostics": "Invalid practice setting coding: display Ophthalmology service does not match the expected display for 788002001 Practice Setting coding is bound to value set https://fhir.nhs.uk/England/ValueSet/England-PracticeSetting", + "diagnostics": "Invalid content format code: urn:nhs-ic:record-contact format code must be 'urn:nhs-ic:unstructured' for Unstructured Document attachments.", "expression": [ - "context.practiceSetting.coding[0]" + "content[0].format.code" ] } """ @@ -615,6 +657,64 @@ Feature: Producer - createDocumentReference - Failure Scenarios | https://nicip.nhs.uk | MAULR | 721981007 | "Nonsense display" | MRA Upper Limb Rt | | https://nicip.nhs.uk | MAXIB | 103693007 | "Nonsense display" | MRI Axilla Both | + Scenario: Invalid practice setting (not in value set) + Given the application 'DataShare' (ID 'z00z-y11y-x22x') is registered to access the API + And the organisation 'X26' is authorised to access pointer types: + | system | value | + | http://snomed.info/sct | 1363501000000100 | + | http://snomed.info/sct | 736253002 | + When producer 'X26' creates a DocumentReference with values: + | property | value | + | subject | 9999999999 | + | status | current | + | type | 736253002 | + | category | 734163000 | + | custodian | X26 | + | author | HAR1 | + | url | https://example.org/my-doc.pdf | + | practiceSetting | 12345 | + Then the response status code is 400 + And the response is an OperationOutcome with 1 issue + And the OperationOutcome contains the issue: + """ + { + "severity": "error", + "code": "value", + "details": { + "coding": [ + { + "system": "https://fhir.nhs.uk/ValueSet/Spine-ErrorOrWarningCode-1", + "code": "INVALID_RESOURCE", + "display": "Invalid validation of resource" + } + ] + }, + "diagnostics": "Invalid practice setting code: 12345 Practice Setting coding must be a member of value set https://fhir.nhs.uk/England/ValueSet/England-PracticeSetting", + "expression": ["context.practiceSetting.coding[0].code"] + } + """ + + Scenario: Invalid practice setting (valid code but wrong display value) + Given the application 'DataShare' (ID 'z00z-y11y-x22x') is registered to access the API + And the organisation 'TSTCUS' is authorised to access pointer types: + | system | value | + | http://snomed.info/sct | 1363501000000100 | + | http://snomed.info/sct | 736253002 | + When producer 'TSTCUS' requests creation of a DocumentReference with default test values except 'context' is: + """ + "context": { + "practiceSetting": { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "788002001", + "display": "Ophthalmology service" + } + ] + } + } + """ + Scenario: Missing content Given the application 'DataShare' (ID 'z00z-y11y-x22x') is registered to access the API And the organisation 'TSTCUS' is authorised to access pointer types: @@ -753,7 +853,7 @@ Feature: Producer - createDocumentReference - Failure Scenarios "content": [ { "attachment": { - "contentType": "application/pdf", + "contentType": "text/html", "url": "https://example.org/my-doc.pdf" }, "format": { diff --git a/tests/features/producer/updateDocumentReference-failure.feature b/tests/features/producer/updateDocumentReference-failure.feature index 20a8bed4f..4dd5ea87c 100644 --- a/tests/features/producer/updateDocumentReference-failure.feature +++ b/tests/features/producer/updateDocumentReference-failure.feature @@ -258,7 +258,7 @@ Feature: Producer - updateDocumentReference - Failure Scenarios "content": [ { "attachment": { - "contentType": "application/pdf", + "contentType": "text/html", "url": "https://example.org/my-doc.pdf" }, "format": { diff --git a/tests/features/producer/upsertDocumentReference-failure.feature b/tests/features/producer/upsertDocumentReference-failure.feature index c4fc4ea68..3855b1b34 100644 --- a/tests/features/producer/upsertDocumentReference-failure.feature +++ b/tests/features/producer/upsertDocumentReference-failure.feature @@ -376,7 +376,7 @@ Feature: Producer - upsertDocumentReference - Failure Scenarios "content": [ { "attachment": { - "contentType": "application/pdf", + "contentType": "text/html", "url": "https://example.org/my-doc.pdf" }, "format": {