davinci_pas_test_kit 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (168) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +201 -0
  3. data/lib/davinci_pas_test_kit/client_suite.rb +289 -0
  4. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/claim_status/pas_claim_status_test.rb +109 -0
  5. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_approval_submit_response_attest.rb +39 -0
  6. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_approval_submit_test.rb +38 -0
  7. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_denial_submit_response_attest.rb +38 -0
  8. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_denial_submit_test.rb +43 -0
  9. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_inquire_must_support_test.rb +51 -0
  10. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_pended_inquire_response_attest.rb +39 -0
  11. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_pended_inquire_test.rb +35 -0
  12. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_pended_submit_response_attest.rb +39 -0
  13. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_pended_submit_test.rb +43 -0
  14. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_submit_must_support_test.rb +57 -0
  15. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_token_request_test.rb +31 -0
  16. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_token_validation_test.rb +18 -0
  17. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/error_tests/nonconformant_pas_bundle.json +16 -0
  18. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/error_tests/pas_inquiry_error_test.rb +38 -0
  19. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/error_tests/pas_submission_error_test.rb +56 -0
  20. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/must_support/device_request_metadata.yml +112 -0
  21. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/must_support/medication_request_metadata.yml +183 -0
  22. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/must_support/nutrition_order_metadata.yml +109 -0
  23. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/must_support/pas_client_must_support_requirement_test.rb +117 -0
  24. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/must_support/pas_server_must_support_requirement_test.rb +116 -0
  25. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/must_support/service_request_metadata.yml +148 -0
  26. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_approval_group.rb +26 -0
  27. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_authentication_group.rb +49 -0
  28. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_denial_group.rb +41 -0
  29. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_pended_group.rb +56 -0
  30. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_error_group.rb +20 -0
  31. data/lib/davinci_pas_test_kit/ext/inferno_core/record_response_route.rb +98 -0
  32. data/lib/davinci_pas_test_kit/ext/inferno_core/request.rb +19 -0
  33. data/lib/davinci_pas_test_kit/ext/inferno_core/runnable.rb +18 -0
  34. data/lib/davinci_pas_test_kit/fhir_resource_navigation.rb +72 -0
  35. data/lib/davinci_pas_test_kit/generated/v2.0.1/beneficiary/client_inquiry_request_beneficiary_must_support_test.rb +75 -0
  36. data/lib/davinci_pas_test_kit/generated/v2.0.1/beneficiary/client_submit_request_beneficiary_must_support_test.rb +75 -0
  37. data/lib/davinci_pas_test_kit/generated/v2.0.1/beneficiary/metadata.yml +162 -0
  38. data/lib/davinci_pas_test_kit/generated/v2.0.1/beneficiary/server_inquiry_request_beneficiary_must_support_test.rb +75 -0
  39. data/lib/davinci_pas_test_kit/generated/v2.0.1/beneficiary/server_inquiry_response_beneficiary_must_support_test.rb +75 -0
  40. data/lib/davinci_pas_test_kit/generated/v2.0.1/beneficiary/server_submit_request_beneficiary_must_support_test.rb +75 -0
  41. data/lib/davinci_pas_test_kit/generated/v2.0.1/beneficiary/server_submit_response_beneficiary_must_support_test.rb +75 -0
  42. data/lib/davinci_pas_test_kit/generated/v2.0.1/claim/claim_operation_test.rb +67 -0
  43. data/lib/davinci_pas_test_kit/generated/v2.0.1/claim/metadata.yml +577 -0
  44. data/lib/davinci_pas_test_kit/generated/v2.0.1/claim_inquiry/claim_inquiry_operation_test.rb +57 -0
  45. data/lib/davinci_pas_test_kit/generated/v2.0.1/claim_inquiry/client_inquiry_request_claim_inquiry_must_support_test.rb +95 -0
  46. data/lib/davinci_pas_test_kit/generated/v2.0.1/claim_inquiry/metadata.yml +516 -0
  47. data/lib/davinci_pas_test_kit/generated/v2.0.1/claim_inquiry/server_inquiry_request_claim_inquiry_must_support_test.rb +95 -0
  48. data/lib/davinci_pas_test_kit/generated/v2.0.1/claim_update/client_submit_request_claim_update_must_support_test.rb +102 -0
  49. data/lib/davinci_pas_test_kit/generated/v2.0.1/claim_update/metadata.yml +591 -0
  50. data/lib/davinci_pas_test_kit/generated/v2.0.1/claim_update/server_submit_request_claim_update_must_support_test.rb +102 -0
  51. data/lib/davinci_pas_test_kit/generated/v2.0.1/claiminquiryresponse/metadata.yml +311 -0
  52. data/lib/davinci_pas_test_kit/generated/v2.0.1/claiminquiryresponse/server_inquiry_response_claiminquiryresponse_must_support_test.rb +73 -0
  53. data/lib/davinci_pas_test_kit/generated/v2.0.1/claimresponse/metadata.yml +318 -0
  54. data/lib/davinci_pas_test_kit/generated/v2.0.1/claimresponse/server_submit_response_claimresponse_must_support_test.rb +75 -0
  55. data/lib/davinci_pas_test_kit/generated/v2.0.1/client_tests/client_denial_pas_response_bundle_validation_test.rb +53 -0
  56. data/lib/davinci_pas_test_kit/generated/v2.0.1/client_tests/client_pas_request_bundle_validation_test.rb +55 -0
  57. data/lib/davinci_pas_test_kit/generated/v2.0.1/client_tests/client_pended_pas_inquiry_request_bundle_validation_test.rb +55 -0
  58. data/lib/davinci_pas_test_kit/generated/v2.0.1/client_tests/client_pended_pas_response_bundle_validation_test.rb +53 -0
  59. data/lib/davinci_pas_test_kit/generated/v2.0.1/communication_request/metadata.yml +130 -0
  60. data/lib/davinci_pas_test_kit/generated/v2.0.1/communication_request/server_submit_response_communication_request_must_support_test.rb +58 -0
  61. data/lib/davinci_pas_test_kit/generated/v2.0.1/coverage/client_inquiry_request_coverage_must_support_test.rb +55 -0
  62. data/lib/davinci_pas_test_kit/generated/v2.0.1/coverage/client_submit_request_coverage_must_support_test.rb +55 -0
  63. data/lib/davinci_pas_test_kit/generated/v2.0.1/coverage/metadata.yml +111 -0
  64. data/lib/davinci_pas_test_kit/generated/v2.0.1/coverage/server_inquiry_request_coverage_must_support_test.rb +55 -0
  65. data/lib/davinci_pas_test_kit/generated/v2.0.1/coverage/server_submit_request_coverage_must_support_test.rb +55 -0
  66. data/lib/davinci_pas_test_kit/generated/v2.0.1/device_request/client_submit_request_device_request_must_support_test.rb +51 -0
  67. data/lib/davinci_pas_test_kit/generated/v2.0.1/device_request/metadata.yml +112 -0
  68. data/lib/davinci_pas_test_kit/generated/v2.0.1/device_request/server_submit_request_device_request_must_support_test.rb +51 -0
  69. data/lib/davinci_pas_test_kit/generated/v2.0.1/encounter/client_submit_request_encounter_must_support_test.rb +67 -0
  70. data/lib/davinci_pas_test_kit/generated/v2.0.1/encounter/metadata.yml +213 -0
  71. data/lib/davinci_pas_test_kit/generated/v2.0.1/encounter/server_submit_request_encounter_must_support_test.rb +67 -0
  72. data/lib/davinci_pas_test_kit/generated/v2.0.1/insurer/client_inquiry_request_insurer_must_support_test.rb +60 -0
  73. data/lib/davinci_pas_test_kit/generated/v2.0.1/insurer/client_submit_request_insurer_must_support_test.rb +60 -0
  74. data/lib/davinci_pas_test_kit/generated/v2.0.1/insurer/metadata.yml +104 -0
  75. data/lib/davinci_pas_test_kit/generated/v2.0.1/insurer/server_inquiry_request_insurer_must_support_test.rb +60 -0
  76. data/lib/davinci_pas_test_kit/generated/v2.0.1/insurer/server_inquiry_response_insurer_must_support_test.rb +60 -0
  77. data/lib/davinci_pas_test_kit/generated/v2.0.1/insurer/server_submit_request_insurer_must_support_test.rb +60 -0
  78. data/lib/davinci_pas_test_kit/generated/v2.0.1/insurer/server_submit_response_insurer_must_support_test.rb +60 -0
  79. data/lib/davinci_pas_test_kit/generated/v2.0.1/medication_request/client_submit_request_medication_request_must_support_test.rb +61 -0
  80. data/lib/davinci_pas_test_kit/generated/v2.0.1/medication_request/metadata.yml +183 -0
  81. data/lib/davinci_pas_test_kit/generated/v2.0.1/medication_request/server_submit_request_medication_request_must_support_test.rb +61 -0
  82. data/lib/davinci_pas_test_kit/generated/v2.0.1/metadata.yml +5253 -0
  83. data/lib/davinci_pas_test_kit/generated/v2.0.1/nutrition_order/client_submit_request_nutrition_order_must_support_test.rb +53 -0
  84. data/lib/davinci_pas_test_kit/generated/v2.0.1/nutrition_order/metadata.yml +109 -0
  85. data/lib/davinci_pas_test_kit/generated/v2.0.1/nutrition_order/server_submit_request_nutrition_order_must_support_test.rb +53 -0
  86. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_client_inquiry_must_support_use_case_group.rb +51 -0
  87. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_client_submit_must_support_use_case_group.rb +61 -0
  88. 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
  89. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_inquiry_request_bundle/metadata.yml +77 -0
  90. 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
  91. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_inquiry_request_bundle/server_pas_inquiry_request_bundle_validation_test.rb +83 -0
  92. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_inquiry_response_bundle/metadata.yml +67 -0
  93. 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
  94. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_inquiry_response_bundle/server_pas_inquiry_response_bundle_validation_test.rb +80 -0
  95. 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
  96. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_request_bundle/metadata.yml +77 -0
  97. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_request_bundle/server_pas_request_bundle_validation_test.rb +83 -0
  98. 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
  99. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_response_bundle/metadata.yml +71 -0
  100. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_response_bundle/server_pas_response_bundle_validation_test.rb +80 -0
  101. 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
  102. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_server_approval_use_case_group.rb +59 -0
  103. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_server_denial_use_case_group.rb +59 -0
  104. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_server_must_support_use_case_group.rb +265 -0
  105. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_server_pended_use_case_group.rb +84 -0
  106. data/lib/davinci_pas_test_kit/generated/v2.0.1/practitioner/client_inquiry_request_practitioner_must_support_test.rb +53 -0
  107. data/lib/davinci_pas_test_kit/generated/v2.0.1/practitioner/client_submit_request_practitioner_must_support_test.rb +53 -0
  108. data/lib/davinci_pas_test_kit/generated/v2.0.1/practitioner/metadata.yml +74 -0
  109. data/lib/davinci_pas_test_kit/generated/v2.0.1/practitioner/server_inquiry_request_practitioner_must_support_test.rb +53 -0
  110. data/lib/davinci_pas_test_kit/generated/v2.0.1/practitioner/server_inquiry_response_practitioner_must_support_test.rb +53 -0
  111. data/lib/davinci_pas_test_kit/generated/v2.0.1/practitioner/server_submit_request_practitioner_must_support_test.rb +53 -0
  112. data/lib/davinci_pas_test_kit/generated/v2.0.1/practitioner/server_submit_response_practitioner_must_support_test.rb +53 -0
  113. data/lib/davinci_pas_test_kit/generated/v2.0.1/practitioner_role/client_inquiry_request_practitioner_role_must_support_test.rb +49 -0
  114. data/lib/davinci_pas_test_kit/generated/v2.0.1/practitioner_role/client_submit_request_practitioner_role_must_support_test.rb +49 -0
  115. data/lib/davinci_pas_test_kit/generated/v2.0.1/practitioner_role/metadata.yml +81 -0
  116. data/lib/davinci_pas_test_kit/generated/v2.0.1/practitioner_role/server_inquiry_request_practitioner_role_must_support_test.rb +49 -0
  117. data/lib/davinci_pas_test_kit/generated/v2.0.1/practitioner_role/server_inquiry_response_practitioner_role_must_support_test.rb +49 -0
  118. data/lib/davinci_pas_test_kit/generated/v2.0.1/practitioner_role/server_submit_request_practitioner_role_must_support_test.rb +49 -0
  119. data/lib/davinci_pas_test_kit/generated/v2.0.1/practitioner_role/server_submit_response_practitioner_role_must_support_test.rb +49 -0
  120. data/lib/davinci_pas_test_kit/generated/v2.0.1/requestor/client_inquiry_request_requestor_must_support_test.rb +63 -0
  121. data/lib/davinci_pas_test_kit/generated/v2.0.1/requestor/client_submit_request_requestor_must_support_test.rb +63 -0
  122. data/lib/davinci_pas_test_kit/generated/v2.0.1/requestor/metadata.yml +107 -0
  123. data/lib/davinci_pas_test_kit/generated/v2.0.1/requestor/server_inquiry_request_requestor_must_support_test.rb +63 -0
  124. data/lib/davinci_pas_test_kit/generated/v2.0.1/requestor/server_inquiry_response_requestor_must_support_test.rb +63 -0
  125. data/lib/davinci_pas_test_kit/generated/v2.0.1/requestor/server_submit_request_requestor_must_support_test.rb +63 -0
  126. data/lib/davinci_pas_test_kit/generated/v2.0.1/requestor/server_submit_response_requestor_must_support_test.rb +63 -0
  127. data/lib/davinci_pas_test_kit/generated/v2.0.1/resource_list.rb +54 -0
  128. data/lib/davinci_pas_test_kit/generated/v2.0.1/server_suite.rb +236 -0
  129. data/lib/davinci_pas_test_kit/generated/v2.0.1/service_request/client_submit_request_service_request_must_support_test.rb +53 -0
  130. data/lib/davinci_pas_test_kit/generated/v2.0.1/service_request/metadata.yml +148 -0
  131. data/lib/davinci_pas_test_kit/generated/v2.0.1/service_request/server_submit_request_service_request_must_support_test.rb +53 -0
  132. data/lib/davinci_pas_test_kit/generated/v2.0.1/subscriber/client_inquiry_request_subscriber_must_support_test.rb +74 -0
  133. data/lib/davinci_pas_test_kit/generated/v2.0.1/subscriber/client_submit_request_subscriber_must_support_test.rb +74 -0
  134. data/lib/davinci_pas_test_kit/generated/v2.0.1/subscriber/metadata.yml +159 -0
  135. data/lib/davinci_pas_test_kit/generated/v2.0.1/subscriber/server_inquiry_request_subscriber_must_support_test.rb +74 -0
  136. data/lib/davinci_pas_test_kit/generated/v2.0.1/subscriber/server_submit_request_subscriber_must_support_test.rb +74 -0
  137. data/lib/davinci_pas_test_kit/generated/v2.0.1/task/metadata.yml +192 -0
  138. data/lib/davinci_pas_test_kit/generated/v2.0.1/task/server_inquiry_response_task_must_support_test.rb +61 -0
  139. data/lib/davinci_pas_test_kit/generated/v2.0.1/task/server_submit_response_task_must_support_test.rb +61 -0
  140. data/lib/davinci_pas_test_kit/generator/group_generator.rb +440 -0
  141. data/lib/davinci_pas_test_kit/generator/group_metadata.rb +73 -0
  142. data/lib/davinci_pas_test_kit/generator/group_metadata_extractor.rb +244 -0
  143. data/lib/davinci_pas_test_kit/generator/ig_loader.rb +78 -0
  144. data/lib/davinci_pas_test_kit/generator/ig_metadata.rb +66 -0
  145. data/lib/davinci_pas_test_kit/generator/ig_metadata_extractor.rb +54 -0
  146. data/lib/davinci_pas_test_kit/generator/ig_resources.rb +74 -0
  147. data/lib/davinci_pas_test_kit/generator/must_support_check_profiles.rb +86 -0
  148. data/lib/davinci_pas_test_kit/generator/must_support_metadata_extractor.rb +327 -0
  149. data/lib/davinci_pas_test_kit/generator/must_support_test_generator.rb +155 -0
  150. data/lib/davinci_pas_test_kit/generator/naming.rb +50 -0
  151. data/lib/davinci_pas_test_kit/generator/operation_test_generator.rb +136 -0
  152. data/lib/davinci_pas_test_kit/generator/resource_list_generator.rb +59 -0
  153. data/lib/davinci_pas_test_kit/generator/suite_generator.rb +94 -0
  154. data/lib/davinci_pas_test_kit/generator/terminology_binding_metadata_extractor.rb +108 -0
  155. data/lib/davinci_pas_test_kit/generator/validation_test_generator.rb +181 -0
  156. data/lib/davinci_pas_test_kit/generator/value_extractor.rb +48 -0
  157. data/lib/davinci_pas_test_kit/generator.rb +80 -0
  158. data/lib/davinci_pas_test_kit/mock_server.rb +189 -0
  159. data/lib/davinci_pas_test_kit/must_support_test.rb +267 -0
  160. data/lib/davinci_pas_test_kit/pas_bundle_validation.rb +568 -0
  161. data/lib/davinci_pas_test_kit/tags.rb +7 -0
  162. data/lib/davinci_pas_test_kit/urls.rb +39 -0
  163. data/lib/davinci_pas_test_kit/user_input_response.rb +32 -0
  164. data/lib/davinci_pas_test_kit/validation_test.rb +58 -0
  165. data/lib/davinci_pas_test_kit/validator_suppressions.rb +143 -0
  166. data/lib/davinci_pas_test_kit/version.rb +5 -0
  167. data/lib/davinci_pas_test_kit.rb +2 -0
  168. 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,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DaVinciPASTestKit
4
+ AUTH_TAG = 'pas_auth'
5
+ SUBMIT_TAG = 'pas_submit'
6
+ INQUIRE_TAG = 'pas_inquire'
7
+ 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