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,189 @@
1
+ require_relative 'user_input_response'
2
+
3
+ module DaVinciPASTestKit
4
+ # Serve responses to PAS requests
5
+ #
6
+ # Note that there are numerous expected validation issues that can safely be ignored.
7
+ # See here for full list: https://hl7.org/fhir/us/davinci-pas/STU2/qa.html#suppressed
8
+ module MockServer
9
+ def token_response(request, _test = nil, _test_result = nil)
10
+ # Placeholder for a more complete mock token endpoint
11
+ request.response_body = { access_token: SecureRandom.hex, token_type: 'bearer', expires_in: 300 }.to_json
12
+ request.status = 200
13
+ end
14
+
15
+ def claim_response(request, test = nil, test_result = nil)
16
+ request.status = 200
17
+ request.response_headers = { 'Content-Type': 'application/json' }
18
+
19
+ user_inputted_response = UserInputResponse.user_inputted_response(test, test_result)
20
+ if test.present? && test_result.present? && user_inputted_response.present?
21
+ request.response_body = user_inputted_response
22
+ return
23
+ end
24
+
25
+ operation = request&.url&.split('$')&.last
26
+ req_bundle = FHIR.from_contents(request&.request_body)
27
+ claim_entry = req_bundle&.entry&.find { |e| e&.resource&.resourceType == 'Claim' }
28
+ root_url = base_url(claim_entry&.fullUrl)
29
+ return unless ['submit', 'inquire'].include?(operation) && claim_entry.present?
30
+
31
+ claim_response = mock_claim_response(claim_entry.resource, req_bundle, operation, root_url)
32
+
33
+ res_bundle = FHIR::Bundle.new(
34
+ id: SecureRandom.uuid,
35
+ meta: FHIR::Meta.new(profile: if operation == 'submit'
36
+ 'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/profile-pas-response-bundle'
37
+ else
38
+ 'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/profile-pas-inquiry-response-bundle'
39
+ end),
40
+ timestamp: Time.now.utc.iso8601,
41
+ type: 'collection',
42
+ entry: [
43
+ FHIR::Bundle::Entry.new(fullUrl: "urn:uuid:#{claim_response.id}",
44
+ resource: claim_response)
45
+ ]
46
+ )
47
+
48
+ res_bundle.entry.concat(referenced_entities(claim_response, req_bundle.entry, root_url))
49
+
50
+ request.response_body = res_bundle.to_json
51
+ request.status = 200
52
+ request.response_headers = { 'Content-Type': 'application/json' }
53
+ end
54
+
55
+ # Note that references from the claim to other resources in the bundle need to be changed to absolute URLs
56
+ # if they are relative, because the ClaimResponse's fullUrl is a urn:uuid
57
+ #
58
+ # @private
59
+ def mock_claim_response(claim, bundle, operation, root_url)
60
+ return FHIR::ClaimResponse.new(id: SecureRandom.uuid) if claim.blank?
61
+
62
+ now = Time.now.utc
63
+
64
+ FHIR::ClaimResponse.new(
65
+ id: SecureRandom.uuid,
66
+ meta: FHIR::Meta.new(profile: if operation == 'submit'
67
+ 'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/profile-claimresponse'
68
+ else
69
+ 'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/profile-claiminquiryresponse'
70
+ end),
71
+ identifier: claim.identifier,
72
+ type: claim.type,
73
+ status: claim.status,
74
+ use: claim.use,
75
+ patient: absolute_reference(claim.patient, bundle.entry, root_url),
76
+ created: now.iso8601,
77
+ insurer: absolute_reference(claim.insurer, bundle.entry, root_url),
78
+ requestor: absolute_reference(claim.provider, bundle.entry, root_url),
79
+ outcome: 'complete',
80
+ item: claim.item.map do |item|
81
+ FHIR::ClaimResponse::Item.new(
82
+ extension: [
83
+ FHIR::Extension.new(
84
+ url: 'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/extension-itemPreAuthIssueDate',
85
+ valueDate: now.strftime('%Y-%m-%d')
86
+ ),
87
+ FHIR::Extension.new(
88
+ url: 'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/extension-itemPreAuthPeriod',
89
+ valuePeriod: FHIR::Period.new(start: now.strftime('%Y-%m-%d'),
90
+ end: (now + 1.month).strftime('%Y-%m-%d'))
91
+ )
92
+ ],
93
+ itemSequence: item.sequence,
94
+ adjudication: [
95
+ FHIR::ClaimResponse::Item::Adjudication.new(
96
+ extension: [
97
+ FHIR::Extension.new(
98
+ url: 'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/extension-reviewAction',
99
+ extension: [
100
+ FHIR::Extension.new(
101
+ url: 'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/extension-reviewActionCode',
102
+ valueCodeableConcept: FHIR::CodeableConcept.new(
103
+ coding: [
104
+ FHIR::Coding.new(
105
+ system: 'https://codesystem.x12.org/005010/306',
106
+ code: 'A1',
107
+ display: 'Certified in total'
108
+ )
109
+ ]
110
+ )
111
+ )
112
+ ]
113
+ )
114
+ ],
115
+ category: FHIR::CodeableConcept.new(
116
+ coding: [
117
+ FHIR::Coding.new(system: 'http://terminology.hl7.org/CodeSystem/adjudication', code: 'submitted')
118
+ ]
119
+ )
120
+ )
121
+ ]
122
+ )
123
+ end
124
+ )
125
+ end
126
+
127
+ def extract_client_id(request)
128
+ URI.decode_www_form(request.request_body).to_h['client_id']
129
+ end
130
+
131
+ # Header expected to be a bearer token of the form "Bearer: <token>"
132
+ def extract_bearer_token(request)
133
+ request.request_header('Authorization')&.value&.split&.last
134
+ end
135
+
136
+ def extract_token_from_query_params(request)
137
+ request.query_parameters['token']
138
+ end
139
+
140
+ # Drop the last two segments of a URL, i.e. the resource type and ID of a FHIR resource
141
+ # e.g. http://example.org/fhir/Patient/123 -> http://example.org/fhir
142
+ # @private
143
+ def base_url(url)
144
+ return unless url.start_with?('http://', 'https://')
145
+
146
+ # Drop everything after the second to last '/', ignoring a trailing slash
147
+ url.sub(%r{/[^/]*/[^/]*(/)?\z}, '')
148
+ end
149
+
150
+ # @private
151
+ def referenced_entities(resource, entries, root_url)
152
+ matches = []
153
+ attributes = resource&.source_hash&.keys
154
+ attributes.each do |attr|
155
+ value = resource.send(attr.to_sym)
156
+ if value.is_a?(FHIR::Reference) && value.reference.present?
157
+ match = find_matching_entry(value.reference, entries, root_url)
158
+ if match.present? && matches.none?(match)
159
+ value.reference = match.fullUrl
160
+ matches.concat([match], referenced_entities(match.resource, entries, root_url))
161
+ end
162
+ elsif value.is_a?(Array) && value.all? { |elmt| elmt.is_a?(FHIR::Model) }
163
+ value.each { |val| matches.concat(referenced_entities(val, entries, root_url)) }
164
+ end
165
+ end
166
+
167
+ matches
168
+ end
169
+
170
+ # @private
171
+ def absolute_reference(ref, entries, root_url)
172
+ url = find_matching_entry(ref&.reference, entries, root_url)&.fullUrl
173
+ ref.reference = url if url
174
+ ref
175
+ end
176
+
177
+ # @private
178
+ def find_matching_entry(ref, entries, root_url = '')
179
+ ref = "#{root_url}/#{ref}" if relative_reference?(ref) && root_url&.present?
180
+
181
+ entries&.find { |entry| entry&.fullUrl == ref }
182
+ end
183
+
184
+ # @private
185
+ def relative_reference?(ref)
186
+ ref&.count('/') == 1
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,267 @@
1
+ require_relative 'fhir_resource_navigation'
2
+
3
+ module DaVinciPASTestKit
4
+ module MustSupportTest
5
+ extend Forwardable
6
+ include FHIRResourceNavigation
7
+
8
+ def_delegators 'self.class', :metadata
9
+
10
+ def all_scratch_resources
11
+ scratch_resources[:all] ||= []
12
+ end
13
+
14
+ def reset_variables
15
+ @missing_elements = @missing_slices = @missing_extensions = nil
16
+ end
17
+
18
+ def tagged_resources(tag)
19
+ resources = []
20
+ load_tagged_requests(tag)
21
+ return resources if requests.empty?
22
+
23
+ requests.each do |req|
24
+ begin
25
+ bundle = FHIR.from_contents(req.request_body)
26
+ rescue StandardError
27
+ next
28
+ end
29
+
30
+ next unless bundle.is_a?(FHIR::Bundle)
31
+
32
+ resources << bundle
33
+ entry_resources = bundle.entry.map(&:resource)
34
+ resources.concat(entry_resources)
35
+ end
36
+
37
+ resources
38
+ end
39
+
40
+ def all_must_support_errors
41
+ @all_must_support_errors ||= []
42
+ end
43
+
44
+ def validate_must_support
45
+ assert all_must_support_errors.empty?, all_must_support_errors.join("\n")
46
+ end
47
+
48
+ def perform_must_support_test(resources)
49
+ assert resources.present?, "No #{resource_type} resources were found"
50
+
51
+ missing_elements(resources)
52
+ missing_slices(resources)
53
+ missing_extensions(resources)
54
+
55
+ handle_must_support_choices if metadata.must_supports[:choices].present?
56
+
57
+ return unless (missing_elements + missing_slices + missing_extensions).compact.reject(&:empty?).present?
58
+
59
+ all_must_support_errors << "Could not find #{missing_must_support_strings.join(', ')} " \
60
+ "in the #{resources.length} provided #{resource_type} resource(s)."
61
+
62
+ all_must_support_errors.reject! { |err| err.downcase.include?('x12') }
63
+ reset_variables
64
+
65
+ # pass if (missing_elements + missing_slices + missing_extensions).empty?
66
+
67
+ # assert false, "Could not find #{missing_must_support_strings.join(', ')} in the #{resources.length} " \
68
+ # "provided #{resource_type} resource(s)"
69
+ end
70
+
71
+ def handle_must_support_choices
72
+ missing_elements.delete_if do |element|
73
+ choices = metadata.must_supports[:choices].find { |choice| choice[:paths]&.include?(element[:path]) }
74
+ is_any_choice_supported?(choices)
75
+ end
76
+
77
+ missing_extensions.delete_if do |extension|
78
+ choices = metadata.must_supports[:choices].find { |choice| choice[:extension_ids]&.include?(extension[:id]) }
79
+ is_any_choice_supported?(choices)
80
+ end
81
+
82
+ missing_slices.delete_if do |slice|
83
+ choices = metadata.must_supports[:choices].find { |choice| choice[:slice_names]&.include?(slice[:slice_id]) }
84
+ is_any_choice_supported?(choices)
85
+ end
86
+ end
87
+
88
+ def is_any_choice_supported?(choices)
89
+ choices.present? &&
90
+ (
91
+ choices[:paths]&.any? { |path| missing_elements.none? { |element| element[:path] == path } } ||
92
+ choices[:extension_ids]&.any? do |extension_id|
93
+ missing_extensions.none? do |extension|
94
+ extension[:id] == extension_id
95
+ end
96
+ end ||
97
+ choices[:slice_names]&.any? { |slice_name| missing_slices.none? { |slice| slice[:slice_id] == slice_name } }
98
+ )
99
+ end
100
+
101
+ def missing_must_support_strings
102
+ missing_elements.map { |element_definition| missing_element_string(element_definition) } +
103
+ missing_slices.map { |slice_definition| slice_definition[:name] } +
104
+ missing_extensions.map { |extension_definition| extension_definition[:id] }
105
+ end
106
+
107
+ def missing_element_string(element_definition)
108
+ if element_definition[:fixed_value].present?
109
+ "#{element_definition[:path]}:#{element_definition[:fixed_value]}"
110
+ else
111
+ element_definition[:path]
112
+ end
113
+ end
114
+
115
+ def exclude_uscdi_only_test?
116
+ config.options[:exclude_uscdi_only_test] == true
117
+ end
118
+
119
+ def must_support_extensions
120
+ if exclude_uscdi_only_test?
121
+ metadata.must_supports[:extensions].reject { |extension| extension[:uscdi_only] }
122
+ else
123
+ metadata.must_supports[:extensions]
124
+ end
125
+ end
126
+
127
+ def missing_extensions(resources = [])
128
+ @missing_extensions ||=
129
+ must_support_extensions.select do |extension_definition|
130
+ resources.none? do |resource|
131
+ path = extension_definition[:path]
132
+ if extension_definition[:path] == 'extension'
133
+ resource.extension.any? { |extension| extension.url == extension_definition[:url] }
134
+ else
135
+ extension = find_a_value_at(resource, path) do |el|
136
+ el.url == extension_definition[:url]
137
+ end
138
+
139
+ extension&.url == extension_definition[:url]
140
+ end
141
+ end
142
+ end
143
+ end
144
+
145
+ def must_support_elements
146
+ if exclude_uscdi_only_test?
147
+ metadata.must_supports[:elements].reject { |element| element[:uscdi_only] }
148
+ else
149
+ metadata.must_supports[:elements]
150
+ end
151
+ end
152
+
153
+ def missing_elements(resources = [])
154
+ @missing_elements ||=
155
+ must_support_elements.select do |element_definition|
156
+ # PAS: The MS Claim.supportingInfo slices do not have timing[x]
157
+ next if resource_type == 'Claim' && element_definition[:path] == 'supportingInfo.timing[x]'
158
+
159
+ resources.none? do |resource|
160
+ path = element_definition[:path] # .delete_suffix('[x]')
161
+ value_found = find_a_value_at(resource, path) do |value|
162
+ value_without_extensions =
163
+ value.respond_to?(:to_hash) ? value.to_hash.except('extension') : value
164
+
165
+ (value_without_extensions.present? || value_without_extensions == false) &&
166
+ (element_definition[:fixed_value].blank? || value == element_definition[:fixed_value])
167
+ end
168
+ # Note that false.present? => false, which is why we need to add this extra check
169
+ value_found.present? || value_found == false
170
+ end
171
+ end
172
+ @missing_elements.compact
173
+ end
174
+
175
+ def must_support_slices
176
+ if exclude_uscdi_only_test?
177
+ metadata.must_supports[:slices].reject { |slice| slice[:uscdi_only] }
178
+ else
179
+ metadata.must_supports[:slices]
180
+ end
181
+ end
182
+
183
+ def missing_slices(resources = [])
184
+ @missing_slices ||=
185
+ must_support_slices.select do |slice|
186
+ resources.none? do |resource|
187
+ path = slice[:path] # .delete_suffix('[x]')
188
+ find_slice(resource, path, slice[:discriminator]).present?
189
+ end
190
+ end
191
+ end
192
+
193
+ def find_slice(resource, path, discriminator)
194
+ find_a_value_at(resource, path) do |element|
195
+ case discriminator[:type]
196
+ when 'patternCodeableConcept'
197
+ coding_path = discriminator[:path].present? ? "#{discriminator[:path]}.coding" : 'coding'
198
+ find_a_value_at(element, coding_path) do |coding|
199
+ coding.code == discriminator[:code] && coding.system == discriminator[:system]
200
+ end
201
+ when 'patternCoding'
202
+ coding_path = discriminator[:path].present? ? discriminator[:path] : ''
203
+ find_a_value_at(element, coding_path) do |coding|
204
+ coding.code == discriminator[:code] && coding.system == discriminator[:system]
205
+ end
206
+ when 'patternIdentifier'
207
+ find_a_value_at(element, discriminator[:path]) { |identifier| identifier.system == discriminator[:system] }
208
+ when 'value'
209
+ values = discriminator[:values].map { |value| value.merge(path: value[:path].split('.')) }
210
+ find_slice_by_values(element, values)
211
+ when 'type'
212
+ case discriminator[:code]
213
+ when 'Date'
214
+ begin
215
+ Date.parse(element)
216
+ rescue ArgumentError
217
+ false
218
+ end
219
+ when 'DateTime'
220
+ begin
221
+ DateTime.parse(element)
222
+ rescue ArgumentError
223
+ false
224
+ end
225
+ when 'String'
226
+ element.is_a? String
227
+ else
228
+ res = element.try(:resource) || element
229
+ res.is_a? FHIR.const_get(discriminator[:code])
230
+ end
231
+ when 'requiredBinding'
232
+ coding_path = discriminator[:path].present? ? "#{discriminator[:path]}.coding" : 'coding'
233
+ find_a_value_at(element, coding_path) { |coding| discriminator[:values].include?(coding.code) }
234
+ end
235
+ end
236
+ end
237
+
238
+ def find_slice_by_values(element, value_definitions)
239
+ path_prefixes = value_definitions.map { |value_definition| value_definition[:path].first }.uniq
240
+ Array.wrap(element).find do |el|
241
+ path_prefixes.all? do |path_prefix|
242
+ value_definitions_for_path =
243
+ value_definitions
244
+ .select { |value_definition| value_definition[:path].first == path_prefix }
245
+ .each { |value_definition| value_definition[:path].shift }
246
+ find_a_value_at(el, path_prefix) do |el_found|
247
+ child_element_value_definitions, current_element_value_definitions =
248
+ value_definitions_for_path.partition { |value_definition| value_definition[:path].present? }
249
+ current_element_values_match = current_element_value_definitions.all? do |value_definition|
250
+ (value_definition[:value].present? && value_definition[:value] == el_found) ||
251
+ (value_definition[:value].blank? && el_found.present?)
252
+ end
253
+
254
+ child_element_values_match =
255
+ if child_element_value_definitions.present?
256
+ find_slice_by_values(el_found, child_element_value_definitions)
257
+ else
258
+ true
259
+ end
260
+
261
+ current_element_values_match && child_element_values_match
262
+ end
263
+ end
264
+ end
265
+ end
266
+ end
267
+ end