davinci_crd_test_kit 0.13.0 → 0.14.1

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 (351) hide show
  1. checksums.yaml +4 -4
  2. data/config/presets/inferno_crd_client_suite.json.erb +20 -14
  3. data/config/presets/inferno_crd_client_suite_prefetch_subset_v221.json.erb +125 -0
  4. data/config/presets/inferno_crd_client_suite_v221.json.erb +188 -0
  5. data/config/presets/inferno_crd_server_suite.json.erb +58 -1
  6. data/config/presets/inferno_crd_server_suite_v221.json.erb +94 -0
  7. data/config/presets/ri_crd_request_generator.json_v221.json.erb +13 -0
  8. data/config/presets/ri_crd_server.json.erb +19 -19
  9. data/lib/davinci_crd_test_kit/client/client_base_urls.rb +80 -0
  10. data/lib/davinci_crd_test_kit/{client_hook_request_validation.rb → client/client_hook_request_validation.rb} +1 -1
  11. data/lib/davinci_crd_test_kit/client/crd_client_options.rb +30 -0
  12. data/lib/davinci_crd_test_kit/client/endpoints/cds_services_discovery_handler.rb +34 -0
  13. data/lib/davinci_crd_test_kit/client/endpoints/custom_service_response.rb +342 -0
  14. data/lib/davinci_crd_test_kit/client/endpoints/gather_response_generation_data.rb +410 -0
  15. data/lib/davinci_crd_test_kit/client/endpoints/hook_request_endpoint.rb +239 -0
  16. data/lib/davinci_crd_test_kit/{mock_service_response.rb → client/endpoints/mock_service_response.rb} +165 -59
  17. data/lib/davinci_crd_test_kit/{card_responses → client/endpoints/mocked_card_responses}/companions_prerequisites.json +1 -0
  18. data/lib/davinci_crd_test_kit/{card_responses → client/endpoints/mocked_card_responses}/create_update_coverage_information.json +3 -2
  19. data/lib/davinci_crd_test_kit/{card_responses → client/endpoints/mocked_card_responses}/external_reference.json +1 -1
  20. data/lib/davinci_crd_test_kit/{card_responses → client/endpoints/mocked_card_responses}/launch_smart_app.json +1 -1
  21. data/lib/davinci_crd_test_kit/{card_responses → client/endpoints/mocked_card_responses}/propose_alternate_request.json +1 -0
  22. data/lib/davinci_crd_test_kit/{card_responses → client/endpoints/mocked_card_responses}/request_form_completion.json +17 -16
  23. data/lib/davinci_crd_test_kit/client/multi_request_message_helper.rb +35 -0
  24. data/lib/davinci_crd_test_kit/client/tagged_request_load_helper.rb +38 -0
  25. data/lib/davinci_crd_test_kit/client/v2.0.1/api/client_fhir_api_create_test.rb +43 -0
  26. data/lib/davinci_crd_test_kit/client/v2.0.1/api/client_fhir_api_read_test.rb +43 -0
  27. data/lib/davinci_crd_test_kit/client/v2.0.1/api/client_fhir_api_search_test.rb +234 -0
  28. data/lib/davinci_crd_test_kit/client/v2.0.1/api/client_fhir_api_update_test.rb +43 -0
  29. data/lib/davinci_crd_test_kit/client/v2.0.1/api/client_fhir_api_validation_test.rb +63 -0
  30. data/lib/davinci_crd_test_kit/client/v2.0.1/auth/decode_auth_token_test.rb +65 -0
  31. data/lib/davinci_crd_test_kit/client/v2.0.1/auth/retrieve_jwks_test.rb +109 -0
  32. data/lib/davinci_crd_test_kit/client/v2.0.1/auth/token_header_test.rb +70 -0
  33. data/lib/davinci_crd_test_kit/client/v2.0.1/auth/token_payload_test.rb +85 -0
  34. data/lib/davinci_crd_test_kit/{routes/cds-services.json → client/v2.0.1/cds-services-v201.json} +1 -1
  35. data/lib/davinci_crd_test_kit/client/v2.0.1/client_appointment_book_group.rb +108 -0
  36. data/lib/davinci_crd_test_kit/client/v2.0.1/client_card_must_support_group.rb +31 -0
  37. data/lib/davinci_crd_test_kit/client/v2.0.1/client_encounter_discharge_group.rb +105 -0
  38. data/lib/davinci_crd_test_kit/client/v2.0.1/client_encounter_start_group.rb +105 -0
  39. data/lib/davinci_crd_test_kit/client/v2.0.1/client_fhir_api_group.rb +790 -0
  40. data/lib/davinci_crd_test_kit/client/v2.0.1/client_hooks_group.rb +74 -0
  41. data/lib/davinci_crd_test_kit/client/v2.0.1/client_order_dispatch_group.rb +111 -0
  42. data/lib/davinci_crd_test_kit/client/v2.0.1/client_order_select_group.rb +116 -0
  43. data/lib/davinci_crd_test_kit/client/v2.0.1/client_order_sign_group.rb +113 -0
  44. data/lib/davinci_crd_test_kit/{client_registration_group.rb → client/v2.0.1/client_registration_group.rb} +12 -8
  45. data/lib/davinci_crd_test_kit/client/v2.0.1/client_urls.rb +13 -0
  46. data/lib/davinci_crd_test_kit/client/v2.0.1/crd_client_suite.rb +134 -0
  47. data/lib/davinci_crd_test_kit/client/v2.0.1/invocation/appointment_book_receive_request_test.rb +129 -0
  48. data/lib/davinci_crd_test_kit/client/v2.0.1/invocation/encounter_discharge_receive_request_test.rb +126 -0
  49. data/lib/davinci_crd_test_kit/client/v2.0.1/invocation/encounter_start_receive_request_test.rb +126 -0
  50. data/lib/davinci_crd_test_kit/client/v2.0.1/invocation/order_dispatch_receive_request_test.rb +138 -0
  51. data/lib/davinci_crd_test_kit/client/v2.0.1/invocation/order_select_receive_request_test.rb +134 -0
  52. data/lib/davinci_crd_test_kit/client/v2.0.1/invocation/order_sign_receive_request_test.rb +136 -0
  53. data/lib/davinci_crd_test_kit/client/v2.0.1/must_support/client_card_must_support_coverage_information.rb +93 -0
  54. data/lib/davinci_crd_test_kit/client/v2.0.1/must_support/client_card_must_support_external_reference.rb +62 -0
  55. data/lib/davinci_crd_test_kit/client/v2.0.1/must_support/client_card_must_support_instructions.rb +62 -0
  56. data/lib/davinci_crd_test_kit/client/v2.0.1/registration/client_registration_verification_test.rb +94 -0
  57. data/lib/davinci_crd_test_kit/client/v2.0.1/registration/client_service_registration_attestation_test.rb +40 -0
  58. data/lib/davinci_crd_test_kit/client/v2.0.1/verify_request/hook_request_fetched_data_test.rb +86 -0
  59. data/lib/davinci_crd_test_kit/client/v2.0.1/verify_request/hook_request_optional_fields_test.rb +63 -0
  60. data/lib/davinci_crd_test_kit/client/v2.0.1/verify_request/hook_request_prefetch_equals_queried_test.rb +96 -0
  61. data/lib/davinci_crd_test_kit/client/v2.0.1/verify_request/hook_request_required_fields_test.rb +55 -0
  62. data/lib/davinci_crd_test_kit/client/v2.0.1/verify_request/hook_request_valid_context_test.rb +70 -0
  63. data/lib/davinci_crd_test_kit/client/v2.0.1/verify_request/hook_request_valid_prefetch_test.rb +62 -0
  64. data/lib/davinci_crd_test_kit/client/v2.0.1/verify_response/client_display_cards_attest.rb +83 -0
  65. data/lib/davinci_crd_test_kit/client/v2.0.1/verify_response/inferno_response_validation.rb +79 -0
  66. data/lib/davinci_crd_test_kit/client/v2.2.1/api/client_coverage_info_update_test.rb +212 -0
  67. data/lib/davinci_crd_test_kit/client/v2.2.1/api/client_crd_update_verification_group.rb +18 -0
  68. data/lib/davinci_crd_test_kit/client/v2.2.1/auth/decode_auth_token_test.rb +69 -0
  69. data/lib/davinci_crd_test_kit/client/v2.2.1/auth/retrieve_jwks_test.rb +120 -0
  70. data/lib/davinci_crd_test_kit/client/v2.2.1/auth/token_header_test.rb +92 -0
  71. data/lib/davinci_crd_test_kit/client/v2.2.1/auth/token_payload_test.rb +93 -0
  72. data/lib/davinci_crd_test_kit/client/v2.2.1/cds-services-prefetch-subset-v221.json +198 -0
  73. data/lib/davinci_crd_test_kit/client/v2.2.1/cds-services-v221.json +202 -0
  74. data/lib/davinci_crd_test_kit/client/v2.2.1/client_appointment_book_group.rb +109 -0
  75. data/lib/davinci_crd_test_kit/client/v2.2.1/client_cross_hook_group.rb +28 -0
  76. data/lib/davinci_crd_test_kit/client/v2.2.1/client_encounter_discharge_group.rb +102 -0
  77. data/lib/davinci_crd_test_kit/client/v2.2.1/client_encounter_start_group.rb +101 -0
  78. data/lib/davinci_crd_test_kit/client/v2.2.1/client_fhir_api_group.rb +88 -0
  79. data/lib/davinci_crd_test_kit/client/v2.2.1/client_hooks_group.rb +84 -0
  80. data/lib/davinci_crd_test_kit/client/v2.2.1/client_long_running_hook_group.rb +32 -0
  81. data/lib/davinci_crd_test_kit/client/v2.2.1/client_order_dispatch_group.rb +107 -0
  82. data/lib/davinci_crd_test_kit/client/v2.2.1/client_order_select_group.rb +108 -0
  83. data/lib/davinci_crd_test_kit/client/v2.2.1/client_order_sign_group.rb +113 -0
  84. data/lib/davinci_crd_test_kit/client/v2.2.1/client_registration_group.rb +33 -0
  85. data/lib/davinci_crd_test_kit/client/v2.2.1/client_urls.rb +27 -0
  86. data/lib/davinci_crd_test_kit/client/v2.2.1/crd_client_suite.rb +234 -0
  87. data/lib/davinci_crd_test_kit/client/v2.2.1/cross_hook/client_card_must_support_coverage_information_test.rb +63 -0
  88. data/lib/davinci_crd_test_kit/client/v2.2.1/cross_hook/client_fhirpath_collection_as_comma_delimited_string_test.rb +60 -0
  89. data/lib/davinci_crd_test_kit/client/v2.2.1/cross_hook/client_hook_instances_unique_test.rb +45 -0
  90. data/lib/davinci_crd_test_kit/client/v2.2.1/cross_hook/client_location_address_propagation_test.rb +135 -0
  91. data/lib/davinci_crd_test_kit/client/v2.2.1/cross_hook/client_prefetch_complete_and_subset_test.rb +103 -0
  92. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/appointment_book_receive_request_test.rb +156 -0
  93. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/encounter_discharge_receive_request_test.rb +157 -0
  94. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/encounter_start_receive_request_test.rb +157 -0
  95. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/order_dispatch_receive_request_test.rb +165 -0
  96. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/order_select_receive_request_test.rb +165 -0
  97. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/order_sign_receive_request_test.rb +165 -0
  98. data/lib/davinci_crd_test_kit/client/v2.2.1/long_running/client_long_running_receive_request_test.rb +64 -0
  99. data/lib/davinci_crd_test_kit/client/v2.2.1/long_running/client_skip_long_running_attestation_test.rb +54 -0
  100. data/lib/davinci_crd_test_kit/client/v2.2.1/registration/client_registration_verification_test.rb +161 -0
  101. data/lib/davinci_crd_test_kit/client/v2.2.1/registration/client_service_registration_attestation_test.rb +108 -0
  102. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_conformance_test.rb +47 -0
  103. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_coverage_verification_test.rb +152 -0
  104. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_data_fetch_verification_test.rb +55 -0
  105. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_granted_scopes_test.rb +123 -0
  106. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_prefetch_complete_test.rb +133 -0
  107. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_prefetch_profiles_test.rb +55 -0
  108. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_requested_version_test.rb +54 -0
  109. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_secured_transport_test.rb +48 -0
  110. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_response/client_display_cards_attest.rb +75 -0
  111. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_response/hook_response_support_coverage_information_test.rb +30 -0
  112. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_response/inferno_response_validation.rb +77 -0
  113. data/lib/davinci_crd_test_kit/cross_suite/base_urls.rb +20 -0
  114. data/lib/davinci_crd_test_kit/cross_suite/cards_identification.rb +321 -0
  115. data/lib/davinci_crd_test_kit/{cards_validation.rb → cross_suite/cards_validation.rb} +104 -47
  116. data/lib/davinci_crd_test_kit/cross_suite/coverage-information_stu201_metadata.yml +27 -0
  117. data/lib/davinci_crd_test_kit/cross_suite/coverage-information_stu221_metadata.yml +60 -0
  118. data/lib/davinci_crd_test_kit/cross_suite/fhirpath_on_cds_request.rb +177 -0
  119. data/lib/davinci_crd_test_kit/{hook_request_field_validation.rb → cross_suite/hook_request_field_validation.rb} +282 -203
  120. data/lib/davinci_crd_test_kit/cross_suite/logical_models_override_helper.rb +220 -0
  121. data/lib/davinci_crd_test_kit/cross_suite/prefetch_completeness_checker.rb +476 -0
  122. data/lib/davinci_crd_test_kit/cross_suite/prefetch_contents_validation.rb +81 -0
  123. data/lib/davinci_crd_test_kit/cross_suite/prefetch_profile_validation.rb +48 -0
  124. data/lib/davinci_crd_test_kit/cross_suite/profiles_and_resource_types.rb +63 -0
  125. data/lib/davinci_crd_test_kit/cross_suite/replace_tokens.rb +38 -0
  126. data/lib/davinci_crd_test_kit/cross_suite/requests_logical_model_validation.rb +203 -0
  127. data/lib/davinci_crd_test_kit/cross_suite/response_logical_model_validation.rb +435 -0
  128. data/lib/davinci_crd_test_kit/{suggestion_actions_validation.rb → cross_suite/suggestion_actions_validation.rb} +70 -50
  129. data/lib/davinci_crd_test_kit/cross_suite/tags.rb +46 -0
  130. data/lib/davinci_crd_test_kit/metadata.rb +10 -44
  131. data/lib/davinci_crd_test_kit/requirements/cds-hooks-library_1.0.1_requirements.xlsx +0 -0
  132. data/lib/davinci_crd_test_kit/requirements/cds-hooks_2.0_requirements.xlsx +0 -0
  133. data/lib/davinci_crd_test_kit/requirements/cds-hooks_3.0.0-ballot_requirements.xlsx +0 -0
  134. data/lib/davinci_crd_test_kit/requirements/davinci_crd_test_kit_requirements.csv +742 -65
  135. data/lib/davinci_crd_test_kit/requirements/generated/crd_client_requirements_coverage.csv +279 -54
  136. data/lib/davinci_crd_test_kit/requirements/generated/crd_client_v221_requirements_coverage.csv +1430 -0
  137. data/lib/davinci_crd_test_kit/requirements/generated/crd_server_requirements_coverage.csv +36 -45
  138. data/lib/davinci_crd_test_kit/requirements/generated/crd_server_v221_requirements_coverage.csv +143 -0
  139. data/lib/davinci_crd_test_kit/requirements/hl7.fhir.us.davinci-crd_2.0.1_requirements.xlsx +0 -0
  140. data/lib/davinci_crd_test_kit/requirements/hl7.fhir.us.davinci-crd_2.2.1_requirements.xlsx +0 -0
  141. data/lib/davinci_crd_test_kit/server/endpoints/jwk_set_endpoint_handler.rb +13 -0
  142. data/lib/davinci_crd_test_kit/server/endpoints/mock_ehr/fhir_create_endpoint.rb +23 -0
  143. data/lib/davinci_crd_test_kit/server/endpoints/mock_ehr/fhir_delete_endpoint.rb +30 -0
  144. data/lib/davinci_crd_test_kit/server/endpoints/mock_ehr/fhir_metadata_endpoint.rb +112 -0
  145. data/lib/davinci_crd_test_kit/server/endpoints/mock_ehr/fhir_read_endpoint.rb +21 -0
  146. data/lib/davinci_crd_test_kit/server/endpoints/mock_ehr/fhir_request_handler.rb +261 -0
  147. data/lib/davinci_crd_test_kit/server/endpoints/mock_ehr/fhir_search_endpoint.rb +561 -0
  148. data/lib/davinci_crd_test_kit/server/endpoints/mock_ehr/fhir_update_endpoint.rb +24 -0
  149. data/lib/davinci_crd_test_kit/server/endpoints/mock_ehr/stress-test-Bundle.json +54687 -0
  150. data/lib/davinci_crd_test_kit/server/endpoints/mock_ehr_endpoints.rb +95 -0
  151. data/lib/davinci_crd_test_kit/server/jobs/invoke_hook.rb +225 -0
  152. data/lib/davinci_crd_test_kit/server/jobs/invoke_hook_no_special_requests.rb +17 -0
  153. data/lib/davinci_crd_test_kit/server/jobs/invoke_hook_with_bad_auth.rb +14 -0
  154. data/lib/davinci_crd_test_kit/{jwt_helper.rb → server/jwt_helper.rb} +1 -12
  155. data/lib/davinci_crd_test_kit/server/resource_extractor.rb +68 -0
  156. data/lib/davinci_crd_test_kit/server/server_abstract_invoke_hook_test.rb +183 -0
  157. data/lib/davinci_crd_test_kit/server/server_base_urls.rb +30 -0
  158. data/lib/davinci_crd_test_kit/{server_hook_helper.rb → server/server_hook_helper.rb} +3 -3
  159. data/lib/davinci_crd_test_kit/{server_hook_request_validation.rb → server/server_hook_request_validation.rb} +1 -1
  160. data/lib/davinci_crd_test_kit/{test_helper.rb → server/server_test_helper.rb} +7 -3
  161. data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/Appointment.yml +5 -0
  162. data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/ClaimResponse.yml +5 -0
  163. data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/CommunicationRequest.yml +5 -0
  164. data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/Coverage.yml +21 -0
  165. data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/Device.yml +5 -0
  166. data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/DeviceRequest.yml +5 -0
  167. data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/Encounter.yml +7 -0
  168. data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/Location.yml +5 -0
  169. data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/MedicationRequest.yml +5 -0
  170. data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/NutritionOrder.yml +5 -0
  171. data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/Organization.yml +5 -0
  172. data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/Patient.yml +5 -0
  173. data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/Practitioner.yml +5 -0
  174. data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/PractitionerRole.yml +40 -0
  175. data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/ServiceRequest.yml +5 -0
  176. data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/Task.yml +5 -0
  177. data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/VisionPrescription.yml +5 -0
  178. data/lib/davinci_crd_test_kit/server/v2.0.1/crd_server_suite.rb +99 -0
  179. data/lib/davinci_crd_test_kit/server/v2.0.1/discovery/discovery_endpoint_test.rb +90 -0
  180. data/lib/davinci_crd_test_kit/server/v2.0.1/discovery/discovery_services_validation_test.rb +67 -0
  181. data/lib/davinci_crd_test_kit/server/v2.0.1/interaction/server_invoke_hook_test.rb +12 -0
  182. data/lib/davinci_crd_test_kit/server/v2.0.1/must_support/coverage_information_system_action_across_hooks_validation_test.rb +34 -0
  183. data/lib/davinci_crd_test_kit/server/v2.0.1/must_support/external_reference_card_across_hooks_validation_test.rb +30 -0
  184. data/lib/davinci_crd_test_kit/server/v2.0.1/must_support/instructions_card_received_across_hooks_test.rb +27 -0
  185. data/lib/davinci_crd_test_kit/server/v2.0.1/server_appointment_book_group.rb +191 -0
  186. data/lib/davinci_crd_test_kit/server/v2.0.1/server_demonstrate_hook_response_group.rb +93 -0
  187. data/lib/davinci_crd_test_kit/server/v2.0.1/server_discovery_group.rb +62 -0
  188. data/lib/davinci_crd_test_kit/server/v2.0.1/server_encounter_discharge_group.rb +186 -0
  189. data/lib/davinci_crd_test_kit/server/v2.0.1/server_encounter_start_group.rb +186 -0
  190. data/lib/davinci_crd_test_kit/server/v2.0.1/server_hooks_group.rb +73 -0
  191. data/lib/davinci_crd_test_kit/server/v2.0.1/server_order_dispatch_group.rb +191 -0
  192. data/lib/davinci_crd_test_kit/server/v2.0.1/server_order_select_group.rb +211 -0
  193. data/lib/davinci_crd_test_kit/server/v2.0.1/server_order_sign_group.rb +216 -0
  194. data/lib/davinci_crd_test_kit/server/v2.0.1/server_required_card_response_validation_group.rb +28 -0
  195. data/lib/davinci_crd_test_kit/server/v2.0.1/server_urls.rb +13 -0
  196. data/lib/davinci_crd_test_kit/server/v2.0.1/verify_request/service_request_context_validation_test.rb +30 -0
  197. data/lib/davinci_crd_test_kit/server/v2.0.1/verify_request/service_request_optional_fields_validation_test.rb +39 -0
  198. data/lib/davinci_crd_test_kit/server/v2.0.1/verify_request/service_request_required_fields_validation_test.rb +40 -0
  199. data/lib/davinci_crd_test_kit/server/v2.0.1/verify_response/additional_orders_validation_test.rb +59 -0
  200. data/lib/davinci_crd_test_kit/server/v2.0.1/verify_response/card_optional_fields_validation_test.rb +51 -0
  201. data/lib/davinci_crd_test_kit/server/v2.0.1/verify_response/coverage_information_system_action_received_test.rb +65 -0
  202. data/lib/davinci_crd_test_kit/server/v2.0.1/verify_response/coverage_information_system_action_validation_test.rb +120 -0
  203. data/lib/davinci_crd_test_kit/server/v2.0.1/verify_response/create_or_update_coverage_info_response_validation_test.rb +70 -0
  204. data/lib/davinci_crd_test_kit/server/v2.0.1/verify_response/external_reference_card_validation_test.rb +37 -0
  205. data/lib/davinci_crd_test_kit/server/v2.0.1/verify_response/form_completion_response_validation_test.rb +67 -0
  206. data/lib/davinci_crd_test_kit/server/v2.0.1/verify_response/instructions_card_received_test.rb +30 -0
  207. data/lib/davinci_crd_test_kit/server/v2.0.1/verify_response/launch_smart_app_card_validation_test.rb +39 -0
  208. data/lib/davinci_crd_test_kit/server/v2.0.1/verify_response/propose_alternate_request_card_validation_test.rb +46 -0
  209. data/lib/davinci_crd_test_kit/server/v2.0.1/verify_response/service_response_validation_test.rb +83 -0
  210. data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/Appointment_withorder.yml +5 -0
  211. data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/Appointment_withoutorder.yml +5 -0
  212. data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/CommunicationRequest.yml +5 -0
  213. data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/Coverage.yml +5 -0
  214. data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/Device.yml +5 -0
  215. data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/DeviceRequest.yml +5 -0
  216. data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/Encounter.yml +5 -0
  217. data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/Location.yml +5 -0
  218. data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/MedicationRequest.yml +5 -0
  219. data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/NutritionOrder.yml +5 -0
  220. data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/Organization.yml +5 -0
  221. data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/Patient.yml +5 -0
  222. data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/PractitionerRole.yml +5 -0
  223. data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/ServiceRequest.yml +5 -0
  224. data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/VisionPrescription.yml +5 -0
  225. data/lib/davinci_crd_test_kit/server/v2.2.1/crd_server_suite.rb +118 -0
  226. data/lib/davinci_crd_test_kit/server/v2.2.1/discovery/discovery_configuration_test.rb +159 -0
  227. data/lib/davinci_crd_test_kit/server/v2.2.1/discovery/discovery_endpoint_test.rb +90 -0
  228. data/lib/davinci_crd_test_kit/server/v2.2.1/discovery/discovery_prefetch_support_test.rb +43 -0
  229. data/lib/davinci_crd_test_kit/server/v2.2.1/discovery/discovery_services_validation_test.rb +121 -0
  230. data/lib/davinci_crd_test_kit/server/v2.2.1/discovery/discovery_standard_prefetch_expressions_test.rb +166 -0
  231. data/lib/davinci_crd_test_kit/server/v2.2.1/interaction/server_invoke_hook_single_request_test.rb +22 -0
  232. data/lib/davinci_crd_test_kit/server/v2.2.1/interaction/server_invoke_hook_test.rb +17 -0
  233. data/lib/davinci_crd_test_kit/server/v2.2.1/must_support/coverage_information_must_support_test.rb +71 -0
  234. data/lib/davinci_crd_test_kit/server/v2.2.1/must_support/coverage_information_system_action_across_hooks_validation_test.rb +36 -0
  235. data/lib/davinci_crd_test_kit/server/v2.2.1/must_support/supported_us_core_versions_test.rb +118 -0
  236. data/lib/davinci_crd_test_kit/server/v2.2.1/server_appointment_book_group.rb +121 -0
  237. data/lib/davinci_crd_test_kit/server/v2.2.1/server_demonstrate_hook_response_group.rb +94 -0
  238. data/lib/davinci_crd_test_kit/server/v2.2.1/server_discovery_group.rb +71 -0
  239. data/lib/davinci_crd_test_kit/server/v2.2.1/server_encounter_discharge_group.rb +107 -0
  240. data/lib/davinci_crd_test_kit/server/v2.2.1/server_encounter_start_group.rb +107 -0
  241. data/lib/davinci_crd_test_kit/server/v2.2.1/server_hooks_group.rb +73 -0
  242. data/lib/davinci_crd_test_kit/server/v2.2.1/server_order_dispatch_group.rb +121 -0
  243. data/lib/davinci_crd_test_kit/server/v2.2.1/server_order_select_group.rb +115 -0
  244. data/lib/davinci_crd_test_kit/server/v2.2.1/server_order_sign_group.rb +132 -0
  245. data/lib/davinci_crd_test_kit/server/v2.2.1/server_required_card_response_validation_group.rb +30 -0
  246. data/lib/davinci_crd_test_kit/server/v2.2.1/server_urls.rb +13 -0
  247. data/lib/davinci_crd_test_kit/server/v2.2.1/specific_coverage_response_group.rb +23 -0
  248. data/lib/davinci_crd_test_kit/server/v2.2.1/specific_coverage_responses/coverage_info_reason_test.rb +108 -0
  249. data/lib/davinci_crd_test_kit/server/v2.2.1/specific_coverage_responses/coverage_not_found_group.rb +116 -0
  250. data/lib/davinci_crd_test_kit/server/v2.2.1/specific_coverage_responses/no_active_coverage_group.rb +115 -0
  251. data/lib/davinci_crd_test_kit/server/v2.2.1/specific_coverage_responses/no_member_found_group.rb +117 -0
  252. data/lib/davinci_crd_test_kit/server/v2.2.1/specific_coverage_responses/technical_issues_group.rb +121 -0
  253. data/lib/davinci_crd_test_kit/server/v2.2.1/specific_coverage_responses/technical_issues_invoke_test.rb +22 -0
  254. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_request/service_request_context_validation_test.rb +31 -0
  255. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_request/service_request_no_custom_extensions_test.rb +120 -0
  256. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_request/service_request_optional_fields_validation_test.rb +40 -0
  257. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_request/service_request_required_fields_validation_test.rb +41 -0
  258. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/additional_orders_validation_test.rb +109 -0
  259. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/all_responses_include_coverage_information_test.rb +123 -0
  260. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/coverage_info_configuration_test.rb +83 -0
  261. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/coverage_information_card_absence_test.rb +76 -0
  262. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/coverage_information_system_action_validation_test.rb +130 -0
  263. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/create_or_update_coverage_info_response_validation_test.rb +119 -0
  264. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/external_reference_card_validation_test.rb +89 -0
  265. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/form_completion_response_validation_test.rb +126 -0
  266. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/hook_request_resource_resolution.rb +174 -0
  267. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/instructions_card_received_test.rb +83 -0
  268. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/launch_smart_app_card_validation_test.rb +88 -0
  269. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/order_dispatch_coverage_information_test.rb +38 -0
  270. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/propose_alternate_request_card_validation_test.rb +81 -0
  271. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/service_response_validation_test.rb +63 -0
  272. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/unknown_cds_hooks_elements_test.rb +78 -0
  273. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/unknown_configuration_test.rb +78 -0
  274. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/unknown_context_test.rb +78 -0
  275. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/verify_response_without_billing_options_test.rb +43 -0
  276. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/verify_response_without_configuration_test.rb +44 -0
  277. data/lib/davinci_crd_test_kit/version.rb +2 -2
  278. data/lib/davinci_crd_test_kit.rb +4 -2
  279. metadata +307 -93
  280. data/lib/davinci_crd_test_kit/client_fhir_api_group.rb +0 -785
  281. data/lib/davinci_crd_test_kit/client_hooks_group.rb +0 -74
  282. data/lib/davinci_crd_test_kit/client_tests/appointment_book_receive_request_test.rb +0 -93
  283. data/lib/davinci_crd_test_kit/client_tests/client_appointment_book_group.rb +0 -75
  284. data/lib/davinci_crd_test_kit/client_tests/client_display_cards_attest.rb +0 -48
  285. data/lib/davinci_crd_test_kit/client_tests/client_encounter_discharge_group.rb +0 -73
  286. data/lib/davinci_crd_test_kit/client_tests/client_encounter_start_group.rb +0 -73
  287. data/lib/davinci_crd_test_kit/client_tests/client_fhir_api_create_test.rb +0 -42
  288. data/lib/davinci_crd_test_kit/client_tests/client_fhir_api_read_test.rb +0 -40
  289. data/lib/davinci_crd_test_kit/client_tests/client_fhir_api_search_test.rb +0 -232
  290. data/lib/davinci_crd_test_kit/client_tests/client_fhir_api_update_test.rb +0 -42
  291. data/lib/davinci_crd_test_kit/client_tests/client_fhir_api_validation_test.rb +0 -61
  292. data/lib/davinci_crd_test_kit/client_tests/client_order_dispatch_group.rb +0 -79
  293. data/lib/davinci_crd_test_kit/client_tests/client_order_select_group.rb +0 -82
  294. data/lib/davinci_crd_test_kit/client_tests/client_order_sign_group.rb +0 -81
  295. data/lib/davinci_crd_test_kit/client_tests/client_registration_verification_test.rb +0 -88
  296. data/lib/davinci_crd_test_kit/client_tests/decode_auth_token_test.rb +0 -60
  297. data/lib/davinci_crd_test_kit/client_tests/encounter_discharge_receive_request_test.rb +0 -90
  298. data/lib/davinci_crd_test_kit/client_tests/encounter_start_receive_request_test.rb +0 -90
  299. data/lib/davinci_crd_test_kit/client_tests/hook_request_optional_fields_test.rb +0 -57
  300. data/lib/davinci_crd_test_kit/client_tests/hook_request_required_fields_test.rb +0 -49
  301. data/lib/davinci_crd_test_kit/client_tests/hook_request_valid_context_test.rb +0 -68
  302. data/lib/davinci_crd_test_kit/client_tests/hook_request_valid_prefetch_test.rb +0 -69
  303. data/lib/davinci_crd_test_kit/client_tests/order_dispatch_receive_request_test.rb +0 -102
  304. data/lib/davinci_crd_test_kit/client_tests/order_select_receive_request_test.rb +0 -98
  305. data/lib/davinci_crd_test_kit/client_tests/order_sign_receive_request_test.rb +0 -101
  306. data/lib/davinci_crd_test_kit/client_tests/retrieve_jwks_test.rb +0 -105
  307. data/lib/davinci_crd_test_kit/client_tests/submitted_response_validation.rb +0 -48
  308. data/lib/davinci_crd_test_kit/client_tests/token_header_test.rb +0 -65
  309. data/lib/davinci_crd_test_kit/client_tests/token_payload_test.rb +0 -78
  310. data/lib/davinci_crd_test_kit/crd_client_suite.rb +0 -193
  311. data/lib/davinci_crd_test_kit/crd_options.rb +0 -9
  312. data/lib/davinci_crd_test_kit/crd_server_suite.rb +0 -125
  313. data/lib/davinci_crd_test_kit/igs/davinci-crd-2.0.1.tgz +0 -0
  314. data/lib/davinci_crd_test_kit/routes/cds_services_discovery_handler.rb +0 -18
  315. data/lib/davinci_crd_test_kit/routes/hook_request_endpoint.rb +0 -77
  316. data/lib/davinci_crd_test_kit/routes/jwk_set_endpoint_handler.rb +0 -15
  317. data/lib/davinci_crd_test_kit/server_appointment_book_group.rb +0 -176
  318. data/lib/davinci_crd_test_kit/server_demonstrate_hook_response_group.rb +0 -77
  319. data/lib/davinci_crd_test_kit/server_discovery_group.rb +0 -60
  320. data/lib/davinci_crd_test_kit/server_encounter_discharge_group.rb +0 -170
  321. data/lib/davinci_crd_test_kit/server_encounter_start_group.rb +0 -170
  322. data/lib/davinci_crd_test_kit/server_hooks_group.rb +0 -71
  323. data/lib/davinci_crd_test_kit/server_order_dispatch_group.rb +0 -176
  324. data/lib/davinci_crd_test_kit/server_order_select_group.rb +0 -195
  325. data/lib/davinci_crd_test_kit/server_order_sign_group.rb +0 -201
  326. data/lib/davinci_crd_test_kit/server_required_card_response_validation_group.rb +0 -26
  327. data/lib/davinci_crd_test_kit/server_tests/additional_orders_validation_test.rb +0 -68
  328. data/lib/davinci_crd_test_kit/server_tests/card_optional_fields_validation_test.rb +0 -47
  329. data/lib/davinci_crd_test_kit/server_tests/coverage_information_system_action_across_hooks_validation_test.rb +0 -32
  330. data/lib/davinci_crd_test_kit/server_tests/coverage_information_system_action_received_test.rb +0 -63
  331. data/lib/davinci_crd_test_kit/server_tests/coverage_information_system_action_validation_test.rb +0 -118
  332. data/lib/davinci_crd_test_kit/server_tests/create_or_update_coverage_info_response_validation_test.rb +0 -71
  333. data/lib/davinci_crd_test_kit/server_tests/discovery_endpoint_test.rb +0 -88
  334. data/lib/davinci_crd_test_kit/server_tests/discovery_services_validation_test.rb +0 -65
  335. data/lib/davinci_crd_test_kit/server_tests/external_reference_card_across_hooks_validation_test.rb +0 -28
  336. data/lib/davinci_crd_test_kit/server_tests/external_reference_card_validation_test.rb +0 -36
  337. data/lib/davinci_crd_test_kit/server_tests/form_completion_response_validation_test.rb +0 -78
  338. data/lib/davinci_crd_test_kit/server_tests/instructions_card_received_across_hooks_test.rb +0 -25
  339. data/lib/davinci_crd_test_kit/server_tests/instructions_card_received_test.rb +0 -26
  340. data/lib/davinci_crd_test_kit/server_tests/launch_smart_app_card_validation_test.rb +0 -38
  341. data/lib/davinci_crd_test_kit/server_tests/propose_alternate_request_card_validation_test.rb +0 -63
  342. data/lib/davinci_crd_test_kit/server_tests/service_call_test.rb +0 -101
  343. data/lib/davinci_crd_test_kit/server_tests/service_request_context_validation_test.rb +0 -28
  344. data/lib/davinci_crd_test_kit/server_tests/service_request_optional_fields_validation_test.rb +0 -37
  345. data/lib/davinci_crd_test_kit/server_tests/service_request_required_fields_validation_test.rb +0 -38
  346. data/lib/davinci_crd_test_kit/server_tests/service_response_validation_test.rb +0 -81
  347. data/lib/davinci_crd_test_kit/tags.rb +0 -10
  348. data/lib/davinci_crd_test_kit/urls.rb +0 -52
  349. /data/lib/davinci_crd_test_kit/{card_responses → client/endpoints/mocked_card_responses}/instructions.json +0 -0
  350. /data/lib/davinci_crd_test_kit/{crd_jwks.json → server/endpoints/crd_jwks.json} +0 -0
  351. /data/lib/davinci_crd_test_kit/{jwks.rb → server/endpoints/jwks.rb} +0 -0
