davinci_crd_test_kit 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (341) 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 +124 -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 +233 -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}/launch_smart_app.json +8 -1
  20. data/lib/davinci_crd_test_kit/{card_responses → client/endpoints/mocked_card_responses}/propose_alternate_request.json +1 -0
  21. data/lib/davinci_crd_test_kit/{card_responses → client/endpoints/mocked_card_responses}/request_form_completion.json +17 -16
  22. data/lib/davinci_crd_test_kit/client/multi_request_message_helper.rb +35 -0
  23. data/lib/davinci_crd_test_kit/client/tagged_request_load_helper.rb +38 -0
  24. data/lib/davinci_crd_test_kit/client/v2.0.1/api/client_fhir_api_create_test.rb +43 -0
  25. data/lib/davinci_crd_test_kit/client/v2.0.1/api/client_fhir_api_read_test.rb +43 -0
  26. data/lib/davinci_crd_test_kit/client/v2.0.1/api/client_fhir_api_search_test.rb +234 -0
  27. data/lib/davinci_crd_test_kit/client/v2.0.1/api/client_fhir_api_update_test.rb +43 -0
  28. data/lib/davinci_crd_test_kit/client/v2.0.1/api/client_fhir_api_validation_test.rb +63 -0
  29. data/lib/davinci_crd_test_kit/client/v2.0.1/auth/decode_auth_token_test.rb +65 -0
  30. data/lib/davinci_crd_test_kit/client/v2.0.1/auth/retrieve_jwks_test.rb +109 -0
  31. data/lib/davinci_crd_test_kit/client/v2.0.1/auth/token_header_test.rb +70 -0
  32. data/lib/davinci_crd_test_kit/client/v2.0.1/auth/token_payload_test.rb +85 -0
  33. data/lib/davinci_crd_test_kit/{routes/cds-services.json → client/v2.0.1/cds-services-v201.json} +1 -1
  34. data/lib/davinci_crd_test_kit/client/v2.0.1/client_appointment_book_group.rb +108 -0
  35. data/lib/davinci_crd_test_kit/client/v2.0.1/client_card_must_support_group.rb +31 -0
  36. data/lib/davinci_crd_test_kit/client/v2.0.1/client_encounter_discharge_group.rb +105 -0
  37. data/lib/davinci_crd_test_kit/client/v2.0.1/client_encounter_start_group.rb +105 -0
  38. data/lib/davinci_crd_test_kit/client/v2.0.1/client_fhir_api_group.rb +790 -0
  39. data/lib/davinci_crd_test_kit/client/v2.0.1/client_hooks_group.rb +74 -0
  40. data/lib/davinci_crd_test_kit/client/v2.0.1/client_order_dispatch_group.rb +111 -0
  41. data/lib/davinci_crd_test_kit/client/v2.0.1/client_order_select_group.rb +116 -0
  42. data/lib/davinci_crd_test_kit/client/v2.0.1/client_order_sign_group.rb +113 -0
  43. data/lib/davinci_crd_test_kit/{client_registration_group.rb → client/v2.0.1/client_registration_group.rb} +12 -8
  44. data/lib/davinci_crd_test_kit/client/v2.0.1/client_urls.rb +13 -0
  45. data/lib/davinci_crd_test_kit/client/v2.0.1/crd_client_suite.rb +134 -0
  46. data/lib/davinci_crd_test_kit/client/v2.0.1/invocation/appointment_book_receive_request_test.rb +129 -0
  47. data/lib/davinci_crd_test_kit/client/v2.0.1/invocation/encounter_discharge_receive_request_test.rb +126 -0
  48. data/lib/davinci_crd_test_kit/client/v2.0.1/invocation/encounter_start_receive_request_test.rb +126 -0
  49. data/lib/davinci_crd_test_kit/client/v2.0.1/invocation/order_dispatch_receive_request_test.rb +138 -0
  50. data/lib/davinci_crd_test_kit/client/v2.0.1/invocation/order_select_receive_request_test.rb +134 -0
  51. data/lib/davinci_crd_test_kit/client/v2.0.1/invocation/order_sign_receive_request_test.rb +136 -0
  52. data/lib/davinci_crd_test_kit/client/v2.0.1/must_support/client_card_must_support_coverage_information.rb +93 -0
  53. data/lib/davinci_crd_test_kit/client/v2.0.1/must_support/client_card_must_support_external_reference.rb +62 -0
  54. data/lib/davinci_crd_test_kit/client/v2.0.1/must_support/client_card_must_support_instructions.rb +62 -0
  55. data/lib/davinci_crd_test_kit/client/v2.0.1/registration/client_registration_verification_test.rb +94 -0
  56. data/lib/davinci_crd_test_kit/client/v2.0.1/registration/client_service_registration_attestation_test.rb +40 -0
  57. data/lib/davinci_crd_test_kit/client/v2.0.1/verify_request/hook_request_fetched_data_test.rb +86 -0
  58. data/lib/davinci_crd_test_kit/client/v2.0.1/verify_request/hook_request_optional_fields_test.rb +63 -0
  59. data/lib/davinci_crd_test_kit/client/v2.0.1/verify_request/hook_request_prefetch_equals_queried_test.rb +96 -0
  60. data/lib/davinci_crd_test_kit/client/v2.0.1/verify_request/hook_request_required_fields_test.rb +55 -0
  61. data/lib/davinci_crd_test_kit/client/v2.0.1/verify_request/hook_request_valid_context_test.rb +70 -0
  62. data/lib/davinci_crd_test_kit/client/v2.0.1/verify_request/hook_request_valid_prefetch_test.rb +62 -0
  63. data/lib/davinci_crd_test_kit/client/v2.0.1/verify_response/client_display_cards_attest.rb +83 -0
  64. data/lib/davinci_crd_test_kit/client/v2.0.1/verify_response/inferno_response_validation.rb +79 -0
  65. data/lib/davinci_crd_test_kit/client/v2.2.1/api/client_coverage_info_update_test.rb +212 -0
  66. data/lib/davinci_crd_test_kit/client/v2.2.1/api/client_crd_update_verification_group.rb +18 -0
  67. data/lib/davinci_crd_test_kit/client/v2.2.1/auth/decode_auth_token_test.rb +69 -0
  68. data/lib/davinci_crd_test_kit/client/v2.2.1/auth/retrieve_jwks_test.rb +120 -0
  69. data/lib/davinci_crd_test_kit/client/v2.2.1/auth/token_header_test.rb +92 -0
  70. data/lib/davinci_crd_test_kit/client/v2.2.1/auth/token_payload_test.rb +93 -0
  71. data/lib/davinci_crd_test_kit/client/v2.2.1/cds-services-prefetch-subset-v221.json +198 -0
  72. data/lib/davinci_crd_test_kit/client/v2.2.1/cds-services-v221.json +202 -0
  73. data/lib/davinci_crd_test_kit/client/v2.2.1/client_appointment_book_group.rb +102 -0
  74. data/lib/davinci_crd_test_kit/client/v2.2.1/client_cross_hook_group.rb +28 -0
  75. data/lib/davinci_crd_test_kit/client/v2.2.1/client_encounter_discharge_group.rb +96 -0
  76. data/lib/davinci_crd_test_kit/client/v2.2.1/client_encounter_start_group.rb +95 -0
  77. data/lib/davinci_crd_test_kit/client/v2.2.1/client_fhir_api_group.rb +88 -0
  78. data/lib/davinci_crd_test_kit/client/v2.2.1/client_hooks_group.rb +64 -0
  79. data/lib/davinci_crd_test_kit/client/v2.2.1/client_long_running_hook_group.rb +32 -0
  80. data/lib/davinci_crd_test_kit/client/v2.2.1/client_order_dispatch_group.rb +101 -0
  81. data/lib/davinci_crd_test_kit/client/v2.2.1/client_order_select_group.rb +102 -0
  82. data/lib/davinci_crd_test_kit/client/v2.2.1/client_order_sign_group.rb +107 -0
  83. data/lib/davinci_crd_test_kit/client/v2.2.1/client_registration_group.rb +27 -0
  84. data/lib/davinci_crd_test_kit/client/v2.2.1/client_urls.rb +27 -0
  85. data/lib/davinci_crd_test_kit/client/v2.2.1/crd_client_suite.rb +229 -0
  86. data/lib/davinci_crd_test_kit/client/v2.2.1/cross_hook/client_card_must_support_coverage_information_test.rb +63 -0
  87. data/lib/davinci_crd_test_kit/client/v2.2.1/cross_hook/client_fhirpath_collection_as_comma_delimited_string_test.rb +60 -0
  88. data/lib/davinci_crd_test_kit/client/v2.2.1/cross_hook/client_hook_instances_unique_test.rb +45 -0
  89. data/lib/davinci_crd_test_kit/client/v2.2.1/cross_hook/client_location_address_propagation_test.rb +135 -0
  90. data/lib/davinci_crd_test_kit/client/v2.2.1/cross_hook/client_prefetch_complete_and_subset_test.rb +103 -0
  91. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/appointment_book_receive_request_test.rb +156 -0
  92. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/encounter_discharge_receive_request_test.rb +157 -0
  93. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/encounter_start_receive_request_test.rb +157 -0
  94. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/order_dispatch_receive_request_test.rb +165 -0
  95. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/order_select_receive_request_test.rb +165 -0
  96. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/order_sign_receive_request_test.rb +165 -0
  97. data/lib/davinci_crd_test_kit/client/v2.2.1/long_running/client_long_running_receive_request_test.rb +64 -0
  98. data/lib/davinci_crd_test_kit/client/v2.2.1/long_running/client_skip_long_running_attestation_test.rb +49 -0
  99. data/lib/davinci_crd_test_kit/client/v2.2.1/registration/client_registration_verification_test.rb +161 -0
  100. data/lib/davinci_crd_test_kit/client/v2.2.1/registration/client_service_registration_attestation_test.rb +107 -0
  101. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_conformance_test.rb +47 -0
  102. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_coverage_verification_test.rb +152 -0
  103. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_data_fetch_verification_test.rb +55 -0
  104. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_granted_scopes_test.rb +123 -0
  105. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_prefetch_complete_test.rb +127 -0
  106. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_prefetch_profiles_test.rb +55 -0
  107. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_requested_version_test.rb +54 -0
  108. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_secured_transport_test.rb +48 -0
  109. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_response/client_display_cards_attest.rb +74 -0
  110. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_response/hook_response_support_coverage_information_test.rb +30 -0
  111. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_response/inferno_response_validation.rb +77 -0
  112. data/lib/davinci_crd_test_kit/cross_suite/base_urls.rb +20 -0
  113. data/lib/davinci_crd_test_kit/cross_suite/cards_identification.rb +312 -0
  114. data/lib/davinci_crd_test_kit/{cards_validation.rb → cross_suite/cards_validation.rb} +104 -47
  115. data/lib/davinci_crd_test_kit/cross_suite/coverage-information_stu201_metadata.yml +27 -0
  116. data/lib/davinci_crd_test_kit/cross_suite/coverage-information_stu221_metadata.yml +60 -0
  117. data/lib/davinci_crd_test_kit/cross_suite/fhirpath_on_cds_request.rb +177 -0
  118. data/lib/davinci_crd_test_kit/{hook_request_field_validation.rb → cross_suite/hook_request_field_validation.rb} +282 -203
  119. data/lib/davinci_crd_test_kit/cross_suite/logical_models_override_helper.rb +220 -0
  120. data/lib/davinci_crd_test_kit/cross_suite/prefetch_completeness_checker.rb +462 -0
  121. data/lib/davinci_crd_test_kit/cross_suite/prefetch_contents_validation.rb +81 -0
  122. data/lib/davinci_crd_test_kit/cross_suite/prefetch_profile_validation.rb +48 -0
  123. data/lib/davinci_crd_test_kit/cross_suite/profiles_and_resource_types.rb +63 -0
  124. data/lib/davinci_crd_test_kit/cross_suite/replace_tokens.rb +38 -0
  125. data/lib/davinci_crd_test_kit/cross_suite/requests_logical_model_validation.rb +202 -0
  126. data/lib/davinci_crd_test_kit/cross_suite/response_logical_model_validation.rb +274 -0
  127. data/lib/davinci_crd_test_kit/{suggestion_actions_validation.rb → cross_suite/suggestion_actions_validation.rb} +70 -50
  128. data/lib/davinci_crd_test_kit/cross_suite/tags.rb +42 -0
  129. data/lib/davinci_crd_test_kit/metadata.rb +10 -44
  130. data/lib/davinci_crd_test_kit/requirements/cds-hooks-library_1.0.1_requirements.xlsx +0 -0
  131. data/lib/davinci_crd_test_kit/requirements/cds-hooks_2.0_requirements.xlsx +0 -0
  132. data/lib/davinci_crd_test_kit/requirements/cds-hooks_3.0.0-ballot_requirements.xlsx +0 -0
  133. data/lib/davinci_crd_test_kit/requirements/davinci_crd_test_kit_requirements.csv +742 -65
  134. data/lib/davinci_crd_test_kit/requirements/generated/crd_client_requirements_coverage.csv +279 -54
  135. data/lib/davinci_crd_test_kit/requirements/generated/crd_client_v221_requirements_coverage.csv +1430 -0
  136. data/lib/davinci_crd_test_kit/requirements/generated/crd_server_requirements_coverage.csv +36 -45
  137. data/lib/davinci_crd_test_kit/requirements/generated/crd_server_v221_requirements_coverage.csv +143 -0
  138. data/lib/davinci_crd_test_kit/requirements/hl7.fhir.us.davinci-crd_2.0.1_requirements.xlsx +0 -0
  139. data/lib/davinci_crd_test_kit/requirements/hl7.fhir.us.davinci-crd_2.2.1_requirements.xlsx +0 -0
  140. data/lib/davinci_crd_test_kit/server/endpoints/jwk_set_endpoint_handler.rb +13 -0
  141. data/lib/davinci_crd_test_kit/server/endpoints/mock_ehr/fhir_create_endpoint.rb +23 -0
  142. data/lib/davinci_crd_test_kit/server/endpoints/mock_ehr/fhir_delete_endpoint.rb +30 -0
  143. data/lib/davinci_crd_test_kit/server/endpoints/mock_ehr/fhir_metadata_endpoint.rb +112 -0
  144. data/lib/davinci_crd_test_kit/server/endpoints/mock_ehr/fhir_read_endpoint.rb +21 -0
  145. data/lib/davinci_crd_test_kit/server/endpoints/mock_ehr/fhir_request_handler.rb +261 -0
  146. data/lib/davinci_crd_test_kit/server/endpoints/mock_ehr/fhir_search_endpoint.rb +561 -0
  147. data/lib/davinci_crd_test_kit/server/endpoints/mock_ehr/fhir_update_endpoint.rb +24 -0
  148. data/lib/davinci_crd_test_kit/server/endpoints/mock_ehr/stress-test-Bundle.json +54687 -0
  149. data/lib/davinci_crd_test_kit/server/endpoints/mock_ehr_endpoints.rb +95 -0
  150. data/lib/davinci_crd_test_kit/server/jobs/invoke_hook.rb +225 -0
  151. data/lib/davinci_crd_test_kit/{jwt_helper.rb → server/jwt_helper.rb} +1 -12
  152. data/lib/davinci_crd_test_kit/server/resource_extractor.rb +68 -0
  153. data/lib/davinci_crd_test_kit/server/server_abstract_invoke_hook_test.rb +165 -0
  154. data/lib/davinci_crd_test_kit/server/server_base_urls.rb +30 -0
  155. data/lib/davinci_crd_test_kit/{server_hook_helper.rb → server/server_hook_helper.rb} +1 -1
  156. data/lib/davinci_crd_test_kit/{server_hook_request_validation.rb → server/server_hook_request_validation.rb} +1 -1
  157. data/lib/davinci_crd_test_kit/{test_helper.rb → server/server_test_helper.rb} +7 -3
  158. data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/Appointment.yml +5 -0
  159. data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/ClaimResponse.yml +5 -0
  160. data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/CommunicationRequest.yml +5 -0
  161. data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/Coverage.yml +21 -0
  162. data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/Device.yml +5 -0
  163. data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/DeviceRequest.yml +5 -0
  164. data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/Encounter.yml +7 -0
  165. data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/Location.yml +5 -0
  166. data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/MedicationRequest.yml +5 -0
  167. data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/NutritionOrder.yml +5 -0
  168. data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/Organization.yml +5 -0
  169. data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/Patient.yml +5 -0
  170. data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/Practitioner.yml +5 -0
  171. data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/PractitionerRole.yml +40 -0
  172. data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/ServiceRequest.yml +5 -0
  173. data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/Task.yml +5 -0
  174. data/lib/davinci_crd_test_kit/server/v2.0.1/crd_metadata/VisionPrescription.yml +5 -0
  175. data/lib/davinci_crd_test_kit/server/v2.0.1/crd_server_suite.rb +99 -0
  176. data/lib/davinci_crd_test_kit/server/v2.0.1/discovery/discovery_endpoint_test.rb +90 -0
  177. data/lib/davinci_crd_test_kit/server/v2.0.1/discovery/discovery_services_validation_test.rb +67 -0
  178. data/lib/davinci_crd_test_kit/server/v2.0.1/interaction/server_invoke_hook_test.rb +12 -0
  179. data/lib/davinci_crd_test_kit/server/v2.0.1/must_support/coverage_information_system_action_across_hooks_validation_test.rb +34 -0
  180. data/lib/davinci_crd_test_kit/server/v2.0.1/must_support/external_reference_card_across_hooks_validation_test.rb +30 -0
  181. data/lib/davinci_crd_test_kit/server/v2.0.1/must_support/instructions_card_received_across_hooks_test.rb +27 -0
  182. data/lib/davinci_crd_test_kit/server/v2.0.1/server_appointment_book_group.rb +191 -0
  183. data/lib/davinci_crd_test_kit/server/v2.0.1/server_demonstrate_hook_response_group.rb +93 -0
  184. data/lib/davinci_crd_test_kit/server/v2.0.1/server_discovery_group.rb +62 -0
  185. data/lib/davinci_crd_test_kit/server/v2.0.1/server_encounter_discharge_group.rb +186 -0
  186. data/lib/davinci_crd_test_kit/server/v2.0.1/server_encounter_start_group.rb +186 -0
  187. data/lib/davinci_crd_test_kit/server/v2.0.1/server_hooks_group.rb +73 -0
  188. data/lib/davinci_crd_test_kit/server/v2.0.1/server_order_dispatch_group.rb +191 -0
  189. data/lib/davinci_crd_test_kit/server/v2.0.1/server_order_select_group.rb +211 -0
  190. data/lib/davinci_crd_test_kit/server/v2.0.1/server_order_sign_group.rb +216 -0
  191. data/lib/davinci_crd_test_kit/server/v2.0.1/server_required_card_response_validation_group.rb +28 -0
  192. data/lib/davinci_crd_test_kit/server/v2.0.1/server_urls.rb +13 -0
  193. data/lib/davinci_crd_test_kit/server/v2.0.1/verify_request/service_request_context_validation_test.rb +30 -0
  194. data/lib/davinci_crd_test_kit/server/v2.0.1/verify_request/service_request_optional_fields_validation_test.rb +39 -0
  195. data/lib/davinci_crd_test_kit/server/v2.0.1/verify_request/service_request_required_fields_validation_test.rb +40 -0
  196. data/lib/davinci_crd_test_kit/server/v2.0.1/verify_response/additional_orders_validation_test.rb +59 -0
  197. data/lib/davinci_crd_test_kit/server/v2.0.1/verify_response/card_optional_fields_validation_test.rb +51 -0
  198. data/lib/davinci_crd_test_kit/server/v2.0.1/verify_response/coverage_information_system_action_received_test.rb +65 -0
  199. data/lib/davinci_crd_test_kit/server/v2.0.1/verify_response/coverage_information_system_action_validation_test.rb +120 -0
  200. data/lib/davinci_crd_test_kit/server/v2.0.1/verify_response/create_or_update_coverage_info_response_validation_test.rb +70 -0
  201. data/lib/davinci_crd_test_kit/server/v2.0.1/verify_response/external_reference_card_validation_test.rb +37 -0
  202. data/lib/davinci_crd_test_kit/server/v2.0.1/verify_response/form_completion_response_validation_test.rb +67 -0
  203. data/lib/davinci_crd_test_kit/server/v2.0.1/verify_response/instructions_card_received_test.rb +30 -0
  204. data/lib/davinci_crd_test_kit/server/v2.0.1/verify_response/launch_smart_app_card_validation_test.rb +39 -0
  205. data/lib/davinci_crd_test_kit/server/v2.0.1/verify_response/propose_alternate_request_card_validation_test.rb +46 -0
  206. data/lib/davinci_crd_test_kit/server/v2.0.1/verify_response/service_response_validation_test.rb +83 -0
  207. data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/Appointment_withorder.yml +5 -0
  208. data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/Appointment_withoutorder.yml +5 -0
  209. data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/CommunicationRequest.yml +5 -0
  210. data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/Coverage.yml +5 -0
  211. data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/Device.yml +5 -0
  212. data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/DeviceRequest.yml +5 -0
  213. data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/Encounter.yml +5 -0
  214. data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/Location.yml +5 -0
  215. data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/MedicationRequest.yml +5 -0
  216. data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/NutritionOrder.yml +5 -0
  217. data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/Organization.yml +5 -0
  218. data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/Patient.yml +5 -0
  219. data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/PractitionerRole.yml +5 -0
  220. data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/ServiceRequest.yml +5 -0
  221. data/lib/davinci_crd_test_kit/server/v2.2.1/crd_metadata/VisionPrescription.yml +5 -0
  222. data/lib/davinci_crd_test_kit/server/v2.2.1/crd_server_suite.rb +115 -0
  223. data/lib/davinci_crd_test_kit/server/v2.2.1/discovery/discovery_configuration_test.rb +159 -0
  224. data/lib/davinci_crd_test_kit/server/v2.2.1/discovery/discovery_endpoint_test.rb +90 -0
  225. data/lib/davinci_crd_test_kit/server/v2.2.1/discovery/discovery_prefetch_support_test.rb +43 -0
  226. data/lib/davinci_crd_test_kit/server/v2.2.1/discovery/discovery_services_validation_test.rb +121 -0
  227. data/lib/davinci_crd_test_kit/server/v2.2.1/interaction/server_invoke_hook_test.rb +17 -0
  228. data/lib/davinci_crd_test_kit/server/v2.2.1/must_support/coverage_information_must_support_test.rb +71 -0
  229. data/lib/davinci_crd_test_kit/server/v2.2.1/must_support/coverage_information_system_action_across_hooks_validation_test.rb +36 -0
  230. data/lib/davinci_crd_test_kit/server/v2.2.1/must_support/supported_us_core_versions_test.rb +118 -0
  231. data/lib/davinci_crd_test_kit/server/v2.2.1/server_appointment_book_group.rb +213 -0
  232. data/lib/davinci_crd_test_kit/server/v2.2.1/server_demonstrate_hook_response_group.rb +93 -0
  233. data/lib/davinci_crd_test_kit/server/v2.2.1/server_discovery_group.rb +69 -0
  234. data/lib/davinci_crd_test_kit/server/v2.2.1/server_encounter_discharge_group.rb +194 -0
  235. data/lib/davinci_crd_test_kit/server/v2.2.1/server_encounter_start_group.rb +194 -0
  236. data/lib/davinci_crd_test_kit/server/v2.2.1/server_hooks_group.rb +73 -0
  237. data/lib/davinci_crd_test_kit/server/v2.2.1/server_order_dispatch_group.rb +214 -0
  238. data/lib/davinci_crd_test_kit/server/v2.2.1/server_order_select_group.rb +219 -0
  239. data/lib/davinci_crd_test_kit/server/v2.2.1/server_order_sign_group.rb +241 -0
  240. data/lib/davinci_crd_test_kit/server/v2.2.1/server_required_card_response_validation_group.rb +30 -0
  241. data/lib/davinci_crd_test_kit/server/v2.2.1/server_urls.rb +13 -0
  242. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_request/service_request_context_validation_test.rb +30 -0
  243. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_request/service_request_no_custom_extensions_test.rb +120 -0
  244. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_request/service_request_optional_fields_validation_test.rb +39 -0
  245. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_request/service_request_required_fields_validation_test.rb +40 -0
  246. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/additional_orders_validation_test.rb +66 -0
  247. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/all_responses_include_coverage_information_test.rb +123 -0
  248. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/card_optional_fields_validation_test.rb +57 -0
  249. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/coverage_info_configuration_test.rb +83 -0
  250. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/coverage_information_system_action_received_test.rb +65 -0
  251. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/coverage_information_system_action_validation_test.rb +184 -0
  252. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/create_or_update_coverage_info_response_validation_test.rb +75 -0
  253. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/external_reference_card_validation_test.rb +47 -0
  254. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/form_completion_response_validation_test.rb +91 -0
  255. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/hook_request_resource_resolution.rb +137 -0
  256. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/instructions_card_received_test.rb +32 -0
  257. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/launch_smart_app_card_validation_test.rb +49 -0
  258. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/order_dispatch_coverage_information_test.rb +38 -0
  259. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/propose_alternate_request_card_validation_test.rb +54 -0
  260. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/service_response_validation_test.rb +97 -0
  261. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/unknown_cds_hooks_elements_test.rb +78 -0
  262. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/unknown_configuration_test.rb +78 -0
  263. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/unknown_context_test.rb +78 -0
  264. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/verify_response_without_billing_options_test.rb +43 -0
  265. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/verify_response_without_configuration_test.rb +44 -0
  266. data/lib/davinci_crd_test_kit/version.rb +2 -2
  267. data/lib/davinci_crd_test_kit.rb +4 -2
  268. metadata +297 -93
  269. data/lib/davinci_crd_test_kit/client_fhir_api_group.rb +0 -785
  270. data/lib/davinci_crd_test_kit/client_hooks_group.rb +0 -74
  271. data/lib/davinci_crd_test_kit/client_tests/appointment_book_receive_request_test.rb +0 -93
  272. data/lib/davinci_crd_test_kit/client_tests/client_appointment_book_group.rb +0 -75
  273. data/lib/davinci_crd_test_kit/client_tests/client_display_cards_attest.rb +0 -48
  274. data/lib/davinci_crd_test_kit/client_tests/client_encounter_discharge_group.rb +0 -73
  275. data/lib/davinci_crd_test_kit/client_tests/client_encounter_start_group.rb +0 -73
  276. data/lib/davinci_crd_test_kit/client_tests/client_fhir_api_create_test.rb +0 -42
  277. data/lib/davinci_crd_test_kit/client_tests/client_fhir_api_read_test.rb +0 -40
  278. data/lib/davinci_crd_test_kit/client_tests/client_fhir_api_search_test.rb +0 -232
  279. data/lib/davinci_crd_test_kit/client_tests/client_fhir_api_update_test.rb +0 -42
  280. data/lib/davinci_crd_test_kit/client_tests/client_fhir_api_validation_test.rb +0 -61
  281. data/lib/davinci_crd_test_kit/client_tests/client_order_dispatch_group.rb +0 -79
  282. data/lib/davinci_crd_test_kit/client_tests/client_order_select_group.rb +0 -82
  283. data/lib/davinci_crd_test_kit/client_tests/client_order_sign_group.rb +0 -81
  284. data/lib/davinci_crd_test_kit/client_tests/client_registration_verification_test.rb +0 -88
  285. data/lib/davinci_crd_test_kit/client_tests/decode_auth_token_test.rb +0 -60
  286. data/lib/davinci_crd_test_kit/client_tests/encounter_discharge_receive_request_test.rb +0 -90
  287. data/lib/davinci_crd_test_kit/client_tests/encounter_start_receive_request_test.rb +0 -90
  288. data/lib/davinci_crd_test_kit/client_tests/hook_request_optional_fields_test.rb +0 -57
  289. data/lib/davinci_crd_test_kit/client_tests/hook_request_required_fields_test.rb +0 -49
  290. data/lib/davinci_crd_test_kit/client_tests/hook_request_valid_context_test.rb +0 -68
  291. data/lib/davinci_crd_test_kit/client_tests/hook_request_valid_prefetch_test.rb +0 -69
  292. data/lib/davinci_crd_test_kit/client_tests/order_dispatch_receive_request_test.rb +0 -102
  293. data/lib/davinci_crd_test_kit/client_tests/order_select_receive_request_test.rb +0 -98
  294. data/lib/davinci_crd_test_kit/client_tests/order_sign_receive_request_test.rb +0 -101
  295. data/lib/davinci_crd_test_kit/client_tests/retrieve_jwks_test.rb +0 -105
  296. data/lib/davinci_crd_test_kit/client_tests/submitted_response_validation.rb +0 -48
  297. data/lib/davinci_crd_test_kit/client_tests/token_header_test.rb +0 -65
  298. data/lib/davinci_crd_test_kit/client_tests/token_payload_test.rb +0 -78
  299. data/lib/davinci_crd_test_kit/crd_client_suite.rb +0 -193
  300. data/lib/davinci_crd_test_kit/crd_options.rb +0 -9
  301. data/lib/davinci_crd_test_kit/crd_server_suite.rb +0 -125
  302. data/lib/davinci_crd_test_kit/igs/davinci-crd-2.0.1.tgz +0 -0
  303. data/lib/davinci_crd_test_kit/routes/cds_services_discovery_handler.rb +0 -18
  304. data/lib/davinci_crd_test_kit/routes/hook_request_endpoint.rb +0 -77
  305. data/lib/davinci_crd_test_kit/routes/jwk_set_endpoint_handler.rb +0 -15
  306. data/lib/davinci_crd_test_kit/server_appointment_book_group.rb +0 -176
  307. data/lib/davinci_crd_test_kit/server_demonstrate_hook_response_group.rb +0 -77
  308. data/lib/davinci_crd_test_kit/server_discovery_group.rb +0 -60
  309. data/lib/davinci_crd_test_kit/server_encounter_discharge_group.rb +0 -170
  310. data/lib/davinci_crd_test_kit/server_encounter_start_group.rb +0 -170
  311. data/lib/davinci_crd_test_kit/server_hooks_group.rb +0 -71
  312. data/lib/davinci_crd_test_kit/server_order_dispatch_group.rb +0 -176
  313. data/lib/davinci_crd_test_kit/server_order_select_group.rb +0 -195
  314. data/lib/davinci_crd_test_kit/server_order_sign_group.rb +0 -201
  315. data/lib/davinci_crd_test_kit/server_required_card_response_validation_group.rb +0 -26
  316. data/lib/davinci_crd_test_kit/server_tests/additional_orders_validation_test.rb +0 -68
  317. data/lib/davinci_crd_test_kit/server_tests/card_optional_fields_validation_test.rb +0 -47
  318. data/lib/davinci_crd_test_kit/server_tests/coverage_information_system_action_across_hooks_validation_test.rb +0 -32
  319. data/lib/davinci_crd_test_kit/server_tests/coverage_information_system_action_received_test.rb +0 -63
  320. data/lib/davinci_crd_test_kit/server_tests/coverage_information_system_action_validation_test.rb +0 -118
  321. data/lib/davinci_crd_test_kit/server_tests/create_or_update_coverage_info_response_validation_test.rb +0 -71
  322. data/lib/davinci_crd_test_kit/server_tests/discovery_endpoint_test.rb +0 -88
  323. data/lib/davinci_crd_test_kit/server_tests/discovery_services_validation_test.rb +0 -65
  324. data/lib/davinci_crd_test_kit/server_tests/external_reference_card_across_hooks_validation_test.rb +0 -28
  325. data/lib/davinci_crd_test_kit/server_tests/external_reference_card_validation_test.rb +0 -36
  326. data/lib/davinci_crd_test_kit/server_tests/form_completion_response_validation_test.rb +0 -78
  327. data/lib/davinci_crd_test_kit/server_tests/instructions_card_received_across_hooks_test.rb +0 -25
  328. data/lib/davinci_crd_test_kit/server_tests/instructions_card_received_test.rb +0 -26
  329. data/lib/davinci_crd_test_kit/server_tests/launch_smart_app_card_validation_test.rb +0 -38
  330. data/lib/davinci_crd_test_kit/server_tests/propose_alternate_request_card_validation_test.rb +0 -63
  331. data/lib/davinci_crd_test_kit/server_tests/service_call_test.rb +0 -101
  332. data/lib/davinci_crd_test_kit/server_tests/service_request_context_validation_test.rb +0 -28
  333. data/lib/davinci_crd_test_kit/server_tests/service_request_optional_fields_validation_test.rb +0 -37
  334. data/lib/davinci_crd_test_kit/server_tests/service_request_required_fields_validation_test.rb +0 -38
  335. data/lib/davinci_crd_test_kit/server_tests/service_response_validation_test.rb +0 -81
  336. data/lib/davinci_crd_test_kit/tags.rb +0 -10
  337. data/lib/davinci_crd_test_kit/urls.rb +0 -52
  338. /data/lib/davinci_crd_test_kit/{card_responses → client/endpoints/mocked_card_responses}/external_reference.json +0 -0
  339. /data/lib/davinci_crd_test_kit/{card_responses → client/endpoints/mocked_card_responses}/instructions.json +0 -0
  340. /data/lib/davinci_crd_test_kit/{crd_jwks.json → server/endpoints/crd_jwks.json} +0 -0
  341. /data/lib/davinci_crd_test_kit/{jwks.rb → server/endpoints/jwks.rb} +0 -0
