davinci_pas_test_kit 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|