@@ -0,0 +1,476 @@
1
+ require_relative 'fhirpath_on_cds_request'
2
+ require_relative 'replace_tokens'
3
+ require 'uri'
4
+
5
+ module DaVinciCRDTestKit
6
+ # -----------------------------------------------------------------------
7
+ # Prefetch Check Helper Class
8
+ # -----------------------------------------------------------------------
9
+ class PrefetchCompletenessChecker
10
+ include FhirpathOnCDSRequest
11
+ include ReplaceTokens
12
+
13
+ attr_accessor :hook_request, :request_index, :services_file_path,
14
+ :instantiated_prefetch_templates,
15
+ :observed_fhirpath_collection_as_comma_delimited_string
16
+
17
+ def initialize(hook_request, request_index, services_file_path)
18
+ @hook_request = hook_request
19
+ @request_index = request_index
20
+ @services_file_path = services_file_path
21
+ @observed_fhirpath_collection_as_comma_delimited_string = false
22
+ @instantiated_prefetch_templates = {}
23
+ extract_prefetched_resources
24
+ end
25
+
26
+ def check_prefetched_data
27
+ return ["#{request_error_prefix} No prefetch data provided."] unless hook_request.key?('prefetch')
28
+
29
+ hook_prefetch_templates.each do |prefetch_key, prefetch_request|
30
+ check_prefetch_template(prefetch_key, prefetch_request)
31
+ end
32
+
33
+ hook_request['prefetch'].each_key do |prefetch_template|
34
+ next if hook_prefetch_templates.key?(prefetch_template)
35
+
36
+ errors << "#{request_error_prefix} Extra prefetch data " \
37
+ "provided in unrequested template '#{prefetch_template}'."
38
+ end
39
+
40
+ errors.uniq
41
+ end
42
+
43
+ # -----------------------------------------------------------------------
44
+ # Complete vs Subset Prefetch Difference Checking
45
+ # -----------------------------------------------------------------------
46
+
47
+ def data_set_different_with_alternate_service?(alternate_services_file_path, compare_key_map)
48
+ PrefetchCompletenessChecker.new(hook_request, request_index, alternate_services_file_path)
49
+ .data_sets_different?(self, compare_key_map)
50
+ end
51
+
52
+ # Precondition: the original checker passed without errors meaning the expected set was present
53
+ # handles both subset < complete and complete > subset checks via two checks.
54
+ # - differences in the calculated id set for `_id` searches signal differences in the data sets.
55
+ # - errors during instantiation signal that a resource in the set wasn't present. Since the original
56
+ # set was complete, this will only happen if the original set was a subset and the complete set
57
+ # includes more data, meaning that the sets are different. (NOTE: triggers an error rather than a different
58
+ # set of ids because fhirpath like `...resolve().id` requires the resource be present to evaluate it)
59
+ def data_sets_different?(original_service_checker, compare_key_map)
60
+ template_key_map = compare_key_map.transform_keys { |key| "%#{key}." }.transform_values { |value| "%#{value}." }
61
+ hook_prefetch_templates.each do |prefetch_key, prefetch_request|
62
+ data_set_different = data_set_different?(original_service_checker,
63
+ compare_key_map,
64
+ template_key_map,
65
+ prefetch_key,
66
+ prefetch_request)
67
+
68
+ return true if data_set_different
69
+ end
70
+
71
+ false
72
+ end
73
+
74
+ private
75
+
76
+ def data_set_different?(original_service_checker, compare_key_map, template_key_map, prefetch_key, prefetch_request)
77
+ return false if ['patient', 'pat', 'encounter', 'enc', 'coverage', 'cov'].include?(prefetch_key)
78
+
79
+ mapped_prefetch_request = prefetch_request.gsub(/%[a-zA-Z]+\./, template_key_map)
80
+ instantiated_request = instantiate_template(prefetch_key, mapped_prefetch_request)
81
+ return true if errors.present? # fetch errors - there was more data to include for complete (complete > subset)
82
+
83
+ compare_prefetch_key = compare_key_map.key?(prefetch_key) ? compare_key_map[prefetch_key] : prefetch_key
84
+ original_instantiated_request = original_service_checker.instantiated_prefetch_templates[compare_prefetch_key]
85
+ # don't try to compare when the original didn't have this key
86
+ return false unless original_instantiated_request.present?
87
+
88
+ _, alternate_ids = resource_type_and_ids_from_search(instantiated_request)
89
+ _, original_ids = resource_type_and_ids_from_search(original_instantiated_request)
90
+
91
+ # missing ids - the subset requested strictly less data (subset < complete)
92
+ alternate_ids != original_ids
93
+ end
94
+
95
+ # -----------------------------------------------------------------------
96
+ # Errors to return
97
+ # -----------------------------------------------------------------------
98
+ def errors
99
+ @errors ||= []
100
+ end
101
+
102
+ def request_error_prefix
103
+ "(Request #{request_index + 1})"
104
+ end
105
+
106
+ def error_prefix
107
+ "#{request_error_prefix} Prefetch Template #{@current_prefetch_key} -"
108
+ end
109
+
110
+ # -----------------------------------------------------------------------
111
+ # Requested Prefetch Templates
112
+ # -----------------------------------------------------------------------
113
+ def hook_prefetch_templates
114
+ @hook_prefetch_templates ||=
115
+ JSON.parse(File.read(services_file_path))['services'].find do |service|
116
+ service['hook'] == hook_request['hook']
117
+ end['prefetch']
118
+ end
119
+
120
+ # -----------------------------------------------------------------------
121
+ # Instantiated Prefetch Templates
122
+ # -----------------------------------------------------------------------
123
+ def instantiate_template(prefetch_key, prefetch_request)
124
+ instantiated_request = replace_tokens_in_string(prefetch_request.dup, hook_request)
125
+ instantiated_prefetch_templates[prefetch_key] = instantiated_request
126
+ instantiated_request
127
+ end
128
+
129
+ # -----------------------------------------------------------------------
130
+ # Check of actual prefetch against an instantiated request
131
+ # -----------------------------------------------------------------------
132
+ def check_prefetch_template(prefetch_key, prefetch_request)
133
+ @current_prefetch_key = prefetch_key
134
+ instantiated_request = instantiate_template(prefetch_key, prefetch_request)
135
+ if demonstrates_collection_as_comma_delimited_string?(prefetch_request, instantiated_request)
136
+ @observed_fhirpath_collection_as_comma_delimited_string = true
137
+ end
138
+ unless hook_request['prefetch'].key?(prefetch_key)
139
+ errors << "#{error_prefix} No prefetch data provided."
140
+ return
141
+ end
142
+ check_provided_against_request(hook_request['prefetch'][prefetch_key], instantiated_request)
143
+ rescue FhirpathServiceError => e
144
+ raise "#{error_prefix} FHIRPath service error while evaluating prefetch template. " \
145
+ "This indicates an implementation problem in Inferno - please log a ticket. Details: #{e.message}"
146
+ end
147
+
148
+ def check_provided_against_request(prefetched_value, instantiated_request)
149
+ if instantiated_request.include?('?')
150
+ if id_search?(instantiated_request)
151
+ check_id_search(prefetched_value, instantiated_request)
152
+ elsif instantiated_request.starts_with?('Coverage')
153
+ check_coverage_search(prefetched_value, instantiated_request)
154
+ else
155
+ raise "#{error_prefix} Unexpected search template '#{instantiated_request}'. " \
156
+ 'This indicates an implementation problem in the test kit — please log a ticket.'
157
+ end
158
+ else
159
+ check_read(prefetched_value, instantiated_request)
160
+ end
161
+ end
162
+
163
+ def id_search?(request_string)
164
+ resource_type = request_string.split('?').first
165
+ request_string.starts_with?("#{resource_type}?_id=")
166
+ end
167
+
168
+ def check_coverage_search(prefetched_value, _instantiated_request)
169
+ unless prefetched_value.present?
170
+ errors << "#{error_prefix} requested Coverage not provided."
171
+ return
172
+ end
173
+
174
+ check_is_fhir_resource(prefetched_value, target_resource_type: 'Bundle')
175
+ if prefetched_value['resourceType'] == 'Bundle'
176
+ unless prefetched_value['entry']&.size == 1
177
+ errors << "#{error_prefix} exactly one Coverage must be provided."
178
+ return
179
+ end
180
+
181
+ check_coverage(prefetched_value.dig('entry', 0, 'resource'))
182
+ elsif prefetched_value['resourceType'] == 'Coverage'
183
+ check_coverage(prefetched_value)
184
+ end
185
+ end
186
+
187
+ def check_coverage(prefetched_coverage)
188
+ unless prefetched_coverage['resourceType'].present?
189
+ errors << "#{error_prefix} entry in prefetched Coverage Bundle is not a FHIR resource (no resourceType)."
190
+ return
191
+ end
192
+
193
+ unless prefetched_coverage['resourceType'] == 'Coverage'
194
+ errors << "#{error_prefix} entry in prefetched Coverage Bundle has an unexpected type: " \
195
+ "expected Coverage, got #{prefetched_coverage['resourceType']}."
196
+ return
197
+ end
198
+
199
+ unless prefetched_coverage['status'] == 'active'
200
+ errors << "#{error_prefix} prefetched Coverage has an unexpected status: " \
201
+ "expected active, got #{prefetched_coverage['status']}."
202
+ end
203
+
204
+ check_coverage_beneficiary(prefetched_coverage)
205
+
206
+ nil
207
+ end
208
+
209
+ def check_coverage_beneficiary(prefetched_coverage)
210
+ target_patient_id = hook_request.dig('context', 'patientId')
211
+ patient_id = target_patient_id&.delete_prefix('Patient/')
212
+ relative_ref = "Patient/#{patient_id}"
213
+ fhir_server = hook_request['fhirServer']&.chomp('/')
214
+ absolute_ref = "#{fhir_server}/#{relative_ref}" if fhir_server.present?
215
+
216
+ actual_ref = prefetched_coverage.dig('beneficiary', 'reference')
217
+ return if actual_ref == relative_ref || (absolute_ref && actual_ref == absolute_ref)
218
+
219
+ expected_refs = [relative_ref, absolute_ref].compact.join(' or ')
220
+ errors << "#{error_prefix} prefetched Coverage has an unexpected beneficiary reference: " \
221
+ "expected #{expected_refs}, got #{actual_ref}."
222
+ end
223
+
224
+ def check_read(prefetched_value, instantiated_request)
225
+ resource_type, resource_id = instantiated_request.split('/')
226
+
227
+ resource_requested = resource_id.present?
228
+
229
+ unless prefetched_value.present?
230
+ if resource_requested
231
+ errors << "#{error_prefix} requested resource '#{full_url_for_target_id(instantiated_request)}' not provided."
232
+ end
233
+ return
234
+ end
235
+
236
+ check_is_fhir_resource(prefetched_value, target_resource_type: resource_type)
237
+ unless prefetched_value.key?('id')
238
+ errors << "#{error_prefix} prefetched #{resource_type} is missing an id."
239
+ return
240
+ end
241
+ unless prefetched_value['id'] == resource_id
242
+ errors << "#{error_prefix} prefetched #{resource_type} has unexpected id: " \
243
+ "expected #{resource_id}, got #{prefetched_value['id']}."
244
+ end
245
+
246
+ nil
247
+ end
248
+
249
+ def resource_type_and_ids_from_search(instantiated_search)
250
+ resource_type, id_list = instantiated_search.split('?_id=')
251
+ target_ids = id_list.present? ? id_list.split(',').map { |id| "#{resource_type}/#{id}" }.uniq.sort : []
252
+
253
+ [resource_type, target_ids]
254
+ end
255
+
256
+ def check_id_search(prefetched_value, instantiated_request)
257
+ resource_type, target_ids = resource_type_and_ids_from_search(instantiated_request)
258
+ resources_requested = target_ids.present?
259
+
260
+ unless prefetched_value.present?
261
+ if resources_requested
262
+ errors << "#{error_prefix} requested resources not provided: " \
263
+ "#{target_ids.map { |id| full_url_for_target_id(id) }.join(', ')}."
264
+ end
265
+ return
266
+ end
267
+
268
+ check_is_fhir_resource(prefetched_value, target_resource_type: 'Bundle')
269
+ check_bundle_entry_resource_type(prefetched_value, resource_type)
270
+ check_ids(target_ids, actual_ids(prefetched_value))
271
+ nil
272
+ end
273
+
274
+ def actual_ids(prefetched_value)
275
+ return [] unless prefetched_value['entry'].present?
276
+
277
+ prefetched_value['entry'].map do |entry|
278
+ type = entry.dig('resource', 'resourceType')
279
+ id = entry.dig('resource', 'id')
280
+ next unless type.present? && id.present?
281
+
282
+ type_and_id = "#{type}/#{id}"
283
+ [type_and_id, entry['fullUrl'].presence || type_and_id]
284
+ end.compact
285
+ end
286
+
287
+ def check_ids(target_ids, actual_id_pairs)
288
+ actual_type_ids = actual_id_pairs.map(&:first)
289
+ unless actual_type_ids.size == actual_type_ids.uniq.size
290
+ errors << "#{error_prefix} prefetched Bundle has multiple entries with the same resource id."
291
+ end
292
+
293
+ actual_ids_map = actual_id_pairs.to_h
294
+
295
+ missing_ids = target_ids - actual_type_ids
296
+ if missing_ids.present?
297
+ errors << "#{error_prefix} prefetched Bundle missing expected entries: " \
298
+ "#{missing_ids.map { |id| full_url_for_target_id(id) }.join('\', \'')}."
299
+ end
300
+
301
+ extra_ids = actual_type_ids - target_ids
302
+ return unless extra_ids.present?
303
+
304
+ errors << "#{error_prefix} prefetched Bundle includes unrequested entries: " \
305
+ "#{extra_ids.map { |id| actual_ids_map[id] }.join('\', \'')}."
306
+ end
307
+
308
+ # NOTE: would possibly fail in the case of duplicate <resource>/<id> with different
309
+ # base urls but this will cause other problems since in the _id search form those
310
+ # would be the same. So not worth trying to work around it.
311
+ def full_url_for_target_id(type_and_id)
312
+ prefetched_resources.keys.find { |url| url.end_with?("/#{type_and_id}") } ||
313
+ fhir_server_url_for(type_and_id)
314
+ end
315
+
316
+ def fhir_server_url_for(type_and_id)
317
+ return type_and_id unless hook_request['fhirServer'].present?
318
+
319
+ "#{hook_request['fhirServer'].chomp('/')}/#{type_and_id}"
320
+ end
321
+
322
+ def check_bundle_entry_resource_type(bundle, target_resource_type)
323
+ bundle['entry']&.each_with_index do |entry, index|
324
+ entry_resource_type = entry.dig('resource', 'resourceType')
325
+ next if entry_resource_type == target_resource_type
326
+
327
+ errors << if entry_resource_type.present?
328
+ "#{error_prefix} prefetched Bundle entry #{index + 1} has an unexpected resourceType: " \
329
+ "expected #{target_resource_type}, got #{entry_resource_type}."
330
+ else
331
+ "#{error_prefix} prefetched Bundle entry #{index + 1} is not a FHIR resource (no resourceType)."
332
+ end
333
+ end
334
+ end
335
+
336
+ def check_is_fhir_resource(prefetched_value, target_resource_type: nil)
337
+ unless prefetched_value.key?('resourceType')
338
+ errors << "#{error_prefix} prefetched value is not a FHIR resource (no resourceType)."
339
+ return
340
+ end
341
+
342
+ return if target_resource_type.blank? || prefetched_value['resourceType'] == target_resource_type
343
+
344
+ errors << "#{error_prefix} prefetched value has unexpected resourceType: " \
345
+ "expected #{target_resource_type}, got #{prefetched_value['resourceType']}."
346
+ end
347
+
348
+ # -----------------------------------------------------------------------
349
+ # Map of prefetched resources by fullUrl
350
+ # -----------------------------------------------------------------------
351
+ def prefetched_resources
352
+ @prefetched_resources ||= {}
353
+ end
354
+
355
+ def extract_prefetched_resources
356
+ hook_request['prefetch']&.each_value do |prefetch_resource|
357
+ next unless prefetch_resource&.dig('resourceType').present?
358
+
359
+ if prefetch_resource['resourceType'] == 'Bundle'
360
+ extract_resources_from_prefetched_bundle(prefetch_resource)
361
+ else
362
+ extract_prefetched_resource_instance(prefetch_resource)
363
+ end
364
+ end
365
+ end
366
+
367
+ def extract_resources_from_prefetched_bundle(bundle)
368
+ bundle['entry']&.each do |entry|
369
+ next unless entry['resource'].present?
370
+
371
+ if entry['fullUrl'].present?
372
+ prefetched_resources[entry['fullUrl']] = entry['resource'] unless prefetched_resources.key?(entry['fullUrl'])
373
+ else
374
+ extract_prefetched_resource_instance(entry['resource'])
375
+ end
376
+ end
377
+ end
378
+
379
+ # no fullUrl available, so assume that it is a restful FHIR url
380
+ # relative to the fhirServer of the hook request
381
+ def extract_prefetched_resource_instance(resource_instance)
382
+ return unless resource_instance['id'].present? &&
383
+ resource_instance['resourceType'].present? &&
384
+ hook_request['fhirServer'].present?
385
+
386
+ fhir_server =
387
+ if hook_request['fhirServer'].ends_with?('/')
388
+ hook_request['fhirServer']
389
+ else
390
+ "#{hook_request['fhirServer']}/"
391
+ end
392
+ key = "#{fhir_server}#{resource_instance['resourceType']}/#{resource_instance['id']}"
393
+ return if prefetched_resources.key?(key)
394
+
395
+ prefetched_resources[key] = resource_instance
396
+ end
397
+
398
+ # -------------------------------------------------------------------------
399
+ # fhirpath resolve() handling
400
+ # -------------------------------------------------------------------------
401
+
402
+ def resolve(reference)
403
+ return nil unless reference.present?
404
+
405
+ key = absolute_reference(reference)
406
+ return nil unless key.present?
407
+
408
+ unless prefetched_resources[key].present?
409
+ errors << "#{error_prefix} resource '#{key}' needed to instantiate the query " \
410
+ 'was not provided in the prefetched values.'
411
+
412
+ return nil
413
+ end
414
+
415
+ @current_base_fhir_server = base_fhir_server_for_identity(key)
416
+ prefetched_resources[key]
417
+ end
418
+
419
+ def absolute_reference(reference)
420
+ reference = reference['reference'] if reference.is_a?(Hash)
421
+ return nil unless reference.present?
422
+
423
+ if URI.parse(reference).absolute?
424
+ reference
425
+ else
426
+ relative_to_absolute_reference(reference)
427
+ end
428
+ rescue URI::InvalidURIError => e
429
+ errors << "#{error_prefix} '#{reference}' needed to instantiate the query " \
430
+ "is invalid: #{e.message}."
431
+ nil
432
+ end
433
+
434
+ def relative_to_absolute_reference(relative_reference)
435
+ return nil unless relative_reference_valid?(relative_reference)
436
+
437
+ if @current_base_fhir_server.nil?
438
+ errors << "#{error_prefix} '#{relative_reference}' needed to instantiate the query " \
439
+ 'is a relative reference, but the base FHIR Server is not known.'
440
+ return nil
441
+ end
442
+
443
+ "#{@current_base_fhir_server}/#{relative_reference}"
444
+ end
445
+
446
+ def relative_reference_valid?(relative_reference)
447
+ if relative_reference.split('/').length > 2
448
+ errors << "#{error_prefix} '#{relative_reference}' needed to instantiate the query " \
449
+ 'is not an absolute reference but has too many segments to be a relative reference.'
450
+ return false
451
+ end
452
+
453
+ resource_type, id = relative_reference.split('/')
454
+ if resource_type.blank? || id.blank?
455
+ errors << "#{error_prefix} '#{relative_reference}' needed to instantiate the query " \
456
+ 'is not a valid relative reference of the form <resource type>/<id>.'
457
+ end
458
+
459
+ true
460
+ end
461
+
462
+ # -------------------------------------------------------------------------
463
+ # Observe a Collection represented as a comma-delimited string in instantiated search
464
+ # -------------------------------------------------------------------------
465
+
466
+ # client will have demonstrated turning a collection into a comma-delimited string if both
467
+ # - the prefetch request follows the RESOURCE?_id={{TOKEN}} form when TOKEN has a | indicating 'and', and
468
+ # - the instantiated query actually has multiple unique ids
469
+ def demonstrates_collection_as_comma_delimited_string?(prefetch_request, instantiated_request)
470
+ token = prefetch_request.match(/[a-zA-Z]*\?_id=\{\{(.+?)\}\}/)&.[](1)
471
+ return false unless token.present? && token.include?('|')
472
+
473
+ instantiated_request.split('?_id=').last.split(',').uniq.size > 1
474
+ end
475
+ end
476
+ end
@@ -0,0 +1,81 @@
1
+ require_relative 'replace_tokens'
2
+ require_relative 'fhirpath_on_cds_request'
3
+
4
+ module DaVinciCRDTestKit
5
+ module PrefetchContentsValidation
6
+ include ReplaceTokens
7
+ include FhirpathOnCDSRequest
8
+
9
+ def request_number
10
+ if @request_number.blank?
11
+ ''
12
+ else
13
+ "Request #{@request_number}: "
14
+ end
15
+ end
16
+
17
+ def prefetched_data?(requests)
18
+ requests.any? do |request|
19
+ JSON.parse(request.request_body)['prefetch']&.keys.present?
20
+ end
21
+ end
22
+
23
+ def check_prefetch_data_against_query(hook_request, advertized_prefetch_queries)
24
+ request_body = JSON.parse(hook_request.request_body)
25
+ request_body['prefetch']&.each do |key, value|
26
+ next unless advertized_prefetch_queries[key].present?
27
+
28
+ instantiated_query = replace_tokens_in_string(advertized_prefetch_queries[key], request_body)
29
+
30
+ queried_resource_list = query_for_data(instantiated_query)
31
+ prefetched_resource_list = prefetched_data_to_resource_list(value)
32
+
33
+ next if prefetched_data_equals_queried_data?(prefetched_resource_list, queried_resource_list)
34
+
35
+ add_message('error', "#{request_number}Prefetched data `#{key}` was different than " \
36
+ "data returned from requested query `#{instantiated_query}`.")
37
+ end
38
+ end
39
+
40
+ def query_for_data(prefetch_query)
41
+ is_search = prefetch_query.include?('?')
42
+
43
+ split_query = is_search ? prefetch_query.split('?') : prefetch_query.split('/')
44
+ resource_type = split_query.first
45
+
46
+ query_result =
47
+ if is_search
48
+ fhir_search(resource_type, params: Rack::Utils.parse_nested_query(split_query.last))
49
+ else
50
+ fhir_read(resource_type, split_query.last)
51
+ end
52
+
53
+ return [] unless query_result.status.to_s.starts_with?('2')
54
+ return [] unless query_result.resource.present?
55
+
56
+ if is_search
57
+ fetch_all_bundled_resources(resource_type:, bundle: query_result.resource)
58
+ else
59
+ [query_result.resource]
60
+ end
61
+ end
62
+
63
+ def prefetched_data_equals_queried_data?(prefetched_data, queried_data)
64
+ prefetched_id_list = prefetched_data.map { |resource| "#{resource.resourceType}/#{resource.id}" }.sort
65
+ queried_id_list = queried_data.map { |resource| "#{resource.resourceType}/#{resource.id}" }.sort
66
+
67
+ prefetched_id_list == queried_id_list
68
+ end
69
+
70
+ def prefetched_data_to_resource_list(prefetched_data)
71
+ return [] unless prefetched_data.present?
72
+
73
+ fhir_data = FHIR.from_contents(prefetched_data.to_json)
74
+ if fhir_data.is_a?(FHIR::Bundle)
75
+ fhir_data.entry.map(&:resource)
76
+ else
77
+ [fhir_data]
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,48 @@
1
+ require_relative 'hook_request_field_validation'
2
+
3
+ module DaVinciCRDTestKit
4
+ module PrefetchProfileValidation
5
+ include HookRequestFieldValidation
6
+
7
+ def check_prefetch_profiles(prefetch, request_index)
8
+ prefetch.each do |key, prefetched_resource|
9
+ next unless prefetched_resource.present? # prefetch must be null if no data matches the template
10
+
11
+ @prefetch_template = key
12
+ check_resource_profile(prefetched_resource, request_index, nil)
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def check_resource_profile(prefetched_resource, request_index, bundle_entry_index)
19
+ if prefetched_resource['resourceType'] == 'Bundle'
20
+ prefetched_resource['entry']&.each_with_index do |entry, entry_index|
21
+ check_resource_profile(entry['resource'], request_index, entry_index) if entry['resource'].present?
22
+ end
23
+ elsif prefetched_resource['resourceType'].present?
24
+ check_non_bundle_resource_profile(prefetched_resource, request_index, bundle_entry_index)
25
+ end
26
+ end
27
+
28
+ def check_non_bundle_resource_profile(prefetched_resource, request_index, bundle_entry_index)
29
+ target_crd_profile = structure_definition_map('v221')[prefetched_resource['resourceType']]
30
+ return unless target_crd_profile.present?
31
+
32
+ validation_details = []
33
+ resource_is_valid?(resource: FHIR.from_contents(prefetched_resource.to_json),
34
+ profile_url: target_crd_profile,
35
+ validator_response_details: validation_details, add_messages_to_runnable: false)
36
+ validation_details.each do |issue|
37
+ prefix = prefetch_profile_error_prefix(request_index, bundle_entry_index)
38
+ add_message(issue.severity, "#{prefix}#{issue.message}")
39
+ end
40
+ end
41
+
42
+ def prefetch_profile_error_prefix(request_index, bundle_entry_index)
43
+ prefix = "(Request #{request_index + 1}) Prefetch Template '#{@prefetch_template}'"
44
+ prefix = "#{prefix} Bundle entry #{bundle_entry_index + 1}" if bundle_entry_index.present?
45
+ "#{prefix} validation issue - "
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,63 @@
1
+ module DaVinciCRDTestKit
2
+ module ProfilesAndResourceTypes
3
+ ORDER_RESOURCE_CLASSES = [
4
+ FHIR::CommunicationRequest, FHIR::DeviceRequest, FHIR::MedicationRequest,
5
+ FHIR::NutritionOrder, FHIR::ServiceRequest, FHIR::VisionPrescription
6
+ ].freeze
7
+ ORDER_OR_ENCOUNTER_RESOURCE_CLASSES = (ORDER_RESOURCE_CLASSES + [FHIR::Encounter]).freeze
8
+ ORDER_RESOURCE_TYPES = ORDER_RESOURCE_CLASSES.map { |c| c.name.split('::').last }.freeze
9
+
10
+ def structure_definition_map(ig_version)
11
+ case ig_version
12
+ when 'v221', '2.2.1'
13
+ structure_definition_map_v221
14
+ when 'v201', '2.0.1'
15
+ structure_definition_map_v201
16
+ end
17
+ end
18
+
19
+ def structure_definition_map_v201
20
+ {
21
+ 'Practitioner' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-practitioner|2.0.1',
22
+ 'PractitionerRole' => 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitionerrole|3.1.1',
23
+ 'Patient' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-patient|2.0.1',
24
+ 'Encounter' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-encounter|2.0.1',
25
+ 'Appointment' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-appointment|2.0.1',
26
+ 'DeviceRequest' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-devicerequest|2.0.1',
27
+ 'MedicationRequest' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-medicationrequest|2.0.1',
28
+ 'NutritionOrder' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-nutritionorder|2.0.1',
29
+ 'ServiceRequest' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-servicerequest|2.0.1',
30
+ 'VisionPrescription' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-visionprescription|2.0.1',
31
+ 'Medication' => 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-medication|3.1.1',
32
+ 'Device' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-device|2.0.1',
33
+ 'CommunicationRequest' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-communicationrequest|2.0.1',
34
+ 'Task' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-taskquestionnaire|2.0.1',
35
+ 'Coverage' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-coverage|2.0.1',
36
+ 'Location' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-location|2.0.1',
37
+ 'Organization' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-organization|2.0.1'
38
+ }.freeze
39
+ end
40
+
41
+ def structure_definition_map_v221
42
+ {
43
+ 'Practitioner' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-practitioner|2.2.1',
44
+ 'PractitionerRole' => 'http://hl7.org/fhir/us/davinci-hrex/StructureDefinition/hrex-practitionerrole|1.2.0',
45
+ 'Patient' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-patient|2.2.1',
46
+ 'Encounter' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-encounter|2.2.1',
47
+ 'Appointment' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-appointment-no-order|2.2.1',
48
+ 'DeviceRequest' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-devicerequest|2.2.1',
49
+ 'MedicationRequest' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-medicationrequest|2.2.1',
50
+ 'NutritionOrder' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-nutritionorder|2.2.1',
51
+ 'ServiceRequest' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-servicerequest|2.2.1',
52
+ 'VisionPrescription' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-visionprescription|2.2.1',
53
+ 'Medication' => 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-medication|3.1.1',
54
+ 'Device' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-device|2.2.1',
55
+ 'CommunicationRequest' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-communicationrequest|2.2.1',
56
+ 'Task' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-taskquestionnaire|2.2.1',
57
+ 'Coverage' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-coverage|2.2.1',
58
+ 'Location' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-location|2.2.1',
59
+ 'Organization' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-organization|2.2.1'
60
+ }.freeze
61
+ end
62
+ end
63
+ end