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,38 @@
1
+ module DaVinciCRDTestKit
2
+ # Replace "prefetch" tokens (fhirpath surrounded by {{}}) in json object representation
3
+ # requires execute_fhirpath_on_cds_request (from the FhirpathOnCDSRequest module)
4
+ module ReplaceTokens
5
+ def replace_tokens(value, request)
6
+ case value
7
+ when Hash
8
+ value.each_value { |sub_value| replace_tokens(sub_value, request) }
9
+ when Array
10
+ value.map! { |entry| replace_tokens(entry, request) }
11
+ else
12
+ replace_tokens_in_string(value.to_s, request)
13
+ end
14
+ end
15
+
16
+ def replace_tokens_in_string(string, request)
17
+ return string unless string.include?('{{')
18
+
19
+ tokens_to_replace = string.scan(/\{\{([^}]+)\}\}/).flatten
20
+ replacements = tokens_to_replace.each_with_object({}) do |expression, dictionary|
21
+ next if dictionary["{{#{expression}}}"].present?
22
+
23
+ dictionary["{{#{expression}}}"] = calculate_expression_value(request, expression)
24
+ end
25
+
26
+ string.gsub!(/\{\{.*?\}\}/, replacements)
27
+ end
28
+
29
+ private
30
+
31
+ def calculate_expression_value(request, expression)
32
+ results = expression.split('|').map do |sub_expression|
33
+ execute_fhirpath_on_cds_request(request, sub_expression)
34
+ end.flatten
35
+ results.map { |res| res.is_a?(Array) || res.is_a?(Hash) ? nil : res }.compact.join(',')
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,203 @@
1
+ require_relative 'logical_models_override_helper'
2
+
3
+ module DaVinciCRDTestKit
4
+ module RequestsLogicalModelValidation
5
+ include LogicalModelsOverrideHelper
6
+
7
+ CRD_CDS_HOOK_REQUEST_MODEL_URL = 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/CRDHooksRequest'.freeze
8
+ USER_ID_ALLOWED_RESOURCE_TYPES = [
9
+ 'Practitioner', 'PractitionerRole', 'Patient', 'RelatedPerson'
10
+ ].freeze
11
+
12
+ PERFORMER_ALLOWED_RESOURCE_TYPES = [
13
+ 'Practitioner', 'PractitionerRole'
14
+ ].freeze
15
+
16
+ def validate_request_against_logical_model(request_body, request_index, ig_semver)
17
+ if ig_semver == '2.2.1'
18
+ check_logical_model_conformance_no_resource_checks(request_body, request_index, ig_semver)
19
+ else
20
+ conforms_to_logical_model?(request_body, "#{CRD_CDS_HOOK_REQUEST_MODEL_URL}|#{ig_semver}",
21
+ message_prefix: "(Request #{request_index + 1}) ")
22
+ end
23
+
24
+ perform_version_specific_additional_verification(request_body, request_index, ig_semver)
25
+ end
26
+
27
+ private
28
+
29
+ # -------------------------------------------------------------------------
30
+ # Additional Validation to cover areas not checked or checked incorrectly by the logical models
31
+ # -------------------------------------------------------------------------
32
+
33
+ def perform_version_specific_additional_verification(request_body, request_index, ig_semver)
34
+ case ig_semver
35
+ when '2.2.1'
36
+ perform_v221_additional_verification(request_body, request_index)
37
+ end
38
+ end
39
+
40
+ def perform_v221_additional_verification(request_body, request_index)
41
+ check_context_resources_for_ids(request_body, request_index)
42
+ check_relative_references(request_body, request_index)
43
+ check_context_resource_profiles(request_body, request_index, '2.2.1')
44
+ end
45
+
46
+ # -------------------------------------------------------------------------
47
+ # Context resources profile check (not working in v2.2.1 logical models)
48
+ # -------------------------------------------------------------------------
49
+
50
+ def check_logical_model_conformance_no_resource_checks(request_body, request_index, ig_semver)
51
+ validation_issues = []
52
+ conforms_to_logical_model?(request_body, "#{CRD_CDS_HOOK_REQUEST_MODEL_URL}|#{ig_semver}",
53
+ add_messages_to_runnable: false, validator_response_details: validation_issues)
54
+
55
+ reject_resource_issues(validation_issues).each do |issue|
56
+ next if issue.filtered
57
+
58
+ add_message(issue.severity, "(Request #{request_index + 1}) #{issue.message}")
59
+ end
60
+ end
61
+
62
+ def check_context_resource_profiles(request_body, request_index, ig_semver)
63
+ case request_body['hook']
64
+ when 'order-sign', 'order-select'
65
+ draft_orders_conform_to_profiles?(request_body, request_index, ig_semver)
66
+ when 'order-dispatch'
67
+ request_body.dig('context', 'fulfillmentTasks')&.each_with_index do |task, index|
68
+ resource = FHIR.from_contents(task.to_json)
69
+ resource_is_valid?(resource:, profile_url: "http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-task-dispatch|#{ig_semver}",
70
+ message_prefix: "(Request #{request_index + 1}) " \
71
+ "context.fulfillmentTasks entry #{index + 1} - ")
72
+ end
73
+ when 'appointment-book'
74
+ check_appointments_profiles(request_body, request_index, ig_semver)
75
+ end
76
+ rescue JSON::ParserError
77
+ nil # no resource to validate - error found elsewhere
78
+ end
79
+
80
+ def draft_orders_conform_to_profiles?(request_body, request_index, ig_semver)
81
+ draft_orders = request_body.dig('context', 'draftOrders').to_json
82
+ resource = FHIR.from_contents(draft_orders)
83
+ resource_is_valid?(resource:, profile_url: "http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-bundle-request|#{ig_semver}",
84
+ message_prefix: "(Request #{request_index + 1}) context.draftOrders - ")
85
+ end
86
+
87
+ # -------------------------------------------------------------------------
88
+ # Appointment conformance (requires extra help to decide profile and check profile-based slicing)
89
+ # -------------------------------------------------------------------------
90
+
91
+ def check_appointments_profiles(request_body, request_index, ig_semver)
92
+ resource = FHIR.from_contents(request_body.dig('context', 'appointments')&.to_json)
93
+ return unless resource.is_a?(FHIR::Bundle)
94
+
95
+ check_bundle_non_entry_resource_conformance(resource,
96
+ "(Request #{request_index + 1}) context.appointments - ",
97
+ ig_semver)
98
+
99
+ resource.entry.each_with_index do |entry, entry_index|
100
+ next unless entry.resource.present? # error caught on Bundle validation
101
+
102
+ error_prefix = "(Request #{request_index + 1}) context.appointments entry #{entry_index + 1} - "
103
+ check_appointment_conformance(entry.resource, request_body, error_prefix, ig_semver)
104
+ end
105
+ end
106
+
107
+ def check_bundle_non_entry_resource_conformance(bundle, error_prefix, ig_semver)
108
+ validation_issues = []
109
+ resource_is_valid?(resource: bundle, profile_url: "http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-bundle-base|#{ig_semver}",
110
+ add_messages_to_runnable: false, validator_response_details: validation_issues)
111
+
112
+ reject_entry_resource_issues(validation_issues).each do |issue|
113
+ add_message(issue.severity, "#{error_prefix}#{issue.message}")
114
+ end
115
+ end
116
+
117
+ def reject_entry_resource_issues(issues)
118
+ issues.reject do |issue|
119
+ issue.filtered || issue.location&.match?(/\ABundle\.entry\[\d+\]\.resource/)
120
+ end
121
+ end
122
+
123
+ # -------------------------------------------------------------------------
124
+ # Context relative reference checks
125
+ # -------------------------------------------------------------------------
126
+
127
+ # verify that context fields required to contain local references (resourceType/id)
128
+ # do contain them and that the resourceType is in the list of expected types.
129
+ def check_relative_references(request_body, request_index)
130
+ # unless order-dispatch, check userId
131
+ # if order-dispatch, check performer and dispatchedOrders list
132
+ # if order-select, check selections
133
+
134
+ if request_body['hook'] == 'order-dispatch'
135
+ local_reference?(request_body['context']['performer'],
136
+ "(Request #{request_index + 1}) context.performer",
137
+ allowed_resource_types: PERFORMER_ALLOWED_RESOURCE_TYPES)
138
+ request_body['context']['dispatchedOrders'].each_with_index do |order_reference, index|
139
+ local_reference?(order_reference,
140
+ "(Request #{request_index + 1}) context.dispatchedOrders entry #{index + 1}",
141
+ allowed_resource_types: ProfilesAndResourceTypes::ORDER_RESOURCE_TYPES)
142
+ end
143
+ else
144
+ local_reference?(request_body['context']['userId'],
145
+ "(Request #{request_index + 1}) context.userId",
146
+ allowed_resource_types: USER_ID_ALLOWED_RESOURCE_TYPES)
147
+ if request_body['hook'] == 'order-select'
148
+ request_body['context']['selections'].each_with_index do |order_reference, index|
149
+ error_prefix = "(Request #{request_index + 1}) context.selections entry #{index + 1}"
150
+ next unless local_reference?(order_reference,
151
+ error_prefix,
152
+ allowed_resource_types: ProfilesAndResourceTypes::ORDER_RESOURCE_TYPES)
153
+
154
+ referenced_resource_present_in_bundle?(request_body['context']['draftOrders'], order_reference,
155
+ error_prefix, 'draftOrders')
156
+ end
157
+ end
158
+ end
159
+ end
160
+
161
+ # -------------------------------------------------------------------------
162
+ # Context resources include ids
163
+ # -------------------------------------------------------------------------
164
+
165
+ # logical models don't explicitly check for resource ids
166
+ def check_context_resources_for_ids(request_body, request_index)
167
+ hook_name = request_body['hook']
168
+ context = request_body['context']
169
+ return unless context.present?
170
+
171
+ case hook_name
172
+ when 'appointment-book'
173
+ check_bundle_resources_for_ids(context['appointments'], request_index, 'appointments')
174
+ when 'order-sign', 'order-select'
175
+ check_bundle_resources_for_ids(context['draftOrders'], request_index, 'draftOrders')
176
+ when 'order-dispatch'
177
+ context['fulfillmentTasks']&.each_with_index do |task, index|
178
+ check_resource_for_id(FHIR.from_contents(task.to_json), request_index, 'fulfillmentTasks', index)
179
+ end
180
+ end
181
+ end
182
+
183
+ def check_bundle_resources_for_ids(parsed_bundle, request_index, context_field_name)
184
+ return unless parsed_bundle.present?
185
+
186
+ bundle = FHIR.from_contents(parsed_bundle.to_json)
187
+ return unless bundle.present?
188
+
189
+ bundle.entry&.each_with_index do |entry, index|
190
+ check_resource_for_id(entry.resource, request_index, context_field_name, index)
191
+ end
192
+ end
193
+
194
+ def check_resource_for_id(resource, request_index, context_field_name, entry_index = nil)
195
+ return if resource.present? && resource.id.present?
196
+
197
+ message = "(Request #{request_index + 1}) " \
198
+ 'FHIR resources provided in the hook context must have an id, none found for ' \
199
+ "`context.#{context_field_name}`#{" entry #{entry_index + 1}" if entry_index.present?}."
200
+ add_message('error', message)
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,435 @@
1
+ require_relative 'cards_identification'
2
+ require_relative 'logical_models_override_helper'
3
+
4
+ module DaVinciCRDTestKit
5
+ module ResponseLogicalModelValidation
6
+ include DaVinciCRDTestKit::CardsIdentification
7
+ include LogicalModelsOverrideHelper
8
+
9
+ CRD_LOGICAL_MODEL_BASE = 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition'.freeze
10
+ CRD_RESPONSE_BASE_LOGICAL_MODEL = 'CRDHooksResponseBase'.freeze
11
+
12
+ CARD_TYPE_TO_LOGICAL_MODEL = {
13
+ DaVinciCRDTestKit::CardsIdentification::ADDITIONAL_ORDERS_RESPONSE_TYPE =>
14
+ 'CRDHooksResponse-additionalOrders',
15
+ DaVinciCRDTestKit::CardsIdentification::CREATE_OR_UPDATE_COVERAGE_RESPONSE_TYPE =>
16
+ 'CRDHooksResponse-adjustCoverage',
17
+ DaVinciCRDTestKit::CardsIdentification::EXTERNAL_REFERENCE_RESPONSE_TYPE =>
18
+ 'CRDHooksResponse-externalReference',
19
+ DaVinciCRDTestKit::CardsIdentification::FORM_COMPLETION_RESPONSE_TYPE =>
20
+ 'CRDHooksResponse-formCompletion',
21
+ DaVinciCRDTestKit::CardsIdentification::INSTRUCTIONS_RESPONSE_TYPE =>
22
+ 'CRDHooksResponse-instructions',
23
+ DaVinciCRDTestKit::CardsIdentification::LAUNCH_SMART_APP_RESPONSE_TYPE =>
24
+ 'CRDHooksResponse-launchSMART',
25
+ DaVinciCRDTestKit::CardsIdentification::PROPOSE_ALTERNATIVE_REQUEST_RESPONSE_TYPE =>
26
+ 'CRDHooksResponse-alternateRequest'
27
+ }.freeze
28
+
29
+ ACTION_TYPE_TO_LOGICAL_MODEL = {
30
+ DaVinciCRDTestKit::CardsIdentification::COVERAGE_INFORMATION_RESPONSE_TYPE =>
31
+ 'CRDHooksResponse-coverageInformation',
32
+ DaVinciCRDTestKit::CardsIdentification::CREATE_OR_UPDATE_COVERAGE_RESPONSE_TYPE =>
33
+ 'CRDHooksResponse-adjustCoverage',
34
+ DaVinciCRDTestKit::CardsIdentification::FORM_COMPLETION_RESPONSE_TYPE =>
35
+ 'CRDHooksResponse-formCompletion'
36
+ }.freeze
37
+
38
+ def logical_model_url(profile_name)
39
+ "#{CRD_LOGICAL_MODEL_BASE}/#{profile_name}"
40
+ end
41
+
42
+ def perform_response_logical_model_validation(cards, system_actions, request_body, response_index, ig_semver)
43
+ if cards.is_a?(Array)
44
+ cards.each_with_index do |card, card_index|
45
+ validate_card_against_logical_model(card, response_index, request_body, card_index, ig_semver)
46
+ end
47
+ end
48
+
49
+ return unless system_actions.is_a?(Array)
50
+
51
+ system_actions.each_with_index do |action, action_index|
52
+ validate_system_action_against_logical_model(action, response_index, request_body, action_index, ig_semver)
53
+ end
54
+ end
55
+
56
+ def validate_card_against_logical_model(card, response_index, request_body, card_index, ig_semver)
57
+ label = logical_model_entity_label(response_index, card_index, 'card')
58
+ unless card.is_a?(Hash)
59
+ add_message('error', "#{label} is not a JSON object; skipping logical model validation.")
60
+ return
61
+ end
62
+
63
+ card_type = identify_card_type(card)
64
+ profile_name = CARD_TYPE_TO_LOGICAL_MODEL[card_type]
65
+ unless profile_name
66
+ add_message('warning',
67
+ "#{label} could not be categorized as a known CRD response type; " \
68
+ 'validating against the base CRD response logical model.')
69
+ profile_name = CRD_RESPONSE_BASE_LOGICAL_MODEL
70
+ end
71
+
72
+ validation_issues = []
73
+ conforms_to_logical_model?({ 'cards' => [card] }, logical_model_url(profile_name),
74
+ add_messages_to_runnable: false, validator_response_details: validation_issues)
75
+
76
+ validate_summary_length(card, label)
77
+ validate_suggestion_uuid_presence(card, label) unless ['v201', '2.0.1'].include? ig_semver
78
+ validate_no_smart_suggestions(card, label) if card_type == CardsIdentification::LAUNCH_SMART_APP_RESPONSE_TYPE
79
+
80
+ error_prefix = "#{label} (#{card_type || 'uncategorized'}): "
81
+ filtered_issues = manually_check_card_specific_errors(card, validation_issues, card_type,
82
+ request_body, error_prefix, ig_semver)
83
+ add_messages_not_excluded(filtered_issues, error_prefix)
84
+ end
85
+
86
+ def validate_summary_length(card, label)
87
+ return unless card['summary'].is_a? String
88
+
89
+ summary_length = card['summary'].length
90
+
91
+ return if summary_length < 140
92
+
93
+ add_message(
94
+ 'error',
95
+ "#{label} summary length of #{summary_length} characters is longer than " \
96
+ 'the maximum allowed of 139 characters.'
97
+ )
98
+ end
99
+
100
+ def validate_suggestion_uuid_presence(card, label)
101
+ return unless card['suggestions'].presence.is_a? Array
102
+ return unless card['suggestions'].all?(Hash)
103
+
104
+ card['suggestions'].each_with_index do |suggestion, index|
105
+ next if suggestion['uuid'].present?
106
+
107
+ add_message(
108
+ 'error',
109
+ "#{label} suggestion #{index + 1} does not contain a `uuid`"
110
+ )
111
+ end
112
+ end
113
+
114
+ def validate_no_smart_suggestions(card, label)
115
+ suggestion_count = card['suggestions']&.length || 0
116
+
117
+ return if suggestion_count.zero?
118
+
119
+ add_message(
120
+ 'error',
121
+ "#{label} CDSHooksResponse.cards.suggestions: max allowed = 0, but found #{suggestion_count} " \
122
+ '(from http://hl7.org/fhir/us/davinci-crd/StructureDefinition/CRDHooksResponse-launchSMART|2.2.1)'
123
+ )
124
+ end
125
+
126
+ def validate_system_action_against_logical_model(action, response_index, request_body, action_index, ig_semver)
127
+ label = logical_model_entity_label(response_index, action_index, 'systemAction')
128
+ unless action.is_a?(Hash)
129
+ add_message('error', "#{label} is not a JSON object; skipping logical model validation.")
130
+ return
131
+ end
132
+
133
+ action_type = identify_action_type(action)
134
+ profile_name = ACTION_TYPE_TO_LOGICAL_MODEL[action_type]
135
+ unless profile_name
136
+ add_message('warning',
137
+ "#{label} could not be categorized as a known CRD response type; " \
138
+ 'validating against the base CRD response logical model.')
139
+ profile_name = CRD_RESPONSE_BASE_LOGICAL_MODEL
140
+ end
141
+ if [DaVinciCRDTestKit::CardsIdentification::CREATE_OR_UPDATE_COVERAGE_RESPONSE_TYPE,
142
+ DaVinciCRDTestKit::CardsIdentification::FORM_COMPLETION_RESPONSE_TYPE]
143
+ .include?(action_type)
144
+ profile_name = CRD_RESPONSE_BASE_LOGICAL_MODEL
145
+ end
146
+
147
+ validation_issues = []
148
+ conforms_to_logical_model?({ 'systemActions' => [action] }, logical_model_url(profile_name),
149
+ add_messages_to_runnable: false, validator_response_details: validation_issues)
150
+
151
+ validation_issues
152
+ .reject! do |issue|
153
+ issue.message.match?(%r{The extension definition http://hl7\.org/fhir/us/davinci-crd/StructureDefinition/CDSHookServiceResponseExtensionIfNoneExist|2\.2\.1 defines the contexts of use as.*CDSHooksResponse.systemActions.extension\z}) # rubocop:disable Layout/LineLength
154
+ end
155
+
156
+ if action_type == CardsIdentification::COVERAGE_INFORMATION_RESPONSE_TYPE
157
+ check_multiple_coverage_info_extension_conformance(action)
158
+ end
159
+ error_prefix = "#{label} (#{action_type || 'uncategorized'}): "
160
+ filtered_issues = manually_check_action_specific_errors(action, validation_issues, action_type,
161
+ request_body, error_prefix, ig_semver)
162
+ add_messages_not_excluded(filtered_issues, error_prefix)
163
+ end
164
+
165
+ def check_multiple_coverage_info_extension_conformance(action)
166
+ resource = FHIR.from_contents(action['resource'].to_json)
167
+
168
+ grouped_coverage_info = extract_and_group_coverage_info(resource)
169
+ multiple_extensions_conformance_check(grouped_coverage_info, resource)
170
+ end
171
+
172
+ def extract_and_group_coverage_info(resource)
173
+ resource.extension.each_with_object({}) do |extension, grouped_extensions|
174
+ next unless extension.url == COVERAGE_INFO_EXT_URL
175
+
176
+ coverage_key = find_extension_value(extension, 'coverage', 'valueReference', 'reference')
177
+ grouped_extensions[coverage_key] ||= []
178
+ grouped_extensions[coverage_key] << extension
179
+ end
180
+ end
181
+
182
+ # For the same coverage, ensure coverage-assertion-ids and satisfied-pa-ids are the same.
183
+ # For different coverages, ensure coverage-assertion-ids and satisfied-pa-ids are distinct.
184
+ def multiple_extensions_conformance_check(grouped_coverage_info, resource)
185
+ resource_ref = "#{resource.resourceType}/#{resource.id}"
186
+ assertion_ids_across_coverages = Set.new
187
+ pa_ids_across_coverages = Set.new
188
+
189
+ grouped_coverage_info.each do |coverage, extensions|
190
+ coverage_assertion_ids = collect_extensions_id(extensions, 'coverage-assertion-id', 'valueString').uniq
191
+ satisfied_pa_ids = collect_extensions_id(extensions, 'satisfied-pa-id', 'valueString').uniq.compact
192
+ if coverage_assertion_ids.length != 1
193
+ add_message(
194
+ 'error',
195
+ same_coverage_conformance_error_msg(resource_ref, coverage, 'coverage-assertion-ids')
196
+ )
197
+ end
198
+
199
+ if satisfied_pa_ids.length > 1
200
+ add_message(
201
+ 'error',
202
+ same_coverage_conformance_error_msg(resource_ref, coverage, 'satisfied-pa-ids')
203
+ )
204
+ end
205
+
206
+ assertion_id = coverage_assertion_ids.first
207
+ if assertion_ids_across_coverages.include?(assertion_id)
208
+ add_message(
209
+ 'error',
210
+ different_coverage_conformance_error_msg(resource_ref, 'coverage-assertion-ids')
211
+ )
212
+ end
213
+
214
+ assertion_ids_across_coverages.add(assertion_id)
215
+ pa_id = satisfied_pa_ids.first
216
+ next unless pa_id
217
+
218
+ if pa_ids_across_coverages.include?(pa_id)
219
+ add_message(
220
+ 'error',
221
+ different_coverage_conformance_error_msg(resource_ref, 'satisfied-pa-ids')
222
+ )
223
+ end
224
+
225
+ pa_ids_across_coverages.add(pa_id)
226
+ end
227
+ end
228
+
229
+ def collect_extensions_id(extensions, url, *properties)
230
+ extensions.map do |extension|
231
+ find_extension_value(extension, url, *properties)
232
+ end
233
+ end
234
+
235
+ def find_extension_value(extension, url, *properties)
236
+ found_extension = extension.extension.find { |ext| ext.url == url }
237
+ return nil unless found_extension
238
+
239
+ properties.reduce(found_extension) do |current, prop|
240
+ return current unless current.respond_to?(prop)
241
+
242
+ current.send(prop)
243
+ end
244
+ end
245
+
246
+ def same_coverage_conformance_error_msg(resource_ref, coverage, id_name)
247
+ "#{resource_ref}: extension has multiple repetitions of coverage `#{coverage}` with different #{id_name}."
248
+ end
249
+
250
+ def different_coverage_conformance_error_msg(resource_ref, id_name)
251
+ "#{resource_ref}: extensions referencing differing coverage SHALL have distinct #{id_name}."
252
+ end
253
+
254
+ def add_messages_not_excluded(issues, error_prefix)
255
+ issues.each do |issue|
256
+ next if issue.filtered || logical_model_extension_issue?(issue)
257
+
258
+ add_message(issue.severity, "#{error_prefix}#{issue.message}")
259
+ end
260
+ end
261
+
262
+ def logical_model_entity_label(response_index, entity_index, kind)
263
+ "Server response #{response_index + 1}, #{kind} #{entity_index + 1}"
264
+ end
265
+
266
+ # -------------------------------------------------------------------------
267
+ # Validator Filtering and Manual Checks Depending on the Card Type
268
+ # -------------------------------------------------------------------------
269
+
270
+ def logical_model_extension_issue?(issue)
271
+ issue.message.match(/\.extension: Unrecognized property/).present?
272
+ end
273
+
274
+ def manually_check_card_specific_errors(card, validation_issues, card_type, request_body, error_prefix,
275
+ ig_semver)
276
+ case card_type
277
+ when CardsIdentification::FORM_COMPLETION_RESPONSE_TYPE
278
+ manually_check_form_completion_errors(card, validation_issues, error_prefix)
279
+ when CardsIdentification::PROPOSE_ALTERNATIVE_REQUEST_RESPONSE_TYPE
280
+ manually_check_propose_alternative_errors(card, validation_issues, request_body,
281
+ error_prefix, ig_semver)
282
+ when CardsIdentification::ADDITIONAL_ORDERS_RESPONSE_TYPE
283
+ manually_check_additional_orders_errors(card, validation_issues, request_body,
284
+ error_prefix, ig_semver)
285
+ when CardsIdentification::LAUNCH_SMART_APP_RESPONSE_TYPE
286
+ manually_check_launch_smart_app_errors(validation_issues)
287
+ else
288
+ validation_issues
289
+ end
290
+ end
291
+
292
+ def manually_check_action_specific_errors(action, validation_issues, action_type, request_body,
293
+ error_prefix, ig_semver)
294
+ case action_type
295
+ when DaVinciCRDTestKit::CardsIdentification::COVERAGE_INFORMATION_RESPONSE_TYPE
296
+ manually_check_coverage_information_errors(action, validation_issues, request_body,
297
+ error_prefix, ig_semver)
298
+ when DaVinciCRDTestKit::CardsIdentification::CREATE_OR_UPDATE_COVERAGE_RESPONSE_TYPE
299
+ manually_check_update_coverage_action_errors(action, validation_issues,
300
+ error_prefix, ig_semver)
301
+ when DaVinciCRDTestKit::CardsIdentification::FORM_COMPLETION_RESPONSE_TYPE
302
+ manually_check_form_completion_action_errors(action, validation_issues,
303
+ error_prefix, ig_semver)
304
+ else
305
+ validation_issues
306
+ end
307
+ end
308
+
309
+ def manually_check_form_completion_errors(card, validation_issues, error_prefix)
310
+ validation_issues.reject do |issue|
311
+ if issue.message.match?(/The type 'Questionnaire' is not valid - must be Task/)
312
+ check_questionnaire_actions(card, issue.message, error_prefix)
313
+ true
314
+ else
315
+ false
316
+ end
317
+ end
318
+ end
319
+
320
+ def check_questionnaire_actions(card, error_message, error_prefix)
321
+ extracted_indexes =
322
+ error_message.match(/CDSHooksResponse\.cards\[0\]\.suggestions\[(\d+)\]\.actions\[(\d+)\]\.resource/)
323
+ unless extracted_indexes
324
+ raise 'Unexpected validator error message format in check_questionnaire_actions: ' \
325
+ "'#{error_message}'. This indicates an implementation problem in the test kit — please log a ticket."
326
+ end
327
+
328
+ suggestion_index = extracted_indexes[1].to_i
329
+ action_index = extracted_indexes[2].to_i
330
+
331
+ message_prefix = "#{error_prefix} suggestion #{suggestion_index + 1}, action #{action_index + 1} - "
332
+ resource = FHIR.from_contents(card['suggestions'][suggestion_index]['actions'][action_index]['resource'].to_json)
333
+ resource_is_valid?(resource:, message_prefix:) # no questionnaire profile applied per CRD
334
+
335
+ return if resource.id.present?
336
+
337
+ add_message('error', "#{message_prefix}Questionnaire must have an id.")
338
+ end
339
+
340
+ def manually_check_launch_smart_app_errors(validation_issues)
341
+ validation_issues.reject do |issue|
342
+ issue.message.match?(/CDSHooksResponse.cards.suggestions: minimum required = 1, but only found 0/)
343
+ end
344
+ end
345
+
346
+ def manually_check_propose_alternative_errors(card, validation_issues, request_body,
347
+ error_prefix, ig_semver)
348
+ no_resource_issues = manually_check_action_resources_for_order_profile_conformance(card,
349
+ validation_issues,
350
+ request_body,
351
+ error_prefix,
352
+ ig_semver)
353
+
354
+ no_resource_issues.each do |issue|
355
+ next unless issue.message.match?(/Constraint failed: crd-respar-1/)
356
+
357
+ new_message =
358
+ "#{issue.message}. In some cases [additional create " \
359
+ 'actions](https://hl7.org/fhir/us/davinci-crd/2.2.1/en/cards.html#propose-alternate-request-response-type) ' \
360
+ 'are permitted. Manually check the suggestion to verify whether this is an actual error.'
361
+ issue.instance_variable_set(:@severity, 'warning')
362
+ issue.instance_variable_set(:@message, new_message)
363
+ end
364
+
365
+ no_resource_issues.reject do |issue|
366
+ issue.message.match?(/but is fixed to 'create' in the profile/)
367
+ end
368
+ end
369
+
370
+ def manually_check_additional_orders_errors(card, validation_issues, request_body,
371
+ error_prefix, ig_semver)
372
+ manually_check_action_resources_for_order_profile_conformance(card,
373
+ validation_issues,
374
+ request_body,
375
+ error_prefix,
376
+ ig_semver)
377
+ end
378
+
379
+ def manually_check_action_resources_for_order_profile_conformance(card, validation_issues, request_body,
380
+ error_prefix, ig_semver)
381
+ if card['suggestions'].present?
382
+ card['suggestions'].each_with_index do |suggestion, suggestion_index|
383
+ next unless suggestion['actions'].present?
384
+
385
+ suggestion['actions'].each_with_index do |action, action_index|
386
+ action_error_prefix = "#{error_prefix}suggestion #{suggestion_index + 1}, action #{action_index + 1} - "
387
+ check_action_target(action, request_body, action_error_prefix, ig_semver)
388
+ end
389
+ end
390
+ end
391
+
392
+ reject_resource_issues(validation_issues)
393
+ end
394
+
395
+ def check_action_target(action, request_body, error_prefix, ig_semver)
396
+ local_reference?(action['resourceId'], error_prefix) if action['resourceId'].present?
397
+ return unless action['resource'].present?
398
+
399
+ check_resource_conformance_to_order_profile(action['resource'], request_body, error_prefix, ig_semver)
400
+ end
401
+
402
+ def manually_check_coverage_information_errors(action, validation_issues, request_body,
403
+ error_prefix, ig_semver)
404
+ if action['resource'].present?
405
+ check_resource_conformance_to_order_or_encounter_profile(action['resource'], request_body,
406
+ error_prefix, ig_semver)
407
+ end
408
+ reject_resource_issues(validation_issues)
409
+ end
410
+
411
+ def manually_check_update_coverage_action_errors(action, validation_issues,
412
+ error_prefix, ig_semver)
413
+ check_required_action_type(action, 'update', error_prefix, 'coverage update action response type')
414
+ if action['resource'].present?
415
+ check_resource_conformance_to_coverage_profile(action['resource'], error_prefix, ig_semver)
416
+ end
417
+ reject_resource_issues(validation_issues)
418
+ end
419
+
420
+ def manually_check_form_completion_action_errors(action, validation_issues,
421
+ error_prefix, ig_semver)
422
+ check_required_action_type(action, 'create', error_prefix, 'form completion action response type')
423
+ if action['resource'].present?
424
+ check_resource_conformance_to_questionnaire_task_profile(action['resource'], error_prefix, ig_semver)
425
+ end
426
+ reject_resource_issues(validation_issues)
427
+ end
428
+
429
+ def check_required_action_type(action, required_type, error_prefix, description)
430
+ return if action['type'] == required_type
431
+
432
+ add_message('error', "#{error_prefix}action type must be '#{required_type}' for a #{description}.")
433
+ end
434
+ end
435
+ end