diff --git a/Gemfile.lock b/Gemfile.lock index 82c1823c..67e678fe 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -5,6 +5,7 @@ PATH inferno_core (~> 1.1) smart_app_launch_test_kit (~> 1.0) tls_test_kit (~> 1.0) + us_core_test_kit (~> 1.0) GEM remote: https://rubygems.org/ @@ -344,6 +345,10 @@ GEM concurrent-ruby (~> 1.0) unicode-display_width (2.6.0) unicode_utils (1.4.0) + us_core_test_kit (1.1.1) + inferno_core (~> 1.0, >= 1.0.2) + smart_app_launch_test_kit (~> 1.0) + tls_test_kit (~> 1.0) webmock (3.23.1) addressable (>= 2.8.0) crack (>= 0.3.2) diff --git a/davinci_crd_test_kit.gemspec b/davinci_crd_test_kit.gemspec index e699b252..71dfab23 100644 --- a/davinci_crd_test_kit.gemspec +++ b/davinci_crd_test_kit.gemspec @@ -11,6 +11,7 @@ Gem::Specification.new do |spec| spec.add_runtime_dependency 'inferno_core', '~> 1.1' spec.add_runtime_dependency 'smart_app_launch_test_kit', '~> 1.0' spec.add_runtime_dependency 'tls_test_kit', '~> 1.0' + spec.add_runtime_dependency 'us_core_test_kit', '~> 1.0' spec.required_ruby_version = Gem::Requirement.new('>= 3.3.6') spec.metadata['inferno_test_kit'] = 'true' spec.metadata['homepage_uri'] = spec.homepage diff --git a/lib/davinci_crd_test_kit/client_fhir_api_group.rb b/lib/davinci_crd_test_kit/client_fhir_api_group.rb index 726ab206..ed61390c 100644 --- a/lib/davinci_crd_test_kit/client_fhir_api_group.rb +++ b/lib/davinci_crd_test_kit/client_fhir_api_group.rb @@ -1,10 +1,11 @@ require 'tls_test_kit' +require 'us_core_test_kit' require_relative 'crd_options' -require_relative 'client_tests/client_fhir_api_read_test' -require_relative 'client_tests/client_fhir_api_search_test' +require_relative 'client_tests/client_fhir_api_include_search_test' require_relative 'client_tests/client_fhir_api_create_test' require_relative 'client_tests/client_fhir_api_update_test' -require_relative 'client_tests/client_fhir_api_validation_test' +require_relative 'client_tests/client_fhir_api_encounter_location_search_test' +require_relative 'client_tests/client_fhir_api_practitioner_role_group' require 'smart_app_launch/smart_stu1_suite' require 'smart_app_launch/smart_stu2_suite' @@ -14,19 +15,18 @@ class ClientFHIRAPIGroup < Inferno::TestGroup description <<~DESCRIPTION Systems wishing to conform to the [CRD Client](https://hl7.org/fhir/us/davinci-crd/STU2/CapabilityStatement-crd-client.html) role are responsible for returning data requested by the CRD Server needed to provide decision support. The Da - Vinci CRD Client FHIR API Test Group contains tests that test the ['server' capabilities](https://hl7.org/fhir/us/davinci-crd/CapabilityStatement-crd-client.html#resourcesSummary1) + Vinci CRD Client FHIR API Test Group contains tests that verify the ['server' capabilities](https://hl7.org/fhir/us/davinci-crd/CapabilityStatement-crd-client.html#resourcesSummary1) of the CRD Client and ensures that the CRD Client can respond to CRD Server queriers. These 'server' capabilities - are based on [US Core](https://hl7.org/fhir/us/core/STU3.1.1/). This test kit does not test the base US Core - capabilities. In addition to the U.S. Core expectations, the CRD Client SHALL support all 'SHOULD' `read` and - `search` capabilities listed for resources referenced in supported hooks and order types if it does not support - returning the associated resources as part of CDS Hooks pre-fetch. The CRD Client SHALL also support `update` - functionality for all resources listed where the client allows invoking hooks based on the resource. - - This test group contains two main groups of tests: - * SMART App Launch Authorization: A group of tests that perform FHIR API authorization using [SMART on FHIR](https://hl7.org/fhir/smart-app-launch/index.html) + are based on [US Core](https://hl7.org/fhir/us/core/STU3.1.1/). This test suite confirms support + for US Core STU 3.1.1 server requirements, plus additional search, create, and update functionality + specified by CRD. + + This test group contains three main sub-groups: + * **SMART App Launch Authorization**: Tests that perform FHIR API authorization using [SMART on FHIR](https://hl7.org/fhir/smart-app-launch/index.html) EHR Launch Sequence - * CRD FHIR RESTful Capabilities: A group of tests that test each CRD resource profile and ensure the CRD client - supports the appropriate FHIR operations required on each resource + * **US Core FHIR API**: Tests that verify that the client can expose data as a US Core server as required by the US Core STU 3.1.1 IG. + * **CRD FHIR API Create and Update Capabilities**: Tests that check for support of FHIR update and create interactions + on resource types that the CRD Client need to make updates to based on systemActions or card suggestions. DESCRIPTION id :crd_client_fhir_api @@ -144,34 +144,67 @@ class ClientFHIRAPIGroup < Inferno::TestGroup end end + group from: :'us_core_v311-us_core_v311_fhir_api' do + description %( + This test group verifies that the CRD Client can respond to queries as required by the + US Core 3.1.1 Server Capability Statement, plus several additional queries required + by the CRD Client Capability Statement, including + - Encounter: the `location` and `_include=Encounter:location` search parameters. + - PractitionerRole: the `_id`, `organization`, `_include=PractitionerRole:organization`, + and `_include=PractitionerRole:practition` search parameters. + + Notes: these tests do not look for crd-specific data and so only verify conformance against + US Core profiles. The hook tests take the CRD-specific profiles into account. + ) + group from: :crd_client_fhir_api_practitioner_role + reorder :crd_client_fhir_api_practitioner_role, 27 + + encounter_group = groups.find { |group| group.title.include?('Encounter') } + encounter_group.description.gsub!('* date + patient', + "* date + patient\n" \ + "* location (additional CRD requirement)\n" \ + '* _id + _include=Encounter:location (additional CRD requirement)') + encounter_group.test(from: :crd_client_fhir_api_encounter_location_search) + encounter_group.reorder(:crd_client_fhir_api_encounter_location_search, 8) + + encounter_group.test from: :crd_client_fhir_api_include_search_test, + id: :crd_client_fhir_api_encounter_location_include_search, + title: 'Search by _id and _include location', + config: { + options: { resource_type: 'Encounter', target_include_element: 'location' }, + inputs: { search_ids: { name: :encounter_id_with_location } } + } + encounter_group.reorder(:crd_client_fhir_api_encounter_location_include_search, 9) + end + group do - title 'FHIR RESTful Capabilities' + title 'CRD FHIR API Create and Update Capabilities' description %( - This test group contains groups of tests for each CRD resource profile and ensures the [CRD Client](https://hl7.org/fhir/us/davinci-crd/STU2/CapabilityStatement-crd-client.html) - supports the appropriate FHIR operations required on each resource. For each resource, Inferno will perform the - required FHIR operations, and then it will validate any resources that are returned as a result of - these FHIR operations. + This test group contains a test for each CRD resource profile that [CRD Client](https://hl7.org/fhir/us/davinci-crd/STU2/CapabilityStatement-crd-client.html) + may need to create or update based on cards that users decide to act on. - The resources that are a part of the CRD IG configuration include: + The resources that support the create or update interaction include: * [Appointment](https://hl7.org/fhir/us/davinci-crd/STU2/CapabilityStatement-crd-client.html#Appointment1-1) + update + * [ClaimResponse](https://hl7.org/fhir/us/davinci-crd/STU2/CapabilityStatement-crd-client.html#ClaimResponse1-15) + create * [CommunicationRequest](https://hl7.org/fhir/us/davinci-crd/STU2/CapabilityStatement-crd-client.html#CommunicationRequest1-2) - * [Coverage](https://hl7.org/fhir/us/davinci-crd/STU2/CapabilityStatement-crd-client.html#Coverage1-3) - * [Device](https://hl7.org/fhir/us/davinci-crd/STU2/CapabilityStatement-crd-client.html#Device1-4) + update * [DeviceRequest](https://hl7.org/fhir/us/davinci-crd/STU2/CapabilityStatement-crd-client.html#DeviceRequest1-5) + update * [Encounter](https://hl7.org/fhir/us/davinci-crd/STU2/CapabilityStatement-crd-client.html#Encounter1-6) - * [Patient](https://hl7.org/fhir/us/davinci-crd/STU2/CapabilityStatement-crd-client.html#Patient1-7) - * [Practitioner](https://hl7.org/fhir/us/davinci-crd/STU2/CapabilityStatement-crd-client.html#Practitioner1-8) - * [PractitionerRole](https://hl7.org/fhir/us/davinci-crd/STU2/CapabilityStatement-crd-client.html#PractitionerRole1-9) - * [Location](https://hl7.org/fhir/us/davinci-crd/STU2/CapabilityStatement-crd-client.html#Location1-10) + update * [MedicationRequest](https://hl7.org/fhir/us/davinci-crd/STU2/CapabilityStatement-crd-client.html#MedicationRequest1-11) + update * [NutritionOrder](https://hl7.org/fhir/us/davinci-crd/STU2/CapabilityStatement-crd-client.html#NutritionOrder1-12) - * [Organization](https://hl7.org/fhir/us/davinci-crd/STU2/CapabilityStatement-crd-client.html#Organization1-13) + update * [ServiceRequest](https://hl7.org/fhir/us/davinci-crd/STU2/CapabilityStatement-crd-client.html#ServiceRequest1-14) - * [ClaimResponse](https://hl7.org/fhir/us/davinci-crd/STU2/CapabilityStatement-crd-client.html#ClaimResponse1-15) + update * [Task](https://hl7.org/fhir/us/davinci-crd/STU2/CapabilityStatement-crd-client.html#Task1-16) + update * [VisionPrescription](https://hl7.org/fhir/us/davinci-crd/STU2/CapabilityStatement-crd-client.html#VisionPrescription1-17) + update ) - verifies_requirements 'hl7.fhir.us.davinci-crd_2.0.1@43' input :url input :smart_auth_info, @@ -185,601 +218,145 @@ class ClientFHIRAPIGroup < Inferno::TestGroup auth_info :smart_auth_info end - group do - title 'Appointment' - description %( - Verify the CRD client can perform the required FHIR interactions on the Appointment resource, and - validate any returned resources against the [CRD Appointment profile](https://hl7.org/fhir/us/davinci-crd/STU2/StructureDefinition-profile-appointment.html) - - Required Appointment resource FHIR interactions: - * SHOULD support `update` - - Resource Conformance: SHOULD - ) - optional - - test from: :crd_client_fhir_api_update_test, - optional: true, - config: { - options: { resource_type: 'Appointment' }, - inputs: { - update_resources: { - name: :appointment_update_resources, - title: 'Appointment Resources' - } - } - } - end - - group do - title 'CommunicationRequest' - description %( - Verify the CRD client can perform the required FHIR interactions on the CommunicationRequest resource, and - validate any returned resources against the [CRD CommunicationRequest profile](https://hl7.org/fhir/us/davinci-crd/STU2/StructureDefinition-profile-communicationrequest.html) - - Required CommunicationRequest resource FHIR interactions: - * SHOULD support `update` - - Resource Conformance: SHOULD - ) - optional - verifies_requirements 'hl7.fhir.us.davinci-crd_2.0.1@150' - - test from: :crd_client_fhir_api_update_test, - optional: true, - config: { - options: { resource_type: 'CommunicationRequest' }, - inputs: { - update_resources: { - name: :communication_request_update_resources, - title: 'CommunicationRequest Resources' - } - } - } - end - - group do - title 'Coverage' - description %( - Verify the CRD client can perform the required FHIR interactions on the Coverage resource, and - validate any returned resources against the [CRD Coverage profile](https://hl7.org/fhir/us/davinci-crd/STU2/StructureDefinition-profile-coverage.html) - - Required Coverage resource FHIR interactions: - * SHALL support search by [`patient`](http://hl7.org/fhir/R4/coverage.html#search) - * SHALL support search by [`status`](http://hl7.org/fhir/R4/coverage.html#search) - - Resource Conformance: SHALL - ) - - test from: :crd_client_fhir_api_search_test, - id: :crd_client_coverage_patient_search_test, - title: 'Search by patient', - config: { - options: { resource_type: 'Coverage', search_type: 'patient' }, - inputs: { search_param_values: { - name: :patient_ids, - title: 'Patient IDs', - description: 'Comma separated list of Patient IDs that in sum contain all MUST SUPPORT elements' - } } - } - - test from: :crd_client_fhir_api_search_test, - id: :crd_client_coverage_status_search_test, - title: 'Search by status', - config: { - options: { resource_type: 'Coverage', search_type: 'status' }, - inputs: { search_param_values: { - name: :patient_ids - } } - } - - test from: :crd_client_fhir_api_validation_test, - config: { - options: { resource_type: 'Coverage' } - } - end - - group do - title 'Device' - description %( - Verify the CRD client can perform the required FHIR interactions on the Device resource, and - validate any returned resources against the [CRD Device profile](https://hl7.org/fhir/us/davinci-crd/STU2/StructureDefinition-profile-device.html) - - Required Device resource FHIR interactions: - * SHOULD support `read` - - Resource Conformance: SHOULD - ) - optional - - test from: :crd_client_fhir_api_read_test, - optional: true, - config: { - options: { resource_type: 'Device' }, - inputs: { - resource_ids: { - name: :device_ids, - title: 'Device IDs', - description: 'Comma separated list of Device IDs that in sum contain all MUST SUPPORT elements' - } - } - } - - test from: :crd_client_fhir_api_validation_test, - config: { - options: { resource_type: 'Device' } - } - end - - group do - title 'DeviceRequest' - description %( - Verify the CRD client can perform the required FHIR interactions on the DeviceRequest resource, and - validate any returned resources against the [CRD DeviceRequest profile](https://hl7.org/fhir/us/davinci-crd/STU2/StructureDefinition-profile-devicerequest.html) - - Required DeviceRequest resource FHIR interactions: - * SHOULD support `update` - - Resource Conformance: SHOULD - ) - optional - verifies_requirements 'hl7.fhir.us.davinci-crd_2.0.1@150' - - test from: :crd_client_fhir_api_update_test, - optional: true, - config: { - options: { resource_type: 'DeviceRequest' }, - inputs: { - update_resources: { - name: :device_request_update_resources, - title: 'DeviceRequest Resources' - } - } - } - end - - group do - title 'Encounter' - description %( - Verify the CRD client can perform the required FHIR interactions on the Encounter resource, and - validate any returned resources against the [CRD Encounter profile](https://hl7.org/fhir/us/davinci-crd/STU2/StructureDefinition-profile-encounter.html) - - Required Encounter resource FHIR interactions: - * SHOULD support `update` - * SHALL support search by [`_id`](http://hl7.org/fhir/R4/encounter.html#search) - * SHALL support search by [`organization`](http://hl7.org/fhir/R4/encounter.html#search) and - performing an `_include` on Location - - Resource Conformance: SHALL - ) - - test from: :crd_client_fhir_api_update_test, - optional: true, - config: { - options: { resource_type: 'Encounter' }, - inputs: { - update_resources: { - name: :encounter_update_resources, - title: 'Encounter Resources' - } - } - } - - test from: :crd_client_fhir_api_search_test, - id: :crd_client_encounter_id_search_test, - title: 'Search by _id', - config: { - options: { resource_type: 'Encounter', search_type: '_id' }, - inputs: { search_param_values: { - name: :encounter_ids, - title: 'Encounter IDs', - description: 'Comma separated list of Encounter IDs that in sum contain all MUST SUPPORT elements' - } } - } - - test from: :crd_client_fhir_api_search_test, - id: :crd_client_encounter_organization_search_test, - title: 'Search by organization', - config: { - options: { resource_type: 'Encounter', search_type: 'organization' }, - inputs: { search_param_values: { - name: :organization_ids, - title: 'Organization IDs', - description: 'Comma separated list of Organization IDs that in sum contain all MUST SUPPORT elements' - } } - } - - test from: :crd_client_fhir_api_search_test, - id: :crd_client_encounter_location_include_test, - title: 'Search by _id and _include location', - config: { - options: { resource_type: 'Encounter', search_type: 'location_include' }, - inputs: { search_param_values: { - name: :encounter_ids, - title: 'Encounter IDs', - description: 'Comma separated list of Encounter IDs that in sum contain all MUST SUPPORT elements' - } } - } - - test from: :crd_client_fhir_api_validation_test, - config: { - options: { resource_type: 'Encounter' } - } - end - - group do - title 'Patient' - description %( - Verify the CRD client can perform the required FHIR interactions on the Patient resource, and - validate any returned resources against the [CRD Patient profile](https://hl7.org/fhir/us/davinci-crd/STU2/StructureDefinition-profile-patient.html) - - Required Patient resource FHIR interactions: - * SHOULD support `read` - - Resource Conformance: SHOULD - ) - optional - - test from: :crd_client_fhir_api_read_test, - optional: true, - config: { - options: { resource_type: 'Patient' }, - inputs: { - resource_ids: { - name: :patient_ids, - title: 'Patient IDs', - description: 'Comma separated list of Patient IDs that in sum contain all MUST SUPPORT elements' - } + test from: :crd_client_fhir_api_update_test, + id: :crd_client_fhir_api_appointment_update_test, + title: 'Appointment Update', + optional: true, + config: { + options: { resource_type: 'Appointment' }, + inputs: { + update_resources: { + name: :appointment_update_resources, + title: 'Appointment Resources' } } - - test from: :crd_client_fhir_api_validation_test, - config: { - options: { resource_type: 'Patient' } - } - end - - group do - title 'Practitioner' - description %( - Verify the CRD client can perform the required FHIR interactions on the Practitioner resource, and - validate any returned resources against the [CRD Practitioner profile](https://hl7.org/fhir/us/davinci-crd/STU2/StructureDefinition-profile-practitioner.html) - - Required Practitioner resource FHIR interactions: - * SHOULD support `read` - - Resource Conformance: SHOULD - ) - optional - - test from: :crd_client_fhir_api_read_test, - optional: true, - config: { - options: { resource_type: 'Practitioner' }, - inputs: { - resource_ids: { - name: :practitioner_ids, - title: 'Practitioner IDs', - description: 'Comma separated list of Practitioner IDs that in sum contain all MUST SUPPORT elements' - } + } + + test from: :crd_client_fhir_api_create_test, + id: :crd_client_fhir_api_claim_response_create_test, + title: 'ClaimResponse Create', + optional: true, + config: { + options: { resource_type: 'ClaimResponse' }, + inputs: { + create_resources: { + name: :claim_response_create_resources, + title: 'ClaimResponse Resources' } } - - test from: :crd_client_fhir_api_validation_test, - config: { - options: { resource_type: 'Practitioner' } - } - end - - group do - title 'PractitionerRole' - description %( - Verify the CRD client can perform the required FHIR interactions on the PractitionerRole resource, and - validate any returned resources against the [US Core PractitionerRole profile](https://hl7.org/fhir/us/core/STU3.1.1/StructureDefinition-us-core-practitionerrole.html) - - Required PractitionerRole resource FHIR interactions: - * SHALL support search by [`_id`](http://hl7.org/fhir/R4/practitionerrole.html#search) - * SHALL support search by [`organization`](http://hl7.org/fhir/R4/practitionerrole.html#search) and - performing an `_include` on Organization - * SHALL support search by [`practitioner`](http://hl7.org/fhir/R4/practitionerrole.html#search) and - performing an `_include` on Practitioner - - Resource Conformance: SHALL - ) - - test from: :crd_client_fhir_api_search_test, - id: :crd_client_practitioner_role_id_search_test, - title: 'Search by _id', - config: { - options: { resource_type: 'PractitionerRole', search_type: '_id' }, - inputs: { search_param_values: { - name: :practitioner_role_ids, - title: 'PractitionerRole IDs', - description: 'Comma separated list of Practitioner IDs that in sum contain all MUST SUPPORT elements' - } } - } - - test from: :crd_client_fhir_api_search_test, - id: :crd_client_practitioner_role_organization_search_test, - title: 'Search by organization', - config: { - options: { resource_type: 'PractitionerRole', search_type: 'organization' }, - inputs: { search_param_values: { - name: :organization_ids, - title: 'Organization IDs', - description: 'Comma separated list of Organization IDs that in sum contain all MUST SUPPORT elements' - } } - } - - test from: :crd_client_fhir_api_search_test, - id: :crd_client_practitioner_role_practitioner_search_test, - title: 'Search by practitioner', - config: { - options: { resource_type: 'PractitionerRole', search_type: 'practitioner' }, - inputs: { search_param_values: { - name: :practitioner_ids, - title: 'Practitioner IDs', - description: 'Comma separated list of Practitioner IDs that in sum contain all MUST SUPPORT elements' - } } - } - - test from: :crd_client_fhir_api_search_test, - id: :crd_client_practitioner_role_organization_include_test, - title: 'Search by _id and _include organization', - config: { - options: { resource_type: 'PractitionerRole', search_type: 'organization_include' }, - inputs: { search_param_values: { - name: :practitioner_role_ids, - title: 'PractitionerRole IDs', - description: %( - Comma separated list of PractitionerRole IDs that in sum contain all MUST SUPPORT elements - ) - } } - } - - test from: :crd_client_fhir_api_search_test, - id: :crd_client_practitioner_role_practitioner_include_test, - title: 'Search by _id and _include practitioner', - config: { - options: { resource_type: 'PractitionerRole', search_type: 'practitioner_include' }, - inputs: { search_param_values: { - name: :practitioner_role_ids, - title: 'PractitionerRole IDs', - description: %( - Comma separated list of PractitionerRole IDs that in sum contain all MUST SUPPORT elements - ) - } } - } - - test from: :crd_client_fhir_api_validation_test, - config: { - options: { resource_type: 'PractitionerRole' } - } - end - - group do - title 'Location' - description %( - Verify the CRD client can perform the required FHIR interactions on the Location resource, and - validate any returned resources against the [CRD Location profile](https://hl7.org/fhir/us/davinci-crd/STU2/StructureDefinition-profile-location.html) - - Required Location resource FHIR interactions: - * SHOULD support `read` - - Resource Conformance: SHOULD - ) - optional - - test from: :crd_client_fhir_api_read_test, - optional: true, - config: { - options: { resource_type: 'Location' }, - inputs: { - resource_ids: { - name: :location_ids, - title: 'Location IDs', - description: 'Comma separated list of Location IDs that in sum contain all MUST SUPPORT elements' - } + } + + test from: :crd_client_fhir_api_update_test, + id: :crd_client_fhir_api_communication_request_update_test, + title: 'CommunicationRequest Update', + optional: true, + config: { + options: { resource_type: 'CommunicationRequest' }, + inputs: { + update_resources: { + name: :communication_request_update_resources, + title: 'CommunicationRequest Resources' } } - - test from: :crd_client_fhir_api_validation_test, - config: { - options: { resource_type: 'Location' } - } - end - - group do - title 'MedicationRequest' - description %( - Verify the CRD client can perform the required FHIR interactions on the MedicationRequest resource, and - validate any returned resources against the [CRD MedicationRequest profile](https://hl7.org/fhir/us/davinci-crd/STU2/StructureDefinition-profile-medicationrequest.html) - - Required MedicationRequest resource FHIR interactions: - * SHOULD support `update` - - Resource Conformance: SHOULD - ) - optional - verifies_requirements 'hl7.fhir.us.davinci-crd_2.0.1@150' - - test from: :crd_client_fhir_api_update_test, - optional: true, - config: { - options: { resource_type: 'MedicationRequest' }, - inputs: { - update_resources: { - name: :medication_request_update_resources, - title: 'MedicationRequest Resources' - } + } + + test from: :crd_client_fhir_api_update_test, + id: :crd_client_fhir_api_device_request_update_test, + title: 'DeviceRequest Update', + optional: true, + config: { + options: { resource_type: 'DeviceRequest' }, + inputs: { + update_resources: { + name: :device_request_update_resources, + title: 'DeviceRequest Resources' } } - end - - group do - title 'NutritionOrder' - description %( - Verify the CRD client can perform the required FHIR interactions on the NutritionOrder resource, and - validate any returned resources against the [CRD NutritionOrder profile](https://hl7.org/fhir/us/davinci-crd/STU2/StructureDefinition-profile-nutritionorder.html) - - Required NutritionOrder resource FHIR interactions: - * SHOULD support `update` - - Resource Conformance: SHOULD - ) - optional - verifies_requirements 'hl7.fhir.us.davinci-crd_2.0.1@150' - - test from: :crd_client_fhir_api_update_test, - optional: true, - config: { - options: { resource_type: 'NutritionOrder' }, - inputs: { - update_resources: { - name: :nutrition_order_update_resources, - title: 'NutritionOrder Resources' - } + } + + test from: :crd_client_fhir_api_update_test, + id: :crd_client_fhir_api_encounter_update_test, + title: 'Encounter Update', + optional: true, + config: { + options: { resource_type: 'Encounter' }, + inputs: { + update_resources: { + name: :encounter_update_resources, + title: 'Encounter Resources' } } - end - - group do - title 'Organization' - description %( - Verify the CRD client can perform the required FHIR interactions on the Organization resource, and - validate any returned resources against the [CRD Organization profile](https://hl7.org/fhir/us/davinci-crd/STU2/StructureDefinition-profile-organization.html) - - Required Organization resource FHIR interactions: - * SHOULD support `read` - - Resource Conformance: SHOULD - ) - optional - - test from: :crd_client_fhir_api_read_test, - optional: true, - config: { - options: { resource_type: 'Organization' }, - inputs: { - resource_ids: { - name: :organization_ids, - title: 'Organization IDs', - description: 'Comma separated list of Organization IDs that in sum contain all MUST SUPPORT elements' - } + } + + test from: :crd_client_fhir_api_update_test, + id: :crd_client_fhir_api_medication_request_update_test, + title: 'MedicationRequest Update', + optional: true, + config: { + options: { resource_type: 'MedicationRequest' }, + inputs: { + update_resources: { + name: :medication_request_update_resources, + title: 'MedicationRequest Resources' } } - - test from: :crd_client_fhir_api_validation_test, - config: { - options: { resource_type: 'Organization' } - } - end - - group do - title 'ServiceRequest' - description %( - Verify the CRD client can perform the required FHIR interactions on the ServiceRequest resource, and - validate any returned resources against the [CRD ServiceRequest profile](https://hl7.org/fhir/us/davinci-crd/STU2/StructureDefinition-profile-servicerequest.html) - - Required ServiceRequest resource FHIR interactions: - * SHOULD support `update` - - Resource Conformance: SHOULD - ) - optional - verifies_requirements 'hl7.fhir.us.davinci-crd_2.0.1@150' - - test from: :crd_client_fhir_api_update_test, - optional: true, - config: { - options: { resource_type: 'ServiceRequest' }, - inputs: { - update_resources: { - name: :service_request_update_resources, - title: 'ServiceRequest Resources' - } + } + + test from: :crd_client_fhir_api_update_test, + id: :crd_client_fhir_api_nutrition_order_update_test, + title: 'NutritionOrder Update', + optional: true, + config: { + options: { resource_type: 'NutritionOrder' }, + inputs: { + update_resources: { + name: :nutrition_order_update_resources, + title: 'NutritionOrder Resources' } } - end - - group do - title 'ClaimResponse' - description %( - Verify the CRD client can perform the required FHIR interactions on the ClaimResponse resource, and - validate any returned resources against the [CRD ClaimResponse profile](https://hl7.org/fhir/us/davinci-hrex/STU1/StructureDefinition-hrex-claimresponse.html) - - Required ClaimResponse resource FHIR interactions: - * SHOULD support `create` - - Resource Conformance: SHOULD - ) - optional - - test from: :crd_client_fhir_api_create_test, - optional: true, - config: { - options: { resource_type: 'ClaimResponse' }, - inputs: { - create_resources: { - name: :claim_response_create_resources, - title: 'ClaimResponse Resources' - } + } + + test from: :crd_client_fhir_api_update_test, + id: :crd_client_fhir_api_service_request_update_test, + title: 'ServiceRequest Update', + optional: true, + config: { + options: { resource_type: 'ServiceRequest' }, + inputs: { + update_resources: { + name: :service_request_update_resources, + title: 'ServiceRequest Resources' } } - end - - group do - title 'Task' - description %( - Verify the CRD client can perform the required FHIR interactions on the Task resource, and - validate any returned resources against the [CRD Task profile](https://hl7.org/fhir/us/davinci-crd/STU2/StructureDefinition-profile-taskquestionnaire.html) - - Required Task resource FHIR interactions: - * SHOULD support `create` - - Resource Conformance: SHOULD - ) - optional - - test from: :crd_client_fhir_api_create_test, - optional: true, - config: { - options: { resource_type: 'Task' }, - inputs: { - create_resources: { - name: :task_create_resources, - title: 'Task Resources' - } + } + + test from: :crd_client_fhir_api_create_test, + id: :crd_client_fhir_api_task_create_test, + title: 'Task Create', + optional: true, + config: { + options: { resource_type: 'Task' }, + inputs: { + create_resources: { + name: :task_create_resources, + title: 'Task Resources' } } - end - - group do - title 'VisionPrescription' - description %( - Verify the CRD client can perform the required FHIR interactions on the VisionPrescription resource, and - validate any returned resources against the [CRD VisionPrescription profile](https://hl7.org/fhir/us/davinci-crd/STU2/StructureDefinition-profile-visionprescription.html) - - Required VisionPrescription resource FHIR interactions: - * SHOULD support `update` - - Resource Conformance: SHOULD - ) - optional - verifies_requirements 'hl7.fhir.us.davinci-crd_2.0.1@150' - - test from: :crd_client_fhir_api_update_test, - optional: true, - config: { - options: { resource_type: 'VisionPrescription' }, - inputs: { - update_resources: { - name: :vision_prescription_update_resources, - title: 'VisionPrescription Resources' - } + } + + test from: :crd_client_fhir_api_update_test, + id: :crd_client_fhir_api_vision_prescription_update_test, + title: 'VisionPrescription Update', + optional: true, + config: { + options: { resource_type: 'VisionPrescription' }, + inputs: { + update_resources: { + name: :vision_prescription_update_resources, + title: 'VisionPrescription Resources' } } - end + } end end end diff --git a/lib/davinci_crd_test_kit/client_tests/client_fhir_api_encounter_location_search_test.rb b/lib/davinci_crd_test_kit/client_tests/client_fhir_api_encounter_location_search_test.rb new file mode 100644 index 00000000..d9b87fa5 --- /dev/null +++ b/lib/davinci_crd_test_kit/client_tests/client_fhir_api_encounter_location_search_test.rb @@ -0,0 +1,54 @@ +require 'us_core_test_kit' + +module DaVinciCRDTestKit + class ClientFHIRApiEncounterLocationSearchTest < USCoreTestKit::USCoreV311::EncounterIdentifierSearchTest + title 'Server returns valid results for Encounter search by location' + description %( +A CRD Client (as a FHIR server) SHALL support searching by +location on the Encounter resource. This test +will pass if resources are returned and match the search criteria. If +none are returned, the test is skipped. + +[CRD Client CapabilityStatement](https://hl7.org/fhir/us/davinci-crd/STU2/CapabilityStatement-crd-client.html) + + ) + + id :crd_client_fhir_api_encounter_location_search + optional false + + output :encounter_id_with_location + + def self.properties + @properties ||= USCoreTestKit::SearchTestProperties.new( + resource_type: 'Encounter', + search_param_names: ['location'], + possible_status_search: false + ) + end + + def add_location_search_to_metadata + metadata.search_definitions[:location] = { + paths: ['location.location'], + full_paths: ['Encounter.location.location'], + comparators: {}, + values: [], + type: 'Reference', + contains_multiple: true, + multiple_or: 'MAY' + } + end + + run do + add_location_search_to_metadata + run_search_test + + return unless requests.present? + + search_response_bundle = FHIR.from_contents(requests[0].response_body) + return unless search_response_bundle.is_a?(FHIR::Bundle) + + encounter_id_with_location = search_response_bundle.entry&.first&.resource&.id + output(encounter_id_with_location:) + end + end +end diff --git a/lib/davinci_crd_test_kit/client_tests/client_fhir_api_include_search_test.rb b/lib/davinci_crd_test_kit/client_tests/client_fhir_api_include_search_test.rb new file mode 100644 index 00000000..0c9fabe2 --- /dev/null +++ b/lib/davinci_crd_test_kit/client_tests/client_fhir_api_include_search_test.rb @@ -0,0 +1,97 @@ +module DaVinciCRDTestKit + class ClientFHIRApiIncludeSearchTest < Inferno::Test + id :crd_client_fhir_api_include_search_test + title 'Search Interaction' + description %( + Verify that the CRD client supports the `_include` directive targeting + the specified element on searches for the resource type. + ) + + input :search_id, + optional: true + + def resource_type + config.options[:resource_type] + end + + def target_include_element + config.options[:target_include_element] + end + + def perform_fhir_search(search_params, tags) + fhir_search(resource_type, params: search_params, tags:) + assert_response_status(200) + assert_resource_type(:bundle) + resource + end + + def include_search_result_check(bundle, search_id, included_resource_type) # rubocop:disable Metrics/CyclomaticComplexity + skip_if bundle.entry.blank?, + "_include search not demonstrated - search result bundle is empty for #{resource_type} " \ + "_include #{target_include_element} search with an id of `#{search_id}`." + + searched_resource_entry = bundle.entry.find do |entry| + entry&.resource&.resourceType == resource_type && entry&.resource&.id == search_id + end + assert(searched_resource_entry.present?, + "The #{included_resource_type} _include search for #{resource_type} resource with id #{search_id} " \ + "did not return a #{resource_type} resource matching the searched id #{search_id}.") + + searched_resource = searched_resource_entry.resource + base_resource_references = Array.wrap(get_reference_field(included_resource_type, searched_resource)).compact + skip_if base_resource_references.blank?, + "#{resource_type} resource with id #{searched_resource.id} did not include references in " \ + "the element targeted to include #{included_resource_type} resources." + + base_resource_references.each do |include_target| + target_resource_type, target_id = + if include_target.reference.include?('/') + include_target.reference.split('/') + else + [included_resource_type, include_target.reference] + end + + target_entry = bundle.entry.find do |entry| + entry&.resource&.resourceType == target_resource_type && entry&.resource&.id == target_id + end + + assert target_entry.present?, + "referenced resource `#{target_resource_type}/#{target_id}` not returned from the search" + end + + returned_resources = bundle.entry + .map(&:resource) + .select { |resource| resource.present? && resource.resourceType != 'OperationOutcome' } + warning do + assert returned_resources.length == base_resource_references.length + 1, + 'Additional resources returned beyond those requested. While servers are allowed ' \ + 'to return additional resources that they deem to be relevant, this may be an indication ' \ + 'that the server is not correctly filtering results.' + end + end + + def get_reference_field(reference_type, entry) + case reference_type + when 'practitioner' + entry.practitioner + when 'organization' + if resource_type == 'Encounter' + entry.serviceProvider + else + entry.organization + end + when 'location' + locations = entry.location + locations&.map(&:location) + end + end + + run do + skip_if search_id.blank?, 'No target id to use for the search, skipping test.' + + bundle = perform_fhir_search({ _id: search_id, _include: "#{resource_type}:#{target_include_element}" }, + [resource_type, "include_#{target_include_element}_search"]) + include_search_result_check(bundle, search_id, target_include_element) + end + end +end diff --git a/lib/davinci_crd_test_kit/client_tests/client_fhir_api_practitioner_role_group.rb b/lib/davinci_crd_test_kit/client_tests/client_fhir_api_practitioner_role_group.rb new file mode 100644 index 00000000..dfb46322 --- /dev/null +++ b/lib/davinci_crd_test_kit/client_tests/client_fhir_api_practitioner_role_group.rb @@ -0,0 +1,110 @@ +require_relative 'practitioner_role/practitioner_role_read_test' +require_relative 'practitioner_role/practitioner_role_id_search_test' +require_relative 'practitioner_role/practitioner_role_specialty_search_test' +require_relative 'practitioner_role/practitioner_role_organization_search_test' +require_relative 'practitioner_role/practitioner_role_practitioner_search_test' +require_relative 'practitioner_role/practitioner_role_validation_test' +require_relative 'practitioner_role/practitioner_role_must_support_test' +require_relative 'practitioner_role/practitioner_role_reference_resolution_test' + +module DaVinciCRDTestKit + class PractitionerRoleGroup < Inferno::TestGroup + title 'PractitionerRole Tests' + short_description 'Verify support for the server capabilities required by the US Core PractitionerRole Profile.' + description %( + # Background + +The US Core PractitionerRole sequence verifies that the system under test is +able to provide correct responses for PractitionerRole queries. These queries +must contain resources conforming to the US Core PractitionerRole Profile as +specified in the US Core v3.1.1 Implementation Guide. + +# Testing Methodology +## Searching +This test sequence will first perform each required search associated +with this resource. This sequence will perform searches with the +following parameters: + +* _id +* specialty +* practitioner +* organization + +### Search Parameters +The first search uses the selected patient(s) from the prior launch +sequence. Any subsequent searches will look for its parameter values +from the results of the first search. For example, the `identifier` +search in the patient sequence is performed by looking for an existing +`Patient.identifier` from any of the resources returned in the `_id` +search. If a value cannot be found this way, the search is skipped. + +### Search Validation +Inferno will retrieve up to the first 20 bundle pages of the reply for +PractitionerRole resources and save them for subsequent tests. Each of +these resources is then checked to see if it matches the searched +parameters in accordance with [FHIR search +guidelines](https://www.hl7.org/fhir/search.html). The test will fail, +for example, if a Patient search for `gender=male` returns a `female` +patient. + + +## Must Support +Each profile contains elements marked as "must support". This test +sequence expects to see each of these elements at least once. If at +least one cannot be found, the test will fail. The test will look +through the PractitionerRole resources found in the first test for these +elements. + +## Profile Validation +Each resource returned from the first search is expected to conform to +the [US Core PractitionerRole Profile](http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitionerrole). +Each element is checked against teminology binding and cardinality requirements. + +Elements with a required binding are validated against their bound +ValueSet. If the code/system in the element is not part of the ValueSet, +then the test will fail. + +## Reference Validation +At least one instance of each external reference in elements marked as +"must support" within the resources provided by the system must resolve. +The test will attempt to read each reference found and will fail if no +read succeeds. + + ) + + id :crd_client_fhir_api_practitioner_role + run_as_group + + def self.metadata + @metadata ||= Generator::GroupMetadata.new(YAML.load_file( + File.join(__dir__, 'practitioner_role', + 'metadata.yml'), aliases: true + )) + end + + test from: :crd_client_fhir_api_practitioner_role_read_test + test from: :crd_client_fhir_api_practitioner_role_id_search_test + test from: :crd_client_fhir_api_practitioner_role_specialty_search_test + test from: :crd_client_fhir_api_practitioner_role_organization_search_test + test from: :crd_client_fhir_api_practitioner_role_practitioner_search_test + test from: :crd_client_fhir_api_include_search_test, + id: :crd_client_fhir_api_practitioner_role_organization_include_search, + title: 'Search by _id and _include organization', + config: { + options: { resource_type: 'PractitionerRole', + target_include_element: 'organization' }, + inputs: { search_id: { name: :practitioner_role_id_with_organization } } + } + test from: :crd_client_fhir_api_include_search_test, + id: :crd_client_fhir_api_practitioner_role_practitioner_include_search, + title: 'Search by _id and _include practitioner', + config: { + options: { resource_type: 'PractitionerRole', + target_include_element: 'practitioner' }, + inputs: { search_id: { name: :practitioner_role_id_with_practitioner } } + } + test from: :crd_client_fhir_api_practitioner_role_validation_test + test from: :crd_client_fhir_api_practitioner_role_must_support_test + test from: :crd_client_fhir_api_practitioner_role_reference_resolution_test + end +end diff --git a/lib/davinci_crd_test_kit/client_tests/client_fhir_api_read_test.rb b/lib/davinci_crd_test_kit/client_tests/client_fhir_api_read_test.rb deleted file mode 100644 index 8c059dea..00000000 --- a/lib/davinci_crd_test_kit/client_tests/client_fhir_api_read_test.rb +++ /dev/null @@ -1,40 +0,0 @@ -module DaVinciCRDTestKit - class ClientFHIRApiReadTest < Inferno::Test - id :crd_client_fhir_api_read_test - title 'Read Interaction' - description %( - Verify that the CRD client supports the read interaction for the given resource. The capabilities required by - each resource can be found here: https://hl7.org/fhir/us/davinci-crd/CapabilityStatement-crd-client.html#resourcesSummary1 - ) - - input :resource_ids, - optional: true - - def resource_type - config.options[:resource_type] - end - - def no_resources_skip_message - "No #{resource_type} resource ids were provided, skipping test. " - end - - def bad_resource_id_message(expected_id) - "Expected resource to have id: `#{expected_id}`, but found `#{resource.id}`" - end - - run do - skip_if resource_ids.blank?, no_resources_skip_message - - resource_id_list = resource_ids.split(',').map(&:strip) - assert resource_id_list.present?, "No #{resource_type} id provided." - - resource_id_list.each do |resource_id_to_read| - fhir_read resource_type, resource_id_to_read, tags: [resource_type, 'read'] - - assert_response_status(200) - assert_resource_type(resource_type) - assert resource.id.present? && resource.id == resource_id_to_read, bad_resource_id_message(resource_id_to_read) - end - end - end -end diff --git a/lib/davinci_crd_test_kit/client_tests/client_fhir_api_search_test.rb b/lib/davinci_crd_test_kit/client_tests/client_fhir_api_search_test.rb deleted file mode 100644 index 3f2d450a..00000000 --- a/lib/davinci_crd_test_kit/client_tests/client_fhir_api_search_test.rb +++ /dev/null @@ -1,232 +0,0 @@ -module DaVinciCRDTestKit - class ClientFHIRApiSearchTest < Inferno::Test - id :crd_client_fhir_api_search_test - title 'Search Interaction' - description %( - Verify that the CRD client supports the specified search interaction for the given resource. The capabilities - required by each resource can be found here: https://hl7.org/fhir/us/davinci-crd/CapabilityStatement-crd-client.html#resourcesSummary1 - ) - - input :search_param_values, - optional: true - - attr_accessor :successful_search - - def resource_type - config.options[:resource_type] - end - - def search_type - config.options[:search_type] - end - - def include_searches - ['organization_include', 'practitioner_include', 'location_include'] - end - - def reference_search_parameters - ['organization', 'practitioner', 'patient'] - end - - def bad_resource_id_message(expected_id, actual_id) - "Expected resource to have id: `#{expected_id}`, but found `#{actual_id}`" - end - - def perform_fhir_search(search_params, tags) - fhir_search(resource_type, params: search_params, tags:) - assert_response_status(200) - assert_resource_type(:bundle) - resource - end - - def status_search_result_check(bundle, status) - return if bundle.entry.empty? - - self.successful_search = true - - bundle.entry - .reject { |entry| entry&.resource&.resourceType == 'OperationOutcome' } - .map(&:resource) - .each do |resource| - assert_resource_type(resource_type, resource:) - assert(resource.status == status, %( - Each #{resource_type} resource in search result bundle should have a status of `#{status}`, instead got: - `#{resource.status}` for resource with id: `#{resource.id}` - )) - end - end - - def check_id_search_result_entry(bundle_entry, search_id, entry_resource_type) - assert_resource_type(entry_resource_type, resource: bundle_entry) - - assert bundle_entry.id.present?, "Expected id field in returned #{entry_resource_type} resource" - - assert bundle_entry.id == search_id, - bad_resource_id_message(search_id, bundle_entry.id) - end - - def id_search_result_check(bundle, search_id) - warning do - assert bundle.entry.any?, - "Search result bundle is empty for #{resource_type} _id search with an id of `#{search_id}`" - end - return if bundle.entry.empty? - - self.successful_search = true - - bundle.entry - .reject { |entry| entry&.resource&.resourceType == 'OperationOutcome' } - .map(&:resource) - .each do |resource| - check_id_search_result_entry(resource, search_id, resource_type) - end - end - - def check_include_reference(base_resource_entry, include_resource_id, include_resource_type) - base_resource_references = Array.wrap(get_reference_field(include_resource_type, base_resource_entry)).compact - - assert(base_resource_references.present?, %( - #{resource_type} resource with id #{base_resource_entry.id} did not include the field that references a - #{include_resource_type} resource} - )) - - base_resource_reference_match_found = base_resource_references.any? do |base_resource_reference| - base_resource_reference.reference_id == include_resource_id - end - - assert(base_resource_reference_match_found, %( - The #{resource_type} resource in search result bundle with id #{base_resource_entry.id} did not have a - #{include_resource_type} reference with an id of `#{include_resource_id}`.` - )) - end - - def include_search_result_check(bundle, search_id, included_resource_type) # rubocop:disable Metrics/CyclomaticComplexity - warning do - assert bundle.entry.any?, - "Search result bundle is empty for #{resource_type} _include #{search_type} search with an id - of `#{search_id}`" - end - return if bundle.entry.empty? - - self.successful_search = true - - base_resource_entry_list = bundle.entry.select do |entry| - entry.resource&.resourceType == resource_type - end - - assert(base_resource_entry_list.length == 1, %( - The #{included_resource_type} _include search for #{resource_type} resource with id #{search_id} - should include exactly 1 #{resource_type} resource, instead got #{base_resource_entry_list.length}. - )) - - base_resource_entry = base_resource_entry_list.first.resource - - bundle.entry - .map(&:resource) - .each do |resource| - entry_resource_type = resource.resourceType - - if entry_resource_type == resource_type - check_id_search_result_entry(resource, search_id, entry_resource_type) - elsif entry_resource_type != 'OperationOutcome' - entry_resource_type = included_resource_type.capitalize - assert_resource_type(entry_resource_type, resource:) - - included_resource_id = resource.id - assert included_resource_id.present?, "Expected id field in returned #{entry_resource_type} resource" - check_include_reference(base_resource_entry, included_resource_id, included_resource_type) - end - end - end - - def get_reference_field(reference_type, entry) - case reference_type - when 'patient' - entry.beneficiary - when 'practitioner' - entry.practitioner - when 'organization' - if resource_type == 'Encounter' - entry.serviceProvider - else - entry.organization - end - when 'location' - locations = entry.location - locations.map(&:location) - end - end - - def reference_search_result_check(bundle, reference_id, reference_type) - warning do - assert bundle.entry.any?, %( - Search result bundle is empty for #{resource_type} #{reference_type} search with a #{reference_type} id - `#{reference_id}` - ) - end - return if bundle.entry.empty? - - self.successful_search = true - - bundle.entry - .reject { |entry| entry&.resource&.resourceType == 'OperationOutcome' } - .map(&:resource) - .each do |resource| - assert_resource_type(resource_type, resource:) - - entry_reference_field = get_reference_field(reference_type, resource) - assert( - entry_reference_field.present?, - %( - #{resource_type} resource with id #{resource.id} did not include the field that references - a #{reference_type} resource - ) - ) - - entry_reference_id = entry_reference_field.reference_id - assert( - entry_reference_id == reference_id, - %( - The #{resource_type} resource in search result bundle with id #{resource.id} should have a - #{reference_type} reference with an id of `#{reference_id}`, instead got: `#{entry_reference_id}` - ) - ) - end - end - - run do - if search_type == 'status' - coverage_status = ['active', 'cancelled', 'draft', 'entered-in-error'] - coverage_status.each do |status| - bundle = perform_fhir_search({ status: }, [resource_type, 'status_search']) - status_search_result_check(bundle, status) - end - else - skip_if search_param_values.blank?, 'No search parameters passed in, skipping test.' - - search_id_list = search_param_values.split(',').map(&:strip) - search_id_list.each do |search_id| - if search_type == '_id' - bundle = perform_fhir_search({ _id: search_id }, [resource_type, 'id_search']) - id_search_result_check(bundle, search_id) - elsif reference_search_parameters.include?(search_type) - search_params = {} - search_params[search_type] = search_id - bundle = perform_fhir_search(search_params, [resource_type, "#{search_type}_search"]) - reference_search_result_check(bundle, search_id, search_type) - elsif include_searches.include?(search_type) - include_resource_type = search_type.gsub('_include', '') - bundle = perform_fhir_search({ _id: search_id, _include: "#{resource_type}:#{include_resource_type}" }, - [resource_type, "include_#{include_resource_type}_search"]) - include_search_result_check(bundle, search_id, include_resource_type) - else - raise StandardError, - 'Passed in search_type does not match to any of the search types handled by this search test.' - end - end - end - skip_if !successful_search, - 'No resources returned in any of the search result bundles.' - end - end -end diff --git a/lib/davinci_crd_test_kit/client_tests/client_fhir_api_validation_test.rb b/lib/davinci_crd_test_kit/client_tests/client_fhir_api_validation_test.rb deleted file mode 100644 index 9e51e96e..00000000 --- a/lib/davinci_crd_test_kit/client_tests/client_fhir_api_validation_test.rb +++ /dev/null @@ -1,61 +0,0 @@ -module DaVinciCRDTestKit - class ClientFHIRApiValidationTest < Inferno::Test - id :crd_client_fhir_api_validation_test - title 'FHIR Resource Validation' - description %( - Verify that the given resources returned from the previous client API interactions are valid resources. Each - resource is validated against its corresponding [CRD resource profile](https://hl7.org/fhir/us/davinci-crd/STU2/artifacts.html). - ) - - def resource_type - config.options[:resource_type] - end - - def structure_definition_map - { - 'Practitioner' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-practitioner', - 'PractitionerRole' => 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitionerrole', - 'Patient' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-patient', - 'Encounter' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-encounter', - 'Coverage' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-coverage', - 'Device' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-device', - 'Location' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-location', - 'Organization' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-organization' - }.freeze - end - - def profile_url - structure_definition_map[resource_type] - end - - run do - load_tagged_requests(resource_type) - skip_if requests.empty?, 'No FHIR api requests were made' - - requests.keep_if { |req| req.status == 200 } - skip_if(requests.blank?, - 'There were no successful FHIR API requests made in previous tests to use in validation.') - - validated_resources = - requests - .map(&:resource) - .compact - .flat_map { |resource| resource.is_a?(FHIR::Bundle) ? resource.entry.map(&:resource) : resource } - .select { |resource| resource.resourceType == resource_type } - .uniq { |resource| resource.to_reference.reference } - .map { |resource| resource_is_valid?(resource:, profile_url:) } - - skip_if(validated_resources.blank?, - %(No #{resource_type} resources were returned from any of the FHIR API requests made in previous tests - that could be validated.)) - - validation_error_count = messages.count { |msg| msg[:type] == 'error' } - invalid_resource_count = validated_resources.reject { |valid| valid }.count - assert(validation_error_count.zero?, - %(#{invalid_resource_count}/#{validated_resources.length} #{resource_type} resources returned from previous - test's FHIR API requests failed validation.)) - - skip_if validated_resources.blank?, 'No FHIR resources were made in previous tests that could be validated.' - end - end -end diff --git a/lib/davinci_crd_test_kit/client_tests/practitioner_role/metadata.yml b/lib/davinci_crd_test_kit/client_tests/practitioner_role/metadata.yml new file mode 100644 index 00000000..db80e3b5 --- /dev/null +++ b/lib/davinci_crd_test_kit/client_tests/practitioner_role/metadata.yml @@ -0,0 +1,185 @@ +--- +:name: us_core_practitionerrole +:class_name: USCorev311PractitionerroleSequence +:version: v3.1.1 +:reformatted_version: v311 +:resource: PractitionerRole +:profile_url: http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitionerrole +:profile_name: US Core PractitionerRole Profile +:profile_version: 3.1.1 +:title: PractitionerRole +:short_description: Verify support for the server capabilities required by the US + Core PractitionerRole Profile. +:is_delayed: true +:interactions: +- :code: create + :expectation: MAY +- :code: search-type + :expectation: SHALL +- :code: read + :expectation: SHALL +- :code: vread + :expectation: SHOULD +- :code: update + :expectation: MAY +- :code: patch + :expectation: MAY +- :code: delete + :expectation: MAY +- :code: history-instance + :expectation: SHOULD +- :code: history-type + :expectation: MAY +:operations: [] +:searches: +- :names: + - _id + :expectation: SHALL + :names_not_must_support_or_mandatory: [] + :must_support_or_mandatory: true +- :names: + - specialty + :expectation: SHALL + :names_not_must_support_or_mandatory: [] + :must_support_or_mandatory: true +- :names: + - organization + :expectation: SHALL + :names_not_must_support_or_mandatory: [] + :must_support_or_mandatory: true +- :names: + - practitioner + :expectation: SHALL + :names_not_must_support_or_mandatory: [] + :must_support_or_mandatory: true +:search_definitions: + :_id: + :paths: + - id + :full_paths: + - PractitionerRole.id + :comparators: {} + :values: [] + :type: http://hl7.org/fhirpath/System.String + :contains_multiple: true + :multiple_or: MAY + :specialty: + :paths: + - specialty + :full_paths: + - PractitionerRole.specialty + :comparators: {} + :values: [] + :type: CodeableConcept + :contains_multiple: true + :multiple_or: MAY + :organization: + :paths: + - organization + :full_paths: + - PractitionerRole.organization + :comparators: {} + :values: [] + :type: Reference + :contains_multiple: false + :multiple_or: MAY + :practitioner: + :paths: + - practitioner + :full_paths: + - PractitionerRole.practitioner + :comparators: {} + :values: [] + :type: Reference + :contains_multiple: false + :multiple_or: MAY + :chain: + - :chain: identifier + :expectation: SHALL + - :chain: name + :expectation: SHALL +:include_params: +- PractitionerRole:endpoint +- PractitionerRole:practitioner +- PractitionerRole:organization +:revincludes: [] +:required_concepts: [] +:must_supports: + :extensions: [] + :slices: [] + :elements: + - :path: practitioner + :types: + - Reference + - :path: organization + :types: + - Reference + - :path: code + - :path: specialty + - :path: location + :types: + - Reference + - :path: telecom + - :path: telecom.system + - :path: telecom.value + - :path: endpoint + :types: + - Reference +:mandatory_elements: +- PractitionerRole.practitioner +- PractitionerRole.organization +- PractitionerRole.telecom.system +- PractitionerRole.telecom.value +- PractitionerRole.notAvailable.description +:bindings: +- :type: code + :strength: required + :system: http://hl7.org/fhir/ValueSet/contact-point-system + :path: telecom.system +- :type: code + :strength: required + :system: http://hl7.org/fhir/ValueSet/contact-point-use + :path: telecom.use +- :type: code + :strength: required + :system: http://hl7.org/fhir/ValueSet/days-of-week + :path: availableTime.daysOfWeek +:references: +- :path: PractitionerRole.practitioner + :profiles: + - http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitioner +- :path: PractitionerRole.organization + :profiles: + - http://hl7.org/fhir/us/core/StructureDefinition/us-core-organization +- :path: PractitionerRole.location + :profiles: + - http://hl7.org/fhir/StructureDefinition/Location +- :path: PractitionerRole.healthcareService + :profiles: + - http://hl7.org/fhir/StructureDefinition/HealthcareService +- :path: PractitionerRole.endpoint + :profiles: + - http://hl7.org/fhir/StructureDefinition/Endpoint +:tests: +- :id: crd_client_fhir_api_practitioner_role_read_test + :file_name: practitioner_role_read_test.rb +- :id: crd_client_fhir_api_practitioner_role_specialty_search_test + :file_name: practitioner_role_specialty_search_test.rb +- :id: crd_client_fhir_api_practitioner_role_practitioner_search_test + :file_name: practitioner_role_practitioner_search_test.rb +- :id: crd_client_fhir_api_practitioner_role_validation_test + :file_name: practitioner_role_validation_test.rb +- :id: crd_client_fhir_api_practitioner_role_must_support_test + :file_name: practitioner_role_must_support_test.rb +- :id: crd_client_fhir_api_practitioner_role_reference_resolution_test + :file_name: practitioner_role_reference_resolution_test.rb +:id: crd_client_fhir_api_practitioner_role +:file_name: practitioner_role_group.rb +:delayed_references: +- :path: practitioner + :resources: + - Practitioner +- :path: organization + :resources: + - Organization +:resource_conformance_expectation: SHALL diff --git a/lib/davinci_crd_test_kit/client_tests/practitioner_role/practitioner_role_id_search_test.rb b/lib/davinci_crd_test_kit/client_tests/practitioner_role/practitioner_role_id_search_test.rb new file mode 100644 index 00000000..d62a0e73 --- /dev/null +++ b/lib/davinci_crd_test_kit/client_tests/practitioner_role/practitioner_role_id_search_test.rb @@ -0,0 +1,39 @@ +require 'us_core_test_kit' + +module DaVinciCRDTestKit + class PractitionerRoleIdSearchTest < Inferno::Test + include USCoreTestKit::SearchTest + + title 'Server returns valid results for PractitionerRole search by _id' + description %( +A server SHALL support searching by +_id on the PractitionerRole resource. This test +will pass if resources are returned and match the search criteria. If +none are returned, the test is skipped. + +[US Core Server CapabilityStatement](http://hl7.org/fhir/us/core/STU3.1.1/CapabilityStatement-us-core-server.html) + + ) + + id :crd_client_fhir_api_practitioner_role_id_search_test + def self.properties + @properties ||= USCoreTestKit::SearchTestProperties.new( + resource_type: 'PractitionerRole', + search_param_names: ['_id'] + ) + end + + def self.metadata + @metadata ||= USCoreTestKit::Generator::GroupMetadata.new(YAML.load_file(File.join(__dir__, 'metadata.yml'), + aliases: true)) + end + + def scratch_resources + scratch[:practitioner_role_resources] ||= {} + end + + run do + run_search_test + end + end +end diff --git a/lib/davinci_crd_test_kit/client_tests/practitioner_role/practitioner_role_must_support_test.rb b/lib/davinci_crd_test_kit/client_tests/practitioner_role/practitioner_role_must_support_test.rb new file mode 100644 index 00000000..3cb20b97 --- /dev/null +++ b/lib/davinci_crd_test_kit/client_tests/practitioner_role/practitioner_role_must_support_test.rb @@ -0,0 +1,44 @@ +require 'us_core_test_kit' + +module DaVinciCRDTestKit + class PractitionerRoleMustSupportTest < Inferno::Test + include USCoreTestKit::MustSupportTest + + title 'All must support elements are provided in the PractitionerRole resources returned' + description %( + US Core Responders SHALL be capable of populating all data elements as + part of the query results as specified by the US Core Server Capability + Statement. This test will look through the PractitionerRole resources + found previously for the following must support elements: + + * PractitionerRole.code + * PractitionerRole.endpoint + * PractitionerRole.location + * PractitionerRole.organization + * PractitionerRole.practitioner + * PractitionerRole.specialty + * PractitionerRole.telecom + * PractitionerRole.telecom.system + * PractitionerRole.telecom.value + ) + + id :crd_client_fhir_api_practitioner_role_must_support_test + + def resource_type + 'PractitionerRole' + end + + def self.metadata + @metadata ||= USCoreTestKit::Generator::GroupMetadata.new(YAML.load_file(File.join(__dir__, 'metadata.yml'), + aliases: true)) + end + + def scratch_resources + scratch[:practitioner_role_resources] ||= {} + end + + run do + perform_must_support_test(all_scratch_resources) + end + end +end diff --git a/lib/davinci_crd_test_kit/client_tests/practitioner_role/practitioner_role_organization_search_test.rb b/lib/davinci_crd_test_kit/client_tests/practitioner_role/practitioner_role_organization_search_test.rb new file mode 100644 index 00000000..4afa9d69 --- /dev/null +++ b/lib/davinci_crd_test_kit/client_tests/practitioner_role/practitioner_role_organization_search_test.rb @@ -0,0 +1,48 @@ +require 'us_core_test_kit' + +module DaVinciCRDTestKit + class PractitionerRoleOrganizationSearchTest < Inferno::Test + include USCoreTestKit::SearchTest + + title 'Server returns valid results for PractitionerRole search by organization' + description %( +A server SHALL support searching by +organization on the PractitionerRole resource. This test +will pass if resources are returned and match the search criteria. If +none are returned, the test is skipped. + +[US Core Server CapabilityStatement](http://hl7.org/fhir/us/core/STU3.1.1/CapabilityStatement-us-core-server.html) + + ) + + id :crd_client_fhir_api_practitioner_role_organization_search_test + + output :practitioner_role_id_with_organization + def self.properties + @properties ||= USCoreTestKit::SearchTestProperties.new( + resource_type: 'PractitionerRole', + search_param_names: ['organization'] + ) + end + + def self.metadata + @metadata ||= USCoreTestKit::Generator::GroupMetadata.new(YAML.load_file(File.join(__dir__, 'metadata.yml'), + aliases: true)) + end + + def scratch_resources + scratch[:practitioner_role_resources] ||= {} + end + + run do + run_search_test + return unless requests.present? + + search_response_bundle = FHIR.from_contents(requests[0].response_body) + return unless search_response_bundle.is_a?(FHIR::Bundle) + + practitioner_role_id_with_organization = search_response_bundle.entry&.first&.resource&.id + output(practitioner_role_id_with_organization:) + end + end +end diff --git a/lib/davinci_crd_test_kit/client_tests/practitioner_role/practitioner_role_practitioner_search_test.rb b/lib/davinci_crd_test_kit/client_tests/practitioner_role/practitioner_role_practitioner_search_test.rb new file mode 100644 index 00000000..f0958e98 --- /dev/null +++ b/lib/davinci_crd_test_kit/client_tests/practitioner_role/practitioner_role_practitioner_search_test.rb @@ -0,0 +1,45 @@ +require 'us_core_test_kit' + +module DaVinciCRDTestKit + class PractitionerRolePractitionerSearchTest < Inferno::Test + include USCoreTestKit::SearchTest + + title 'Server returns valid results for PractitionerRole search by practitioner' + description %( +A server SHALL support searching by +practitioner on the PractitionerRole resource. This test +will pass if resources are returned and match the search criteria. If +none are returned, the test is skipped. + +[US Core Server CapabilityStatement](http://hl7.org/fhir/us/core/STU3.1.1/CapabilityStatement-us-core-server.html) + + ) + + id :crd_client_fhir_api_practitioner_role_practitioner_search_test + output :practitioner_role_id_with_practitioner + def self.properties + @properties ||= USCoreTestKit::SearchTestProperties.new( + resource_type: 'PractitionerRole', + search_param_names: ['practitioner'] + ) + end + + def self.metadata + @metadata ||= USCoreTestKit::Generator::GroupMetadata.new(YAML.load_file(File.join(__dir__, 'metadata.yml'), + aliases: true)) + end + + def scratch_resources + scratch[:practitioner_role_resources] ||= {} + end + + run do + run_search_test + search_response_bundle = FHIR.from_contents(requests[0].response_body) + return unless search_response_bundle.is_a?(FHIR::Bundle) + + practitioner_role_id_with_practitioner = search_response_bundle.entry&.first&.resource&.id + output(practitioner_role_id_with_practitioner:) + end + end +end diff --git a/lib/davinci_crd_test_kit/client_tests/practitioner_role/practitioner_role_read_test.rb b/lib/davinci_crd_test_kit/client_tests/practitioner_role/practitioner_role_read_test.rb new file mode 100644 index 00000000..60fdda79 --- /dev/null +++ b/lib/davinci_crd_test_kit/client_tests/practitioner_role/practitioner_role_read_test.rb @@ -0,0 +1,38 @@ +require 'us_core_test_kit' + +module DaVinciCRDTestKit + class PractitionerRoleReadTest < Inferno::Test + include USCoreTestKit::ReadTest + + title 'Server returns correct PractitionerRole resource from PractitionerRole read interaction' + description 'A server SHALL support the PractitionerRole read interaction.' + + id :crd_client_fhir_api_practitioner_role_read_test + input :practitioner_role_ids, + title: 'PractitionerRole IDs', + description: %( + Comma-delimited list of PractitionerRole IDs for Inferno to use to check + for PractitionerRole read and search API support. In sum, the resources + must demonstrate all must support elements. (Note: if Hook tests are run first, + this will default to PractitionerRole resource IDs referenced in any hook + invocations sent to Inferno during those tests.) + ) + + def resource_type + 'PractitionerRole' + end + + def scratch_resources + scratch[:practitioner_role_resources] ||= {} + end + + run do + resources_to_read = practitioner_role_ids.split(',').map do |id| + FHIR::Reference.new(reference: "PractitionerRole/#{id.strip}") + end + resources_to_read.each do |resource| + read_and_validate(resource) + end + end + end +end diff --git a/lib/davinci_crd_test_kit/client_tests/practitioner_role/practitioner_role_reference_resolution_test.rb b/lib/davinci_crd_test_kit/client_tests/practitioner_role/practitioner_role_reference_resolution_test.rb new file mode 100644 index 00000000..ded8d4ef --- /dev/null +++ b/lib/davinci_crd_test_kit/client_tests/practitioner_role/practitioner_role_reference_resolution_test.rb @@ -0,0 +1,42 @@ +require 'us_core_test_kit' + +module DaVinciCRDTestKit + class PractitionerRoleReferenceResolutionTest < Inferno::Test + include USCoreTestKit::ReferenceResolutionTest + + title 'MustSupport references within PractitionerRole resources are valid' + description %( + This test will attempt to read external references provided within elements + marked as 'MustSupport', if any are available. + + It verifies that at least one external reference for each MustSupport Reference element + can be accessed by the test client, and conforms to corresponding US Core profile. + + Elements which may provide external references include: + + * PractitionerRole.endpoint + * PractitionerRole.location + * PractitionerRole.organization + * PractitionerRole.practitioner + ) + + id :crd_client_fhir_api_practitioner_role_reference_resolution_test + + def resource_type + 'PractitionerRole' + end + + def self.metadata + @metadata ||= USCoreTestKit::Generator::GroupMetadata.new(YAML.load_file(File.join(__dir__, 'metadata.yml'), + aliases: true)) + end + + def scratch_resources + scratch[:practitioner_role_resources] ||= {} + end + + run do + perform_reference_resolution_test(scratch_resources[:all]) + end + end +end diff --git a/lib/davinci_crd_test_kit/client_tests/practitioner_role/practitioner_role_specialty_search_test.rb b/lib/davinci_crd_test_kit/client_tests/practitioner_role/practitioner_role_specialty_search_test.rb new file mode 100644 index 00000000..24038b57 --- /dev/null +++ b/lib/davinci_crd_test_kit/client_tests/practitioner_role/practitioner_role_specialty_search_test.rb @@ -0,0 +1,51 @@ +require 'us_core_test_kit' + +module DaVinciCRDTestKit + class PractitionerRoleSpecialtySearchTest < Inferno::Test + include USCoreTestKit::SearchTest + + title 'Server returns valid results for PractitionerRole search by specialty' + description %( +A server SHALL support searching by +specialty on the PractitionerRole resource. This test +will pass if resources are returned and match the search criteria. If +none are returned, the test is skipped. + +Because this is the first search of the sequence, resources in the +response will be used for subsequent tests. + +Additionally, this test will check that GET and POST search methods +return the same number of results. Search by POST is required by the +FHIR R4 specification, and these tests interpret search by GET as a +requirement of US Core v3.1.1. + +[US Core Server CapabilityStatement](http://hl7.org/fhir/us/core/STU3.1.1/CapabilityStatement-us-core-server.html) + + ) + + id :crd_client_fhir_api_practitioner_role_specialty_search_test + def self.properties + @properties ||= USCoreTestKit::SearchTestProperties.new( + first_search: true, + resource_type: 'PractitionerRole', + search_param_names: ['specialty'], + saves_delayed_references: true, + token_search_params: ['specialty'], + test_post_search: true + ) + end + + def self.metadata + @metadata ||= USCoreTestKit::Generator::GroupMetadata.new(YAML.load_file(File.join(__dir__, 'metadata.yml'), + aliases: true)) + end + + def scratch_resources + scratch[:practitioner_role_resources] ||= {} + end + + run do + run_search_test + end + end +end diff --git a/lib/davinci_crd_test_kit/client_tests/practitioner_role/practitioner_role_validation_test.rb b/lib/davinci_crd_test_kit/client_tests/practitioner_role/practitioner_role_validation_test.rb new file mode 100644 index 00000000..d80c8b32 --- /dev/null +++ b/lib/davinci_crd_test_kit/client_tests/practitioner_role/practitioner_role_validation_test.rb @@ -0,0 +1,38 @@ +require 'us_core_test_kit' + +module DaVinciCRDTestKit + class PractitionerRoleValidationTest < Inferno::Test + include USCoreTestKit::ValidationTest + + id :crd_client_fhir_api_practitioner_role_validation_test + title 'PractitionerRole resources returned during previous tests conform to the US Core PractitionerRole Profile' + description %( +This test verifies resources returned from the first search conform to +the [US Core PractitionerRole Profile](http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitionerrole). +Systems must demonstrate at least one valid example in order to pass this test. + +It verifies the presence of mandatory elements and that elements with +required bindings contain appropriate values. CodeableConcept element +bindings will fail if none of their codings have a code/system belonging +to the bound ValueSet. Quantity, Coding, and code element bindings will +fail if their code/system are not found in the valueset. + + ) + output :dar_code_found, :dar_extension_found + + def resource_type + 'PractitionerRole' + end + + def scratch_resources + scratch[:practitioner_role_resources] ||= {} + end + + run do + perform_validation_test(scratch_resources[:all] || [], + 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitionerrole', + '3.1.1', + skip_if_empty: true) + end + end +end diff --git a/lib/davinci_crd_test_kit/requirements/generated/crd_client_requirements_coverage.csv b/lib/davinci_crd_test_kit/requirements/generated/crd_client_requirements_coverage.csv index f5dc557a..a0155658 100644 --- a/lib/davinci_crd_test_kit/requirements/generated/crd_client_requirements_coverage.csv +++ b/lib/davinci_crd_test_kit/requirements/generated/crd_client_requirements_coverage.csv @@ -25,7 +25,7 @@ hl7.fhir.us.davinci-crd_2.0.1,43,https://hl7.org/fhir/us/davinci-crd/STU2/founda -Requested performing Organization (if specified) -Requested Location (if specified) -Associated Medication (if any) --Associated Device (if any)",SHALL,Client,,,,"1.2.1.10, 1.2.2.10, 1.2.3.10, 1.2.4.10, 1.2.5.10, 1.2.6.10, 2.2","crd_client-crd_client_hook_invocation-crd_client_hooks-crd_client_appointment_book-crd_hook_request_fetched_data, crd_client-crd_client_hook_invocation-crd_client_hooks-crd_client_encounter_start-crd_hook_request_fetched_data, crd_client-crd_client_hook_invocation-crd_client_hooks-crd_client_encounter_discharge-crd_hook_request_fetched_data, crd_client-crd_client_hook_invocation-crd_client_hooks-crd_client_order_select-crd_hook_request_fetched_data, crd_client-crd_client_hook_invocation-crd_client_hooks-crd_client_order_dispatch-crd_hook_request_fetched_data, crd_client-crd_client_hook_invocation-crd_client_hooks-crd_client_order_sign-crd_hook_request_fetched_data, crd_client-crd_client_fhir_api-Group02" +-Associated Device (if any)",SHALL,Client,,,,"1.2.1.10, 1.2.2.10, 1.2.3.10, 1.2.4.10, 1.2.5.10, 1.2.6.10","crd_client-crd_client_hook_invocation-crd_client_hooks-crd_client_appointment_book-crd_hook_request_fetched_data, crd_client-crd_client_hook_invocation-crd_client_hooks-crd_client_encounter_start-crd_hook_request_fetched_data, crd_client-crd_client_hook_invocation-crd_client_hooks-crd_client_encounter_discharge-crd_hook_request_fetched_data, crd_client-crd_client_hook_invocation-crd_client_hooks-crd_client_order_select-crd_hook_request_fetched_data, crd_client-crd_client_hook_invocation-crd_client_hooks-crd_client_order_dispatch-crd_hook_request_fetched_data, crd_client-crd_client_hook_invocation-crd_client_hooks-crd_client_order_sign-crd_hook_request_fetched_data" hl7.fhir.us.davinci-crd_2.0.1,44,https://hl7.org/fhir/us/davinci-crd/STU2/foundation.html#prefetch,"In addition to the [base prefetch capabilities](https://cds-hooks.hl7.org/2.0/#prefetch-template) defined in the CDS Hooks specification, systems that support prefetch SHOULD support the [additional prefetch capabilities](https://hl7.org/fhir/us/davinci-crd/STU2/deviations.html#additional-prefetch-capabilities) defined in this specification.",SHOULD,Client,,,,"","" hl7.fhir.us.davinci-crd_2.0.1,45,https://hl7.org/fhir/us/davinci-crd/STU2/foundation.html#prefetch,"'standard' prefetch queries ... SHOULD be supported for [Appointment]: ... @@ -142,7 +142,7 @@ hl7.fhir.us.davinci-crd_2.0.1,146,https://hl7.org/fhir/us/davinci-crd/STU2/devia hl7.fhir.us.davinci-crd_2.0.1,147,https://hl7.org/fhir/us/davinci-crd/STU2/hooks.html#supported-hooks,the `order-select` hook SHOULD fire whenever a user of a CRD Client creates a new order or referral.,SHOULD,Client,,,,"","" hl7.fhir.us.davinci-crd_2.0.1,148,https://hl7.org/fhir/us/davinci-crd/STU2/hooks.html#supported-hooks,"CRD Clients conforming to this implementation guide SHALL be able to determine the correct payer CRD Service to use for each request. [appointment-book, encounter-start, encounter-discharge, order-dispatch, order-select, and order-sign]",SHALL,Client,,,,"","" hl7.fhir.us.davinci-crd_2.0.1,149,https://hl7.org/fhir/us/davinci-crd/STU2/hooks.html#supported-hooks,"CRD Clients conforming to this implementation guide SHALL support at least one of the hooks [appointment-book, encounter-start, encounter-discharge, order-dispatch, order-select, and order-sign]",SHALL,Client,,,,1.2,crd_client-crd_client_hook_invocation-crd_client_hooks -hl7.fhir.us.davinci-crd_2.0.1,150,https://hl7.org/fhir/us/davinci-crd/STU2/hooks.html#supported-hooks,"CRD Clients conforming to this implementation guide SHALL support ... (for order-centric hooks), at least one of the order resource types [CommunicationRequest, DeviceRequest, MedicationRequest, ServiceRequest, NutritionOrder, VisionPrescription]",SHALL,Client,,,,"1.2.4.08, 1.2.5.08, 1.2.6.08, 2.2.2, 2.2.5, 2.2.11, 2.2.12, 2.2.14, 2.2.17","crd_client-crd_client_hook_invocation-crd_client_hooks-crd_client_order_select-crd_hook_request_valid_context, crd_client-crd_client_hook_invocation-crd_client_hooks-crd_client_order_dispatch-crd_hook_request_valid_context, crd_client-crd_client_hook_invocation-crd_client_hooks-crd_client_order_sign-crd_hook_request_valid_context, crd_client-crd_client_fhir_api-Group02-Group02, crd_client-crd_client_fhir_api-Group02-Group05, crd_client-crd_client_fhir_api-Group02-Group11, crd_client-crd_client_fhir_api-Group02-Group12, crd_client-crd_client_fhir_api-Group02-Group14, crd_client-crd_client_fhir_api-Group02-Group17" +hl7.fhir.us.davinci-crd_2.0.1,150,https://hl7.org/fhir/us/davinci-crd/STU2/hooks.html#supported-hooks,"CRD Clients conforming to this implementation guide SHALL support ... (for order-centric hooks), at least one of the order resource types [CommunicationRequest, DeviceRequest, MedicationRequest, ServiceRequest, NutritionOrder, VisionPrescription]",SHALL,Client,,,,"1.2.4.08, 1.2.5.08, 1.2.6.08","crd_client-crd_client_hook_invocation-crd_client_hooks-crd_client_order_select-crd_hook_request_valid_context, crd_client-crd_client_hook_invocation-crd_client_hooks-crd_client_order_dispatch-crd_hook_request_valid_context, crd_client-crd_client_hook_invocation-crd_client_hooks-crd_client_order_sign-crd_hook_request_valid_context" hl7.fhir.us.davinci-crd_2.0.1,151,https://hl7.org/fhir/us/davinci-crd/STU2/hooks.html#supported-hooks,"CRD Clients conforming to this implementation guide ... SHOULD support all [CommunicationRequest, DeviceRequest, MedicationRequest, ServiceRequest, NutritionOrder, VisionPrescription] that apply to the context of their system.",SHOULD,Client,,,,"","" hl7.fhir.us.davinci-crd_2.0.1,154,https://hl7.org/fhir/us/davinci-crd/STU2/hooks.html#supported-hooks,CRD Clients … MAY choose to support additional hooks available in the registry on the [CDS Hooks continuous integration build](https://cds-hooks.org/) or custom hooks defined elsewhere.,MAY,Client,,,,"","" hl7.fhir.us.davinci-crd_2.0.1,156,https://hl7.org/fhir/us/davinci-crd/STU2/hooks.html#supported-hooks,[Clients who choose to support additional hooks] SHOULD adhere to the conformance expectations defined in this specification for any hooks listed here.,SHOULD,Client,,,,"","" diff --git a/spec/davinci_crd_test_kit/client_fhir_api_encounter_location_search_test_spec.rb b/spec/davinci_crd_test_kit/client_fhir_api_encounter_location_search_test_spec.rb new file mode 100644 index 00000000..4deae8c6 --- /dev/null +++ b/spec/davinci_crd_test_kit/client_fhir_api_encounter_location_search_test_spec.rb @@ -0,0 +1,167 @@ +RSpec.describe DaVinciCRDTestKit::ClientFHIRApiEncounterLocationSearchTest, :runnable do + let(:suite_id) { 'crd_client' } + + let(:server_endpoint) { 'http://example.com/fhir' } + let(:ehr_smart_credentials) do + { + access_token: 'SAMPLE_TOKEN', + refresh_token: 'REFRESH_TOKEN', + expires_in: 3600, + client_id: 'CLIENT_ID', + issue_time: Time.now.iso8601, + token_url: 'http://example.com/token' + } + end + let(:smart_auth_info) { Inferno::DSL::AuthInfo.new(ehr_smart_credentials) } + + let(:patient_id) { 'example' } + let(:encounter_id) { 'example' } + let(:encounter_location_search_request) do + "#{server_endpoint}/Encounter?location=Location/example" + end + + let(:crd_encounter) do + JSON.parse( + File.read(File.join( + __dir__, '..', 'fixtures', 'crd_encounter_example.json' + )) + ) + end + + let(:crd_encounter_second) do + crd_encounter_second = crd_encounter.dup + crd_encounter_second['id'] = 'example2' + crd_encounter_second.delete('location') + crd_encounter_second + end + + let(:crd_encounter_search_bundle_extra) do + bundle = FHIR::Bundle.new(type: 'searchset') + bundle.entry.append(FHIR::Bundle::Entry.new( + fullUrl: "#{server_endpoint}/Encounter/encounter_example", + resource: FHIR.from_contents(crd_encounter.to_json) + ), + FHIR::Bundle::Entry.new( + fullUrl: "#{server_endpoint}/Encounter/encounter_example2", + resource: FHIR.from_contents(crd_encounter_second.to_json) + )) + bundle + end + + let(:crd_encounter_search_bundle_correct) do + bundle = FHIR::Bundle.new(type: 'searchset') + bundle.entry.append(FHIR::Bundle::Entry.new( + fullUrl: "#{server_endpoint}/Encounter/encounter_example", + resource: FHIR.from_contents(crd_encounter.to_json) + )) + bundle + end + + let(:empty_bundle) do + FHIR::Bundle.new(type: 'searchset') + end + + describe 'Encounter location search test' do + let(:test) do + Class.new(described_class) do + fhir_client do + url :server_endpoint + auth_info :smart_auth_info + end + + input :server_endpoint + input :smart_auth_info, type: :auth_info + end + end + + it 'passes and outputs an id if search values found and return results' do + allow_any_instance_of(test).to receive(:scratch_resources_for_patient) + .and_return([ + FHIR.from_contents(crd_encounter.to_json), + FHIR.from_contents(crd_encounter_second.to_json) + ]) + + encounter_search_request = stub_request(:get, encounter_location_search_request) + .with( + headers: { Authorization: 'Bearer SAMPLE_TOKEN' } + ) + .to_return(status: 200, body: crd_encounter_search_bundle_correct.to_json) + + result = run(test, server_endpoint:, smart_auth_info:) + + expect(result.result).to eq('pass') + expect(encounter_search_request).to have_been_made + + outputs_hash = JSON.parse(result.output_json) + encounter_id_output = outputs_hash.find do |output| + output['name'] == 'encounter_id_with_location' + end + + expect(encounter_id_output).to be_present + expect(encounter_id_output['value']).to eq('example') + end + + it 'skips if search values found but no results returned' do + allow_any_instance_of(test).to receive(:scratch_resources_for_patient) + .and_return([ + FHIR.from_contents(crd_encounter.to_json), + FHIR.from_contents(crd_encounter_second.to_json) + ]) + + encounter_search_request = stub_request(:get, encounter_location_search_request) + .with( + headers: { Authorization: 'Bearer SAMPLE_TOKEN' } + ) + .to_return(status: 200, body: empty_bundle.to_json) + + result = run(test, server_endpoint:, smart_auth_info:) + + expect(result.result).to eq('skip') + expect(encounter_search_request).to have_been_made + + outputs_hash = JSON.parse(result.output_json) + encounter_id_output = outputs_hash.find do |output| + output['name'] == 'encounter_id_with_location' + end + expect(encounter_id_output).to be_blank + end + + it 'fails if non-matching encounters returned' do + allow_any_instance_of(test).to receive(:scratch_resources_for_patient) + .and_return([ + FHIR.from_contents(crd_encounter.to_json), + FHIR.from_contents(crd_encounter_second.to_json) + ]) + + encounter_search_request = stub_request(:get, encounter_location_search_request) + .with( + headers: { Authorization: 'Bearer SAMPLE_TOKEN' } + ) + .to_return(status: 200, body: crd_encounter_search_bundle_extra.to_json) + + result = run(test, server_endpoint:, smart_auth_info:) + + expect(result.result).to eq('fail') + expect(encounter_search_request).to have_been_made + + outputs_hash = JSON.parse(result.output_json) + encounter_id_output = outputs_hash.find do |output| + output['name'] == 'encounter_id_with_location' + end + expect(encounter_id_output).to be_blank + end + + it 'skips if no encounter previously found with a location value' do + result = run(test, server_endpoint:, smart_auth_info:) + + expect(result.result).to eq('skip') + expect(result.result_message).to match('Could not find values for all search params `location`') + + outputs_hash = JSON.parse(result.output_json) + encounter_id_output = outputs_hash.find do |output| + output['name'] == 'encounter_id_with_location' + end + expect(encounter_id_output).to be_blank + end + end +end diff --git a/spec/davinci_crd_test_kit/client_fhir_api_include_search_test_spec.rb b/spec/davinci_crd_test_kit/client_fhir_api_include_search_test_spec.rb new file mode 100644 index 00000000..0487c47d --- /dev/null +++ b/spec/davinci_crd_test_kit/client_fhir_api_include_search_test_spec.rb @@ -0,0 +1,339 @@ +require_relative '../../lib/davinci_crd_test_kit/client_tests/client_fhir_api_include_search_test' + +RSpec.describe DaVinciCRDTestKit::ClientFHIRApiIncludeSearchTest, :runnable do + let(:suite_id) { 'crd_client' } + + let(:server_endpoint) { 'http://example.com/fhir' } + let(:ehr_smart_credentials) do + { + access_token: 'SAMPLE_TOKEN', + refresh_token: 'REFRESH_TOKEN', + expires_in: 3600, + client_id: 'CLIENT_ID', + issue_time: Time.now.iso8601, + token_url: 'http://example.com/token' + } + end + let(:smart_auth_info) { Inferno::DSL::AuthInfo.new(ehr_smart_credentials) } + + let(:patient_id) { 'example' } + let(:encounter_id) { 'example' } + let(:encounter_include_search_request) do + "#{server_endpoint}/Encounter?_id=#{encounter_id}&_include=Encounter:location" + end + let(:encounter_include_search_request_different_id) do + "#{server_endpoint}/Encounter?_id=example2&_include=Encounter:location" + end + + let(:crd_encounter) do + JSON.parse( + File.read(File.join( + __dir__, '..', 'fixtures', 'crd_encounter_example.json' + )) + ) + end + + let(:crd_encounter_second) do + crd_encounter_second = crd_encounter.dup + crd_encounter_second['id'] = 'example2' + crd_encounter_second.delete('location') + crd_encounter_second + end + + let(:crd_location) do + JSON.parse( + File.read(File.join( + __dir__, '..', 'fixtures', 'crd_location_example.json' + )) + ) + end + + let(:crd_location_second) do + crd_location_second = crd_location.dup + crd_location_second['id'] = 'example2' + crd_location_second + end + + let(:operation_outcome) do + FHIR::OperationOutcome.new( + issue: [ + { + severity: 'information', + code: 'informational', + details: { + text: 'All OK' + } + } + ] + ) + end + + let(:encounter_example_include_location_search_bundle) do + bundle = FHIR::Bundle.new(type: 'searchset') + bundle.entry.append(FHIR::Bundle::Entry.new( + fullUrl: "#{server_endpoint}/Encounter/example", + resource: FHIR.from_contents(crd_encounter.to_json) + )) + bundle.entry.append(FHIR::Bundle::Entry.new( + fullUrl: "#{server_endpoint}/Location/example", + resource: FHIR.from_contents(crd_location.to_json) + )) + bundle.entry.append(FHIR::Bundle::Entry.new( + fullUrl: "#{server_endpoint}/Location/example2", + resource: FHIR.from_contents(crd_location_second.to_json) + )) + bundle + end + + let(:encounter_example_include_location_search_bundle_with_outcome) do + bundle = FHIR::Bundle.new(type: 'searchset') + bundle.entry.append(FHIR::Bundle::Entry.new( + fullUrl: "#{server_endpoint}/Encounter/example", + resource: FHIR.from_contents(crd_encounter.to_json) + )) + bundle.entry.append(FHIR::Bundle::Entry.new( + fullUrl: "#{server_endpoint}/Location/example", + resource: FHIR.from_contents(crd_location.to_json) + )) + bundle.entry.append(FHIR::Bundle::Entry.new( + fullUrl: "#{server_endpoint}/Location/example2", + resource: FHIR.from_contents(crd_location_second.to_json) + )) + bundle.entry.append(FHIR::Bundle::Entry.new( + fullUrl: "#{server_endpoint}/OperationOutcome/operation_outcome_example", + resource: FHIR.from_contents(operation_outcome.to_json) + )) + bundle + end + + let(:crd_location_search_bundle) do + bundle = FHIR::Bundle.new(type: 'searchset') + bundle.entry.append(FHIR::Bundle::Entry.new( + fullUrl: "#{server_endpoint}/Location/location_example", + resource: FHIR.from_contents(crd_location.to_json) + )) + bundle + end + + let(:crd_encounter_search_bundle_multiple_entries) do + bundle = FHIR::Bundle.new(type: 'searchset') + bundle.entry.append(FHIR::Bundle::Entry.new( + fullUrl: "#{server_endpoint}/Encounter/example", + resource: FHIR.from_contents(crd_encounter.to_json) + )) + bundle.entry.append(FHIR::Bundle::Entry.new( + fullUrl: "#{server_endpoint}/Location/example", + resource: FHIR.from_contents(crd_location.to_json) + )) + bundle.entry.append(FHIR::Bundle::Entry.new( + fullUrl: "#{server_endpoint}/Location/example2", + resource: FHIR.from_contents(crd_location_second.to_json) + )) + bundle.entry.append(FHIR::Bundle::Entry.new( + fullUrl: "#{server_endpoint}/Encounter/encounter_example2", + resource: FHIR.from_contents(crd_encounter_second.to_json) + )) + bundle + end + + let(:crd_encounter_search_bundle_missing_location) do + bundle = FHIR::Bundle.new(type: 'searchset') + bundle.entry.append(FHIR::Bundle::Entry.new( + fullUrl: "#{server_endpoint}/Encounter/example", + resource: FHIR.from_contents(crd_encounter.to_json) + )) + bundle.entry.append(FHIR::Bundle::Entry.new( + fullUrl: "#{server_endpoint}/Location/example", + resource: FHIR.from_contents(crd_location.to_json) + )) + bundle + end + + let(:crd_encounter_search_bundle_wrong_encounter) do + bundle = FHIR::Bundle.new(type: 'searchset') + bundle.entry.append(FHIR::Bundle::Entry.new( + fullUrl: "#{server_endpoint}/Encounter/encounter_example", + resource: FHIR.from_contents(crd_encounter.to_json) + ), FHIR::Bundle::Entry.new( + fullUrl: "#{server_endpoint}/Location/example", + resource: FHIR.from_contents(crd_location.to_json) + )) + bundle + end + + let(:crd_encounter_search_bundle_encounter_second) do + bundle = FHIR::Bundle.new(type: 'searchset') + bundle.entry.append(FHIR::Bundle::Entry.new( + fullUrl: "#{server_endpoint}/Encounter/example2", + resource: FHIR.from_contents(crd_encounter_second.to_json) + )) + bundle + end + + let(:empty_bundle) do + FHIR::Bundle.new(type: 'searchset') + end + + describe 'Encounter search test with `_include` search parameter' do + let(:test) do + Class.new(DaVinciCRDTestKit::ClientFHIRApiIncludeSearchTest) do + fhir_client do + url :server_endpoint + auth_info :smart_auth_info + end + + config( + options: { resource_type: 'Encounter', target_include_element: 'location' } + ) + + input :server_endpoint + input :smart_auth_info, type: :auth_info + end + end + + it 'passes if valid Encounter id is passed in that can be used to _include search for Encounter resources' do + encounter_search_request = stub_request(:get, encounter_include_search_request) + .with( + headers: { Authorization: 'Bearer SAMPLE_TOKEN' } + ) + .to_return(status: 200, body: encounter_example_include_location_search_bundle.to_json) + + result = run(test, search_id: encounter_id, server_endpoint:, smart_auth_info:) + + expect(result.result).to eq('pass') + expect(encounter_search_request).to have_been_made + end + + it 'passes if _include search result includes an OperationOutcome resource' do + encounter_search_request = stub_request(:get, encounter_include_search_request) + .with( + headers: { Authorization: 'Bearer SAMPLE_TOKEN' } + ) + .to_return(status: 200, body: encounter_example_include_location_search_bundle_with_outcome.to_json) + + result = run(test, search_id: encounter_id, server_endpoint:, smart_auth_info:) + + expect(result.result).to eq('pass') + expect(encounter_search_request).to have_been_made + expect(Inferno::Repositories::Messages.new.messages_for_result(result.id)).to be_blank + end + + it 'skips if no resources returned in Encounter _include search' do + encounter_search_request = stub_request(:get, encounter_include_search_request) + .with( + headers: { Authorization: 'Bearer SAMPLE_TOKEN' } + ) + .to_return(status: 200, body: empty_bundle.to_json) + result = run(test, search_id: encounter_id, server_endpoint:, smart_auth_info:) + + expect(result.result).to eq('skip') + expect(result.result_message) + .to eq('_include search not demonstrated - search result bundle is empty for Encounter _include ' \ + 'location search with an id of `example`.') + expect(encounter_search_request).to have_been_made + end + + it 'skips if no Encounter ids are inputted' do + result = run(test, search_id: '', server_endpoint:, smart_auth_info:) + + expect(result.result).to eq('skip') + expect(result.result_message).to eq('No target id to use for the search, skipping test.') + end + + it 'fails if Encounter _id search returns non 200' do + encounter_search_request = stub_request(:get, encounter_include_search_request) + .with( + headers: { Authorization: 'Bearer SAMPLE_TOKEN' } + ) + .to_return(status: 400, body: empty_bundle.to_json) + + result = run(test, search_id: encounter_id, server_endpoint:, smart_auth_info:) + + expect(result.result).to eq('fail') + expect(result.result_message).to eq('Unexpected response status: expected 200, but received 400') + expect(encounter_search_request).to have_been_made + end + + it 'fails if Encounter _include search returns a bundle with no Encounter resource' do + encounter_search_request = stub_request(:get, encounter_include_search_request) + .with( + headers: { Authorization: 'Bearer SAMPLE_TOKEN' } + ) + .to_return(status: 200, body: crd_location_search_bundle.to_json) + + result = run(test, search_id: encounter_id, server_endpoint:, smart_auth_info:) + + expect(result.result).to eq('fail') + expect(result.result_message).to match( + 'The location _include search for Encounter resource with id example did not return a Encounter resource ' \ + 'matching the searched id example.' + ) + expect(encounter_search_request).to have_been_made + end + + it 'warns if Encounter _include search returns a bundle with more than 1 Encounter resource' do + encounter_search_request = stub_request(:get, encounter_include_search_request) + .with( + headers: { Authorization: 'Bearer SAMPLE_TOKEN' } + ) + .to_return(status: 200, body: crd_encounter_search_bundle_multiple_entries.to_json) + + result = run(test, search_id: encounter_id, server_endpoint:, smart_auth_info:) + + expect(result.result).to eq('pass') + expect(encounter_search_request).to have_been_made + messages = Inferno::Repositories::Messages.new.messages_for_result(result.id) + expect(messages).to be_present + expect(messages.length).to eq(1) + expect(messages.first.type).to eq('warning') + expect(messages.first.message).to match('Additional resources returned beyond those requested.') + end + + it 'fails if Encounter _include search does not return the referenced locations' do + encounter_search_request = stub_request(:get, encounter_include_search_request) + .with( + headers: { Authorization: 'Bearer SAMPLE_TOKEN' } + ) + .to_return(status: 200, body: crd_encounter_search_bundle_missing_location.to_json) + + result = run(test, search_id: encounter_id, server_endpoint:, smart_auth_info:) + + expect(result.result).to eq('fail') + expect(result.result_message).to match('referenced resource `Location/example2` not returned from the search') + expect(encounter_search_request).to have_been_made + end + + it 'fails if Encounter _include search returns a bundle with wrong Encounter id' do + encounter_search_request = stub_request(:get, encounter_include_search_request_different_id) + .with( + headers: { Authorization: 'Bearer SAMPLE_TOKEN' } + ) + .to_return(status: 200, body: crd_encounter_search_bundle_wrong_encounter.to_json) + + result = run(test, search_id: 'example2', server_endpoint:, smart_auth_info:) + + expect(result.result).to eq('fail') + expect(result.result_message) + .to match('The location _include search for Encounter resource with id example2 did not return a Encounter ' \ + 'resource matching the searched id example2') + expect(encounter_search_request).to have_been_made + end + + it 'skips if provided encounter does not has data in the included element' do + encounter_search_request = stub_request(:get, encounter_include_search_request_different_id) + .with( + headers: { Authorization: 'Bearer SAMPLE_TOKEN' } + ) + .to_return(status: 200, body: crd_encounter_search_bundle_encounter_second.to_json) + + result = run(test, search_id: 'example2', server_endpoint:, smart_auth_info:) + + expect(result.result).to eq('skip') + expect(result.result_message).to match( + 'Encounter resource with id example2 did not include references in the element targeted to include ' \ + 'location resources.' + ) + expect(encounter_search_request).to have_been_made + end + end +end diff --git a/spec/davinci_crd_test_kit/client_fhir_api_read_test_spec.rb b/spec/davinci_crd_test_kit/client_fhir_api_read_test_spec.rb deleted file mode 100644 index 6a1e6759..00000000 --- a/spec/davinci_crd_test_kit/client_fhir_api_read_test_spec.rb +++ /dev/null @@ -1,163 +0,0 @@ -require_relative '../../lib/davinci_crd_test_kit/client_tests/client_fhir_api_read_test' - -RSpec.describe DaVinciCRDTestKit::ClientFHIRApiReadTest do - let(:suite_id) { 'crd_client' } - let(:server_endpoint) { 'http://example.com/fhir' } - let(:client_smart_credentials) do - { - access_token: 'SAMPLE_TOKEN', - refresh_token: 'REFRESH_TOKEN', - expires_in: 3600, - client_id: 'CLIENT_ID', - issue_time: Time.now.iso8601, - token_url: 'http://example.com/token' - } - end - let(:smart_auth_info) { Inferno::DSL::AuthInfo.new(client_smart_credentials) } - - let(:patient_ids) { 'example' } - - let(:crd_patient_first) do - JSON.parse( - File.read(File.join( - __dir__, '..', 'fixtures', 'crd_patient_example.json' - )) - ) - end - - let(:crd_patient_second) do - patient = JSON.parse( - File.read(File.join( - __dir__, '..', 'fixtures', 'crd_patient_example.json' - )) - ) - patient['id'] = 'example2' - patient - end - - let(:crd_patient_no_id) do - crd_patient_first.except('id') - end - - let(:crd_practitioner) do - JSON.parse( - File.read(File.join( - __dir__, '..', 'fixtures', 'crd_practitioner_example.json' - )) - ) - end - - describe 'Patient FHIR Read Test' do - let(:test) do - Class.new(DaVinciCRDTestKit::ClientFHIRApiReadTest) do - fhir_client do - url :server_endpoint - auth_info :smart_auth_info - end - - config( - options: { resource_type: 'Patient' } - ) - - input :server_endpoint, :resource_ids - input :smart_auth_info, type: :auth_info - end - end - - it 'passes if valid list of readable Patient ids are passed in' do - patient_resource_request_first = stub_request(:get, "#{server_endpoint}/Patient/example") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: crd_patient_first.to_json) - - result = run(test, resource_ids: patient_ids, server_endpoint:, smart_auth_info:) - - expect(result.result).to eq('pass') - expect(patient_resource_request_first).to have_been_made - end - - it 'passes if valid list of more than 1 readable Patient id is passed in' do - patient_resource_request_first = stub_request(:get, "#{server_endpoint}/Patient/example") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: crd_patient_first.to_json) - patient_resource_request_second = stub_request(:get, "#{server_endpoint}/Patient/example2") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: crd_patient_second.to_json) - - patient_ids = 'example, example2' - result = run(test, resource_ids: patient_ids, server_endpoint:, smart_auth_info:) - - expect(result.result).to eq('pass') - expect(patient_resource_request_first).to have_been_made - expect(patient_resource_request_second).to have_been_made - end - - it 'skips if no Patient ids are inputted' do - result = run(test, resource_ids: '', server_endpoint:, smart_auth_info:) - - expect(result.result).to eq('skip') - end - - it 'fails if Patient id read returns non 200' do - patient_resource_request_first = stub_request(:get, "#{server_endpoint}/Patient/example") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 400, body: crd_patient_first.to_json) - - result = run(test, resource_ids: patient_ids, server_endpoint:, smart_auth_info:) - - expect(result.result).to eq('fail') - expect(result.result_message).to eq('Unexpected response status: expected 200, but received 400') - expect(patient_resource_request_first).to have_been_made - end - - it 'fails if Patient id read returns Patient with wrong id' do - patient_resource_request_first = stub_request(:get, "#{server_endpoint}/Patient/wrong-id") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: crd_patient_first.to_json) - - patient_ids = 'wrong-id' - result = run(test, resource_ids: patient_ids, server_endpoint:, smart_auth_info:) - - expect(result.result).to eq('fail') - expect(result.result_message).to eq('Expected resource to have id: `wrong-id`, but found `example`') - expect(patient_resource_request_first).to have_been_made - end - - it 'fails if Patient id read returns Patient with no id' do - patient_resource_request_first = stub_request(:get, "#{server_endpoint}/Patient/example") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: crd_patient_no_id.to_json) - - result = run(test, resource_ids: patient_ids, server_endpoint:, smart_auth_info:) - - expect(result.result).to eq('fail') - expect(result.result_message).to eq('Expected resource to have id: `example`, but found ``') - expect(patient_resource_request_first).to have_been_made - end - - it 'fails if Patient id read returns wrong resource type' do - patient_resource_request_first = stub_request(:get, "#{server_endpoint}/Patient/example") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: crd_practitioner.to_json) - - result = run(test, resource_ids: patient_ids, server_endpoint:, smart_auth_info:) - - expect(result.result).to eq('fail') - expect(result.result_message).to eq('Unexpected resource type: expected Patient, but received Practitioner') - expect(patient_resource_request_first).to have_been_made - end - end -end diff --git a/spec/davinci_crd_test_kit/client_fhir_api_search_test_spec.rb b/spec/davinci_crd_test_kit/client_fhir_api_search_test_spec.rb deleted file mode 100644 index 662b0eee..00000000 --- a/spec/davinci_crd_test_kit/client_fhir_api_search_test_spec.rb +++ /dev/null @@ -1,838 +0,0 @@ -require_relative '../../lib/davinci_crd_test_kit/client_tests/client_fhir_api_search_test' - -RSpec.describe DaVinciCRDTestKit::ClientFHIRApiSearchTest, :runnable do - let(:suite_id) { 'crd_client' } - - let(:server_endpoint) { 'http://example.com/fhir' } - let(:ehr_smart_credentials) do - { - access_token: 'SAMPLE_TOKEN', - refresh_token: 'REFRESH_TOKEN', - expires_in: 3600, - client_id: 'CLIENT_ID', - issue_time: Time.now.iso8601, - token_url: 'http://example.com/token' - } - end - let(:smart_auth_info) { Inferno::DSL::AuthInfo.new(ehr_smart_credentials) } - - let(:patient_id) { 'example' } - let(:encounter_id) { 'example' } - let(:encounter_include_search_request) do - "#{server_endpoint}/Encounter?_id=#{encounter_id}&_include=Encounter:location" - end - let(:encounter_include_search_request_different_id) do - "#{server_endpoint}/Encounter?_id=example2&_include=Encounter:location" - end - - let(:crd_coverage_active) do - JSON.parse( - File.read(File.join( - __dir__, '..', 'fixtures', 'crd_coverage_example.json' - )) - ) - end - - let(:crd_coverage_cancelled) do - crd_coverage_active.merge('status' => 'cancelled') - end - - let(:crd_coverage_draft) do - crd_coverage_active.merge('status' => 'draft') - end - - let(:crd_coverage_entered_in_error) do - crd_coverage_active.merge('status' => 'entered-in-error') - end - - let(:crd_encounter) do - JSON.parse( - File.read(File.join( - __dir__, '..', 'fixtures', 'crd_encounter_example.json' - )) - ) - end - - let(:crd_encounter_second) do - crd_encounter_second = crd_encounter.dup - crd_encounter_second['id'] = 'example2' - crd_encounter_second - end - - let(:crd_location) do - JSON.parse( - File.read(File.join( - __dir__, '..', 'fixtures', 'crd_location_example.json' - )) - ) - end - - let(:operation_outcome) do - FHIR::OperationOutcome.new( - issue: [ - { - severity: 'information', - code: 'informational', - details: { - text: 'All OK' - } - } - ] - ) - end - - let(:crd_coverage_search_bundle_active) do - bundle = FHIR::Bundle.new(type: 'searchset') - bundle.entry.append(FHIR::Bundle::Entry.new( - fullUrl: "#{server_endpoint}/Coverage/coverage_example", - resource: FHIR.from_contents(crd_coverage_active.to_json) - )) - bundle - end - - let(:crd_coverage_search_bundle_with_operation_outcome) do - bundle = FHIR::Bundle.new(type: 'searchset') - bundle.entry.append(FHIR::Bundle::Entry.new( - fullUrl: "#{server_endpoint}/Coverage/coverage_example", - resource: FHIR.from_contents(crd_coverage_active.to_json) - ), - FHIR::Bundle::Entry.new( - fullUrl: "#{server_endpoint}/OperationOutcome/operation_outcome_example", - resource: FHIR.from_contents(operation_outcome.to_json) - )) - bundle - end - - let(:crd_coverage_search_bundle_cancelled) do - bundle = FHIR::Bundle.new(type: 'searchset') - bundle.entry.append(FHIR::Bundle::Entry.new( - fullUrl: "#{server_endpoint}/Coverage/coverage_example", - resource: FHIR.from_contents(crd_coverage_cancelled.to_json) - )) - bundle - end - - let(:crd_coverage_search_bundle_draft) do - bundle = FHIR::Bundle.new(type: 'searchset') - bundle.entry.append(FHIR::Bundle::Entry.new( - fullUrl: "#{server_endpoint}/Coverage/coverage_example", - resource: FHIR.from_contents(crd_coverage_draft.to_json) - )) - bundle - end - - let(:crd_coverage_search_bundle_entered_in_error) do - bundle = FHIR::Bundle.new(type: 'searchset') - bundle.entry.append(FHIR::Bundle::Entry.new( - fullUrl: "#{server_endpoint}/Coverage/coverage_example", - resource: FHIR.from_contents(crd_coverage_entered_in_error.to_json) - )) - bundle - end - - let(:crd_encounter_search_bundle) do - bundle = FHIR::Bundle.new(type: 'searchset') - bundle.entry.append(FHIR::Bundle::Entry.new( - fullUrl: "#{server_endpoint}/Encounter/encounter_example", - resource: FHIR.from_contents(crd_encounter.to_json) - )) - bundle - end - - let(:crd_location_search_bundle) do - bundle = FHIR::Bundle.new(type: 'searchset') - bundle.entry.append(FHIR::Bundle::Entry.new( - fullUrl: "#{server_endpoint}/Location/location_example", - resource: FHIR.from_contents(crd_location.to_json) - )) - bundle - end - - let(:crd_encounter_search_bundle_multiple_entries) do - bundle = FHIR::Bundle.new(type: 'searchset') - bundle.entry.append(FHIR::Bundle::Entry.new( - fullUrl: "#{server_endpoint}/Encounter/encounter_example", - resource: FHIR.from_contents(crd_encounter.to_json) - ), FHIR::Bundle::Entry.new( - fullUrl: "#{server_endpoint}/Encounter/encounter_example2", - resource: FHIR.from_contents(crd_encounter_second.to_json) - )) - bundle - end - - let(:crd_encounter_search_bundle_wrong_entries) do - bundle = FHIR::Bundle.new(type: 'searchset') - bundle.entry.append(FHIR::Bundle::Entry.new( - fullUrl: "#{server_endpoint}/Encounter/encounter_example", - resource: FHIR.from_contents(crd_encounter.to_json) - ), FHIR::Bundle::Entry.new( - fullUrl: "#{server_endpoint}/Encounter/encounter_example2", - resource: FHIR.from_contents(crd_coverage_active.to_json) - )) - bundle - end - - let(:crd_encounter_search_bundle_with_location) do - bundle = FHIR::Bundle.new(type: 'searchset') - bundle.entry.append(FHIR::Bundle::Entry.new( - fullUrl: "#{server_endpoint}/Encounter/encounter_example", - resource: FHIR.from_contents(crd_encounter.to_json) - ), FHIR::Bundle::Entry.new( - fullUrl: "#{server_endpoint}/Encounter/encounter_example2", - resource: FHIR.from_contents(crd_location.to_json) - )) - bundle - end - - let(:crd_encounter_search_bundle_with_location_wrong_id) do - crd_location['id'] = 'wrong_id' - bundle = FHIR::Bundle.new(type: 'searchset') - bundle.entry.append(FHIR::Bundle::Entry.new( - fullUrl: "#{server_endpoint}/Encounter/encounter_example", - resource: FHIR.from_contents(crd_encounter.to_json) - ), FHIR::Bundle::Entry.new( - fullUrl: "#{server_endpoint}/Encounter/encounter_example2", - resource: FHIR.from_contents(crd_location.to_json) - )) - bundle - end - - let(:crd_encounter_search_bundle_with_operation_outcome) do - bundle = FHIR::Bundle.new(type: 'searchset') - bundle.entry.append(FHIR::Bundle::Entry.new( - fullUrl: "#{server_endpoint}/Encounter/encounter_example", - resource: FHIR.from_contents(crd_encounter.to_json) - ), - FHIR::Bundle::Entry.new( - fullUrl: "#{server_endpoint}/OperationOutcome/operation_outcome_example", - resource: FHIR.from_contents(operation_outcome.to_json) - )) - bundle - end - - let(:empty_bundle) do - FHIR::Bundle.new(type: 'searchset') - end - - describe 'Coverage search test with reference search parameter `patient`' do - let(:test) do - Inferno::Repositories::Tests.new.find( - 'crd_client-crd_client_fhir_api-Group02-Group03-crd_client_coverage_patient_search_test' - ) do - fhir_client do - url :url - auth_info :smart_auth_info - end - end - end - - it 'passes if valid Patient id is passed in that can be used to search for Coverage resources' do - coverage_search_request = stub_request(:get, "#{server_endpoint}/Coverage?patient=#{patient_id}") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: crd_coverage_search_bundle_active.to_json) - - result = run(test, search_param_values: patient_id, url: server_endpoint, smart_auth_info:) - - expect(result.result).to eq('pass') - expect(coverage_search_request).to have_been_made - end - - it 'passes if patient search result includes an OperationOutcome resource' do - coverage_search_request = stub_request(:get, "#{server_endpoint}/Coverage?patient=#{patient_id}") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: crd_coverage_search_bundle_with_operation_outcome.to_json) - - result = run(test, search_param_values: patient_id, url: server_endpoint, smart_auth_info:) - - expect(result.result).to eq('pass') - expect(coverage_search_request).to have_been_made - end - - it 'passes if at least 1 of list of Patient ids returns resources in Coverage search' do - coverage_search_request = stub_request(:get, "#{server_endpoint}/Coverage?patient=#{patient_id}") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: crd_coverage_search_bundle_active.to_json) - coverage_search_request_empty = stub_request(:get, "#{server_endpoint}/Coverage?patient=example2") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: empty_bundle.to_json) - - patient_id_list = "#{patient_id}, example2" - result = run(test, search_param_values: patient_id_list, url: server_endpoint, smart_auth_info:) - - expect(result.result).to eq('pass') - expect(coverage_search_request).to have_been_made - expect(coverage_search_request_empty).to have_been_made - end - - it 'skips if no resources returned in Coverage search' do - coverage_search_request_empty = stub_request(:get, "#{server_endpoint}/Coverage?patient=#{patient_id}") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: empty_bundle.to_json) - coverage_search_request_empty_second = stub_request(:get, "#{server_endpoint}/Coverage?patient=example2") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: empty_bundle.to_json) - - patient_id_list = "#{patient_id}, example2" - result = run(test, search_param_values: patient_id_list, url: server_endpoint, smart_auth_info:) - - expect(result.result).to eq('skip') - expect(result.result_message).to eq('No resources returned in any of the search result bundles.') - expect(coverage_search_request_empty).to have_been_made - expect(coverage_search_request_empty_second).to have_been_made - end - - it 'skips if no Patient ids are inputted' do - result = run(test, search_param_values: '', url: server_endpoint, smart_auth_info:) - - expect(result.result).to eq('skip') - expect(result.result_message).to eq('No search parameters passed in, skipping test.') - end - - it 'fails if patient Coverage search returns non 200' do - coverage_search_request = stub_request(:get, "#{server_endpoint}/Coverage?patient=#{patient_id}") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 400, body: crd_coverage_search_bundle_active.to_json) - - result = run(test, search_param_values: patient_id, url: server_endpoint, smart_auth_info:) - - expect(result.result).to eq('fail') - expect(result.result_message).to eq('Unexpected response status: expected 200, but received 400') - expect(coverage_search_request).to have_been_made - end - - it 'fails if patient Coverage search returns bundle with non Coverage resources' do - coverage_search_request = stub_request(:get, "#{server_endpoint}/Coverage?patient=#{patient_id}") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: crd_encounter_search_bundle.to_json) - - result = run(test, search_param_values: patient_id, url: server_endpoint, smart_auth_info:) - - expect(result.result).to eq('fail') - expect(result.result_message).to eq('Unexpected resource type: expected Coverage, but received Encounter') - expect(coverage_search_request).to have_been_made - end - - it 'fails if patient Coverage search returns Coverage resource with incorrect beneficiary id' do - coverage_search_request = stub_request(:get, "#{server_endpoint}/Coverage?patient=wrong_id") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: crd_coverage_search_bundle_active.to_json) - - result = run(test, search_param_values: 'wrong_id', url: server_endpoint, smart_auth_info:) - - expect(result.result).to eq('fail') - expect(result.result_message).to match( - 'The Coverage resource in search result bundle with id coverage_example should have a\npatient' - ) - expect(coverage_search_request).to have_been_made - end - end - - describe 'Coverage search test with `status` search parameter' do - let(:test) do - Inferno::Repositories::Tests.new.find( - 'crd_client-crd_client_fhir_api-Group02-Group03-crd_client_coverage_status_search_test' - ) do - fhir_client do - url :url - auth_info :smart_auth_info - end - end - end - - it 'passes if all Coverage status search returns a valid bundle with Coverage resources' do - active_coverage_search_request = stub_request(:get, "#{server_endpoint}/Coverage?status=active") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: crd_coverage_search_bundle_active.to_json) - cancelled_coverage_search_request = stub_request(:get, "#{server_endpoint}/Coverage?status=cancelled") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: crd_coverage_search_bundle_cancelled.to_json) - draft_coverage_search_request = stub_request(:get, "#{server_endpoint}/Coverage?status=draft") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: crd_coverage_search_bundle_draft.to_json) - entered_in_error_coverage_search_request = - stub_request(:get, "#{server_endpoint}/Coverage?status=entered-in-error") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: crd_coverage_search_bundle_entered_in_error.to_json) - - result = run(test, url: server_endpoint, smart_auth_info:) - - expect(result.result).to eq('pass') - expect(active_coverage_search_request).to have_been_made - expect(cancelled_coverage_search_request).to have_been_made - expect(draft_coverage_search_request).to have_been_made - expect(entered_in_error_coverage_search_request).to have_been_made - end - - it 'passes if status search result includes an OperationOutcome resource' do - active_coverage_search_request = stub_request(:get, "#{server_endpoint}/Coverage?status=active") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: crd_coverage_search_bundle_with_operation_outcome.to_json) - cancelled_coverage_search_request = stub_request(:get, "#{server_endpoint}/Coverage?status=cancelled") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: crd_coverage_search_bundle_cancelled.to_json) - draft_coverage_search_request = stub_request(:get, "#{server_endpoint}/Coverage?status=draft") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: crd_coverage_search_bundle_draft.to_json) - entered_in_error_coverage_search_request = - stub_request(:get, "#{server_endpoint}/Coverage?status=entered-in-error") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: crd_coverage_search_bundle_entered_in_error.to_json) - - result = run(test, url: server_endpoint, smart_auth_info:) - - expect(result.result).to eq('pass') - expect(active_coverage_search_request).to have_been_made - expect(cancelled_coverage_search_request).to have_been_made - expect(draft_coverage_search_request).to have_been_made - expect(entered_in_error_coverage_search_request).to have_been_made - end - - it 'passes if at least 1 Coverage status search returns a valid bundle with Coverage resources' do - active_coverage_search_request = stub_request(:get, "#{server_endpoint}/Coverage?status=active") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: empty_bundle.to_json) - cancelled_coverage_search_request = stub_request(:get, "#{server_endpoint}/Coverage?status=cancelled") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: empty_bundle.to_json) - draft_coverage_search_request = stub_request(:get, "#{server_endpoint}/Coverage?status=draft") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: crd_coverage_search_bundle_draft.to_json) - entered_in_error_coverage_search_request = - stub_request(:get, "#{server_endpoint}/Coverage?status=entered-in-error") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: empty_bundle.to_json) - - result = run(test, url: server_endpoint, smart_auth_info:) - - expect(result.result).to eq('pass') - expect(active_coverage_search_request).to have_been_made - expect(cancelled_coverage_search_request).to have_been_made - expect(draft_coverage_search_request).to have_been_made - expect(entered_in_error_coverage_search_request).to have_been_made - end - - it 'skips if all Coverage status search returns empty bundles' do - active_coverage_search_request = stub_request(:get, "#{server_endpoint}/Coverage?status=active") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: empty_bundle.to_json) - cancelled_coverage_search_request = stub_request(:get, "#{server_endpoint}/Coverage?status=cancelled") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: empty_bundle.to_json) - draft_coverage_search_request = stub_request(:get, "#{server_endpoint}/Coverage?status=draft") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: empty_bundle.to_json) - entered_in_error_coverage_search_request = - stub_request(:get, "#{server_endpoint}/Coverage?status=entered-in-error") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: empty_bundle.to_json) - - result = run(test, url: server_endpoint, smart_auth_info:) - expect(result.result).to eq('skip') - expect(result.result_message).to eq('No resources returned in any of the search result bundles.') - expect(active_coverage_search_request).to have_been_made - expect(cancelled_coverage_search_request).to have_been_made - expect(draft_coverage_search_request).to have_been_made - expect(entered_in_error_coverage_search_request).to have_been_made - end - - it 'fails if status Coverage search returns non 200' do - active_coverage_search_request = stub_request(:get, "#{server_endpoint}/Coverage?status=active") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 400, body: crd_coverage_search_bundle_active.to_json) - - result = run(test, url: server_endpoint, smart_auth_info:) - - expect(result.result).to eq('fail') - expect(result.result_message).to eq('Unexpected response status: expected 200, but received 400') - expect(active_coverage_search_request).to have_been_made - end - - it 'fails if status Coverage search returns bundle with non Coverage resources' do - active_coverage_search_request = stub_request(:get, "#{server_endpoint}/Coverage?status=active") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: crd_encounter_search_bundle.to_json) - - result = run(test, url: server_endpoint, smart_auth_info:) - - expect(result.result).to eq('fail') - expect(result.result_message).to eq('Unexpected resource type: expected Coverage, but received Encounter') - expect(active_coverage_search_request).to have_been_made - end - - it 'fails if status Coverage search returns bundle with incorrect Coverage status' do - active_coverage_search_request = stub_request(:get, "#{server_endpoint}/Coverage?status=active") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: crd_coverage_search_bundle_cancelled.to_json) - - result = run(test, url: server_endpoint, smart_auth_info:) - - expect(result.result).to eq('fail') - expect(result.result_message).to match( - 'Each Coverage resource in search result bundle should have a status of `active`, instead got' - ) - expect(active_coverage_search_request).to have_been_made - end - end - - describe 'Encounter search test with `_id` search parameter' do - let(:test) do - Inferno::Repositories::Tests.new.find( - 'crd_client-crd_client_fhir_api-Group02-Group06-crd_client_encounter_id_search_test' - ) do - fhir_client do - url :url - auth_info :smart_auth_info - end - end - end - - it 'passes if valid Encounter id is passed in that can be used to search for Encounter resources' do - encounter_search_request = stub_request(:get, "#{server_endpoint}/Encounter?_id=#{encounter_id}") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: crd_encounter_search_bundle.to_json) - - result = run(test, search_param_values: encounter_id, url: server_endpoint, smart_auth_info:) - - expect(result.result).to eq('pass') - expect(encounter_search_request).to have_been_made - end - - it 'passes if _id search result includes an OperationOutcome resource' do - encounter_search_request = stub_request(:get, "#{server_endpoint}/Encounter?_id=#{encounter_id}") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: crd_encounter_search_bundle_with_operation_outcome.to_json) - - result = run(test, search_param_values: encounter_id, url: server_endpoint, smart_auth_info:) - - expect(result.result).to eq('pass') - expect(encounter_search_request).to have_been_made - end - - it 'passes if at least 1 of list of Encounter ids returns resources in Encounter _id search' do - encounter_search_request = stub_request(:get, "#{server_endpoint}/Encounter?_id=#{encounter_id}") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: crd_encounter_search_bundle.to_json) - encounter_search_request_empty = stub_request(:get, "#{server_endpoint}/Encounter?_id=example2") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: empty_bundle.to_json) - - encounter_id_list = "#{encounter_id}, example2" - result = run(test, search_param_values: encounter_id_list, url: server_endpoint, smart_auth_info:) - - expect(result.result).to eq('pass') - expect(encounter_search_request).to have_been_made - expect(encounter_search_request_empty).to have_been_made - end - - it 'skips if no resources returned in Encounter _id search' do - encounter_search_request = stub_request(:get, "#{server_endpoint}/Encounter?_id=#{encounter_id}") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: empty_bundle.to_json) - encounter_search_request_empty = stub_request(:get, "#{server_endpoint}/Encounter?_id=example2") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: empty_bundle.to_json) - - encounter_id_list = "#{encounter_id}, example2" - result = run(test, search_param_values: encounter_id_list, url: server_endpoint, smart_auth_info:) - - expect(result.result).to eq('skip') - expect(result.result_message).to eq('No resources returned in any of the search result bundles.') - expect(encounter_search_request).to have_been_made - expect(encounter_search_request_empty).to have_been_made - end - - it 'skips if no Encounter ids are inputted' do - result = run(test, search_param_values: '', url: server_endpoint, smart_auth_info:) - - expect(result.result).to eq('skip') - expect(result.result_message).to eq('No search parameters passed in, skipping test.') - end - - it 'fails if Encounter _id search returns non 200' do - encounter_search_request = stub_request(:get, "#{server_endpoint}/Encounter?_id=#{encounter_id}") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 400, body: crd_encounter_search_bundle.to_json) - - result = run(test, search_param_values: encounter_id, url: server_endpoint, smart_auth_info:) - - expect(result.result).to eq('fail') - expect(result.result_message).to eq('Unexpected response status: expected 200, but received 400') - expect(encounter_search_request).to have_been_made - end - - it 'fails if Encounter _id search returns bundle with non Encounter resource' do - encounter_search_request = stub_request(:get, "#{server_endpoint}/Encounter?_id=#{encounter_id}") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: crd_coverage_search_bundle_active.to_json) - - result = run(test, search_param_values: encounter_id, url: server_endpoint, smart_auth_info:) - - expect(result.result).to eq('fail') - expect(result.result_message).to eq('Unexpected resource type: expected Encounter, but received Coverage') - expect(encounter_search_request).to have_been_made - end - - it 'fails if Encounter _id search returns Encounter resource with wrong id' do - encounter_search_request = stub_request(:get, "#{server_endpoint}/Encounter?_id=wrong_id") - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: crd_encounter_search_bundle.to_json) - - result = run(test, search_param_values: 'wrong_id', url: server_endpoint, smart_auth_info:) - - expect(result.result).to eq('fail') - expect(result.result_message).to eq('Expected resource to have id: `wrong_id`, but found `example`') - expect(encounter_search_request).to have_been_made - end - end - - describe 'Encounter search test with `_include` search parameter' do - let(:test) do - Inferno::Repositories::Tests.new.find( - 'crd_client-crd_client_fhir_api-Group02-Group06-crd_client_encounter_location_include_test' - ) do - fhir_client do - url :url - auth_info :smart_auth_info - end - end - end - - it 'passes if valid Encounter id is passed in that can be used to _include search for Encounter resources' do - encounter_search_request = stub_request(:get, encounter_include_search_request) - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: crd_encounter_search_bundle_with_location.to_json) - - result = run(test, search_param_values: encounter_id, url: server_endpoint, smart_auth_info:) - - expect(result.result).to eq('pass') - expect(encounter_search_request).to have_been_made - end - - it 'passes if _include search result includes an OperationOutcome resource' do - encounter_search_request = stub_request(:get, encounter_include_search_request) - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: crd_encounter_search_bundle_with_operation_outcome.to_json) - - result = run(test, search_param_values: encounter_id, url: server_endpoint, smart_auth_info:) - - expect(result.result).to eq('pass') - expect(encounter_search_request).to have_been_made - end - - it 'passes if at least 1 of list of Encounter ids returns resources in Encounter _include search' do - encounter_search_request = stub_request(:get, encounter_include_search_request) - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: crd_encounter_search_bundle_with_location.to_json) - encounter_search_request_empty = stub_request(:get, encounter_include_search_request_different_id) - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: empty_bundle.to_json) - - encounter_id_list = "#{encounter_id}, example2" - result = run(test, search_param_values: encounter_id_list, url: server_endpoint, smart_auth_info:) - - expect(result.result).to eq('pass') - expect(encounter_search_request).to have_been_made - expect(encounter_search_request_empty).to have_been_made - end - - it 'skips if no resources returned in Encounter _include search' do - encounter_search_request = stub_request(:get, encounter_include_search_request) - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: empty_bundle.to_json) - encounter_search_request_empty = stub_request(:get, encounter_include_search_request_different_id) - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: empty_bundle.to_json) - - encounter_id_list = "#{encounter_id}, example2" - result = run(test, search_param_values: encounter_id_list, url: server_endpoint, smart_auth_info:) - - expect(result.result).to eq('skip') - expect(result.result_message).to eq('No resources returned in any of the search result bundles.') - expect(encounter_search_request).to have_been_made - expect(encounter_search_request_empty).to have_been_made - end - - it 'skips if no Encounter ids are inputted' do - result = run(test, search_param_values: '', url: server_endpoint, smart_auth_info:) - - expect(result.result).to eq('skip') - expect(result.result_message).to eq('No search parameters passed in, skipping test.') - end - - it 'fails if Encounter _id search returns non 200' do - encounter_search_request = stub_request(:get, encounter_include_search_request) - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 400, body: crd_encounter_search_bundle_with_location.to_json) - - result = run(test, search_param_values: encounter_id, url: server_endpoint, smart_auth_info:) - - expect(result.result).to eq('fail') - expect(result.result_message).to eq('Unexpected response status: expected 200, but received 400') - expect(encounter_search_request).to have_been_made - end - - it 'fails if Encounter _include search returns a bundle with no Encounter resource' do - encounter_search_request = stub_request(:get, encounter_include_search_request) - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: crd_location_search_bundle.to_json) - - result = run(test, search_param_values: encounter_id, url: server_endpoint, smart_auth_info:) - - expect(result.result).to eq('fail') - expect(result.result_message).to match( - 'should include exactly 1 Encounter resource, instead got 0' - ) - expect(encounter_search_request).to have_been_made - end - - it 'fails if Encounter _include search returns a bundle with more than 1 Encounter resource' do - encounter_search_request = stub_request(:get, encounter_include_search_request) - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: crd_encounter_search_bundle_multiple_entries.to_json) - - result = run(test, search_param_values: encounter_id, url: server_endpoint, smart_auth_info:) - - expect(result.result).to eq('fail') - expect(result.result_message).to match( - 'should include exactly 1 Encounter resource, instead got 2' - ) - expect(encounter_search_request).to have_been_made - end - - it 'fails if Encounter _include search returns a bundle with incorrect resource types' do - encounter_search_request = stub_request(:get, encounter_include_search_request) - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: crd_encounter_search_bundle_wrong_entries.to_json) - - result = run(test, search_param_values: encounter_id, url: server_endpoint, smart_auth_info:) - - expect(result.result).to eq('fail') - expect(result.result_message).to match('Unexpected resource type: expected Location, but received Coverage') - expect(encounter_search_request).to have_been_made - end - - it 'fails if Encounter _include search returns a bundle with wrong Encounter id' do - encounter_search_request = stub_request(:get, encounter_include_search_request_different_id) - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: crd_encounter_search_bundle_with_location.to_json) - - result = run(test, search_param_values: 'example2', url: server_endpoint, smart_auth_info:) - - expect(result.result).to eq('fail') - expect(result.result_message).to match('Expected resource to have id: `example2`, but found `example`') - expect(encounter_search_request).to have_been_made - end - - it 'fails if Encounter _id search returns Location resources that are not referenced by the Encounter resource' do - encounter_search_request = stub_request(:get, encounter_include_search_request) - .with( - headers: { Authorization: 'Bearer SAMPLE_TOKEN' } - ) - .to_return(status: 200, body: crd_encounter_search_bundle_with_location_wrong_id.to_json) - - result = run(test, search_param_values: encounter_id, url: server_endpoint, smart_auth_info:) - - expect(result.result).to eq('fail') - expect(result.result_message).to match( - 'The Encounter resource in search result bundle with id example did not have a\nlocation reference' - ) - expect(encounter_search_request).to have_been_made - end - end -end diff --git a/spec/davinci_crd_test_kit/client_fhir_api_validation_test_spec.rb b/spec/davinci_crd_test_kit/client_fhir_api_validation_test_spec.rb deleted file mode 100644 index bab56c00..00000000 --- a/spec/davinci_crd_test_kit/client_fhir_api_validation_test_spec.rb +++ /dev/null @@ -1,262 +0,0 @@ -require_relative '../../lib/davinci_crd_test_kit/client_tests/client_fhir_api_validation_test' - -RSpec.describe DaVinciCRDTestKit::ClientFHIRApiValidationTest do - let(:suite_id) { 'crd_client' } - let(:result) { repo_create(:result, test_session_id: test_session.id) } - - let(:server_endpoint) { 'http://example.com/fhir' } - - let(:encounter_id) { 'example' } - let(:organization_id) { 'example' } - - let(:crd_encounter) do - JSON.parse( - File.read(File.join( - __dir__, '..', 'fixtures', 'crd_encounter_example.json' - )) - ) - end - - let(:crd_encounter_second) do - crd_encounter_second = crd_encounter.dup - crd_encounter_second['id'] = 'example2' - crd_encounter_second - end - - let(:crd_location) do - JSON.parse( - File.read(File.join( - __dir__, '..', 'fixtures', 'crd_location_example.json' - )) - ) - end - - let(:crd_encounter_search_bundle) do - bundle = FHIR::Bundle.new(type: 'searchset') - bundle.entry.append(FHIR::Bundle::Entry.new( - fullUrl: "#{server_endpoint}/Encounter/encounter_example", - resource: FHIR.from_contents(crd_encounter.to_json), - id: 'encounter_entry' - )) - bundle - end - - let(:crd_encounter_search_bundle_multiple_entries) do - bundle = FHIR::Bundle.new(type: 'searchset') - bundle.entry.append(FHIR::Bundle::Entry.new( - fullUrl: "#{server_endpoint}/Encounter/encounter_example", - resource: FHIR.from_contents(crd_encounter.to_json) - ), FHIR::Bundle::Entry.new( - fullUrl: "#{server_endpoint}/Encounter/encounter_example", - resource: FHIR.from_contents(crd_encounter_second.to_json) - )) - bundle - end - - let(:crd_encounter_search_bundle_with_location) do - bundle = FHIR::Bundle.new(type: 'searchset') - bundle.entry.append(FHIR::Bundle::Entry.new( - fullUrl: "#{server_endpoint}/Encounter/encounter_example", - resource: FHIR.from_contents(crd_encounter.to_json) - ), FHIR::Bundle::Entry.new( - fullUrl: "#{server_endpoint}/Encounter/encounter_example2", - resource: FHIR.from_contents(crd_location.to_json) - )) - bundle - end - - let(:operation_outcome_success) do - { - outcomes: [{ - issues: [] - }], - sessionId: 'b8cf5547-1dc7-4714-a797-dc2347b93fe2' - } - end - - let(:operation_outcome_failure) do - { - outcomes: [{ - issues: [{ - level: 'ERROR' - }] - }], - sessionId: 'b8cf5547-1dc7-4714-a797-dc2347b93fe2' - } - end - - def create_fhir_api_requests(url: nil, body: nil, status: 200, search_tag: nil, name: nil) - headers ||= [ - { - type: 'request', - name: 'Authorization', - value: 'Bearer SAMPLE_TOKEN' - } - ] - repo_create( - :request, - direction: 'outgoing', - url:, - name:, - test_session_id: test_session.id, - result:, - response_body: body.is_a?(Hash) ? body.to_json : body, - tags: ['Encounter', search_tag], - status:, - headers: - ) - end - - describe 'FHIR Resource Validation' do - let(:test) do - Class.new(DaVinciCRDTestKit::ClientFHIRApiValidationTest) do - fhir_resource_validator do - url ENV.fetch('FHIR_RESOURCE_VALIDATOR_URL', nil) - - cli_context do - txServer nil - displayWarnings true - disableDefaultResourceFetcher true - end - - igs('hl7.fhir.us.davinci-crd', 'hl7.fhir.us.core') - end - config( - options: { resource_type: 'Encounter' } - ) - end - end - - it 'passes if several fhir api requests return all valid resources' do - validation_request = stub_request(:post, validation_url) - .to_return(status: 200, body: operation_outcome_success.to_json) - - create_fhir_api_requests( - url: "#{server_endpoint}/Encounter?_id=#{encounter_id}", - body: crd_encounter_search_bundle.to_json, - search_tag: 'id_search', - name: 'encounter_id_search' - ) - create_fhir_api_requests( - url: "#{server_endpoint}/Encounter/#{encounter_id}", - body: crd_encounter.to_json, - search_tag: 'read', - name: 'encounter_readh' - ) - create_fhir_api_requests( - url: "#{server_endpoint}/Encounter?organization=#{organization_id}", - body: crd_encounter_search_bundle_multiple_entries.to_json, - search_tag: 'organization_search', - name: 'encounter_organization_search' - ) - create_fhir_api_requests( - url: "#{server_endpoint}/Encounter?_id=#{encounter_id}&_include=Encounter:location", - body: crd_encounter_search_bundle_with_location.to_json, - search_tag: 'include_location_search', - name: 'encounter_include_search' - ) - - result = run(test) - expect(result.result).to eq('pass') - expect(validation_request).to have_been_made.times(2) - end - - it 'fails if any fhir api requests return invalid resources' do - validation_request = stub_request(:post, validation_url) - .to_return(status: 200, body: operation_outcome_failure.to_json) - - create_fhir_api_requests( - url: "#{server_endpoint}/Encounter?_id=#{encounter_id}", - body: crd_encounter_search_bundle.to_json, - search_tag: 'id_search', - name: 'encounter_id_search' - ) - create_fhir_api_requests( - url: "#{server_endpoint}/Encounter?organization=#{organization_id}", - body: crd_encounter_search_bundle_multiple_entries.to_json, - search_tag: 'organization_search', - name: 'encounter_organization_search' - ) - create_fhir_api_requests( - url: "#{server_endpoint}/Encounter?_id=#{encounter_id}&_include=Encounter:location", - body: crd_encounter_search_bundle_with_location.to_json, - search_tag: 'include_location_search', - name: 'encounter_include_search' - ) - - result = run(test) - - expect(result.result).to eq('fail') - expect(result.result_message).to match('2/2 Encounter resources returned from previous') - expect(validation_request).to have_been_made.times(2) - end - - it 'skips if no fhir api requests were made' do - result = run(test) - - expect(result.result).to eq('skip') - expect(result.result_message).to eq('No FHIR api requests were made') - end - - it 'passes if at least one fhir api request returns a 200 even if one returns a non 200' do - validation_request = stub_request(:post, validation_url) - .to_return(status: 200, body: operation_outcome_success.to_json) - - create_fhir_api_requests( - url: "#{server_endpoint}/Encounter?_id=#{encounter_id}", - body: operation_outcome_failure.to_json, - search_tag: 'id_search', - name: 'encounter_id_search', - status: 400 - ) - create_fhir_api_requests( - url: "#{server_endpoint}/Encounter?organization=#{organization_id}", - body: crd_encounter_search_bundle_multiple_entries.to_json, - search_tag: 'organization_search', - name: 'encounter_organization_search' - ) - create_fhir_api_requests( - url: "#{server_endpoint}/Encounter?_id=#{encounter_id}&_include=Encounter:location", - body: crd_encounter_search_bundle_with_location.to_json, - search_tag: 'include_location_search', - name: 'encounter_include_search' - ) - - result = run(test) - - expect(result.result).to eq('pass') - expect(validation_request).to have_been_made.times(2) - end - - it 'skips if all fhir api requests return a non 200' do - create_fhir_api_requests( - url: "#{server_endpoint}/Encounter?_id=#{encounter_id}", - body: operation_outcome_failure.to_json, - search_tag: 'id_search', - name: 'encounter_id_search', - status: 400 - ) - create_fhir_api_requests( - url: "#{server_endpoint}/Encounter?organization=#{organization_id}", - body: operation_outcome_failure.to_json, - search_tag: 'organization_search', - name: 'encounter_organization_search', - status: 400 - ) - create_fhir_api_requests( - url: "#{server_endpoint}/Encounter?_id=#{encounter_id}&_include=Encounter:location", - body: operation_outcome_failure.to_json, - search_tag: 'include_location_search', - name: 'encounter_include_search', - status: 400 - ) - - result = run(test) - - expect(result.result).to eq('skip') - expect(result.result_message).to match( - 'There were no successful FHIR API requests made in previous tests to use in validation.' - ) - end - end -end