davinci_crd_test_kit 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/config/presets/inferno_crd_client_suite.json.erb +20 -14
- data/config/presets/inferno_crd_client_suite_prefetch_subset_v221.json.erb +125 -0
- data/config/presets/inferno_crd_client_suite_v221.json.erb +124 -0
- data/config/presets/inferno_crd_server_suite.json.erb +58 -1
- data/config/presets/inferno_crd_server_suite_v221.json.erb +94 -0
- data/config/presets/ri_crd_request_generator.json_v221.json.erb +13 -0
- data/config/presets/ri_crd_server.json.erb +19 -19
- data/lib/davinci_crd_test_kit/client/client_base_urls.rb +80 -0
- data/lib/davinci_crd_test_kit/{client_hook_request_validation.rb → client/client_hook_request_validation.rb} +1 -1
- data/lib/davinci_crd_test_kit/client/crd_client_options.rb +30 -0
- data/lib/davinci_crd_test_kit/client/endpoints/cds_services_discovery_handler.rb +34 -0
- data/lib/davinci_crd_test_kit/client/endpoints/custom_service_response.rb +342 -0
- data/lib/davinci_crd_test_kit/client/endpoints/gather_response_generation_data.rb +410 -0
- data/lib/davinci_crd_test_kit/client/endpoints/hook_request_endpoint.rb +233 -0
- data/lib/davinci_crd_test_kit/{mock_service_response.rb → client/endpoints/mock_service_response.rb} +165 -59
- data/lib/davinci_crd_test_kit/{card_responses → client/endpoints/mocked_card_responses}/companions_prerequisites.json +1 -0
- data/lib/davinci_crd_test_kit/{card_responses → client/endpoints/mocked_card_responses}/create_update_coverage_information.json +3 -2
- data/lib/davinci_crd_test_kit/{card_responses → client/endpoints/mocked_card_responses}/launch_smart_app.json +8 -1
- data/lib/davinci_crd_test_kit/{card_responses → client/endpoints/mocked_card_responses}/propose_alternate_request.json +1 -0
- data/lib/davinci_crd_test_kit/{card_responses → client/endpoints/mocked_card_responses}/request_form_completion.json +17 -16
- data/lib/davinci_crd_test_kit/client/multi_request_message_helper.rb +35 -0
- data/lib/davinci_crd_test_kit/client/tagged_request_load_helper.rb +38 -0
- data/lib/davinci_crd_test_kit/client/v2.0.1/api/client_fhir_api_create_test.rb +43 -0
- data/lib/davinci_crd_test_kit/client/v2.0.1/api/client_fhir_api_read_test.rb +43 -0
- data/lib/davinci_crd_test_kit/client/v2.0.1/api/client_fhir_api_search_test.rb +234 -0
- data/lib/davinci_crd_test_kit/client/v2.0.1/api/client_fhir_api_update_test.rb +43 -0
- data/lib/davinci_crd_test_kit/client/v2.0.1/api/client_fhir_api_validation_test.rb +63 -0
- data/lib/davinci_crd_test_kit/client/v2.0.1/auth/decode_auth_token_test.rb +65 -0
- data/lib/davinci_crd_test_kit/client/v2.0.1/auth/retrieve_jwks_test.rb +109 -0
- data/lib/davinci_crd_test_kit/client/v2.0.1/auth/token_header_test.rb +70 -0
- data/lib/davinci_crd_test_kit/client/v2.0.1/auth/token_payload_test.rb +85 -0
- data/lib/davinci_crd_test_kit/{routes/cds-services.json → client/v2.0.1/cds-services-v201.json} +1 -1
- data/lib/davinci_crd_test_kit/client/v2.0.1/client_appointment_book_group.rb +108 -0
- data/lib/davinci_crd_test_kit/client/v2.0.1/client_card_must_support_group.rb +31 -0
- data/lib/davinci_crd_test_kit/client/v2.0.1/client_encounter_discharge_group.rb +105 -0
- data/lib/davinci_crd_test_kit/client/v2.0.1/client_encounter_start_group.rb +105 -0
- data/lib/davinci_crd_test_kit/client/v2.0.1/client_fhir_api_group.rb +790 -0
- data/lib/davinci_crd_test_kit/client/v2.0.1/client_hooks_group.rb +74 -0
- data/lib/davinci_crd_test_kit/client/v2.0.1/client_order_dispatch_group.rb +111 -0
- data/lib/davinci_crd_test_kit/client/v2.0.1/client_order_select_group.rb +116 -0
- data/lib/davinci_crd_test_kit/client/v2.0.1/client_order_sign_group.rb +113 -0
- data/lib/davinci_crd_test_kit/{client_registration_group.rb → client/v2.0.1/client_registration_group.rb} +12 -8
- data/lib/davinci_crd_test_kit/client/v2.0.1/client_urls.rb +13 -0
- data/lib/davinci_crd_test_kit/client/v2.0.1/crd_client_suite.rb +134 -0
- data/lib/davinci_crd_test_kit/client/v2.0.1/invocation/appointment_book_receive_request_test.rb +129 -0
- data/lib/davinci_crd_test_kit/client/v2.0.1/invocation/encounter_discharge_receive_request_test.rb +126 -0
- data/lib/davinci_crd_test_kit/client/v2.0.1/invocation/encounter_start_receive_request_test.rb +126 -0
- data/lib/davinci_crd_test_kit/client/v2.0.1/invocation/order_dispatch_receive_request_test.rb +138 -0
- data/lib/davinci_crd_test_kit/client/v2.0.1/invocation/order_select_receive_request_test.rb +134 -0
- data/lib/davinci_crd_test_kit/client/v2.0.1/invocation/order_sign_receive_request_test.rb +136 -0
- data/lib/davinci_crd_test_kit/client/v2.0.1/must_support/client_card_must_support_coverage_information.rb +93 -0
- data/lib/davinci_crd_test_kit/client/v2.0.1/must_support/client_card_must_support_external_reference.rb +62 -0
- data/lib/davinci_crd_test_kit/client/v2.0.1/must_support/client_card_must_support_instructions.rb +62 -0
- data/lib/davinci_crd_test_kit/client/v2.0.1/registration/client_registration_verification_test.rb +94 -0
- data/lib/davinci_crd_test_kit/client/v2.0.1/registration/client_service_registration_attestation_test.rb +40 -0
- data/lib/davinci_crd_test_kit/client/v2.0.1/verify_request/hook_request_fetched_data_test.rb +86 -0
- data/lib/davinci_crd_test_kit/client/v2.0.1/verify_request/hook_request_optional_fields_test.rb +63 -0
- data/lib/davinci_crd_test_kit/client/v2.0.1/verify_request/hook_request_prefetch_equals_queried_test.rb +96 -0
- data/lib/davinci_crd_test_kit/client/v2.0.1/verify_request/hook_request_required_fields_test.rb +55 -0
- data/lib/davinci_crd_test_kit/client/v2.0.1/verify_request/hook_request_valid_context_test.rb +70 -0
- data/lib/davinci_crd_test_kit/client/v2.0.1/verify_request/hook_request_valid_prefetch_test.rb +62 -0
- data/lib/davinci_crd_test_kit/client/v2.0.1/verify_response/client_display_cards_attest.rb +83 -0
- data/lib/davinci_crd_test_kit/client/v2.0.1/verify_response/inferno_response_validation.rb +79 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/api/client_coverage_info_update_test.rb +212 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/api/client_crd_update_verification_group.rb +18 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/auth/decode_auth_token_test.rb +69 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/auth/retrieve_jwks_test.rb +120 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/auth/token_header_test.rb +92 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/auth/token_payload_test.rb +93 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/cds-services-prefetch-subset-v221.json +198 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/cds-services-v221.json +202 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/client_appointment_book_group.rb +102 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/client_cross_hook_group.rb +28 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/client_encounter_discharge_group.rb +96 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/client_encounter_start_group.rb +95 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/client_fhir_api_group.rb +88 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/client_hooks_group.rb +64 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/client_long_running_hook_group.rb +32 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/client_order_dispatch_group.rb +101 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/client_order_select_group.rb +102 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/client_order_sign_group.rb +107 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/client_registration_group.rb +27 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/client_urls.rb +27 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/crd_client_suite.rb +229 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/cross_hook/client_card_must_support_coverage_information_test.rb +63 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/cross_hook/client_fhirpath_collection_as_comma_delimited_string_test.rb +60 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/cross_hook/client_hook_instances_unique_test.rb +45 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/cross_hook/client_location_address_propagation_test.rb +135 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/cross_hook/client_prefetch_complete_and_subset_test.rb +103 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/appointment_book_receive_request_test.rb +156 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/encounter_discharge_receive_request_test.rb +157 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/encounter_start_receive_request_test.rb +157 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/order_dispatch_receive_request_test.rb +165 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/order_select_receive_request_test.rb +165 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/order_sign_receive_request_test.rb +165 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/long_running/client_long_running_receive_request_test.rb +64 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/long_running/client_skip_long_running_attestation_test.rb +49 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/registration/client_registration_verification_test.rb +161 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/registration/client_service_registration_attestation_test.rb +107 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_conformance_test.rb +47 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_coverage_verification_test.rb +152 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_data_fetch_verification_test.rb +55 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_granted_scopes_test.rb +123 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_prefetch_complete_test.rb +127 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_prefetch_profiles_test.rb +55 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_requested_version_test.rb +54 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_secured_transport_test.rb +48 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/verify_response/client_display_cards_attest.rb +74 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/verify_response/hook_response_support_coverage_information_test.rb +30 -0
- data/lib/davinci_crd_test_kit/client/v2.2.1/verify_response/inferno_response_validation.rb +77 -0
- data/lib/davinci_crd_test_kit/cross_suite/base_urls.rb +20 -0
- data/lib/davinci_crd_test_kit/cross_suite/cards_identification.rb +312 -0
- data/lib/davinci_crd_test_kit/{cards_validation.rb → cross_suite/cards_validation.rb} +104 -47
- data/lib/davinci_crd_test_kit/cross_suite/coverage-information_stu201_metadata.yml +27 -0
- data/lib/davinci_crd_test_kit/cross_suite/coverage-information_stu221_metadata.yml +60 -0
- data/lib/davinci_crd_test_kit/cross_suite/fhirpath_on_cds_request.rb +177 -0
- data/lib/davinci_crd_test_kit/{hook_request_field_validation.rb → cross_suite/hook_request_field_validation.rb} +282 -203
- data/lib/davinci_crd_test_kit/cross_suite/logical_models_override_helper.rb +220 -0
- data/lib/davinci_crd_test_kit/cross_suite/prefetch_completeness_checker.rb +462 -0
- data/lib/davinci_crd_test_kit/cross_suite/prefetch_contents_validation.rb +81 -0
- data/lib/davinci_crd_test_kit/cross_suite/prefetch_profile_validation.rb +48 -0
- data/lib/davinci_crd_test_kit/cross_suite/profiles_and_resource_types.rb +63 -0
- data/lib/davinci_crd_test_kit/cross_suite/replace_tokens.rb +38 -0
- data/lib/davinci_crd_test_kit/cross_suite/requests_logical_model_validation.rb +202 -0
- data/lib/davinci_crd_test_kit/cross_suite/response_logical_model_validation.rb +274 -0
- data/lib/davinci_crd_test_kit/{suggestion_actions_validation.rb → cross_suite/suggestion_actions_validation.rb} +70 -50
- data/lib/davinci_crd_test_kit/cross_suite/tags.rb +42 -0
- data/lib/davinci_crd_test_kit/metadata.rb +10 -44
- data/lib/davinci_crd_test_kit/requirements/cds-hooks-library_1.0.1_requirements.xlsx +0 -0
- data/lib/davinci_crd_test_kit/requirements/cds-hooks_2.0_requirements.xlsx +0 -0
- data/lib/davinci_crd_test_kit/requirements/cds-hooks_3.0.0-ballot_requirements.xlsx +0 -0
- data/lib/davinci_crd_test_kit/requirements/davinci_crd_test_kit_requirements.csv +742 -65
- data/lib/davinci_crd_test_kit/requirements/generated/crd_client_requirements_coverage.csv +279 -54
- data/lib/davinci_crd_test_kit/requirements/generated/crd_client_v221_requirements_coverage.csv +1430 -0
- data/lib/davinci_crd_test_kit/requirements/generated/crd_server_requirements_coverage.csv +36 -45
- data/lib/davinci_crd_test_kit/requirements/generated/crd_server_v221_requirements_coverage.csv +143 -0
- data/lib/davinci_crd_test_kit/requirements/hl7.fhir.us.davinci-crd_2.0.1_requirements.xlsx +0 -0
- data/lib/davinci_crd_test_kit/requirements/hl7.fhir.us.davinci-crd_2.2.1_requirements.xlsx +0 -0
- data/lib/davinci_crd_test_kit/server/endpoints/jwk_set_endpoint_handler.rb +13 -0
- data/lib/davinci_crd_test_kit/server/endpoints/mock_ehr/fhir_create_endpoint.rb +23 -0
- data/lib/davinci_crd_test_kit/server/endpoints/mock_ehr/fhir_delete_endpoint.rb +30 -0
- data/lib/davinci_crd_test_kit/server/endpoints/mock_ehr/fhir_metadata_endpoint.rb +112 -0
- data/lib/davinci_crd_test_kit/server/endpoints/mock_ehr/fhir_read_endpoint.rb +21 -0
- data/lib/davinci_crd_test_kit/server/endpoints/mock_ehr/fhir_request_handler.rb +261 -0
- data/lib/davinci_crd_test_kit/server/endpoints/mock_ehr/fhir_search_endpoint.rb +561 -0
- data/lib/davinci_crd_test_kit/server/endpoints/mock_ehr/fhir_update_endpoint.rb +24 -0
- data/lib/davinci_crd_test_kit/server/endpoints/mock_ehr/stress-test-Bundle.json +54687 -0
- data/lib/davinci_crd_test_kit/server/endpoints/mock_ehr_endpoints.rb +95 -0
- data/lib/davinci_crd_test_kit/server/jobs/invoke_hook.rb +225 -0
- data/lib/davinci_crd_test_kit/{jwt_helper.rb → server/jwt_helper.rb} +1 -12
- data/lib/davinci_crd_test_kit/server/resource_extractor.rb +68 -0
- data/lib/davinci_crd_test_kit/server/server_abstract_invoke_hook_test.rb +165 -0
- data/lib/davinci_crd_test_kit/server/server_base_urls.rb +30 -0
- data/lib/davinci_crd_test_kit/{server_hook_helper.rb → server/server_hook_helper.rb} +1 -1
- data/lib/davinci_crd_test_kit/{server_hook_request_validation.rb → server/server_hook_request_validation.rb} +1 -1
- data/lib/davinci_crd_test_kit/{test_helper.rb → server/server_test_helper.rb} +7 -3
- data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/Appointment.yml +5 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/ClaimResponse.yml +5 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/CommunicationRequest.yml +5 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/Coverage.yml +21 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/Device.yml +5 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/DeviceRequest.yml +5 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/Encounter.yml +7 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/Location.yml +5 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/MedicationRequest.yml +5 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/NutritionOrder.yml +5 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/Organization.yml +5 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/Patient.yml +5 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/Practitioner.yml +5 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/PractitionerRole.yml +40 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/ServiceRequest.yml +5 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/Task.yml +5 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/VisionPrescription.yml +5 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/crd_server_suite.rb +99 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/discovery/discovery_endpoint_test.rb +90 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/discovery/discovery_services_validation_test.rb +67 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/interaction/server_invoke_hook_test.rb +12 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/must_support/coverage_information_system_action_across_hooks_validation_test.rb +34 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/must_support/external_reference_card_across_hooks_validation_test.rb +30 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/must_support/instructions_card_received_across_hooks_test.rb +27 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/server_appointment_book_group.rb +191 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/server_demonstrate_hook_response_group.rb +93 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/server_discovery_group.rb +62 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/server_encounter_discharge_group.rb +186 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/server_encounter_start_group.rb +186 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/server_hooks_group.rb +73 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/server_order_dispatch_group.rb +191 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/server_order_select_group.rb +211 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/server_order_sign_group.rb +216 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/server_required_card_response_validation_group.rb +28 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/server_urls.rb +13 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/verify_request/service_request_context_validation_test.rb +30 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/verify_request/service_request_optional_fields_validation_test.rb +39 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/verify_request/service_request_required_fields_validation_test.rb +40 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/verify_response/additional_orders_validation_test.rb +59 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/verify_response/card_optional_fields_validation_test.rb +51 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/verify_response/coverage_information_system_action_received_test.rb +65 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/verify_response/coverage_information_system_action_validation_test.rb +120 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/verify_response/create_or_update_coverage_info_response_validation_test.rb +70 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/verify_response/external_reference_card_validation_test.rb +37 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/verify_response/form_completion_response_validation_test.rb +67 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/verify_response/instructions_card_received_test.rb +30 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/verify_response/launch_smart_app_card_validation_test.rb +39 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/verify_response/propose_alternate_request_card_validation_test.rb +46 -0
- data/lib/davinci_crd_test_kit/server/v2.0.1/verify_response/service_response_validation_test.rb +83 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/Appointment_withorder.yml +5 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/Appointment_withoutorder.yml +5 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/CommunicationRequest.yml +5 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/Coverage.yml +5 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/Device.yml +5 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/DeviceRequest.yml +5 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/Encounter.yml +5 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/Location.yml +5 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/MedicationRequest.yml +5 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/NutritionOrder.yml +5 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/Organization.yml +5 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/Patient.yml +5 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/PractitionerRole.yml +5 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/ServiceRequest.yml +5 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/VisionPrescription.yml +5 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/crd_server_suite.rb +115 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/discovery/discovery_configuration_test.rb +159 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/discovery/discovery_endpoint_test.rb +90 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/discovery/discovery_prefetch_support_test.rb +43 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/discovery/discovery_services_validation_test.rb +121 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/interaction/server_invoke_hook_test.rb +17 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/must_support/coverage_information_must_support_test.rb +71 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/must_support/coverage_information_system_action_across_hooks_validation_test.rb +36 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/must_support/supported_us_core_versions_test.rb +118 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/server_appointment_book_group.rb +213 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/server_demonstrate_hook_response_group.rb +93 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/server_discovery_group.rb +69 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/server_encounter_discharge_group.rb +194 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/server_encounter_start_group.rb +194 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/server_hooks_group.rb +73 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/server_order_dispatch_group.rb +214 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/server_order_select_group.rb +219 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/server_order_sign_group.rb +241 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/server_required_card_response_validation_group.rb +30 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/server_urls.rb +13 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/verify_request/service_request_context_validation_test.rb +30 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/verify_request/service_request_no_custom_extensions_test.rb +120 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/verify_request/service_request_optional_fields_validation_test.rb +39 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/verify_request/service_request_required_fields_validation_test.rb +40 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/additional_orders_validation_test.rb +66 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/all_responses_include_coverage_information_test.rb +123 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/card_optional_fields_validation_test.rb +57 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/coverage_info_configuration_test.rb +83 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/coverage_information_system_action_received_test.rb +65 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/coverage_information_system_action_validation_test.rb +184 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/create_or_update_coverage_info_response_validation_test.rb +75 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/external_reference_card_validation_test.rb +47 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/form_completion_response_validation_test.rb +91 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/hook_request_resource_resolution.rb +137 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/instructions_card_received_test.rb +32 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/launch_smart_app_card_validation_test.rb +49 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/order_dispatch_coverage_information_test.rb +38 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/propose_alternate_request_card_validation_test.rb +54 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/service_response_validation_test.rb +97 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/unknown_cds_hooks_elements_test.rb +78 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/unknown_configuration_test.rb +78 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/unknown_context_test.rb +78 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/verify_response_without_billing_options_test.rb +43 -0
- data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/verify_response_without_configuration_test.rb +44 -0
- data/lib/davinci_crd_test_kit/version.rb +2 -2
- data/lib/davinci_crd_test_kit.rb +4 -2
- metadata +297 -93
- data/lib/davinci_crd_test_kit/client_fhir_api_group.rb +0 -785
- data/lib/davinci_crd_test_kit/client_hooks_group.rb +0 -74
- data/lib/davinci_crd_test_kit/client_tests/appointment_book_receive_request_test.rb +0 -93
- data/lib/davinci_crd_test_kit/client_tests/client_appointment_book_group.rb +0 -75
- data/lib/davinci_crd_test_kit/client_tests/client_display_cards_attest.rb +0 -48
- data/lib/davinci_crd_test_kit/client_tests/client_encounter_discharge_group.rb +0 -73
- data/lib/davinci_crd_test_kit/client_tests/client_encounter_start_group.rb +0 -73
- data/lib/davinci_crd_test_kit/client_tests/client_fhir_api_create_test.rb +0 -42
- data/lib/davinci_crd_test_kit/client_tests/client_fhir_api_read_test.rb +0 -40
- data/lib/davinci_crd_test_kit/client_tests/client_fhir_api_search_test.rb +0 -232
- data/lib/davinci_crd_test_kit/client_tests/client_fhir_api_update_test.rb +0 -42
- data/lib/davinci_crd_test_kit/client_tests/client_fhir_api_validation_test.rb +0 -61
- data/lib/davinci_crd_test_kit/client_tests/client_order_dispatch_group.rb +0 -79
- data/lib/davinci_crd_test_kit/client_tests/client_order_select_group.rb +0 -82
- data/lib/davinci_crd_test_kit/client_tests/client_order_sign_group.rb +0 -81
- data/lib/davinci_crd_test_kit/client_tests/client_registration_verification_test.rb +0 -88
- data/lib/davinci_crd_test_kit/client_tests/decode_auth_token_test.rb +0 -60
- data/lib/davinci_crd_test_kit/client_tests/encounter_discharge_receive_request_test.rb +0 -90
- data/lib/davinci_crd_test_kit/client_tests/encounter_start_receive_request_test.rb +0 -90
- data/lib/davinci_crd_test_kit/client_tests/hook_request_optional_fields_test.rb +0 -57
- data/lib/davinci_crd_test_kit/client_tests/hook_request_required_fields_test.rb +0 -49
- data/lib/davinci_crd_test_kit/client_tests/hook_request_valid_context_test.rb +0 -68
- data/lib/davinci_crd_test_kit/client_tests/hook_request_valid_prefetch_test.rb +0 -69
- data/lib/davinci_crd_test_kit/client_tests/order_dispatch_receive_request_test.rb +0 -102
- data/lib/davinci_crd_test_kit/client_tests/order_select_receive_request_test.rb +0 -98
- data/lib/davinci_crd_test_kit/client_tests/order_sign_receive_request_test.rb +0 -101
- data/lib/davinci_crd_test_kit/client_tests/retrieve_jwks_test.rb +0 -105
- data/lib/davinci_crd_test_kit/client_tests/submitted_response_validation.rb +0 -48
- data/lib/davinci_crd_test_kit/client_tests/token_header_test.rb +0 -65
- data/lib/davinci_crd_test_kit/client_tests/token_payload_test.rb +0 -78
- data/lib/davinci_crd_test_kit/crd_client_suite.rb +0 -193
- data/lib/davinci_crd_test_kit/crd_options.rb +0 -9
- data/lib/davinci_crd_test_kit/crd_server_suite.rb +0 -125
- data/lib/davinci_crd_test_kit/igs/davinci-crd-2.0.1.tgz +0 -0
- data/lib/davinci_crd_test_kit/routes/cds_services_discovery_handler.rb +0 -18
- data/lib/davinci_crd_test_kit/routes/hook_request_endpoint.rb +0 -77
- data/lib/davinci_crd_test_kit/routes/jwk_set_endpoint_handler.rb +0 -15
- data/lib/davinci_crd_test_kit/server_appointment_book_group.rb +0 -176
- data/lib/davinci_crd_test_kit/server_demonstrate_hook_response_group.rb +0 -77
- data/lib/davinci_crd_test_kit/server_discovery_group.rb +0 -60
- data/lib/davinci_crd_test_kit/server_encounter_discharge_group.rb +0 -170
- data/lib/davinci_crd_test_kit/server_encounter_start_group.rb +0 -170
- data/lib/davinci_crd_test_kit/server_hooks_group.rb +0 -71
- data/lib/davinci_crd_test_kit/server_order_dispatch_group.rb +0 -176
- data/lib/davinci_crd_test_kit/server_order_select_group.rb +0 -195
- data/lib/davinci_crd_test_kit/server_order_sign_group.rb +0 -201
- data/lib/davinci_crd_test_kit/server_required_card_response_validation_group.rb +0 -26
- data/lib/davinci_crd_test_kit/server_tests/additional_orders_validation_test.rb +0 -68
- data/lib/davinci_crd_test_kit/server_tests/card_optional_fields_validation_test.rb +0 -47
- data/lib/davinci_crd_test_kit/server_tests/coverage_information_system_action_across_hooks_validation_test.rb +0 -32
- data/lib/davinci_crd_test_kit/server_tests/coverage_information_system_action_received_test.rb +0 -63
- data/lib/davinci_crd_test_kit/server_tests/coverage_information_system_action_validation_test.rb +0 -118
- data/lib/davinci_crd_test_kit/server_tests/create_or_update_coverage_info_response_validation_test.rb +0 -71
- data/lib/davinci_crd_test_kit/server_tests/discovery_endpoint_test.rb +0 -88
- data/lib/davinci_crd_test_kit/server_tests/discovery_services_validation_test.rb +0 -65
- data/lib/davinci_crd_test_kit/server_tests/external_reference_card_across_hooks_validation_test.rb +0 -28
- data/lib/davinci_crd_test_kit/server_tests/external_reference_card_validation_test.rb +0 -36
- data/lib/davinci_crd_test_kit/server_tests/form_completion_response_validation_test.rb +0 -78
- data/lib/davinci_crd_test_kit/server_tests/instructions_card_received_across_hooks_test.rb +0 -25
- data/lib/davinci_crd_test_kit/server_tests/instructions_card_received_test.rb +0 -26
- data/lib/davinci_crd_test_kit/server_tests/launch_smart_app_card_validation_test.rb +0 -38
- data/lib/davinci_crd_test_kit/server_tests/propose_alternate_request_card_validation_test.rb +0 -63
- data/lib/davinci_crd_test_kit/server_tests/service_call_test.rb +0 -101
- data/lib/davinci_crd_test_kit/server_tests/service_request_context_validation_test.rb +0 -28
- data/lib/davinci_crd_test_kit/server_tests/service_request_optional_fields_validation_test.rb +0 -37
- data/lib/davinci_crd_test_kit/server_tests/service_request_required_fields_validation_test.rb +0 -38
- data/lib/davinci_crd_test_kit/server_tests/service_response_validation_test.rb +0 -81
- data/lib/davinci_crd_test_kit/tags.rb +0 -10
- data/lib/davinci_crd_test_kit/urls.rb +0 -52
- /data/lib/davinci_crd_test_kit/{card_responses → client/endpoints/mocked_card_responses}/external_reference.json +0 -0
- /data/lib/davinci_crd_test_kit/{card_responses → client/endpoints/mocked_card_responses}/instructions.json +0 -0
- /data/lib/davinci_crd_test_kit/{crd_jwks.json → server/endpoints/crd_jwks.json} +0 -0
- /data/lib/davinci_crd_test_kit/{jwks.rb → server/endpoints/jwks.rb} +0 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
require_relative 'profiles_and_resource_types'
|
|
2
|
+
|
|
3
|
+
module DaVinciCRDTestKit
|
|
4
|
+
module LogicalModelsOverrideHelper
|
|
5
|
+
# -------------------------------------------------------------------------
|
|
6
|
+
# Clean up messages returned from logical model validation
|
|
7
|
+
# -------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
def reject_resource_issues(issues)
|
|
10
|
+
issues.reject do |issue|
|
|
11
|
+
issue.location&.match?(%r{/\*[A-Za-z]+/}) || # looking for /*<resourceType>/
|
|
12
|
+
issue.message&.match(/.resource: Unable to find a match for the specified profile among choices/)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# -------------------------------------------------------------------------
|
|
17
|
+
# Check resource conformance outside the logical models
|
|
18
|
+
# -------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
def check_resource_conformance_to_coverage_profile(resource_hash, error_prefix, ig_semver)
|
|
21
|
+
check_resource_type_and_validate(resource_hash, error_prefix, ig_semver, FHIR::Coverage)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def check_resource_conformance_to_questionnaire_task_profile(resource_hash, error_prefix, ig_semver)
|
|
25
|
+
check_resource_type_and_validate(resource_hash, error_prefix, ig_semver, FHIR::Task)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def check_resource_conformance_to_order_or_encounter_profile(resource_hash, request_body, error_prefix, ig_semver)
|
|
29
|
+
check_order_like_resource_conformance(resource_hash, request_body, error_prefix, ig_semver,
|
|
30
|
+
allowed_types: ProfilesAndResourceTypes::ORDER_OR_ENCOUNTER_RESOURCE_CLASSES,
|
|
31
|
+
disallowed_message: 'is not allowed as a target ' \
|
|
32
|
+
'for a coverage-information action')
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def check_resource_conformance_to_order_profile(resource_hash, request_body, error_prefix, ig_semver)
|
|
36
|
+
check_order_like_resource_conformance(resource_hash, request_body, error_prefix, ig_semver,
|
|
37
|
+
allowed_types: ProfilesAndResourceTypes::ORDER_RESOURCE_CLASSES,
|
|
38
|
+
disallowed_message: 'is not allowed for CRD orders')
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# -------------------------------------------------------------------------
|
|
42
|
+
# Resource conformance helpers
|
|
43
|
+
# -------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
def parse_action_resource(resource_hash, error_prefix)
|
|
46
|
+
resource = FHIR.from_contents(resource_hash.to_json)
|
|
47
|
+
unless resource.present?
|
|
48
|
+
add_message('error', "#{error_prefix}resource is not FHIR.")
|
|
49
|
+
return
|
|
50
|
+
end
|
|
51
|
+
yield resource
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def check_resource_type_and_validate(resource_hash, error_prefix, ig_semver, expected_class)
|
|
55
|
+
parse_action_resource(resource_hash, error_prefix) do |resource|
|
|
56
|
+
if resource.is_a?(expected_class)
|
|
57
|
+
resource_is_valid?(resource:, profile_url: structure_definition_map(ig_semver)[resource.resourceType],
|
|
58
|
+
message_prefix: error_prefix)
|
|
59
|
+
else
|
|
60
|
+
add_message('error', "#{error_prefix}found resource type '#{resource.resourceType}' " \
|
|
61
|
+
"expected '#{expected_class.name.split('::').last}'.")
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def check_order_like_resource_conformance(resource_hash, request_body, error_prefix, ig_semver,
|
|
67
|
+
allowed_types:, disallowed_message:)
|
|
68
|
+
parse_action_resource(resource_hash, error_prefix) do |resource|
|
|
69
|
+
case resource
|
|
70
|
+
when FHIR::Appointment
|
|
71
|
+
check_appointment_conformance(resource, request_body, error_prefix, ig_semver)
|
|
72
|
+
when *allowed_types
|
|
73
|
+
resource_is_valid?(resource:, profile_url: structure_definition_map(ig_semver)[resource.resourceType],
|
|
74
|
+
message_prefix: error_prefix)
|
|
75
|
+
else
|
|
76
|
+
add_message('error', "#{error_prefix}resource type '#{resource.resourceType}' #{disallowed_message}.")
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# -------------------------------------------------------------------------
|
|
82
|
+
# Appointment conformance (requires extra help to decide profile and check profile-based slicing)
|
|
83
|
+
# -------------------------------------------------------------------------
|
|
84
|
+
|
|
85
|
+
def check_appointment_conformance(appointment, request_body, error_prefix, ig_semver)
|
|
86
|
+
target_appointment_profile =
|
|
87
|
+
if appointment.basedOn.present?
|
|
88
|
+
'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-appointment-with-order'
|
|
89
|
+
else
|
|
90
|
+
'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-appointment-no-order'
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
validation_issues = []
|
|
94
|
+
resource_is_valid?(resource: appointment, profile_url: "#{target_appointment_profile}|#{ig_semver}",
|
|
95
|
+
add_messages_to_runnable: false, validator_response_details: validation_issues)
|
|
96
|
+
|
|
97
|
+
manually_check_appointment_validation_errors(validation_issues, appointment, request_body)
|
|
98
|
+
.each do |issue|
|
|
99
|
+
add_message(issue.severity, "#{error_prefix}#{issue.message}")
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def manually_check_appointment_validation_errors(validation_issues, appointment, request_body)
|
|
104
|
+
@matched_participant_slice_indexes = []
|
|
105
|
+
validation_issues.reverse.reject do |issue|
|
|
106
|
+
issue.filtered ||
|
|
107
|
+
resolved_participant_primary_performer_slice_issue?(issue, appointment) ||
|
|
108
|
+
resolved_participant_patient_slice_issue?(issue, appointment, request_body) ||
|
|
109
|
+
(
|
|
110
|
+
# list reversed to hit these issues after the slice matching
|
|
111
|
+
@matched_participant_slice_indexes.present? &&
|
|
112
|
+
issue.message.match(/Appointment\.participant\[#{Regexp.union(@matched_participant_slice_indexes.map(&:to_s))}\]: This element does not match any known slice defined in the profile/) # rubocop:disable Layout/LineLength
|
|
113
|
+
)
|
|
114
|
+
end.reverse
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def resolved_participant_primary_performer_slice_issue?(issue, appointment)
|
|
118
|
+
return false unless issue.message.match(/Slice 'Appointment.participant:PrimaryPerformer': a matching slice is required, but not found/) # rubocop:disable Layout/LineLength
|
|
119
|
+
|
|
120
|
+
appointment.participant.each_with_index.any? do |participant, index|
|
|
121
|
+
# type + profile of the reference checked during prefetch checking
|
|
122
|
+
match = participant.actor.present? &&
|
|
123
|
+
participant.actor.reference.present? &&
|
|
124
|
+
primary_performer_type?(participant.type)
|
|
125
|
+
@matched_participant_slice_indexes << index if match
|
|
126
|
+
match
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def primary_performer_type?(types)
|
|
131
|
+
types&.any? do |type|
|
|
132
|
+
type.coding&.any? do |coding|
|
|
133
|
+
coding.code == 'PPRF' &&
|
|
134
|
+
coding.system == 'http://terminology.hl7.org/CodeSystem/v3-ParticipationType'
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# NOTE: for simplicity and to avoid duplication of checks, this looks for
|
|
140
|
+
# a particular patient reference from the context.patientId,
|
|
141
|
+
# the profile of which will be verified during prefetch profile checking
|
|
142
|
+
def resolved_participant_patient_slice_issue?(issue, appointment, request_body)
|
|
143
|
+
return false unless issue.message.match(/Slice 'Appointment.participant:Patient': a matching slice is required, but not found/) # rubocop:disable Layout/LineLength
|
|
144
|
+
|
|
145
|
+
local_patient_ref = "Patient/#{request_body.dig('context', 'patientId')}"
|
|
146
|
+
absolute_patient_ref = "#{request_body['fhirServer'].chomp('/')}/#{local_patient_ref}"
|
|
147
|
+
appointment.participant.each_with_index.any? do |participant, index|
|
|
148
|
+
match = [local_patient_ref, absolute_patient_ref].include?(participant.actor&.reference)
|
|
149
|
+
@matched_participant_slice_indexes << index if match
|
|
150
|
+
match
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# -------------------------------------------------------------------------
|
|
155
|
+
# Local/Relative reference helpers
|
|
156
|
+
# -------------------------------------------------------------------------
|
|
157
|
+
|
|
158
|
+
def local_reference?(value, error_prefix, allowed_resource_types: nil)
|
|
159
|
+
is_local_reference = true
|
|
160
|
+
local_reference_match = value.match(%r{^([A-Za-z]+)/(.+)$})
|
|
161
|
+
if local_reference_match.present?
|
|
162
|
+
resource_type = local_reference_match[1]
|
|
163
|
+
id = local_reference_match[2]
|
|
164
|
+
|
|
165
|
+
unless allowed_resource_type?(resource_type, allowed_resource_types)
|
|
166
|
+
allowed_types_error_suffix =
|
|
167
|
+
if allowed_resource_types.nil?
|
|
168
|
+
'a valid FHIR resource type.'
|
|
169
|
+
else
|
|
170
|
+
"one of the allowed resource types (#{allowed_resource_types.join(', ')})"
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
add_message('error',
|
|
174
|
+
"#{error_prefix} local reference resourceType '#{resource_type}' " \
|
|
175
|
+
"is not #{allowed_types_error_suffix}.")
|
|
176
|
+
is_local_reference = false
|
|
177
|
+
end
|
|
178
|
+
unless id.match(/\A[A-Za-z0-9\-.]{1,64}\z/)
|
|
179
|
+
add_message('error',
|
|
180
|
+
"#{error_prefix} local reference id '#{id}' does not meet " \
|
|
181
|
+
'[FHIR id data type](https://hl7.org/fhir/R4/datatypes.html#id) requirements.')
|
|
182
|
+
is_local_reference = false
|
|
183
|
+
end
|
|
184
|
+
else
|
|
185
|
+
add_message('error', "#{error_prefix} expected a local reference, got '#{value}'.")
|
|
186
|
+
is_local_reference = false
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
is_local_reference
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def allowed_resource_type?(resource_type, allowed_resource_types)
|
|
193
|
+
if allowed_resource_types.nil?
|
|
194
|
+
FHIR::RESOURCES.include?(resource_type)
|
|
195
|
+
else
|
|
196
|
+
allowed_resource_types.include?(resource_type)
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def referenced_resource_present_in_bundle?(bundle, local_reference, error_prefix, bundle_location)
|
|
201
|
+
unless bundle.present? && bundle['entry'].present?
|
|
202
|
+
add_message('error',
|
|
203
|
+
"#{error_prefix} referenced resource '#{local_reference}' " \
|
|
204
|
+
"not found in the #{bundle_location} Bundle.")
|
|
205
|
+
return false
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
target_resource_type, target_id = local_reference.split('/')
|
|
209
|
+
found = bundle['entry'].any? do |entry|
|
|
210
|
+
entry.dig('resource', 'resourceType') == target_resource_type && entry.dig('resource', 'id') == target_id
|
|
211
|
+
end
|
|
212
|
+
return true if found
|
|
213
|
+
|
|
214
|
+
add_message('error',
|
|
215
|
+
"#{error_prefix} referenced resource '#{local_reference}' " \
|
|
216
|
+
"not found in the #{bundle_location} Bundle.")
|
|
217
|
+
false
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
require_relative 'fhirpath_on_cds_request'
|
|
2
|
+
require_relative 'replace_tokens'
|
|
3
|
+
require 'uri'
|
|
4
|
+
|
|
5
|
+
module DaVinciCRDTestKit
|
|
6
|
+
# -----------------------------------------------------------------------
|
|
7
|
+
# Prefetch Check Helper Class
|
|
8
|
+
# -----------------------------------------------------------------------
|
|
9
|
+
class PrefetchCompletenessChecker
|
|
10
|
+
include FhirpathOnCDSRequest
|
|
11
|
+
include ReplaceTokens
|
|
12
|
+
|
|
13
|
+
attr_accessor :hook_request, :request_index, :services_file_path,
|
|
14
|
+
:instantiated_prefetch_templates,
|
|
15
|
+
:observed_fhirpath_collection_as_comma_delimited_string
|
|
16
|
+
|
|
17
|
+
def initialize(hook_request, request_index, services_file_path)
|
|
18
|
+
@hook_request = hook_request
|
|
19
|
+
@request_index = request_index
|
|
20
|
+
@services_file_path = services_file_path
|
|
21
|
+
@observed_fhirpath_collection_as_comma_delimited_string = false
|
|
22
|
+
@instantiated_prefetch_templates = {}
|
|
23
|
+
extract_prefetched_resources
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def check_prefetched_data
|
|
27
|
+
return ["#{request_error_prefix} No prefetch data provided."] unless hook_request.key?('prefetch')
|
|
28
|
+
|
|
29
|
+
hook_prefetch_templates.each do |prefetch_key, prefetch_request|
|
|
30
|
+
check_prefetch_template(prefetch_key, prefetch_request)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
hook_request['prefetch'].each_key do |prefetch_template|
|
|
34
|
+
next if hook_prefetch_templates.key?(prefetch_template)
|
|
35
|
+
|
|
36
|
+
errors << "#{request_error_prefix} Extra prefetch data " \
|
|
37
|
+
"provided in unrequested template '#{prefetch_template}'."
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
errors.uniq
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# -----------------------------------------------------------------------
|
|
44
|
+
# Complete vs Subset Prefetch Difference Checking
|
|
45
|
+
# -----------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
def data_set_different_with_alternate_service?(alternate_services_file_path, compare_key_map)
|
|
48
|
+
PrefetchCompletenessChecker.new(hook_request, request_index, alternate_services_file_path)
|
|
49
|
+
.data_sets_different?(self, compare_key_map)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Precondition: the original checker passed without errors meaning the expected set was present
|
|
53
|
+
# handles both subset < complete and complete > subset checks via two checks.
|
|
54
|
+
# - differences in the calculated id set for `_id` searches signal differences in the data sets.
|
|
55
|
+
# - errors during instantiation signal that a resource in the set wasn't present. Since the original
|
|
56
|
+
# set was complete, this will only happen if the original set was a subset and the complete set
|
|
57
|
+
# includes more data, meaning that the sets are different. (NOTE: triggers an error rather than a different
|
|
58
|
+
# set of ids because fhirpath like `...resolve().id` requires the resource be present to evaluate it)
|
|
59
|
+
def data_sets_different?(original_service_checker, compare_key_map)
|
|
60
|
+
template_key_map = compare_key_map.transform_keys { |key| "%#{key}." }.transform_values { |value| "%#{value}." }
|
|
61
|
+
hook_prefetch_templates.each do |prefetch_key, prefetch_request|
|
|
62
|
+
data_set_different = data_set_different?(original_service_checker,
|
|
63
|
+
compare_key_map,
|
|
64
|
+
template_key_map,
|
|
65
|
+
prefetch_key,
|
|
66
|
+
prefetch_request)
|
|
67
|
+
|
|
68
|
+
return true if data_set_different
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
false
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def data_set_different?(original_service_checker, compare_key_map, template_key_map, prefetch_key, prefetch_request)
|
|
77
|
+
return false if ['patient', 'pat', 'encounter', 'enc', 'coverage', 'cov'].include?(prefetch_key)
|
|
78
|
+
|
|
79
|
+
mapped_prefetch_request = prefetch_request.gsub(/%[a-zA-Z]+\./, template_key_map)
|
|
80
|
+
instantiated_request = instantiate_template(prefetch_key, mapped_prefetch_request)
|
|
81
|
+
return true if errors.present? # fetch errors - there was more data to include for complete (complete > subset)
|
|
82
|
+
|
|
83
|
+
compare_prefetch_key = compare_key_map.key?(prefetch_key) ? compare_key_map[prefetch_key] : prefetch_key
|
|
84
|
+
original_instantiated_request = original_service_checker.instantiated_prefetch_templates[compare_prefetch_key]
|
|
85
|
+
# don't try to compare when the original didn't have this key
|
|
86
|
+
return false unless original_instantiated_request.present?
|
|
87
|
+
|
|
88
|
+
_, alternate_ids = resource_type_and_ids_from_search(instantiated_request)
|
|
89
|
+
_, original_ids = resource_type_and_ids_from_search(original_instantiated_request)
|
|
90
|
+
|
|
91
|
+
# missing ids - the subset requested strictly less data (subset < complete)
|
|
92
|
+
alternate_ids != original_ids
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# -----------------------------------------------------------------------
|
|
96
|
+
# Errors to return
|
|
97
|
+
# -----------------------------------------------------------------------
|
|
98
|
+
def errors
|
|
99
|
+
@errors ||= []
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def request_error_prefix
|
|
103
|
+
"(Request #{request_index + 1})"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def error_prefix
|
|
107
|
+
"#{request_error_prefix} Prefetch Template #{@current_prefetch_key} -"
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# -----------------------------------------------------------------------
|
|
111
|
+
# Requested Prefetch Templates
|
|
112
|
+
# -----------------------------------------------------------------------
|
|
113
|
+
def hook_prefetch_templates
|
|
114
|
+
@hook_prefetch_templates ||=
|
|
115
|
+
JSON.parse(File.read(services_file_path))['services'].find do |service|
|
|
116
|
+
service['hook'] == hook_request['hook']
|
|
117
|
+
end['prefetch']
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# -----------------------------------------------------------------------
|
|
121
|
+
# Instantiated Prefetch Templates
|
|
122
|
+
# -----------------------------------------------------------------------
|
|
123
|
+
def instantiate_template(prefetch_key, prefetch_request)
|
|
124
|
+
instantiated_request = replace_tokens_in_string(prefetch_request.dup, hook_request)
|
|
125
|
+
instantiated_prefetch_templates[prefetch_key] = instantiated_request
|
|
126
|
+
instantiated_request
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# -----------------------------------------------------------------------
|
|
130
|
+
# Check of actual prefetch against an instantiated request
|
|
131
|
+
# -----------------------------------------------------------------------
|
|
132
|
+
def check_prefetch_template(prefetch_key, prefetch_request)
|
|
133
|
+
@current_prefetch_key = prefetch_key
|
|
134
|
+
instantiated_request = instantiate_template(prefetch_key, prefetch_request)
|
|
135
|
+
if demonstrates_collection_as_comma_delimited_string?(prefetch_request, instantiated_request)
|
|
136
|
+
@observed_fhirpath_collection_as_comma_delimited_string = true
|
|
137
|
+
end
|
|
138
|
+
unless hook_request['prefetch'].key?(prefetch_key)
|
|
139
|
+
errors << "#{error_prefix} No prefetch data provided."
|
|
140
|
+
return
|
|
141
|
+
end
|
|
142
|
+
check_provided_against_request(hook_request['prefetch'][prefetch_key], instantiated_request)
|
|
143
|
+
rescue FhirpathServiceError => e
|
|
144
|
+
raise "#{error_prefix} FHIRPath service error while evaluating prefetch template. " \
|
|
145
|
+
"This indicates an implementation problem in Inferno - please log a ticket. Details: #{e.message}"
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def check_provided_against_request(prefetched_value, instantiated_request)
|
|
149
|
+
if instantiated_request.include?('?')
|
|
150
|
+
if id_search?(instantiated_request)
|
|
151
|
+
check_id_search(prefetched_value, instantiated_request)
|
|
152
|
+
elsif instantiated_request.starts_with?('Coverage')
|
|
153
|
+
check_coverage_search(prefetched_value, instantiated_request)
|
|
154
|
+
else
|
|
155
|
+
raise "#{error_prefix} Unexpected search template '#{instantiated_request}'. " \
|
|
156
|
+
'This indicates an implementation problem in the test kit — please log a ticket.'
|
|
157
|
+
end
|
|
158
|
+
else
|
|
159
|
+
check_read(prefetched_value, instantiated_request)
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def id_search?(request_string)
|
|
164
|
+
resource_type = request_string.split('?').first
|
|
165
|
+
request_string.starts_with?("#{resource_type}?_id=")
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def check_coverage_search(prefetched_value, _instantiated_request)
|
|
169
|
+
unless prefetched_value.present?
|
|
170
|
+
errors << "#{error_prefix} requested Coverage not provided."
|
|
171
|
+
return
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
check_is_fhir_resource(prefetched_value, target_resource_type: 'Bundle')
|
|
175
|
+
unless prefetched_value['entry'].size == 1
|
|
176
|
+
errors << "#{error_prefix} exactly one Coverage must be provided."
|
|
177
|
+
return
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
check_coverage(prefetched_value.dig('entry', 0, 'resource'))
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def check_coverage(prefetched_coverage)
|
|
184
|
+
unless prefetched_coverage['resourceType'].present?
|
|
185
|
+
errors << "#{error_prefix} entry in prefetched Coverage Bundle is not a FHIR resource (no resourceType)."
|
|
186
|
+
return
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
unless prefetched_coverage['resourceType'] == 'Coverage'
|
|
190
|
+
errors << "#{error_prefix} entry in prefetched Coverage Bundle has an unexpected type: " \
|
|
191
|
+
"expected Coverage, got #{prefetched_coverage['resourceType']}."
|
|
192
|
+
return
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
unless prefetched_coverage['status'] == 'active'
|
|
196
|
+
errors << "#{error_prefix} prefetched Coverage has an unexpected status: " \
|
|
197
|
+
"expected active, got #{prefetched_coverage['status']}."
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
target_patient_id = hook_request.dig('context', 'patientId')
|
|
201
|
+
unless prefetched_coverage.dig('beneficiary', 'reference') == "Patient/#{target_patient_id}"
|
|
202
|
+
errors << "#{error_prefix} prefetched Coverage has an unexpected beneficiary reference: " \
|
|
203
|
+
"expected Patient/#{target_patient_id}, got #{prefetched_coverage.dig('beneficiary',
|
|
204
|
+
'reference')}."
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
nil
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def check_read(prefetched_value, instantiated_request)
|
|
211
|
+
resource_type, resource_id = instantiated_request.split('/')
|
|
212
|
+
|
|
213
|
+
resource_requested = resource_id.present?
|
|
214
|
+
|
|
215
|
+
unless prefetched_value.present?
|
|
216
|
+
if resource_requested
|
|
217
|
+
errors << "#{error_prefix} requested resource '#{full_url_for_target_id(instantiated_request)}' not provided."
|
|
218
|
+
end
|
|
219
|
+
return
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
check_is_fhir_resource(prefetched_value, target_resource_type: resource_type)
|
|
223
|
+
unless prefetched_value.key?('id')
|
|
224
|
+
errors << "#{error_prefix} prefetched #{resource_type} is missing an id."
|
|
225
|
+
return
|
|
226
|
+
end
|
|
227
|
+
unless prefetched_value['id'] == resource_id
|
|
228
|
+
errors << "#{error_prefix} prefetched #{resource_type} has unexpected id: " \
|
|
229
|
+
"expected #{resource_id}, got #{prefetched_value['id']}."
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
nil
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def resource_type_and_ids_from_search(instantiated_search)
|
|
236
|
+
resource_type, id_list = instantiated_search.split('?_id=')
|
|
237
|
+
target_ids = id_list.present? ? id_list.split(',').map { |id| "#{resource_type}/#{id}" }.uniq.sort : []
|
|
238
|
+
|
|
239
|
+
[resource_type, target_ids]
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def check_id_search(prefetched_value, instantiated_request)
|
|
243
|
+
resource_type, target_ids = resource_type_and_ids_from_search(instantiated_request)
|
|
244
|
+
resources_requested = target_ids.present?
|
|
245
|
+
|
|
246
|
+
unless prefetched_value.present?
|
|
247
|
+
if resources_requested
|
|
248
|
+
errors << "#{error_prefix} requested resources not provided: " \
|
|
249
|
+
"#{target_ids.map { |id| full_url_for_target_id(id) }.join(', ')}."
|
|
250
|
+
end
|
|
251
|
+
return
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
check_is_fhir_resource(prefetched_value, target_resource_type: 'Bundle')
|
|
255
|
+
check_bundle_entry_resource_type(prefetched_value, resource_type)
|
|
256
|
+
check_ids(target_ids, actual_ids(prefetched_value))
|
|
257
|
+
nil
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def actual_ids(prefetched_value)
|
|
261
|
+
return [] unless prefetched_value['entry'].present?
|
|
262
|
+
|
|
263
|
+
prefetched_value['entry'].map do |entry|
|
|
264
|
+
type = entry.dig('resource', 'resourceType')
|
|
265
|
+
id = entry.dig('resource', 'id')
|
|
266
|
+
next unless type.present? && id.present?
|
|
267
|
+
|
|
268
|
+
type_and_id = "#{type}/#{id}"
|
|
269
|
+
[type_and_id, entry['fullUrl'].presence || type_and_id]
|
|
270
|
+
end.compact
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def check_ids(target_ids, actual_id_pairs)
|
|
274
|
+
actual_type_ids = actual_id_pairs.map(&:first)
|
|
275
|
+
unless actual_type_ids.size == actual_type_ids.uniq.size
|
|
276
|
+
errors << "#{error_prefix} prefetched Bundle has multiple entries with the same resource id."
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
actual_ids_map = actual_id_pairs.to_h
|
|
280
|
+
|
|
281
|
+
missing_ids = target_ids - actual_type_ids
|
|
282
|
+
if missing_ids.present?
|
|
283
|
+
errors << "#{error_prefix} prefetched Bundle missing expected entries: " \
|
|
284
|
+
"#{missing_ids.map { |id| full_url_for_target_id(id) }.join('\', \'')}."
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
extra_ids = actual_type_ids - target_ids
|
|
288
|
+
return unless extra_ids.present?
|
|
289
|
+
|
|
290
|
+
errors << "#{error_prefix} prefetched Bundle includes unrequested entries: " \
|
|
291
|
+
"#{extra_ids.map { |id| actual_ids_map[id] }.join('\', \'')}."
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# NOTE: would possibly fail in the case of duplicate <resource>/<id> with different
|
|
295
|
+
# base urls but this will cause other problems since in the _id search form those
|
|
296
|
+
# would be the same. So not worth trying to work around it.
|
|
297
|
+
def full_url_for_target_id(type_and_id)
|
|
298
|
+
prefetched_resources.keys.find { |url| url.end_with?("/#{type_and_id}") } ||
|
|
299
|
+
fhir_server_url_for(type_and_id)
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def fhir_server_url_for(type_and_id)
|
|
303
|
+
return type_and_id unless hook_request['fhirServer'].present?
|
|
304
|
+
|
|
305
|
+
"#{hook_request['fhirServer'].chomp('/')}/#{type_and_id}"
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def check_bundle_entry_resource_type(bundle, target_resource_type)
|
|
309
|
+
bundle['entry']&.each_with_index do |entry, index|
|
|
310
|
+
entry_resource_type = entry.dig('resource', 'resourceType')
|
|
311
|
+
next if entry_resource_type == target_resource_type
|
|
312
|
+
|
|
313
|
+
errors << if entry_resource_type.present?
|
|
314
|
+
"#{error_prefix} prefetched Bundle entry #{index + 1} has an unexpected resourceType: " \
|
|
315
|
+
"expected #{target_resource_type}, got #{entry_resource_type}."
|
|
316
|
+
else
|
|
317
|
+
"#{error_prefix} prefetched Bundle entry #{index + 1} is not a FHIR resource (no resourceType)."
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def check_is_fhir_resource(prefetched_value, target_resource_type: nil)
|
|
323
|
+
unless prefetched_value.key?('resourceType')
|
|
324
|
+
errors << "#{error_prefix} prefetched value is not a FHIR resource (no resourceType)."
|
|
325
|
+
return
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
return if target_resource_type.blank? || prefetched_value['resourceType'] == target_resource_type
|
|
329
|
+
|
|
330
|
+
errors << "#{error_prefix} prefetched value has unexpected resourceType: " \
|
|
331
|
+
"expected #{target_resource_type}, got #{prefetched_value['resourceType']}."
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# -----------------------------------------------------------------------
|
|
335
|
+
# Map of prefetched resources by fullUrl
|
|
336
|
+
# -----------------------------------------------------------------------
|
|
337
|
+
def prefetched_resources
|
|
338
|
+
@prefetched_resources ||= {}
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def extract_prefetched_resources
|
|
342
|
+
hook_request['prefetch']&.each_value do |prefetch_resource|
|
|
343
|
+
next unless prefetch_resource&.dig('resourceType').present?
|
|
344
|
+
|
|
345
|
+
if prefetch_resource['resourceType'] == 'Bundle'
|
|
346
|
+
extract_resources_from_prefetched_bundle(prefetch_resource)
|
|
347
|
+
else
|
|
348
|
+
extract_prefetched_resource_instance(prefetch_resource)
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def extract_resources_from_prefetched_bundle(bundle)
|
|
354
|
+
bundle['entry']&.each do |entry|
|
|
355
|
+
next unless entry['resource'].present?
|
|
356
|
+
|
|
357
|
+
if entry['fullUrl'].present?
|
|
358
|
+
prefetched_resources[entry['fullUrl']] = entry['resource'] unless prefetched_resources.key?(entry['fullUrl'])
|
|
359
|
+
else
|
|
360
|
+
extract_prefetched_resource_instance(entry['resource'])
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
# no fullUrl available, so assume that it is a restful FHIR url
|
|
366
|
+
# relative to the fhirServer of the hook request
|
|
367
|
+
def extract_prefetched_resource_instance(resource_instance)
|
|
368
|
+
return unless resource_instance['id'].present? &&
|
|
369
|
+
resource_instance['resourceType'].present? &&
|
|
370
|
+
hook_request['fhirServer'].present?
|
|
371
|
+
|
|
372
|
+
fhir_server =
|
|
373
|
+
if hook_request['fhirServer'].ends_with?('/')
|
|
374
|
+
hook_request['fhirServer']
|
|
375
|
+
else
|
|
376
|
+
"#{hook_request['fhirServer']}/"
|
|
377
|
+
end
|
|
378
|
+
key = "#{fhir_server}#{resource_instance['resourceType']}/#{resource_instance['id']}"
|
|
379
|
+
return if prefetched_resources.key?(key)
|
|
380
|
+
|
|
381
|
+
prefetched_resources[key] = resource_instance
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
# -------------------------------------------------------------------------
|
|
385
|
+
# fhirpath resolve() handling
|
|
386
|
+
# -------------------------------------------------------------------------
|
|
387
|
+
|
|
388
|
+
def resolve(reference)
|
|
389
|
+
return nil unless reference.present?
|
|
390
|
+
|
|
391
|
+
key = absolute_reference(reference)
|
|
392
|
+
return nil unless key.present?
|
|
393
|
+
|
|
394
|
+
unless prefetched_resources[key].present?
|
|
395
|
+
errors << "#{error_prefix} resource '#{key}' needed to instantiate the query " \
|
|
396
|
+
'was not provided in the prefetched values.'
|
|
397
|
+
|
|
398
|
+
return nil
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
@current_base_fhir_server = base_fhir_server_for_identity(key)
|
|
402
|
+
prefetched_resources[key]
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
def absolute_reference(reference)
|
|
406
|
+
reference = reference['reference'] if reference.is_a?(Hash)
|
|
407
|
+
return nil unless reference.present?
|
|
408
|
+
|
|
409
|
+
if URI.parse(reference).absolute?
|
|
410
|
+
reference
|
|
411
|
+
else
|
|
412
|
+
relative_to_absolute_reference(reference)
|
|
413
|
+
end
|
|
414
|
+
rescue URI::InvalidURIError => e
|
|
415
|
+
errors << "#{error_prefix} '#{reference}' needed to instantiate the query " \
|
|
416
|
+
"is invalid: #{e.message}."
|
|
417
|
+
nil
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
def relative_to_absolute_reference(relative_reference)
|
|
421
|
+
return nil unless relative_reference_valid?(relative_reference)
|
|
422
|
+
|
|
423
|
+
if @current_base_fhir_server.nil?
|
|
424
|
+
errors << "#{error_prefix} '#{relative_reference}' needed to instantiate the query " \
|
|
425
|
+
'is a relative reference, but the base FHIR Server is not known.'
|
|
426
|
+
return nil
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
"#{@current_base_fhir_server}/#{relative_reference}"
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
def relative_reference_valid?(relative_reference)
|
|
433
|
+
if relative_reference.split('/').length > 2
|
|
434
|
+
errors << "#{error_prefix} '#{relative_reference}' needed to instantiate the query " \
|
|
435
|
+
'is not an absolute reference but has too many segments to be a relative reference.'
|
|
436
|
+
return false
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
resource_type, id = relative_reference.split('/')
|
|
440
|
+
if resource_type.blank? || id.blank?
|
|
441
|
+
errors << "#{error_prefix} '#{relative_reference}' needed to instantiate the query " \
|
|
442
|
+
'is not a valid relative reference of the form <resource type>/<id>.'
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
true
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
# -------------------------------------------------------------------------
|
|
449
|
+
# Observe a Collection represented as a comma-delimited string in instantiated search
|
|
450
|
+
# -------------------------------------------------------------------------
|
|
451
|
+
|
|
452
|
+
# client will have demonstrated turning a collection into a comma-delimited string if both
|
|
453
|
+
# - the prefetch request follows the RESOURCE?_id={{TOKEN}} form when TOKEN has a | indicating 'and', and
|
|
454
|
+
# - the instantiated query actually has multiple unique ids
|
|
455
|
+
def demonstrates_collection_as_comma_delimited_string?(prefetch_request, instantiated_request)
|
|
456
|
+
token = prefetch_request.match(/[a-zA-Z]*\?_id=\{\{(.+?)\}\}/)&.[](1)
|
|
457
|
+
return false unless token.present? && token.include?('|')
|
|
458
|
+
|
|
459
|
+
instantiated_request.split('?_id=').last.split(',').uniq.size > 1
|
|
460
|
+
end
|
|
461
|
+
end
|
|
462
|
+
end
|