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,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