davinci_pas_test_kit 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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