@@ -0,0 +1,410 @@
1
+ require_relative '../../cross_suite/tags'
2
+
3
+ module DaVinciCRDTestKit
4
+ # Make requests to client's FHIR server to use in building responses
5
+ module GatherResponseGenerationData
6
+ REFERENCES_TO_READ_BY_RESOURCE_TYPE = {
7
+ Appointment: [
8
+ 'basedOn',
9
+ 'participant.actor'
10
+ ],
11
+ CommunicationRequest: [
12
+ 'requester',
13
+ 'sender',
14
+ 'recipient'
15
+ ],
16
+ DeviceRequest: [
17
+ 'requester',
18
+ 'performer'
19
+ ],
20
+ MedicationRequest: [
21
+ 'requester',
22
+ 'performer',
23
+ 'medicationReference',
24
+ 'dispenseRequest.performer'
25
+ ],
26
+ NutritionOrder: [
27
+ 'orderer'
28
+ ],
29
+ ServiceRequest: [
30
+ 'requester',
31
+ 'performer',
32
+ 'locationReference'
33
+ ],
34
+ VisionPrescription: [
35
+ 'prescriber'
36
+ ],
37
+ Encounter: [
38
+ 'participant.individual',
39
+ 'location.location',
40
+ 'serviceProvider'
41
+ ],
42
+ PractitionerRole: [
43
+ 'practitioner',
44
+ 'organization',
45
+ 'location'
46
+ ]
47
+ }.freeze
48
+
49
+ def hook_instance_data_fetch_tag
50
+ @hook_instance_data_fetch_tag ||= TagMethods.hook_instance_data_fetch_tag(request_body['hookInstance'])
51
+ end
52
+
53
+ def gather_appointment_book_data
54
+ appointment_book_to_read = []
55
+ patient_id = request_body.dig('context', 'patientId')
56
+ encounter_id = request_body.dig('context', 'encounterId')
57
+ user_id = request_body.dig('context', 'userId')
58
+ appointment_book_to_read << "Patient/#{patient_id}" if patient_id.present?
59
+ appointment_book_to_read << "Encounter/#{encounter_id}" if encounter_id.present?
60
+ appointment_book_to_read << user_id if user_id.present?
61
+
62
+ appointment_book_to_analyze = extract_bundle_entries(request_body.dig('context', 'appointments'))
63
+
64
+ gather_data_for_request(appointment_book_to_read, appointment_book_to_analyze)
65
+ end
66
+
67
+ def gather_encounter_start_data
68
+ encounter_start_to_read = []
69
+ patient_id = request_body.dig('context', 'patientId')
70
+ encounter_id = request_body.dig('context', 'encounterId')
71
+ user_id = request_body.dig('context', 'userId')
72
+ encounter_start_to_read << "Patient/#{patient_id}" if patient_id.present?
73
+ encounter_start_to_read << "Encounter/#{encounter_id}" if encounter_id.present?
74
+ encounter_start_to_read << user_id if user_id.present?
75
+
76
+ gather_data_for_request(encounter_start_to_read, [])
77
+ end
78
+
79
+ def gather_encounter_discharge_data
80
+ encounter_discharge_to_read = []
81
+ patient_id = request_body.dig('context', 'patientId')
82
+ encounter_id = request_body.dig('context', 'encounterId')
83
+ user_id = request_body.dig('context', 'userId')
84
+ encounter_discharge_to_read << "Patient/#{patient_id}" if patient_id.present?
85
+ encounter_discharge_to_read << "Encounter/#{encounter_id}" if encounter_id.present?
86
+ encounter_discharge_to_read << user_id if user_id.present?
87
+
88
+ gather_data_for_request(encounter_discharge_to_read, [])
89
+ end
90
+
91
+ def gather_order_select_data
92
+ order_sign_to_read = []
93
+ patient_id = request_body.dig('context', 'patientId')
94
+ encounter_id = request_body.dig('context', 'encounterId')
95
+ user_id = request_body.dig('context', 'userId')
96
+ order_sign_to_read << "Patient/#{patient_id}" if patient_id.present?
97
+ order_sign_to_read << "Encounter/#{encounter_id}" if encounter_id.present?
98
+ order_sign_to_read << user_id if user_id.present?
99
+
100
+ order_sign_to_analyze = extract_bundle_entries(request_body.dig('context', 'draftOrders')).select do |resource|
101
+ request_body.dig('context', 'selections').include?("#{resource['resourceType']}/#{resource['id']}")
102
+ end
103
+
104
+ gather_data_for_request(order_sign_to_read, order_sign_to_analyze)
105
+ end
106
+
107
+ def gather_order_sign_data
108
+ order_sign_to_read = []
109
+ patient_id = request_body.dig('context', 'patientId')
110
+ encounter_id = request_body.dig('context', 'encounterId')
111
+ user_id = request_body.dig('context', 'userId')
112
+ order_sign_to_read << "Patient/#{patient_id}" if patient_id.present?
113
+ order_sign_to_read << "Encounter/#{encounter_id}" if encounter_id.present?
114
+ order_sign_to_read << user_id if user_id.present?
115
+
116
+ order_sign_to_analyze = extract_bundle_entries(request_body.dig('context', 'draftOrders'))
117
+
118
+ gather_data_for_request(order_sign_to_read, order_sign_to_analyze)
119
+ end
120
+
121
+ def gather_order_dispatch_data
122
+ order_dispatch_to_read = []
123
+ patient_id = request_body.dig('context', 'patientId')
124
+ order_id = request_body.dig('context', 'order')
125
+ performer_id = request_body.dig('context', 'performer')
126
+ order_dispatch_to_read << "Patient/#{patient_id}" if patient_id.present?
127
+ order_dispatch_to_read << order_id if order_id.present?
128
+ order_dispatch_to_read << performer_id if performer_id.present?
129
+
130
+ gather_data_for_request(order_dispatch_to_read, [request_body.dig('context', 'task')].compact)
131
+ end
132
+
133
+ def extract_bundle_entries(bundle)
134
+ return [] unless bundle.present? && bundle.is_a?(Hash) && bundle['entry'].is_a?(Array)
135
+
136
+ bundle['entry'].map do |entry|
137
+ next unless entry.is_a?(Hash) && entry['resource'].is_a?(Hash)
138
+
139
+ entry['resource']
140
+ end.compact
141
+ end
142
+
143
+ def gather_data_for_request(to_read_list, to_analyze_list)
144
+ to_analyze_list.each do |resource|
145
+ next unless resource.present?
146
+
147
+ find_references_to_read(resource, to_read_list)
148
+ analyzed_resources["#{resource['resourceType']}/#{resource['id']}"] = resource
149
+ end
150
+
151
+ read_and_analyze(to_read_list.pop, to_read_list) until to_read_list.empty?
152
+ end
153
+
154
+ def read_and_analyze(reference, to_read_list)
155
+ normalized_reference = normalize_reference(reference)
156
+ return if analyzed_resources.key?(normalized_reference)
157
+
158
+ resource =
159
+ if prefetched_resources.key?(normalized_reference)
160
+ prefetched_resources[normalized_reference]
161
+ else
162
+ fetch_reference(normalized_reference)
163
+ end
164
+ analyzed_resources[normalized_reference] = resource
165
+ return unless resource.present?
166
+
167
+ find_references_to_read(resource, to_read_list)
168
+ end
169
+
170
+ # turn absolute references into relative if
171
+ # for the fhir server indicated in the request
172
+ def normalize_reference(reference)
173
+ server = "#{request_body['fhirServer']&.chomp('/')}/"
174
+
175
+ if request_body['fhirServer'].present? && reference.starts_with?(server)
176
+ reference[server.length..]
177
+ else
178
+ reference
179
+ end
180
+ end
181
+
182
+ def fetch_reference(reference, additional_tag: nil, parse_as_fhir: false)
183
+ response = execute_request(reference)
184
+ return nil unless response.present?
185
+
186
+ tags = [DATA_FETCH_TAG, hook_instance_data_fetch_tag]
187
+ tags << additional_tag if additional_tag.present?
188
+ persist_query_request(response, tags)
189
+ return nil unless response.status.to_s.starts_with?('2')
190
+
191
+ begin
192
+ parse_as_fhir ? FHIR.from_contents(response.body) : JSON.parse(response.body)
193
+ rescue JSON::ParserError
194
+ nil
195
+ end
196
+ end
197
+
198
+ def find_references_to_read(resource, to_read_list)
199
+ references_to_read = REFERENCES_TO_READ_BY_RESOURCE_TYPE[resource['resourceType']&.to_sym]
200
+ return [] unless references_to_read.present?
201
+
202
+ references_to_read.each do |target_path|
203
+ get_literal_reference_values(resource, target_path).each do |reference|
204
+ to_read_list << reference unless to_read_list.include?(reference)
205
+ end
206
+ end
207
+ end
208
+
209
+ # Precondition: at most one level of nesting in the path
210
+ def get_literal_reference_values(resource, path)
211
+ if path.include?('.')
212
+ first_element, path = path.split('.')
213
+ resource = resource[first_element]
214
+ end
215
+
216
+ reference_objects =
217
+ case resource
218
+ when Hash
219
+ [resource[path]].flatten
220
+ when Array
221
+ resource.map { |entry| entry[path] }
222
+ else
223
+ []
224
+ end
225
+
226
+ reference_objects.map { |entry| entry['reference'] if entry.present? }.compact
227
+ end
228
+
229
+ def request_coverage
230
+ @request_coverage ||= find_coverage_for_request
231
+ end
232
+
233
+ def prefetched_coverage
234
+ @prefetched_coverage ||=
235
+ if request_body.dig('prefetch', 'coverage').present?
236
+ FHIR.from_contents(request_body.dig('prefetch', 'coverage').to_json)
237
+ elsif request_body.dig('prefetch', 'cov').present?
238
+ FHIR.from_contents(request_body.dig('prefetch', 'cov').to_json)
239
+ end
240
+ end
241
+
242
+ def find_coverage_for_request
243
+ resource =
244
+ if prefetched_coverage.present?
245
+ prefetched_coverage
246
+ else
247
+ query_for_coverages
248
+ end
249
+
250
+ return unless resource.is_a?(FHIR::Bundle)
251
+
252
+ resource.entry&.first&.resource
253
+ end
254
+
255
+ def fhir_server_connection
256
+ @fhir_server_connection ||=
257
+ if request_body['fhirServer'].present? &&
258
+ request_body['fhirAuthorization'].present? &&
259
+ request_body['fhirAuthorization']['access_token'].present?
260
+ Faraday.new(url: request_body['fhirServer'], request: { open_timeout: 10 },
261
+ headers: data_request_headers)
262
+ end
263
+ end
264
+
265
+ def data_request_headers
266
+ @data_request_headers ||=
267
+ { 'Authorization' => "Bearer #{request_body['fhirAuthorization']['access_token']}" }
268
+ end
269
+
270
+ def execute_request(query)
271
+ fhir_server_connection&.get(query)
272
+ rescue Faraday::Error => e
273
+ # Warning: This is a hack. If there is an error with the request such that we never get a response, we have
274
+ # no clean way to persist that information for the Inferno test to check later. The solution here
275
+ # is to persist the request anyway with a status of nil, using the error message as response body
276
+ Faraday::Response.new(response_body: e.message, url: fhir_server_connection.url_prefix.to_s)
277
+ end
278
+
279
+ def persist_query_request(response, tags)
280
+ inferno_request_headers = data_request_headers.map { |name, value| { name:, value: } }
281
+ inferno_response_headers = response.headers&.map { |name, value| { name:, value: } }
282
+ requests_repo.create(
283
+ verb: 'GET',
284
+ url: response.env.url.to_s,
285
+ direction: 'outgoing',
286
+ status: response.status,
287
+ request_body: response.env.request_body,
288
+ response_body: response.env.response_body,
289
+ test_session_id: test_run.test_session_id,
290
+ result_id: result.id,
291
+ request_headers: inferno_request_headers,
292
+ response_headers: inferno_response_headers,
293
+ tags:
294
+ )
295
+ end
296
+
297
+ def query_for_coverages
298
+ query = "Coverage?patient=#{request_body.dig('context', 'patientId')}&status=active"
299
+ response = execute_request(query)
300
+ return nil unless response.present?
301
+
302
+ persist_query_request(response, [DATA_FETCH_TAG, hook_instance_data_fetch_tag])
303
+ return nil unless response.status.to_s.starts_with?('2')
304
+
305
+ FHIR.from_contents(response.body)
306
+ end
307
+
308
+ def analyzed_resources
309
+ @analyzed_resources ||= {}
310
+ end
311
+
312
+ def prefetched_resources
313
+ @prefetched_resources ||=
314
+ if request_body['prefetch'].blank? || !request_body['prefetch'].is_a?(Hash)
315
+ {}
316
+ else
317
+ request_body['prefetch'].values.each_with_object({}) do |prefetched_resource, resource_hash|
318
+ next unless prefetched_resource.is_a?(Hash)
319
+
320
+ add_prefetch_resource_to_resource_hash(prefetched_resource, resource_hash)
321
+ end
322
+ end
323
+ end
324
+
325
+ def add_prefetch_resource_to_resource_hash(prefetched_resource, resource_hash)
326
+ if prefetched_resource['resourceType'] == 'Bundle'
327
+ prefetched_resource['entry']&.each do |entry|
328
+ next unless entry_has_required_details?(entry)
329
+
330
+ one_resource = entry['resource']
331
+ key = "#{one_resource['resourceType']}/#{one_resource['id']}"
332
+ resource_hash[key] = one_resource
333
+ end
334
+ elsif resource_has_required_details?(prefetched_resource)
335
+
336
+ key = "#{prefetched_resource['resourceType']}/#{prefetched_resource['id']}"
337
+ resource_hash[key] = prefetched_resource
338
+ end
339
+ end
340
+
341
+ def entry_has_required_details?(entry)
342
+ entry.present? &&
343
+ entry.is_a?(Hash) &&
344
+ entry['resource'].present? &&
345
+ resource_has_required_details?(entry['resource'])
346
+ end
347
+
348
+ def resource_has_required_details?(resource)
349
+ resource.is_a?(Hash) && resource['resourceType'].present? && resource['id'].present?
350
+ end
351
+
352
+ # gather additional fhir resources not requested via prefetch
353
+ # not used for response generation, but verified later
354
+ def request_additional_fhir_data
355
+ request_coverage_payer
356
+ request_parent_locations
357
+ end
358
+
359
+ def prefetched_coverage_resource
360
+ if prefetched_coverage.is_a?(FHIR::Bundle)
361
+ prefetched_coverage.entry.first&.resource
362
+ else
363
+ prefetched_coverage
364
+ end
365
+ end
366
+
367
+ def request_coverage_payer
368
+ return unless prefetched_coverage.present?
369
+
370
+ coverage_resource = prefetched_coverage_resource
371
+ return unless coverage_resource.present? && coverage_resource.payor.first&.reference.present?
372
+
373
+ fetch_reference(coverage_resource.payor.first.reference, additional_tag: PAYER_ORG_FETCH_TAG)
374
+ end
375
+
376
+ def request_parent_locations
377
+ prefetched_locations_and_parents_hash
378
+ end
379
+
380
+ def prefetched_location_bundle
381
+ locations_data = request_body.dig('prefetch', 'locations') || request_body.dig('prefetch', 'locs')
382
+ locations = FHIR.from_contents(locations_data.to_json) if locations_data.present?
383
+ return locations if locations.is_a?(FHIR::Bundle)
384
+ return nil unless locations.is_a?(FHIR::Location)
385
+
386
+ FHIR::Bundle.new({ entry: [FHIR::Bundle::Entry.new({ resource: locations })] })
387
+ end
388
+
389
+ def prefetched_locations_and_parents_hash
390
+ prefetched_location_bundle&.entry&.each_with_object({}) do |entry, hash|
391
+ add_location_to_hash(entry.resource, hash)
392
+ end
393
+ end
394
+
395
+ def add_location_to_hash(location, location_hash)
396
+ return unless location.is_a?(FHIR::Location) && location.id.present?
397
+
398
+ relative_reference = "#{location.resourceType}/#{location.id}"
399
+ return if location_hash.key?(relative_reference)
400
+
401
+ location_hash[relative_reference] = location
402
+ return unless location.partOf&.reference.present?
403
+
404
+ parent_location = fetch_reference(location.partOf.reference,
405
+ additional_tag: PARENT_LOCATION_FETCH_TAG,
406
+ parse_as_fhir: true)
407
+ add_location_to_hash(parent_location, location_hash)
408
+ end
409
+ end
410
+ end
@@ -0,0 +1,233 @@
1
+ require_relative 'gather_response_generation_data'
2
+ require_relative 'mock_service_response'
3
+ require_relative 'custom_service_response'
4
+ require_relative '../../cross_suite/cards_identification'
5
+ require_relative '../../cross_suite/tags'
6
+
7
+ module DaVinciCRDTestKit
8
+ class HookRequestEndpoint < Inferno::DSL::SuiteEndpoint
9
+ include DaVinciCRDTestKit::MockServiceResponse
10
+ include DaVinciCRDTestKit::GatherResponseGenerationData
11
+ include DaVinciCRDTestKit::CustomServiceResponse
12
+ include DaVinciCRDTestKit::CardsIdentification
13
+
14
+ AVAILABLE_HOOKS = [
15
+ 'appointment-book',
16
+ 'encounter-start',
17
+ 'encounter-discharge',
18
+ 'order-select',
19
+ 'order-sign',
20
+ 'order-dispatch'
21
+ ].freeze
22
+
23
+ def ig_version
24
+ @ig_version ||= requested_version || request.env['PATH_INFO'].match(/(v\d+)/)&.[](1) || 'v201'
25
+ end
26
+
27
+ # JSON round-trip normalizes the Hanami params object to a plain string-keyed Hash
28
+ def request_body
29
+ @request_body ||= JSON.parse(request.params.to_json)
30
+ end
31
+
32
+ def requested_version
33
+ requested = request_body.dig('extension', 'davinci-crd.requestedVersion').to_s
34
+ if requested == '2.2'
35
+ 'v221'
36
+ elsif requested == '2.0'
37
+ 'v201'
38
+ end
39
+ end
40
+
41
+ def test_run_identifier
42
+ iss
43
+ end
44
+
45
+ def iss
46
+ @iss ||=
47
+ begin
48
+ payload, = JWT.decode(token, nil, false)
49
+ payload['iss']
50
+ rescue JWT::DecodeError
51
+ nil
52
+ end
53
+ end
54
+
55
+ def token
56
+ @token ||= request.headers['authorization']&.delete_prefix('Bearer ')
57
+ end
58
+
59
+ # from the hook body
60
+ def requested_hook
61
+ @requested_hook ||= request_body['hook']
62
+ end
63
+
64
+ # from the url
65
+ def invoked_hook
66
+ @invoked_hook ||= request.env['PATH_INFO'].match(%r{/([^/]+)-(?:service|subset)$})&.[](1)
67
+ end
68
+
69
+ # from the waiting test
70
+ def tested_hook
71
+ @tested_hook ||= test.config.options[:hook_name]
72
+ end
73
+
74
+ def wrong_hook_for_test?
75
+ tested_hook.present? && tested_hook != ANY_HOOK_TAG && requested_hook != tested_hook
76
+ end
77
+
78
+ def make_response
79
+ if invoked_hook != requested_hook
80
+ error_response("#{request.env['PATH_INFO']} serves the #{invoked_hook}, but the client " \
81
+ "requested the #{requested_hook} hook.",
82
+ code: 400,
83
+ outcome_code: 'value')
84
+ elsif wrong_hook_for_test?
85
+ error_response("Hook '#{requested_hook}' is not being tested in the current session. " \
86
+ "This session is currently testing the '#{tested_hook}' hook.",
87
+ code: 422,
88
+ outcome_code: 'value')
89
+ elsif hook_instance_already_used?
90
+ error_response(
91
+ "Invalid Request: Hook instance `#{request_body['hookInstance']}` has already been used in this session.",
92
+ outcome_code: 'value'
93
+ )
94
+ elsif AVAILABLE_HOOKS.include?(requested_hook)
95
+ process_valid_hook
96
+ else
97
+ error_response("Invalid Request: hook `#{requested_hook}` is not supported by this server.",
98
+ outcome_code: 'value')
99
+ end
100
+ rescue StandardError => e
101
+ error_response("Inferno failed to generate a response: #{e.message} at #{e.backtrace.first}",
102
+ code: 500,
103
+ outcome_code: 'exception')
104
+ end
105
+
106
+ def process_valid_hook
107
+ if ig_version == 'v201'
108
+ send(:"gather_#{requested_hook.gsub('-', '_')}_data")
109
+ request_coverage
110
+ elsif ig_version == 'v221'
111
+ request_additional_fhir_data
112
+ end
113
+ response_body = apply_hook_configuration(hook_response)
114
+ return unless response_body.present?
115
+
116
+ response.body = response_body.to_json
117
+ response.headers.merge!({ 'Content-Type' => 'application/json', 'Access-Control-Allow-Origin' => '*' })
118
+ response.status = 200
119
+ response.format = :json
120
+ end
121
+
122
+ def response_approach
123
+ JSON.parse(result.input_json)
124
+ .find { |input| input['name'].ends_with?('_response_approach') }
125
+ &.dig('value')
126
+ end
127
+
128
+ def hook_response
129
+ if response_approach == 'custom'
130
+ build_custom_hook_response
131
+ else
132
+ build_mock_hook_response
133
+ end
134
+ rescue StandardError => e
135
+ error_response("Inferno failed to generate a response: #{e.message} at #{e.backtrace.first}", code: 500)
136
+ nil
137
+ end
138
+
139
+ def apply_hook_configuration(response_body)
140
+ return response_body unless response_body.present? && coverage_info_disabled?
141
+
142
+ cards = response_body['cards']
143
+ response_body['cards'] = cards.reject { |card| coverage_info_card_type?(card) } if cards.is_a?(Array)
144
+
145
+ system_actions = response_body['systemActions']
146
+ if system_actions.is_a?(Array)
147
+ response_body['systemActions'] = system_actions.reject { |action| coverage_info_system_action_type?(action) }
148
+ end
149
+
150
+ response_body
151
+ end
152
+
153
+ def coverage_info_disabled?
154
+ request_body.dig('extension', 'davinci-crd.configuration', COVERAGE_INFO_CONFIGURATION_CODE) == false
155
+ end
156
+
157
+ def hook_instance_already_used?
158
+ requests_repo.tagged_requests(test_run.test_session_id, [hook_instance_tag]).present?
159
+ end
160
+
161
+ def tags
162
+ return [LONG_RUNNING_GROUP_TAG] if long_running_group?
163
+ return [DUPLICATED_HOOK_INSTANCE_TAG] if hook_instance_already_used?
164
+
165
+ return [] if invoked_hook != requested_hook ||
166
+ wrong_hook_for_test? ||
167
+ !AVAILABLE_HOOKS.include?(requested_hook)
168
+
169
+ [hook_instance_tag, hook_or_group_tag]
170
+ end
171
+
172
+ def hook_instance_tag
173
+ TagMethods.hook_instance_tag(request_body['hookInstance'])
174
+ end
175
+
176
+ def hook_or_group_tag
177
+ if test.config.options[:crd_test_group].present?
178
+ test.config.options[:crd_test_group]
179
+ else
180
+ DaVinciCRDTestKit.const_get(:"#{name.upcase}_TAG")
181
+ end
182
+ end
183
+
184
+ def error_response(error_message, code: 400, outcome_code: 'invalid')
185
+ response.status = code
186
+ response.body = error_operation_outcome(outcome_code, error_message).to_json
187
+ response.headers.merge!({ 'Content-Type' => 'application/json', 'Access-Control-Allow-Origin' => '*' })
188
+ response.format = :json
189
+ end
190
+
191
+ def error_operation_outcome(code, text)
192
+ {
193
+ resourceType: 'OperationOutcome',
194
+ issue: [
195
+ {
196
+ severity: 'error',
197
+ code:,
198
+ details: {
199
+ text:
200
+ }
201
+ }
202
+ ]
203
+ }
204
+ end
205
+
206
+ def name
207
+ requested_hook.gsub('-', '_')
208
+ end
209
+
210
+ # -----------------------
211
+ # Long Running Group handling
212
+ # -----------------------
213
+
214
+ def long_running_group?
215
+ test.config.options[:crd_test_group] == LONG_RUNNING_GROUP_TAG
216
+ end
217
+
218
+ def long_running_pause_time
219
+ JSON.parse(result.input_json)
220
+ .find { |input| input['name'].include?('long_running_pause_time') }
221
+ &.dig('value').to_i
222
+ end
223
+
224
+ # end the wait immediately after the long-running request returns
225
+ # pause here because update_result runs before response generation
226
+ def update_result
227
+ return unless long_running_group?
228
+
229
+ sleep long_running_pause_time
230
+ results_repo.update(result.id, result: 'pass', result_message: '')
231
+ end
232
+ end
233
+ end