davinci_pas_test_kit 0.9.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 +7 -0
- data/LICENSE +201 -0
- data/lib/davinci_pas_test_kit/client_suite.rb +289 -0
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/claim_status/pas_claim_status_test.rb +109 -0
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_approval_submit_response_attest.rb +39 -0
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_approval_submit_test.rb +38 -0
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_denial_submit_response_attest.rb +38 -0
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_denial_submit_test.rb +43 -0
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_inquire_must_support_test.rb +51 -0
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_pended_inquire_response_attest.rb +39 -0
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_pended_inquire_test.rb +35 -0
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_pended_submit_response_attest.rb +39 -0
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_pended_submit_test.rb +43 -0
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_submit_must_support_test.rb +57 -0
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_token_request_test.rb +31 -0
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_token_validation_test.rb +18 -0
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/error_tests/nonconformant_pas_bundle.json +16 -0
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/error_tests/pas_inquiry_error_test.rb +38 -0
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/error_tests/pas_submission_error_test.rb +56 -0
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/must_support/device_request_metadata.yml +112 -0
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/must_support/medication_request_metadata.yml +183 -0
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/must_support/nutrition_order_metadata.yml +109 -0
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/must_support/pas_client_must_support_requirement_test.rb +117 -0
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/must_support/pas_server_must_support_requirement_test.rb +116 -0
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/must_support/service_request_metadata.yml +148 -0
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_approval_group.rb +26 -0
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_authentication_group.rb +49 -0
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_denial_group.rb +41 -0
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_pended_group.rb +56 -0
- data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_error_group.rb +20 -0
- data/lib/davinci_pas_test_kit/ext/inferno_core/record_response_route.rb +98 -0
- data/lib/davinci_pas_test_kit/ext/inferno_core/request.rb +19 -0
- data/lib/davinci_pas_test_kit/ext/inferno_core/runnable.rb +18 -0
- data/lib/davinci_pas_test_kit/fhir_resource_navigation.rb +72 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/beneficiary/client_inquiry_request_beneficiary_must_support_test.rb +75 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/beneficiary/client_submit_request_beneficiary_must_support_test.rb +75 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/beneficiary/metadata.yml +162 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/beneficiary/server_inquiry_request_beneficiary_must_support_test.rb +75 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/beneficiary/server_inquiry_response_beneficiary_must_support_test.rb +75 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/beneficiary/server_submit_request_beneficiary_must_support_test.rb +75 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/beneficiary/server_submit_response_beneficiary_must_support_test.rb +75 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/claim/claim_operation_test.rb +67 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/claim/metadata.yml +577 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/claim_inquiry/claim_inquiry_operation_test.rb +57 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/claim_inquiry/client_inquiry_request_claim_inquiry_must_support_test.rb +95 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/claim_inquiry/metadata.yml +516 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/claim_inquiry/server_inquiry_request_claim_inquiry_must_support_test.rb +95 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/claim_update/client_submit_request_claim_update_must_support_test.rb +102 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/claim_update/metadata.yml +591 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/claim_update/server_submit_request_claim_update_must_support_test.rb +102 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/claiminquiryresponse/metadata.yml +311 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/claiminquiryresponse/server_inquiry_response_claiminquiryresponse_must_support_test.rb +73 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/claimresponse/metadata.yml +318 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/claimresponse/server_submit_response_claimresponse_must_support_test.rb +75 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/client_tests/client_denial_pas_response_bundle_validation_test.rb +53 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/client_tests/client_pas_request_bundle_validation_test.rb +55 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/client_tests/client_pended_pas_inquiry_request_bundle_validation_test.rb +55 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/client_tests/client_pended_pas_response_bundle_validation_test.rb +53 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/communication_request/metadata.yml +130 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/communication_request/server_submit_response_communication_request_must_support_test.rb +58 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/coverage/client_inquiry_request_coverage_must_support_test.rb +55 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/coverage/client_submit_request_coverage_must_support_test.rb +55 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/coverage/metadata.yml +111 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/coverage/server_inquiry_request_coverage_must_support_test.rb +55 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/coverage/server_submit_request_coverage_must_support_test.rb +55 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/device_request/client_submit_request_device_request_must_support_test.rb +51 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/device_request/metadata.yml +112 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/device_request/server_submit_request_device_request_must_support_test.rb +51 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/encounter/client_submit_request_encounter_must_support_test.rb +67 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/encounter/metadata.yml +213 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/encounter/server_submit_request_encounter_must_support_test.rb +67 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/insurer/client_inquiry_request_insurer_must_support_test.rb +60 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/insurer/client_submit_request_insurer_must_support_test.rb +60 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/insurer/metadata.yml +104 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/insurer/server_inquiry_request_insurer_must_support_test.rb +60 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/insurer/server_inquiry_response_insurer_must_support_test.rb +60 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/insurer/server_submit_request_insurer_must_support_test.rb +60 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/insurer/server_submit_response_insurer_must_support_test.rb +60 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/medication_request/client_submit_request_medication_request_must_support_test.rb +61 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/medication_request/metadata.yml +183 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/medication_request/server_submit_request_medication_request_must_support_test.rb +61 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/metadata.yml +5253 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/nutrition_order/client_submit_request_nutrition_order_must_support_test.rb +53 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/nutrition_order/metadata.yml +109 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/nutrition_order/server_submit_request_nutrition_order_must_support_test.rb +53 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_client_inquiry_must_support_use_case_group.rb +51 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_client_submit_must_support_use_case_group.rb +61 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_inquiry_request_bundle/client_inquiry_request_pas_inquiry_request_bundle_must_support_test.rb +53 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_inquiry_request_bundle/metadata.yml +77 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_inquiry_request_bundle/server_inquiry_request_pas_inquiry_request_bundle_must_support_test.rb +53 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_inquiry_request_bundle/server_pas_inquiry_request_bundle_validation_test.rb +83 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_inquiry_response_bundle/metadata.yml +67 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_inquiry_response_bundle/server_inquiry_response_pas_inquiry_response_bundle_must_support_test.rb +52 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_inquiry_response_bundle/server_pas_inquiry_response_bundle_validation_test.rb +80 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_request_bundle/client_submit_request_pas_request_bundle_must_support_test.rb +53 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_request_bundle/metadata.yml +77 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_request_bundle/server_pas_request_bundle_validation_test.rb +83 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_request_bundle/server_submit_request_pas_request_bundle_must_support_test.rb +53 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_response_bundle/metadata.yml +71 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_response_bundle/server_pas_response_bundle_validation_test.rb +80 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_response_bundle/server_submit_response_pas_response_bundle_must_support_test.rb +52 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_server_approval_use_case_group.rb +59 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_server_denial_use_case_group.rb +59 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_server_must_support_use_case_group.rb +265 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_server_pended_use_case_group.rb +84 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/practitioner/client_inquiry_request_practitioner_must_support_test.rb +53 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/practitioner/client_submit_request_practitioner_must_support_test.rb +53 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/practitioner/metadata.yml +74 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/practitioner/server_inquiry_request_practitioner_must_support_test.rb +53 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/practitioner/server_inquiry_response_practitioner_must_support_test.rb +53 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/practitioner/server_submit_request_practitioner_must_support_test.rb +53 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/practitioner/server_submit_response_practitioner_must_support_test.rb +53 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/practitioner_role/client_inquiry_request_practitioner_role_must_support_test.rb +49 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/practitioner_role/client_submit_request_practitioner_role_must_support_test.rb +49 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/practitioner_role/metadata.yml +81 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/practitioner_role/server_inquiry_request_practitioner_role_must_support_test.rb +49 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/practitioner_role/server_inquiry_response_practitioner_role_must_support_test.rb +49 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/practitioner_role/server_submit_request_practitioner_role_must_support_test.rb +49 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/practitioner_role/server_submit_response_practitioner_role_must_support_test.rb +49 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/requestor/client_inquiry_request_requestor_must_support_test.rb +63 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/requestor/client_submit_request_requestor_must_support_test.rb +63 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/requestor/metadata.yml +107 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/requestor/server_inquiry_request_requestor_must_support_test.rb +63 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/requestor/server_inquiry_response_requestor_must_support_test.rb +63 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/requestor/server_submit_request_requestor_must_support_test.rb +63 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/requestor/server_submit_response_requestor_must_support_test.rb +63 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/resource_list.rb +54 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/server_suite.rb +236 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/service_request/client_submit_request_service_request_must_support_test.rb +53 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/service_request/metadata.yml +148 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/service_request/server_submit_request_service_request_must_support_test.rb +53 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/subscriber/client_inquiry_request_subscriber_must_support_test.rb +74 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/subscriber/client_submit_request_subscriber_must_support_test.rb +74 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/subscriber/metadata.yml +159 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/subscriber/server_inquiry_request_subscriber_must_support_test.rb +74 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/subscriber/server_submit_request_subscriber_must_support_test.rb +74 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/task/metadata.yml +192 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/task/server_inquiry_response_task_must_support_test.rb +61 -0
- data/lib/davinci_pas_test_kit/generated/v2.0.1/task/server_submit_response_task_must_support_test.rb +61 -0
- data/lib/davinci_pas_test_kit/generator/group_generator.rb +440 -0
- data/lib/davinci_pas_test_kit/generator/group_metadata.rb +73 -0
- data/lib/davinci_pas_test_kit/generator/group_metadata_extractor.rb +244 -0
- data/lib/davinci_pas_test_kit/generator/ig_loader.rb +78 -0
- data/lib/davinci_pas_test_kit/generator/ig_metadata.rb +66 -0
- data/lib/davinci_pas_test_kit/generator/ig_metadata_extractor.rb +54 -0
- data/lib/davinci_pas_test_kit/generator/ig_resources.rb +74 -0
- data/lib/davinci_pas_test_kit/generator/must_support_check_profiles.rb +86 -0
- data/lib/davinci_pas_test_kit/generator/must_support_metadata_extractor.rb +327 -0
- data/lib/davinci_pas_test_kit/generator/must_support_test_generator.rb +155 -0
- data/lib/davinci_pas_test_kit/generator/naming.rb +50 -0
- data/lib/davinci_pas_test_kit/generator/operation_test_generator.rb +136 -0
- data/lib/davinci_pas_test_kit/generator/resource_list_generator.rb +59 -0
- data/lib/davinci_pas_test_kit/generator/suite_generator.rb +94 -0
- data/lib/davinci_pas_test_kit/generator/terminology_binding_metadata_extractor.rb +108 -0
- data/lib/davinci_pas_test_kit/generator/validation_test_generator.rb +181 -0
- data/lib/davinci_pas_test_kit/generator/value_extractor.rb +48 -0
- data/lib/davinci_pas_test_kit/generator.rb +80 -0
- data/lib/davinci_pas_test_kit/mock_server.rb +189 -0
- data/lib/davinci_pas_test_kit/must_support_test.rb +267 -0
- data/lib/davinci_pas_test_kit/pas_bundle_validation.rb +568 -0
- data/lib/davinci_pas_test_kit/tags.rb +7 -0
- data/lib/davinci_pas_test_kit/urls.rb +39 -0
- data/lib/davinci_pas_test_kit/user_input_response.rb +32 -0
- data/lib/davinci_pas_test_kit/validation_test.rb +58 -0
- data/lib/davinci_pas_test_kit/validator_suppressions.rb +143 -0
- data/lib/davinci_pas_test_kit/version.rb +5 -0
- data/lib/davinci_pas_test_kit.rb +2 -0
- metadata +281 -0
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'validation_test'
|
|
4
|
+
|
|
5
|
+
module DaVinciPASTestKit
|
|
6
|
+
module PasBundleValidation
|
|
7
|
+
include DaVinciPASTestKit::ValidationTest
|
|
8
|
+
|
|
9
|
+
CLAIM_PROFILE = 'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/profile-claim-update'
|
|
10
|
+
CLAIM_RESPONSE_PROFILE = 'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/profile-claimresponse'
|
|
11
|
+
CLAIM_INQUIRY_PROFILE = 'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/profile-claim-inquiry'
|
|
12
|
+
CLAIM_INQUIRY_RESPONSE_PROFILE = 'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/profile-claiminquiryresponse'
|
|
13
|
+
|
|
14
|
+
def all_scratch_resources
|
|
15
|
+
scratch_resources[:all] ||= []
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def save_bundles_and_entries_to_scratch(bundles)
|
|
19
|
+
bundles.each do |bundle|
|
|
20
|
+
all_scratch_resources << bundle
|
|
21
|
+
all_scratch_resources.concat(bundle.entry.map(&:resource))
|
|
22
|
+
all_scratch_resources.uniq!
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def validation_error_messages
|
|
27
|
+
@validation_error_messages ||= []
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def perform_request_validation(bundle, profile_url, version, request_type)
|
|
31
|
+
validate_pa_request_payload_structure(bundle, request_type)
|
|
32
|
+
validate_resources_conformance_against_profile(bundle, profile_url, version, request_type)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def perform_response_validation(response_bundle, profile_url, version, request_type, request_bundle = nil)
|
|
36
|
+
validate_pa_response_body_structure(response_bundle, request_bundle) if request_type == 'submit'
|
|
37
|
+
validate_resources_conformance_against_profile(response_bundle, profile_url, version, request_type)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def validate_pas_bundle_json(json, profile_url, version, request_type, bundle_type, skips: false, message: '')
|
|
41
|
+
assert_valid_json(json)
|
|
42
|
+
bundle = FHIR.from_contents(json)
|
|
43
|
+
assert bundle.present?, 'Not a FHIR resource'
|
|
44
|
+
assert_resource_type(:bundle, resource: bundle)
|
|
45
|
+
|
|
46
|
+
if bundle_type == 'request_bundle'
|
|
47
|
+
perform_request_validation(bundle, profile_url, version, request_type)
|
|
48
|
+
else
|
|
49
|
+
perform_response_validation(bundle, profile_url, version, request_type)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
validation_error_messages.each do |msg|
|
|
53
|
+
messages << { type: 'error', message: msg }
|
|
54
|
+
end
|
|
55
|
+
msg = 'Bundle response returned and/or entry resources are not conformant. Check messages for issues found.'
|
|
56
|
+
assert validation_error_messages.blank?, msg
|
|
57
|
+
rescue Inferno::Exceptions::AssertionException => e
|
|
58
|
+
msg = "#{message} #{e.message}".strip
|
|
59
|
+
raise e.class, msg unless skips
|
|
60
|
+
|
|
61
|
+
skip msg
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Validates the structure of a Prior Authorization (PA) request Bundle.
|
|
65
|
+
#
|
|
66
|
+
# @param bundle [FHIR::Bundle] The FHIR Bundle of the PA request.
|
|
67
|
+
#
|
|
68
|
+
# This method performs various checks on the PA request payload, including validating
|
|
69
|
+
# the FHIR bundle structure, checking the resource type, and validating the resources
|
|
70
|
+
# referenced in the Claim resource are included in the bundle. It ensures that the first
|
|
71
|
+
# entry in the Bundle is a Claim resource and additional entries are populated
|
|
72
|
+
# with referenced resources, following the traversal of references.
|
|
73
|
+
# Duplicate resources are handled as required( appearing only once
|
|
74
|
+
# in the bundle entry).
|
|
75
|
+
def validate_pa_request_payload_structure(bundle, request_type)
|
|
76
|
+
bundle_entry_resources = bundle.entry.map(&:resource)
|
|
77
|
+
first_entry = bundle_entry_resources.first
|
|
78
|
+
base_url = extract_base_url(bundle.entry.first&.fullUrl)
|
|
79
|
+
|
|
80
|
+
check_presence_of_referenced_resources(first_entry, base_url, bundle.entry)
|
|
81
|
+
|
|
82
|
+
if request_type == 'submit'
|
|
83
|
+
unless first_entry.is_a?(FHIR::Claim)
|
|
84
|
+
validation_error_messages << "[Bundle/#{bundle.id}]: The first bundle entry must be a Claim"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
validate_uniqueness_of_supporting_info_sequences(first_entry)
|
|
88
|
+
validate_bundle_entries_full_url(bundle)
|
|
89
|
+
else
|
|
90
|
+
claim_resource = bundle_entry_resources.find { |resource| resource.resourceType == 'Claim' }
|
|
91
|
+
if claim_resource.blank?
|
|
92
|
+
validation_error_messages << "[Bundle/#{bundle.id}]: Claim must be present for inquiry request"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# The inquiry operation must contain a requesting provider organization,
|
|
96
|
+
# a payer organization, and a patient for the inquiry
|
|
97
|
+
patient_reference = claim_resource&.patient&.reference
|
|
98
|
+
provider_reference = claim_resource&.provider&.reference
|
|
99
|
+
payer_reference = claim_resource&.insurer&.reference
|
|
100
|
+
|
|
101
|
+
if patient_reference.blank?
|
|
102
|
+
validation_error_messages <<
|
|
103
|
+
"[Bundle/#{bundle.id}]: The Claim for inquiry operation must reference a patient."
|
|
104
|
+
end
|
|
105
|
+
if provider_reference.blank?
|
|
106
|
+
validation_error_messages << "[Bundle/#{bundle.id}]: The claim for inquiry operation must reference " \
|
|
107
|
+
'a requesting provider organization.'
|
|
108
|
+
end
|
|
109
|
+
if payer_reference.blank?
|
|
110
|
+
validation_error_messages << "[Bundle/#{bundle.id}]: The Claim for inquiry operation must contain " \
|
|
111
|
+
'a payer organization.'
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Validates the response body structure of a Prior Authorization (PA) response.
|
|
117
|
+
#
|
|
118
|
+
# @param pa_response_bundle [FHIR::Bundle] The FHIR bundle representing the PA response.
|
|
119
|
+
# @param pa_request_bundle [String] The JSON payload of the PA request bundle.
|
|
120
|
+
#
|
|
121
|
+
# This method performs validation of the PA response bundle structure.
|
|
122
|
+
# It follows the PAS IG requirement that the FHIR Bundle generated
|
|
123
|
+
# from the response starts with a ClaimResponse entry.
|
|
124
|
+
# For response of $submit request: Additional Bundle entries are populated
|
|
125
|
+
# with resources referenced by the ClaimResponse
|
|
126
|
+
# or descendant references, ensuring that only one resource is created for
|
|
127
|
+
# a given combination of content. Resources echoed back from the request are validated
|
|
128
|
+
# to ensure the same fullUrl and resource identifiers as in the
|
|
129
|
+
# request are used.
|
|
130
|
+
def validate_pa_response_body_structure(pa_response_bundle, pa_request_bundle)
|
|
131
|
+
first_entry = pa_response_bundle.entry.first.resource
|
|
132
|
+
unless first_entry.is_a?(FHIR::ClaimResponse)
|
|
133
|
+
validation_error_messages <<
|
|
134
|
+
"[Bundle/#{pa_response_bundle.id}]: The first bundle entry must be a ClaimResponse"
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
base_url = extract_base_url(pa_response_bundle.entry.last&.fullUrl)
|
|
138
|
+
check_presence_of_referenced_resources(first_entry, base_url, pa_response_bundle.entry)
|
|
139
|
+
|
|
140
|
+
# Testing: When echoing back resources that are the same as were present in the prior authorization request,
|
|
141
|
+
# the system SHALL ensure that the same fullUrl and resource identifiers are used in the response as appeared
|
|
142
|
+
# in the request
|
|
143
|
+
# pa_request_bundle = FHIR.from_contents(pa_request_bundle)
|
|
144
|
+
# pa_response_bundle.entry.each do |entry|
|
|
145
|
+
# res = entry.resource
|
|
146
|
+
# request_entry = pa_request_bundle.entry.find do |ent|
|
|
147
|
+
# ent.resource.resourceType == res.resourceType && ent.resource.id == res.id
|
|
148
|
+
# end
|
|
149
|
+
# next unless request_entry.present?
|
|
150
|
+
|
|
151
|
+
# assert(
|
|
152
|
+
# request_entry.fullUrl == entry.fullUrl && request_entry.resource.identifier == res.identifier,
|
|
153
|
+
# resource_present_in_pa_request_and_response_msg(res)
|
|
154
|
+
# )
|
|
155
|
+
# end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Profile conformance of Prior Authorization (PA) resources.
|
|
159
|
+
#
|
|
160
|
+
# @param bundle [FHIR::Bundle] The FHIR bundle representing the PA request/response.
|
|
161
|
+
# @param profile_url [String] The URL of the FHIR profile to validate against.
|
|
162
|
+
# @param version [String] The version of the profile.
|
|
163
|
+
# @param request_type [String] the request operation: submit or inquiry
|
|
164
|
+
#
|
|
165
|
+
# This method performs conformance validation on the PA bundle and bundle entries.
|
|
166
|
+
# The request/response bundle and includes resources are validated against their
|
|
167
|
+
# respective profile.
|
|
168
|
+
def validate_resources_conformance_against_profile(bundle, profile_url, version, request_type)
|
|
169
|
+
add_resource_target_profile_to_map('bundle', bundle, profile_url)
|
|
170
|
+
|
|
171
|
+
bundle_entry = bundle.entry
|
|
172
|
+
|
|
173
|
+
root_entry = bundle_entry.find do |entry|
|
|
174
|
+
entry.resource.resourceType == 'Claim' || entry.resource.resourceType == 'ClaimResponse'
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
if root_entry.present?
|
|
178
|
+
root_resource_profile_url = find_profile_url(request_type)[root_entry.resource.resourceType]
|
|
179
|
+
|
|
180
|
+
add_resource_target_profile_to_map(root_entry.fullUrl, root_entry.resource, root_resource_profile_url)
|
|
181
|
+
extract_profiles_to_validate_each_entry(bundle_entry, root_entry, root_resource_profile_url, version)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
validate_bundle_entries_against_profiles(version)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Returns a hash map where the keys are resource full URLs and the values are a hash containing the resource
|
|
188
|
+
# object and an array of associated profile URLs.
|
|
189
|
+
# @return [Hash] The resource target profile map.
|
|
190
|
+
def bundle_resources_target_profile_map
|
|
191
|
+
@bundle_resources_target_profile_map ||= {}
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Adds a resource and its associated profile URL to the resource target profile map.
|
|
195
|
+
# If the resource is already in the map, it adds the profile URL to the resource's list of profile URLs.
|
|
196
|
+
# @param resource_full_url [String] The full URL of the resource.
|
|
197
|
+
# @param resource [Object] The resource object.
|
|
198
|
+
# @param profile_url [String] The profile URL to associate with the resource.
|
|
199
|
+
def add_resource_target_profile_to_map(resource_full_url, resource, profile_url = nil)
|
|
200
|
+
entry = bundle_resources_target_profile_map[resource_full_url] ||= { resource:, profile_urls: [] }
|
|
201
|
+
|
|
202
|
+
return if profile_url.blank? || entry[:profile_urls].include?(profile_url)
|
|
203
|
+
|
|
204
|
+
entry[:profile_urls] << profile_url
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Validates bundle resource and each entry in the bundle against its target profiles.
|
|
208
|
+
# It logs a message for each conformant entry
|
|
209
|
+
# and collects error messages for non-conformant entries. Asserts that there are no validation errors.
|
|
210
|
+
# @param version [String] The version of the IG.
|
|
211
|
+
def validate_bundle_entries_against_profiles(version)
|
|
212
|
+
bundle_resources_target_profile_map.each_value do |item|
|
|
213
|
+
success_profile = item[:profile_urls].find do |url|
|
|
214
|
+
profile_with_version = "#{url}|#{version}"
|
|
215
|
+
resource_is_valid?(resource: item[:resource], profile_url: profile_with_version)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
validation_error_messages << generate_non_conformance_message(item) unless success_profile
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def generate_non_conformance_message(item)
|
|
223
|
+
"#{item[:resource].resourceType}/#{item[:resource].id} is not conformant to any of the " \
|
|
224
|
+
"target profiles: #{item[:profile_urls]}."
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Processes each entry in a FHIR bundle to extract resource and possible profiles to validate against.
|
|
228
|
+
# It recursively evaluates referenced instances and their profiles, expanding the validation scope.
|
|
229
|
+
# @param bundle_entry [Object] The current bundle entry being processed.
|
|
230
|
+
# @param current_entry [Object] The current entry within the bundle.
|
|
231
|
+
# @param current_entry_profile_url [String] The profile URL associated with the current entry.
|
|
232
|
+
# @param version [String] The IG version.
|
|
233
|
+
def extract_profiles_to_validate_each_entry(bundle_entry, current_entry, current_entry_profile_url, version)
|
|
234
|
+
return if current_entry.blank?
|
|
235
|
+
|
|
236
|
+
# NOTE: the IG does not have the metadata for us-core profiles.
|
|
237
|
+
metadata = metadata_map("v#{version}")[current_entry_profile_url]
|
|
238
|
+
return if metadata.blank?
|
|
239
|
+
|
|
240
|
+
bundle_map = bundle_entry_map(bundle_entry)
|
|
241
|
+
reference_elements = metadata.references
|
|
242
|
+
|
|
243
|
+
# Special handling for Claim submit profile
|
|
244
|
+
if current_entry_profile_url == CLAIM_PROFILE
|
|
245
|
+
handle_claim_profile(reference_elements,
|
|
246
|
+
current_entry_profile_url)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
reference_elements.each do |reference_element|
|
|
250
|
+
process_reference_element(reference_element, current_entry, bundle_entry, bundle_map, version)
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# Handles the special case for the Claim profile in a FHIR bundle.
|
|
255
|
+
# It adds missing reference elements for the Claim profile.
|
|
256
|
+
# Claim.item.extension:requestedService value is a reference, but somehow not included in the metadata references.
|
|
257
|
+
# @param reference_elements [Array] The array of reference elements to be updated.
|
|
258
|
+
# @param current_entry_profile_url [String] The profile URL of the current entry being processed.
|
|
259
|
+
def handle_claim_profile(reference_elements, current_entry_profile_url)
|
|
260
|
+
return unless current_entry_profile_url == CLAIM_PROFILE
|
|
261
|
+
|
|
262
|
+
claim_ref_element = {
|
|
263
|
+
path: 'Claim.item.extension.value',
|
|
264
|
+
profiles: [
|
|
265
|
+
'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/profile-medicationrequest',
|
|
266
|
+
'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/profile-servicerequest',
|
|
267
|
+
'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/profile-devicerequest',
|
|
268
|
+
'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/profile-nutritionorder'
|
|
269
|
+
]
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
reference_elements << claim_ref_element
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# Processes a given reference element definition from a FHIR bundle entry.
|
|
276
|
+
# It evaluates FHIRPath expressions and processes each referenced instance and its profiles.
|
|
277
|
+
# @param reference_element [Hash] The reference element to process.
|
|
278
|
+
# @param current_entry [Object] The current entry within the FHIR bundle.
|
|
279
|
+
# @param bundle_entry [Array] The bundle.entry.
|
|
280
|
+
# @param bundle_map [Hash] A map of the bundle contents.
|
|
281
|
+
# @param version [String] The FHIR version.
|
|
282
|
+
def process_reference_element(reference_element, current_entry, bundle_entry, bundle_map, version)
|
|
283
|
+
reference_element_values = evaluate_fhirpath_expression(current_entry.resource, reference_element[:path])
|
|
284
|
+
referenced_instances = reference_element_values.map do |value|
|
|
285
|
+
find_referenced_instance_in_bundle(value, current_entry.fullUrl, bundle_map)
|
|
286
|
+
end.compact
|
|
287
|
+
|
|
288
|
+
referenced_instances.each do |instance|
|
|
289
|
+
process_instance_profiles(instance, bundle_entry, reference_element, version)
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# Processes the profiles associated with a given instance in a FHIR bundle.
|
|
294
|
+
# It adds the instance's profiles to the resource target profile map and handles recursive profile extraction.
|
|
295
|
+
# The profiles collected here are possible profiles the given instance may conform to.
|
|
296
|
+
# The conformance validation will ensure that the resource is comformant to at least one of the target profiles.
|
|
297
|
+
# @param instance [Object] The instance whose profiles are to be processed.
|
|
298
|
+
# @param bundle_entry [Array] The bundle.entry contents.
|
|
299
|
+
# @param reference_element [Hash] The reference element related to the instance.
|
|
300
|
+
# @param version [String] The IG version.
|
|
301
|
+
def process_instance_profiles(instance, bundle_entry, reference_element, version)
|
|
302
|
+
add_resource_target_profile_to_map(instance.fullUrl, instance.resource)
|
|
303
|
+
# Add the declared profile conformance
|
|
304
|
+
add_declared_profiles(instance, bundle_entry, version)
|
|
305
|
+
|
|
306
|
+
reference_element[:profiles].each do |profile_url|
|
|
307
|
+
# NOTE: the IG does not have the metadata for us-core profiles.
|
|
308
|
+
target_metadata = metadata_map("v#{version}")[profile_url]
|
|
309
|
+
resource_type = instance.resource.resourceType
|
|
310
|
+
next unless target_metadata&.resource == resource_type || profile_url.include?(resource_type)
|
|
311
|
+
|
|
312
|
+
add_profile_to_instance(instance, profile_url, bundle_entry, version)
|
|
313
|
+
# NOTE: The algorithm assumes OR semantics for profile conformance, where the resource needs to conform to
|
|
314
|
+
# at least one of the collected profiles. However, it may not cover all scenarios, such as cases
|
|
315
|
+
# where AND semantics are required for multiple profile conformance. Also, it does not address complex
|
|
316
|
+
# situations where profile requirements may conflict or have dependencies across referenced instances.
|
|
317
|
+
# Therefore, this algorithm may not provide complete validation for all scenarios, and additional checks may be
|
|
318
|
+
# necessary depending on the use case.
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
# Adds declared profiles from an instance (meta.profile) to the resource target profile map.
|
|
323
|
+
# It recursively processes each entry for further profile extraction.
|
|
324
|
+
# @param instance [Object] The instance from which profiles are extracted.
|
|
325
|
+
# @param bundle_entry [Array] The bundle.entry contents.
|
|
326
|
+
# @param version [String] The IG version.
|
|
327
|
+
def add_declared_profiles(instance, bundle_entry, version)
|
|
328
|
+
instance.resource&.meta&.profile&.each do |url|
|
|
329
|
+
next if bundle_resources_target_profile_map[instance.fullUrl][:profile_urls].include?(url)
|
|
330
|
+
|
|
331
|
+
bundle_resources_target_profile_map[instance.fullUrl][:profile_urls] << url
|
|
332
|
+
extract_profiles_to_validate_each_entry(bundle_entry, instance, url, version)
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
# Adds a specific profile URL to an instance in the resource target profile map.
|
|
337
|
+
# It recursively processes the instance for further profilce extraction.
|
|
338
|
+
# @param instance [Object] The instance to which the profile URL is added.
|
|
339
|
+
# @param profile_url [String] The profile URL to be added.
|
|
340
|
+
# @param bundle_entry [Array] The bundle.entry contents.
|
|
341
|
+
# @param version [String] The IG version.
|
|
342
|
+
def add_profile_to_instance(instance, profile_url, bundle_entry, version)
|
|
343
|
+
return if bundle_resources_target_profile_map[instance.fullUrl][:profile_urls].include?(profile_url)
|
|
344
|
+
|
|
345
|
+
bundle_resources_target_profile_map[instance.fullUrl][:profile_urls] << profile_url
|
|
346
|
+
extract_profiles_to_validate_each_entry(bundle_entry, instance, profile_url, version)
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
# Mapping profile url to metadata
|
|
350
|
+
def metadata_map(version)
|
|
351
|
+
@metadata ||= YAML.load_file(File.join(__dir__, "generated/#{version}/metadata.yml"),
|
|
352
|
+
aliases: true)
|
|
353
|
+
@metadata_map ||= @metadata[:groups].each_with_object({}) do |group, obj|
|
|
354
|
+
obj[group[:profile_url]] = Generator::GroupMetadata.new(group)
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def bundle_entry_map(bundle_entry)
|
|
359
|
+
@bundle_entry_map ||= bundle_entry.each_with_object({}) do |entry, obj|
|
|
360
|
+
obj[entry.fullUrl] = entry
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
# Evaluates a FHIRPath expression against a FHIR resource using an external FHIRPath validator.
|
|
365
|
+
# @param resource [Object] The FHIR resource to be evaluated.
|
|
366
|
+
# @param expression [String] The FHIRPath expression to evaluate.
|
|
367
|
+
# @return [Array] An array of references extracted from the evaluation result, or an empty array in case of failure.
|
|
368
|
+
def evaluate_fhirpath_expression(resource, expression)
|
|
369
|
+
return [] unless expression && resource
|
|
370
|
+
|
|
371
|
+
logger = Logger.new($stdout)
|
|
372
|
+
begin
|
|
373
|
+
validator_url = ENV.fetch('VALIDATOR_URL') # 'https://inferno.healthit.gov/validatorapi/'
|
|
374
|
+
path = "#{validator_url}/evaluate?path=#{expression}"
|
|
375
|
+
|
|
376
|
+
response = Faraday.post(path, resource.to_json, 'Content-Type' => 'application/json')
|
|
377
|
+
if response.status.to_s.start_with? '2'
|
|
378
|
+
result = JSON.parse(response.body)
|
|
379
|
+
return result.map { |entry| entry.dig('element', 'reference') if entry['type'] == 'Reference' }.compact
|
|
380
|
+
else
|
|
381
|
+
logger.error "External validator failed: #{response.status}"
|
|
382
|
+
end
|
|
383
|
+
rescue Faraday::Error => e
|
|
384
|
+
logger.error "HTTP request failed: #{e.message}"
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
[]
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
# Finds a referenced instance in a FHIR bundle based on a reference and the full URL of the enclosing entry.
|
|
391
|
+
# @param reference [String] The reference to find.
|
|
392
|
+
# @param enclosing_entry_fullurl [String] The full URL of the enclosing entry.
|
|
393
|
+
# @param bundle_map [Hash] A map of the bundle contents.
|
|
394
|
+
# @return [Object] The found instance, or nil if not found.
|
|
395
|
+
def find_referenced_instance_in_bundle(reference, enclosing_entry_fullurl, bundle_map)
|
|
396
|
+
base_url = extract_base_url(enclosing_entry_fullurl)
|
|
397
|
+
key = absolute_url(reference, base_url)
|
|
398
|
+
|
|
399
|
+
bundle_map[key]
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
def absolute_url(reference, base_url)
|
|
403
|
+
return if reference.blank?
|
|
404
|
+
return reference if base_url.blank? || reference.starts_with?('urn:uuid:') || URI(reference).absolute?
|
|
405
|
+
|
|
406
|
+
"#{base_url}/#{reference}"
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
# Extracts the base URL from an absolute URL by removing the resource type and ID.
|
|
410
|
+
# @param absolute_url [String] The absolute URL.
|
|
411
|
+
# @return [String] The base URL, or an empty string if the URL format is not as expected.
|
|
412
|
+
def extract_base_url(absolute_url)
|
|
413
|
+
return '' if absolute_url.blank?
|
|
414
|
+
|
|
415
|
+
uri = URI(absolute_url)
|
|
416
|
+
return '' unless uri.scheme && uri.host
|
|
417
|
+
|
|
418
|
+
# Split the path segments and remove the last two segments (resource type and id)
|
|
419
|
+
path_segments = uri.path.split('/')
|
|
420
|
+
base_path = path_segments[0...-2].join('/')
|
|
421
|
+
|
|
422
|
+
"#{uri.scheme}://#{uri.host}#{base_path}"
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
# Resource Types to validate in request/ response bundle
|
|
426
|
+
def find_profile_url(request_type)
|
|
427
|
+
{
|
|
428
|
+
'Claim' => request_type == 'submit' ? CLAIM_PROFILE : CLAIM_INQUIRY_PROFILE,
|
|
429
|
+
'ClaimResponse' => request_type == 'submit' ? CLAIM_RESPONSE_PROFILE : CLAIM_INQUIRY_RESPONSE_PROFILE
|
|
430
|
+
}
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
# Checks the following requirement:
|
|
434
|
+
# The Claim.supportingInfo.sequence for each entry SHALL be unique within the Claim.
|
|
435
|
+
#
|
|
436
|
+
# @param claim [FHIR::Claim] The FHIR Claim resource.
|
|
437
|
+
# Since the cardinality for Claim.supportingInfo is 0..*,
|
|
438
|
+
# we will check the uniqueness if Array not empty.
|
|
439
|
+
def validate_uniqueness_of_supporting_info_sequences(claim)
|
|
440
|
+
return unless claim.is_a?(FHIR::Claim)
|
|
441
|
+
|
|
442
|
+
supporting_info = claim.supportingInfo
|
|
443
|
+
return unless supporting_info.present?
|
|
444
|
+
|
|
445
|
+
sequences = supporting_info.map(&:sequence)
|
|
446
|
+
is_unique = sequences.uniq.length == sequences.length
|
|
447
|
+
return if is_unique
|
|
448
|
+
|
|
449
|
+
validation_error_messages << "[Claim/#{claim.id}]: The sequence element for each supportingInfo entry SHALL be " \
|
|
450
|
+
'unique within the Claim.'
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
def validate_bundle_entries_full_url(bundle)
|
|
454
|
+
msg = "[Bundle/#{bundle.id}]: Bundle.entry.fullUrl values SHALL be a valid url or in the form " \
|
|
455
|
+
"'urn:uuid:[some guid]'."
|
|
456
|
+
bundle.entry.each do |entry|
|
|
457
|
+
validation_error_messages << msg unless valid_url_or_urn_uuid?(entry.fullUrl)
|
|
458
|
+
end
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
# Checks if a string is a valid url or in the form “urn:uuid:[some guid]”
|
|
462
|
+
# @param string [String] The url string to check
|
|
463
|
+
# @return true if valid url or urn_uuid, otherwise false
|
|
464
|
+
def valid_url_or_urn_uuid?(string)
|
|
465
|
+
url_regex = /\A#{URI::DEFAULT_PARSER.make_regexp(%w[http https])}\z/
|
|
466
|
+
urn_uuid_regex = /\Aurn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\z/i
|
|
467
|
+
|
|
468
|
+
string.match?(url_regex) || string.match?(urn_uuid_regex)
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
# This method traverses references within a FHIR resource, ensuring that referenced resources
|
|
472
|
+
# are populated in the bundle. It also enforces that a referenced resource appears only once in the bundle,
|
|
473
|
+
# as required by the PAS IG.
|
|
474
|
+
# @param target_resource [FHIR::Model] The FHIR resource to traverse and validate.
|
|
475
|
+
# @param base_url [String] The server base url.
|
|
476
|
+
# @param resources_to_match [Array<FHIR:Bundel:Entry] The list of FHIR bundle entries to match references against.
|
|
477
|
+
def check_presence_of_referenced_resources(target_resource, base_url, resources_to_match)
|
|
478
|
+
return if target_resource.blank?
|
|
479
|
+
|
|
480
|
+
if target_resource.is_a?(FHIR::Reference) && target_resource.reference.present?
|
|
481
|
+
ref = target_resource.reference
|
|
482
|
+
return if ref.blank?
|
|
483
|
+
|
|
484
|
+
absolute_ref = absolute_url(ref, base_url)
|
|
485
|
+
resource_type, resource_id = ref.split('/')
|
|
486
|
+
matching_resources = resources_to_match.find_all { |res| res.fullUrl == absolute_ref }
|
|
487
|
+
|
|
488
|
+
if matching_resources.length != 1
|
|
489
|
+
validation_error_messages << resource_shall_appear_once_message(resource_type, resource_id,
|
|
490
|
+
matching_resources.length)
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
if matching_resources.length.positive?
|
|
494
|
+
check_presence_of_referenced_resources(matching_resources.first, base_url, resources_to_match)
|
|
495
|
+
end
|
|
496
|
+
else
|
|
497
|
+
target_resource.source_hash.each_key do |attr|
|
|
498
|
+
value = target_resource.send(attr.to_sym)
|
|
499
|
+
if value.is_a?(FHIR::Model)
|
|
500
|
+
check_presence_of_referenced_resources(value, base_url, resources_to_match)
|
|
501
|
+
elsif value.is_a?(Array) && value.all? { |elmt| elmt.is_a?(FHIR::Model) }
|
|
502
|
+
value.each { |elmt| check_presence_of_referenced_resources(elmt, base_url, resources_to_match) }
|
|
503
|
+
end
|
|
504
|
+
end
|
|
505
|
+
end
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
# Extracts resources from a bundle while following "next" links.
|
|
509
|
+
#
|
|
510
|
+
# @param bundle [FHIR::Bundle] The initial FHIR bundle to extract resources from.
|
|
511
|
+
# @param response [Object] The HTTP response object for the bundle retrieval.
|
|
512
|
+
# @param reply_handler [Proc] A callback function to handle responses.
|
|
513
|
+
# @param max_pages [Integer] The maximum number of pages to process.
|
|
514
|
+
#
|
|
515
|
+
# This method extracts resources from a FHIR bundle, following "next" links in the bundle
|
|
516
|
+
# until the specified maximum number of pages is reached. It collects resources and
|
|
517
|
+
# invokes the reply_handler for each response.
|
|
518
|
+
def extract_resources_from_bundle(
|
|
519
|
+
bundle: nil,
|
|
520
|
+
response: nil,
|
|
521
|
+
reply_handler: nil,
|
|
522
|
+
max_pages: 20
|
|
523
|
+
)
|
|
524
|
+
page_count = 1
|
|
525
|
+
resources = []
|
|
526
|
+
|
|
527
|
+
until bundle.nil? || page_count == max_pages
|
|
528
|
+
resources += bundle&.entry&.map { |entry| entry&.resource }
|
|
529
|
+
next_bundle_link = bundle&.link&.find { |link| link.relation == 'next' }&.url
|
|
530
|
+
reply_handler&.call(response)
|
|
531
|
+
|
|
532
|
+
break if next_bundle_link.blank?
|
|
533
|
+
|
|
534
|
+
page_count += 1
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
resources
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
# Generates a message for a resource that appears more than once in a bundle.
|
|
541
|
+
#
|
|
542
|
+
# @param reference_resource_type [String] The resource type being referenced.
|
|
543
|
+
# @param reference_resource_id [String] The resource ID being referenced.
|
|
544
|
+
# @param total_matches [Integer] The total number of matches found in the bundle.
|
|
545
|
+
#
|
|
546
|
+
# This method generates an error message when a referenced resource appears more than once
|
|
547
|
+
# in a FHIR bundle, which is not allowed.
|
|
548
|
+
def resource_shall_appear_once_message(reference_resource_type, reference_resource_id, total_matches)
|
|
549
|
+
"
|
|
550
|
+
The referenced #{reference_resource_type}/#{reference_resource_id} resource
|
|
551
|
+
SHALL only appear once in the Bundle, but found #{total_matches}.
|
|
552
|
+
"
|
|
553
|
+
end
|
|
554
|
+
|
|
555
|
+
# Generates a message for a resource present in both the PA request and response bundles.
|
|
556
|
+
#
|
|
557
|
+
# @param resource [FHIR::Model] The resource present in both bundles.
|
|
558
|
+
#
|
|
559
|
+
# This method generates an error message when a resource appears in both the PA request
|
|
560
|
+
# and response bundles but does not have the same fullUrl or identifiers.
|
|
561
|
+
def resource_present_in_pa_request_and_response_msg(resource)
|
|
562
|
+
"
|
|
563
|
+
Resource #{resource.resourceType}/#{resource.id} is an entry in both the PA Request Bundle
|
|
564
|
+
and the Response Bundle, but they do not have the same fullUrl or identifiers
|
|
565
|
+
"
|
|
566
|
+
end
|
|
567
|
+
end
|
|
568
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DaVinciPASTestKit
|
|
4
|
+
TOKEN_PATH = '/mock_auth/token'
|
|
5
|
+
SUBMIT_PATH = '/fhir/Claim/$submit'
|
|
6
|
+
INQUIRE_PATH = '/fhir/Claim/$inquire'
|
|
7
|
+
RESUME_PASS_PATH = '/resume_pass'
|
|
8
|
+
RESUME_FAIL_PATH = '/resume_fail'
|
|
9
|
+
|
|
10
|
+
module URLs
|
|
11
|
+
def base_url
|
|
12
|
+
@base_url ||= "#{Inferno::Application['base_url']}/custom/#{suite_id}"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def token_url
|
|
16
|
+
@token_url ||= base_url + TOKEN_PATH
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def submit_url
|
|
20
|
+
@submit_url ||= base_url + SUBMIT_PATH
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def inquire_url
|
|
24
|
+
@inquire_url ||= base_url + INQUIRE_PATH
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def resume_pass_url
|
|
28
|
+
@resume_pass_url ||= base_url + RESUME_PASS_PATH
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def resume_fail_url
|
|
32
|
+
@resume_fail_url ||= base_url + RESUME_FAIL_PATH
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def suite_id
|
|
36
|
+
self.class.suite.id
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module DaVinciPASTestKit
|
|
2
|
+
module UserInputResponse
|
|
3
|
+
def self.included(klass)
|
|
4
|
+
klass.extend ClassMethods
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def self.user_inputted_response(configurable, result)
|
|
8
|
+
input_key = configurable.config.options[:respond_with]
|
|
9
|
+
return unless input_key.present?
|
|
10
|
+
|
|
11
|
+
JSON.parse(result.input_json)&.find { |i| i['name'] == input_key.to_s }&.dig('value')
|
|
12
|
+
rescue JSON::ParserError
|
|
13
|
+
nil
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def check_user_inputted_response(input_key, message = nil)
|
|
17
|
+
skip_if send(input_key).blank?,
|
|
18
|
+
message ||
|
|
19
|
+
"To run this test a response body must be provided in the '**#{input_title(input_key)}**' input"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def input_title(input_key)
|
|
23
|
+
config.inputs[input_key]&.title || config.inputs[input_key]&.name
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
module ClassMethods
|
|
27
|
+
def respond_with(key)
|
|
28
|
+
config options: { respond_with: key }
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|