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,69 @@
1
+ require_relative '../../multi_request_message_helper'
2
+ require_relative '../../tagged_request_load_helper'
3
+
4
+ module DaVinciCRDTestKit
5
+ module V221
6
+ class DecodeAuthTokenTest < Inferno::Test
7
+ include DaVinciCRDTestKit::MultiRequestMessageHelper
8
+ include DaVinciCRDTestKit::TaggedRequestLoadHelper
9
+
10
+ id :crd_v221_decode_auth_token
11
+ title 'Bearer tokens can be decoded'
12
+ description %(
13
+ During this test, Inferno will verify that for each request the Bearer token is a properly constructed JWT.
14
+ As per the [CDS hooks specification](https://cds-hooks.hl7.org/2026Jan/en/#trusting-cds-clients),
15
+ each time a CDS client transmits a request to a CDS Service which requires authentication, the request MUST
16
+ include an Authorization header presenting the JWT as a "Bearer" token.
17
+ )
18
+
19
+ verifies_requirements 'cds-hooks_3.0.0-ballot@178'
20
+
21
+ output :auth_tokens, :auth_token_payloads_json, :auth_token_headers_json
22
+
23
+ run do
24
+ load_hook_requests
25
+ skip_if requests.empty?, "No #{hook_name} requests were made in a previous test as expected."
26
+ auth_tokens = []
27
+ auth_token_payloads_json = []
28
+ auth_token_headers_json = []
29
+
30
+ requests.each_with_index do |request, index|
31
+ authorization_header = request.request_header('Authorization')&.value
32
+
33
+ unless authorization_header&.start_with?('Bearer ')
34
+ add_request_message('error', 'Authorization token must be a JWT presented as a `Bearer` token', index)
35
+ auth_tokens << nil
36
+ auth_token_payloads_json << nil
37
+ auth_token_headers_json << nil
38
+ next
39
+ end
40
+
41
+ auth_token = authorization_header.delete_prefix('Bearer ')
42
+ auth_tokens << auth_token
43
+
44
+ begin
45
+ payload, header =
46
+ JWT.decode(
47
+ auth_token,
48
+ nil,
49
+ false
50
+ )
51
+
52
+ auth_token_payloads_json << payload.to_json
53
+ auth_token_headers_json << header.to_json
54
+ rescue StandardError => e
55
+ add_request_message('error', "Token is not a properly constructed JWT: #{e.message}", index)
56
+ auth_token_payloads_json << nil
57
+ auth_token_headers_json << nil
58
+ end
59
+ end
60
+ output auth_tokens: auth_tokens.to_json,
61
+ auth_token_payloads_json: auth_token_payloads_json.to_json,
62
+ auth_token_headers_json: auth_token_headers_json.to_json
63
+
64
+ assert_no_error_messages("#{requests_with_errors_prefix}Decoding Authorization header Bearer tokens failed. " \
65
+ 'See Messages for details.')
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,120 @@
1
+ require_relative '../../multi_request_message_helper'
2
+
3
+ module DaVinciCRDTestKit
4
+ module V221
5
+ class RetrieveJWKSTest < Inferno::Test
6
+ include DaVinciCRDTestKit::MultiRequestMessageHelper
7
+
8
+ id :crd_v221_retrieve_jwks
9
+ title 'JWKSs can be retrieved'
10
+ description %(
11
+ During this test, Inferno will verify that for each request the JWKS can be retrieved from the JWKS uri if
12
+ it is present in the `jku` field within the JWT token header. Additionally, keys will be extracted and
13
+ outputted for use in subsequent tests. If the client does not provide a uri in the `jku` field,
14
+ Inferno will extract keys from the raw JWKS JSON provided out of band as a part of the "Registration" group.
15
+ )
16
+
17
+ verifies_requirements 'cds-hooks_3.0.0-ballot@183', 'cds-hooks_3.0.0-ballot@185', 'cds-hooks_3.0.0-ballot@197',
18
+ 'cds-hooks_3.0.0-ballot@199'
19
+
20
+ input :auth_token_headers_json
21
+ input :cds_jwk_set,
22
+ title: 'CRD JSON Web Key Set (JWKS)',
23
+ type: 'textarea',
24
+ description: %(
25
+ The client's registered JWK Set containing it's public key. Used
26
+ only when a request was received with a JWT without the `jku` header.
27
+ Inferno assumes this input, provided during the "Registration"
28
+ group, contains the raw JSON representation of a JWKS (if a URI was provided
29
+ it would be populated in the `jku` header). Run or re-run the "Registration"
30
+ group to set or change this value.
31
+ ),
32
+ locked: true,
33
+ optional: true
34
+ output :crd_jwks_keys_json
35
+
36
+ run do
37
+ auth_token_headers = JSON.parse(auth_token_headers_json) # NOTE: pre-verified json
38
+ skip_if auth_token_headers.compact.empty?, 'No Authorization tokens produced from the previous test.'
39
+ skip_if cds_jwk_set.blank? && cds_jwk_set_input_needed?(auth_token_headers),
40
+ "JWK Set must be inputted if the client's JWK Set is not available"
41
+
42
+ crd_jwks_keys_json = []
43
+ auth_token_headers.each_with_index do |token_header, index|
44
+ unless token_header.present?
45
+ crd_jwks_keys_json << nil
46
+ next
47
+ end
48
+
49
+ jku = JSON.parse(token_header)['jku'] # NOTE: pre-verified json
50
+ jwks =
51
+ if jku.present?
52
+ get(jku)
53
+
54
+ if response[:status] == 200
55
+ parse_json_request_entity(response[:body], 'Fetched jku url response', index)
56
+ else
57
+ add_request_message('error',
58
+ "Unexpected response status: expected 200, but received #{response[:status]}",
59
+ index)
60
+ nil
61
+ end
62
+ else
63
+ parse_json_request_entity(cds_jwk_set, 'JWK Set input', index)
64
+ end
65
+ if jwks.blank?
66
+ crd_jwks_keys_json << nil
67
+ next
68
+ end
69
+
70
+ keys = jwks['keys']
71
+ unless keys.is_a?(Array)
72
+ add_request_message('error', 'JWKS `keys` field must be an array', index)
73
+ crd_jwks_keys_json << nil
74
+ next
75
+ end
76
+
77
+ if keys.blank?
78
+ add_request_message('error', 'The JWK set returned contains no public keys', index)
79
+ crd_jwks_keys_json << nil
80
+ next
81
+ end
82
+
83
+ keys.each do |jwk|
84
+ JWT::JWK.import(jwk.deep_symbolize_keys)
85
+ rescue StandardError
86
+ add_request_message('error', "Invalid JWK: #{jwk.to_json}", index)
87
+ end
88
+
89
+ kid_presence = keys.all? { |key| key['kid'].present? }
90
+ if kid_presence.blank?
91
+ add_request_message('error',
92
+ '`kid` field must be present in each key if JWKS contains multiple keys',
93
+ index)
94
+ crd_jwks_keys_json << nil
95
+ next
96
+ end
97
+
98
+ kid_uniqueness = keys.map { |key| key['kid'] }.uniq.length == keys.length
99
+ if kid_uniqueness.blank?
100
+ add_request_message('error', "`kid` must be unique within the client's JWK Set.", index)
101
+ crd_jwks_keys_json << nil
102
+ next
103
+ end
104
+
105
+ crd_jwks_keys_json << keys.to_json
106
+ end
107
+
108
+ output crd_jwks_keys_json: crd_jwks_keys_json.to_json
109
+
110
+ assert_no_error_messages("#{requests_with_errors_prefix}Retrieving JWKS failed. See Messages for details.")
111
+ end
112
+
113
+ def cds_jwk_set_input_needed?(auth_token_headers)
114
+ auth_token_headers.any? do |token_header|
115
+ token_header.present? && JSON.parse(token_header)['jku'].blank?
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,92 @@
1
+ require_relative '../../multi_request_message_helper'
2
+
3
+ module DaVinciCRDTestKit
4
+ module V221
5
+ class TokenHeaderTest < Inferno::Test
6
+ include DaVinciCRDTestKit::MultiRequestMessageHelper
7
+
8
+ id :crd_v221_token_header
9
+ title 'Authorization token headers contain required information'
10
+ description %(
11
+ During this test, Inferno will verify that for each request the JWT header is conformant to the
12
+ requirements in the [CDS hooks specification](https://cds-hooks.hl7.org/2026Jan/en/#trusting-cds-clients),
13
+ including the following:
14
+ - The `alg`, `kid`, and `typ` fields are required.
15
+ - The `typ` field must be "JWT".
16
+ - The key used to sign the token must be present in the JWKS.
17
+ )
18
+
19
+ verifies_requirements 'cds-hooks_3.0.0-ballot@182', 'cds-hooks_3.0.0-ballot@184', 'cds-hooks_3.0.0-ballot@202'
20
+
21
+ input :auth_token_headers_json, :crd_jwks_keys_json
22
+ output :auth_tokens_jwk_json
23
+
24
+ run do
25
+ auth_token_headers = JSON.parse(auth_token_headers_json)
26
+ crd_jwks_keys = JSON.parse(crd_jwks_keys_json)
27
+ skip_if auth_token_headers.compact.empty?, 'No Authorization tokens produced from the previous tests.'
28
+ skip_if crd_jwks_keys.compact.empty?, 'No JWKS keys produced from the previous test.'
29
+
30
+ auth_tokens_jwk_json = []
31
+ auth_token_headers.each_with_index do |token_header, index|
32
+ unless token_header.present?
33
+ auth_tokens_jwk_json << nil
34
+ next
35
+ end
36
+
37
+ header = JSON.parse(token_header) # NOTE: pre-verified json
38
+ algorithm = header['alg']
39
+
40
+ if algorithm.blank?
41
+ add_request_message('error', 'Token header must have the `alg` field', index)
42
+ auth_tokens_jwk_json << nil
43
+ next
44
+ end
45
+
46
+ if algorithm == 'none'
47
+ add_request_message('error', 'Token header `alg` field cannot be set to none', index)
48
+ auth_tokens_jwk_json << nil
49
+ next
50
+ end
51
+
52
+ if header['typ'].blank?
53
+ add_request_message('error', 'Token header must have the `typ` field', index)
54
+ elsif header['typ'] != 'JWT'
55
+ add_request_message('error',
56
+ "Token header `typ` field must be set to 'JWT', instead was #{header['typ']}",
57
+ index)
58
+ end
59
+
60
+ if header['kid'].blank?
61
+ add_request_message('error', 'Token header must have the `kid` field', index)
62
+ auth_tokens_jwk_json << nil
63
+ next
64
+ end
65
+
66
+ kid = header['kid']
67
+ if crd_jwks_keys[index].nil?
68
+ add_request_message('error', 'No JWKS keys available for this request', index)
69
+ auth_tokens_jwk_json << nil
70
+ next
71
+ end
72
+
73
+ keys = JSON.parse(crd_jwks_keys[index]) # NOTE: pre-verified json
74
+
75
+ jwk = keys.find { |key| key['kid'] == kid }
76
+ if jwk.blank?
77
+ add_request_message('error', "JWKS did not contain a public key with an id of `#{kid}`", index)
78
+ auth_tokens_jwk_json << nil
79
+ next
80
+ end
81
+
82
+ auth_tokens_jwk_json << jwk.to_json
83
+ end
84
+
85
+ output auth_tokens_jwk_json: auth_tokens_jwk_json.to_json
86
+
87
+ assert_no_error_messages("#{requests_with_errors_prefix}Token header missing required information. " \
88
+ 'See Messages for details.')
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,93 @@
1
+ require_relative '../../multi_request_message_helper'
2
+ require_relative '../../tagged_request_load_helper'
3
+
4
+ module DaVinciCRDTestKit
5
+ module V221
6
+ class TokenPayloadTest < Inferno::Test
7
+ include DaVinciCRDTestKit::MultiRequestMessageHelper
8
+ include DaVinciCRDTestKit::TaggedRequestLoadHelper
9
+ include ClientURLs
10
+
11
+ id :crd_v221_token_payload
12
+ title 'Authorization token payloads have required claims and valid signatures'
13
+ description %(
14
+ During this test, Inferno will verify that for each request the JWT payload is conformant to the
15
+ requirements in the [CDS hooks specification](https://cds-hooks.hl7.org/2026Jan/en/#trusting-cds-clients),
16
+ including the following:
17
+ - The `iss`, `aud`, `exp`, `iat`, and `jti` claims are required.
18
+ - `iss` must match the `issuer` from the **CRD JWT Issuer** input.
19
+ - `aud` must match the URL of the CDS Service endpoint being invoked.
20
+ - `exp` must represent a time in the future.
21
+ - `jti` must be a non-blank string that uniquely identifies this authentication JWT.
22
+ )
23
+
24
+ verifies_requirements 'cds-hooks_3.0.0-ballot@180', 'cds-hooks_3.0.0-ballot@181', 'cds-hooks_3.0.0-ballot@187',
25
+ 'cds-hooks_3.0.0-ballot@189', 'cds-hooks_3.0.0-ballot@190', 'cds-hooks_3.0.0-ballot@191',
26
+ 'cds-hooks_3.0.0-ballot@192', 'cds-hooks_3.0.0-ballot@196', 'cds-hooks_3.0.0-ballot@203'
27
+
28
+ REQUIRED_CLAIMS = ['iss', 'aud', 'exp', 'iat', 'jti'].freeze
29
+
30
+ def required_claims
31
+ REQUIRED_CLAIMS.dup
32
+ end
33
+
34
+ # Replace the scheme+host of request.url with the configured external host
35
+ # so that aud validation works correctly when Inferno is behind a reverse proxy.
36
+ def public_hook_url(request)
37
+ hook_suffix = URI.parse(request.url).path.delete_prefix(URI.parse(inferno_base_url).path)
38
+ inferno_base_url + hook_suffix
39
+ end
40
+
41
+ input :auth_tokens,
42
+ :auth_tokens_jwk_json,
43
+ :cds_jwt_iss
44
+
45
+ run do
46
+ auth_tokens_list = JSON.parse(auth_tokens)
47
+ auth_tokens_jwk = JSON.parse(auth_tokens_jwk_json)
48
+ requests = load_hook_requests
49
+ skip_if auth_tokens_list.compact.empty?, 'No Authorization tokens produced from the previous tests.'
50
+ skip_if auth_tokens_jwk.compact.empty?, 'No Authorization token JWK produced from the previous test.'
51
+
52
+ auth_tokens_jwk.each_with_index do |auth_token_jwk, index|
53
+ next unless auth_token_jwk.present?
54
+
55
+ request = requests[index]
56
+
57
+ begin
58
+ jwk = JSON.parse(auth_token_jwk).deep_symbolize_keys # NOTE: pre-verified json
59
+
60
+ payload, =
61
+ JWT.decode(
62
+ auth_tokens_list[index],
63
+ JWT::JWK.import(jwk).public_key,
64
+ true,
65
+ algorithms: [jwk[:alg]],
66
+ exp_leeway: 60,
67
+ iss: cds_jwt_iss,
68
+ aud: public_hook_url(request),
69
+ verify_not_before: false,
70
+ verify_iat: false,
71
+ verify_jti: true,
72
+ verify_iss: true,
73
+ verify_aud: true
74
+ )
75
+ rescue StandardError => e
76
+ add_request_message('error', "Token validation error: #{e.message}", index)
77
+ next
78
+ end
79
+
80
+ missing_claims = required_claims - payload.keys
81
+ missing_claims_string = missing_claims.map { |claim| "`#{claim}`" }.join(', ')
82
+
83
+ unless missing_claims.empty?
84
+ add_request_message('error', "JWT payload missing required claims: #{missing_claims_string}", index)
85
+ next
86
+ end
87
+ end
88
+ assert_no_error_messages("#{requests_with_errors_prefix}Token payload is missing required claims or " \
89
+ 'does not have a valid signature. See Messages for details.')
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,198 @@
1
+
2
+ {
3
+ "services": [
4
+ {
5
+ "hook": "appointment-book",
6
+ "title": "Appointment Booking CDS Service",
7
+ "description": "A simulated CDS Service requesting a subset of the standard prefetches that is invoked when user of a CRD client books a future appointment for a patient",
8
+ "id": "appointment-book-subset",
9
+ "prefetch" : {
10
+ "pat": "Patient/{{context.patientId}}",
11
+ "enc": "Encounter/{{context.encounterId}}",
12
+ "cov": "Coverage?patient={{context.patientId}}&status=active",
13
+ "devReqs" : "DeviceRequest?_id={{context.appointments.entry.resource.basedOn.extension('http://hl7.org/fhir/StructureDefinition/alternate-reference').value.resolve().ofType(DeviceRequest).id}}",
14
+ "servReqs" : "ServiceRequest?_id={{context.appointments.entry.resource.basedOn.resolve().ofType(ServiceRequest).id}}",
15
+ "medReqs" : "MedicationRequest?_id={{context.appointments.entry.resource.basedOn.extension('http://hl7.org/fhir/StructureDefinition/alternate-reference').value.resolve().ofType(MedicationRequest).id}}",
16
+ "devs": "Device?_id={{%devReqs.entry.resource.code.resolve().id}}",
17
+ "meds": "Medication?_id={{%medReqs.entry.resource.medication.resolve().id}}",
18
+ "roles" : "PractitionerRole?_id={{%devReqs.entry.resource.performer.resolve().ofType(PractitionerRole).id|%medReqs.entry.resource.performer.resolve().ofType(PractitionerRole).id|%servReqs.entry.resource.performer.resolve().ofType(PractitionerRole).id}}",
19
+ "pracs" : "Practitioner?_id={{%roles.entry.resource.practitioner.resolve().id|%devReqs.entry.resource.performer.resolve().ofType(Practitioner).id|%medReqs.entry.resource.performer.resolve().ofType(Practitioner).id|%servReqs.entry.resource.performer.resolve().ofType(Practitioner).id}}",
20
+ "locs" : "Location?_id={{%roles.entry.resource.location.resolve().id|%enc.location.location.resolve().id|%servReqs.entry.resource.locationReference.resolve().ofType(Location).id}}"
21
+ },
22
+ "extension" : {
23
+ "davinci-crd.version" : [
24
+ "2.2",
25
+ "2.0"
26
+ ],
27
+ "davinci-crd.configuration-options" : [
28
+ {
29
+ "code" : "coverage-info",
30
+ "type" : "boolean",
31
+ "name" : "Coverage Information",
32
+ "description" : "Information related to the patient's coverage, including whether a service is covered, requires prior authorization, is approved without seeking prior authorization, and/or requires additional documentation or data collection",
33
+ "default" : true
34
+ }
35
+ ]
36
+ }
37
+ },
38
+ {
39
+ "hook": "encounter-start",
40
+ "title": "Encounter Start CDS Service",
41
+ "description": "A simulated CDS Service requesting a subset of the standard prefetches that is invoked when the user is initiating a new encounter.",
42
+ "id": "encounter-start-subset",
43
+ "prefetch" : {
44
+ "pat": "Patient/{{context.patientId}}",
45
+ "enc": "Encounter/{{context.encounterId}}",
46
+ "cov": "Coverage?patient={{context.patientId}}&status=active",
47
+ "roles" : "PractitionerRole?_id={{%enc.participant.individual.resolve().ofType(PractitionerRole).id}}",
48
+ "pracs" : "Practitioner?_id={{%roles.entry.resource.practitioner.resolve().id|%enc.participant.individual.resolve().ofType(Practitioner).id}}",
49
+ "orgs" : "Organization?_id={{%roles.entry.resource.organization.resolve().id}}",
50
+ "locs" : "Location?_id={{%roles.entry.resource.location.resolve().id}}"
51
+ },
52
+ "extension" : {
53
+ "davinci-crd.version" : [
54
+ "2.2",
55
+ "2.0"
56
+ ],
57
+ "davinci-crd.configuration-options" : [
58
+ {
59
+ "code" : "coverage-info",
60
+ "type" : "boolean",
61
+ "name" : "Coverage Information",
62
+ "description" : "Information related to the patient's coverage, including whether a service is covered, requires prior authorization, is approved without seeking prior authorization, and/or requires additional documentation or data collection",
63
+ "default" : true
64
+ }
65
+ ]
66
+ }
67
+ },
68
+ {
69
+ "hook": "encounter-discharge",
70
+ "title": "Encounter Disharge CDS Service Example",
71
+ "description": "A simulated CDS Service requesting a subset of the standard prefetches that is invoked when the user is performing the discharge process for an encounter - typically an inpatient encounter.",
72
+ "id": "encounter-discharge-subset",
73
+ "prefetch" : {
74
+ "pat": "Patient/{{context.patientId}}",
75
+ "enc": "Encounter/{{context.encounterId}}",
76
+ "cov": "Coverage?patient={{context.patientId}}&status=active",
77
+ "roles" : "PractitionerRole?_id={{%enc.participant.individual.resolve().ofType(PractitionerRole).id}}",
78
+ "pracs" : "Practitioner?_id={{%roles.entry.resource.practitioner.resolve().id|%enc.participant.individual.resolve().ofType(Practitioner).id}}",
79
+ "orgs" : "Organization?_id={{%roles.entry.resource.organization.resolve().id}}",
80
+ "locs" : "Location?_id={{%roles.entry.resource.location.resolve().id}}"
81
+ },
82
+ "extension" : {
83
+ "davinci-crd.version" : [
84
+ "2.2",
85
+ "2.0"
86
+ ],
87
+ "davinci-crd.configuration-options" : [
88
+ {
89
+ "code" : "coverage-info",
90
+ "type" : "boolean",
91
+ "name" : "Coverage Information",
92
+ "description" : "Information related to the patient's coverage, including whether a service is covered, requires prior authorization, is approved without seeking prior authorization, and/or requires additional documentation or data collection",
93
+ "default" : true
94
+ }
95
+ ]
96
+ }
97
+ },
98
+ {
99
+ "hook": "order-dispatch",
100
+ "title": "Order Dispatch CDS Service Example",
101
+ "description": "A simulated CDS Service requesting a subset of the standard prefetches that fires when a practitioner is selecting a candidate performer for a pre-existing order that was not tied to a specific performer",
102
+ "id": "order-dispatch-subset",
103
+ "prefetch": {
104
+ "pat": "Patient/{{context.patientId}}",
105
+ "enc": "Encounter/{{context.encounterId}}",
106
+ "cov": "Coverage?patient={{context.patientId}}&status=active",
107
+ "comReqs": "CommunicationRequest?_id={{context.dispatchedOrders.resolve().ofType(CommunicationRequest).id}}",
108
+ "devReqs": "DeviceRequest?_id={{context.dispatchedOrders.resolve().ofType(DeviceRequest).id}}",
109
+ "medReqs": "MedicationRequest?_id={{context.dispatchedOrders.resolve().ofType(MedicationRequest).id}}",
110
+ "nutOrds": "NutritionOrder?_id={{context.dispatchedOrders.resolve().ofType(NutritionOrder).id}}",
111
+ "servReqs": "ServiceRequest?_id={{context.dispatchedOrders.resolve().ofType(ServiceRequest).id}}",
112
+ "visRxs": "VisionPrescription?_id={{context.dispatchedOrders.resolve().ofType(VisionPrescription).id}}",
113
+ "devs": "Device?_id={{%devReqs.entry.resource.code.resolve().id}}",
114
+ "meds": "Medication?_id={{%medReqs.entry.resource.medication.resolve().id}}",
115
+ "roles": "PractitionerRole?_id={{%comReqs.entry.resource.sender.resolve().ofType(PractitionerRole).id|%comReqs.entry.resource.recipient.resolve().ofType(PractitionerRole).id|%devReqs.entry.resource.performer.resolve().ofType(PractitionerRole).id|%medReqs.entry.resource.performer.resolve().ofType(PractitionerRole).id|%servReqs.entry.resource.performer.resolve().ofType(PractitionerRole).id}}",
116
+ "pracs": "Practitioner?_id={{%roles.entry.resource.practitioner.resolve().id|%comReqs.entry.resource.sender.resolve().ofType(Practitioner).id|%comReqs.entry.resource.recipient.resolve().ofType(Practitioner).id|%devReqs.entry.resource.performer.resolve().ofType(Practitioner).id|%medReqs.entry.resource.performer.resolve().ofType(Practitioner).id|%servReqs.entry.resource.performer.resolve().ofType(Practitioner).id}}",
117
+ "locs": "Location?_id={{%roles.entry.resource.location.resolve().id|%enc.location.location.resolve().id|%servReqs.entry.resource.locationReference.resolve().id}}"
118
+ },
119
+ "extension" : {
120
+ "davinci-crd.version" : [
121
+ "2.2",
122
+ "2.0"
123
+ ],
124
+ "davinci-crd.configuration-options" : [
125
+ {
126
+ "code" : "coverage-info",
127
+ "type" : "boolean",
128
+ "name" : "Coverage Information",
129
+ "description" : "Information related to the patient's coverage, including whether a service is covered, requires prior authorization, is approved without seeking prior authorization, and/or requires additional documentation or data collection",
130
+ "default" : true
131
+ }
132
+ ]
133
+ }
134
+ },
135
+ {
136
+ "hook": "order-select",
137
+ "title": "Order Select CDS Service",
138
+ "description": "A simulated CDS Service requesting a subset of the standard prefetches that fires when a clinician selects one or more orders to place for a patient",
139
+ "id": "order-select-subset",
140
+ "prefetch": {
141
+ "pat": "Patient/{{context.patientId}}",
142
+ "enc": "Encounter/{{context.encounterId}}",
143
+ "cov": "Coverage?patient={{context.patientId}}&status=active",
144
+ "devs": "Device?_id={{context.draftOrders.entry.resource.ofType(DeviceRequest).code.resolve().id}}",
145
+ "meds": "Medication?_id={{context.draftOrders.entry.resource.ofType(MedicationRequest).medication.resolve().id}}",
146
+ "roles": "PractitionerRole?_id={{context.draftOrders.entry.resource.sender.resolve().ofType(PractitionerRole).id|context.draftOrders.entry.resource.recipient.resolve().ofType(PractitionerRole).id|context.draftOrders.entry.resource.performer.resolve().ofType(PractitionerRole).id}}",
147
+ "pracs": "Practitioner?_id={{%roles.entry.resource.practitioner.resolve().id|context.draftOrders.entry.resource.sender.resolve().ofType(Practitioner).id|context.draftOrders.entry.resource.recipient.resolve().ofType(Practitioner).id|context.draftOrders.entry.resource.performer.resolve().ofType(Practitioner).id}}",
148
+ "locs": "Location?_id={{%roles.entry.resource.location.resolve().id|%enc.location.location.resolve().id|context.draftOrders.entry.resource.locationReference.resolve().id}}"
149
+ },
150
+ "extension" : {
151
+ "davinci-crd.version" : [
152
+ "2.2",
153
+ "2.0"
154
+ ],
155
+ "davinci-crd.configuration-options" : [
156
+ {
157
+ "code" : "coverage-info",
158
+ "type" : "boolean",
159
+ "name" : "Coverage Information",
160
+ "description" : "Information related to the patient's coverage, including whether a service is covered, requires prior authorization, is approved without seeking prior authorization, and/or requires additional documentation or data collection",
161
+ "default" : true
162
+ }
163
+ ]
164
+ }
165
+ },
166
+ {
167
+ "hook": "order-sign",
168
+ "title": "Order Sign CDS Service",
169
+ "description": "A simulated CDS Service requesting a subset of the standard prefetches that fires when a clinician is ready to sign one or more orders for a patient",
170
+ "id": "order-sign-subset",
171
+ "prefetch": {
172
+ "pat": "Patient/{{context.patientId}}",
173
+ "enc": "Encounter/{{context.encounterId}}",
174
+ "cov": "Coverage?patient={{context.patientId}}&status=active",
175
+ "devs": "Device?_id={{context.draftOrders.entry.resource.ofType(DeviceRequest).code.resolve().id}}",
176
+ "meds": "Medication?_id={{context.draftOrders.entry.resource.ofType(MedicationRequest).medication.resolve().id}}",
177
+ "roles": "PractitionerRole?_id={{context.draftOrders.entry.resource.sender.resolve().ofType(PractitionerRole).id|context.draftOrders.entry.resource.recipient.resolve().ofType(PractitionerRole).id|context.draftOrders.entry.resource.performer.resolve().ofType(PractitionerRole).id}}",
178
+ "pracs": "Practitioner?_id={{%roles.entry.resource.practitioner.resolve().id|context.draftOrders.entry.resource.sender.resolve().ofType(Practitioner).id|context.draftOrders.entry.resource.recipient.resolve().ofType(Practitioner).id|context.draftOrders.entry.resource.performer.resolve().ofType(Practitioner).id}}",
179
+ "locs": "Location?_id={{%roles.entry.resource.location.resolve().id|%enc.location.location.resolve().id|context.draftOrders.entry.resource.locationReference.resolve().id}}"
180
+ },
181
+ "extension" : {
182
+ "davinci-crd.version" : [
183
+ "2.2",
184
+ "2.0"
185
+ ],
186
+ "davinci-crd.configuration-options" : [
187
+ {
188
+ "code" : "coverage-info",
189
+ "type" : "boolean",
190
+ "name" : "Coverage Information",
191
+ "description" : "Information related to the patient's coverage, including whether a service is covered, requires prior authorization, is approved without seeking prior authorization, and/or requires additional documentation or data collection",
192
+ "default" : true
193
+ }
194
+ ]
195
+ }
196
+ }
197
+ ]
198
+ }