davinci_crd_test_kit 0.14.1 → 0.14.2

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 (121) hide show
  1. checksums.yaml +4 -4
  2. data/config/presets/{inferno_crd_server_suite.json.erb → client_against_inferno_crd_server_suite.json.erb} +0 -33
  3. data/config/presets/{ri_crd_request_generator.json.erb → client_against_ri_crd_request_generator.json.erb} +1 -14
  4. data/config/presets/client_v221_against_inferno_crd_server_suite.json.erb +84 -0
  5. data/config/presets/client_v221_against_ri_br_provider.json.erb +31 -0
  6. data/config/presets/{ri_crd_request_generator.json_v221.json.erb → client_v221_against_ri_crd_request_generator.json.erb} +1 -1
  7. data/config/presets/{inferno_crd_client_suite.json.erb → server_against_inferno_crd_client_suite.json.erb} +0 -43
  8. data/config/presets/{ri_crd_server.json.erb → server_against_ri_crd_server.json.erb} +1 -44
  9. data/config/presets/{inferno_crd_client_suite_v221.json.erb → server_v221_against_inferno_crd_client_suite.json.erb} +0 -61
  10. data/config/presets/{inferno_crd_client_suite_prefetch_subset_v221.json.erb → server_v221_against_inferno_crd_client_suite_prefetch_subset.json.erb} +0 -46
  11. data/config/presets/server_v221_against_inferno_g31_suite.json.erb +49 -0
  12. data/lib/davinci_crd_test_kit/client/cross_hook_helper.rb +26 -0
  13. data/lib/davinci_crd_test_kit/client/endpoints/custom_service_response.rb +7 -7
  14. data/lib/davinci_crd_test_kit/client/endpoints/gather_response_generation_data.rb +4 -1
  15. data/lib/davinci_crd_test_kit/client/endpoints/hook_request_endpoint.rb +63 -38
  16. data/lib/davinci_crd_test_kit/client/endpoints/mock_service_response.rb +26 -7
  17. data/lib/davinci_crd_test_kit/client/endpoints/scenario_service_response.rb +54 -0
  18. data/lib/davinci_crd_test_kit/client/tagged_request_load_helper.rb +8 -27
  19. data/lib/davinci_crd_test_kit/client/v2.0.1/auth/retrieve_jwks_test.rb +35 -23
  20. data/lib/davinci_crd_test_kit/client/v2.0.1/auth/token_payload_test.rb +34 -21
  21. data/lib/davinci_crd_test_kit/client/v2.0.1/cds-services-v201.json +20 -20
  22. data/lib/davinci_crd_test_kit/client/v2.0.1/client_appointment_book_group.rb +12 -10
  23. data/lib/davinci_crd_test_kit/client/v2.0.1/client_encounter_discharge_group.rb +12 -10
  24. data/lib/davinci_crd_test_kit/client/v2.0.1/client_encounter_start_group.rb +12 -10
  25. data/lib/davinci_crd_test_kit/client/v2.0.1/client_hooks_group.rb +5 -5
  26. data/lib/davinci_crd_test_kit/client/v2.0.1/client_order_dispatch_group.rb +12 -10
  27. data/lib/davinci_crd_test_kit/client/v2.0.1/client_order_select_group.rb +12 -10
  28. data/lib/davinci_crd_test_kit/client/v2.0.1/client_order_sign_group.rb +12 -10
  29. data/lib/davinci_crd_test_kit/client/v2.0.1/must_support/client_card_must_support_coverage_information.rb +3 -36
  30. data/lib/davinci_crd_test_kit/client/v2.0.1/must_support/client_card_must_support_external_reference.rb +3 -36
  31. data/lib/davinci_crd_test_kit/client/v2.0.1/must_support/client_card_must_support_instructions.rb +3 -36
  32. data/lib/davinci_crd_test_kit/client/v2.0.1/verify_request/hook_request_fetched_data_test.rb +4 -13
  33. data/lib/davinci_crd_test_kit/client/v2.0.1/verify_request/hook_request_prefetch_equals_queried_test.rb +3 -13
  34. data/lib/davinci_crd_test_kit/client/v2.0.1/verify_response/client_display_cards_attest.rb +3 -13
  35. data/lib/davinci_crd_test_kit/client/v2.0.1/verify_response/inferno_response_validation.rb +3 -13
  36. data/lib/davinci_crd_test_kit/client/v2.2.1/api/client_coverage_info_update_test.rb +1 -1
  37. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/attestation_instructions.rb +10 -0
  38. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_authorized_scope_attestation_test.rb +45 -0
  39. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_coverage_based_invocation_attestation_test.rb +44 -0
  40. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_data_element_expectations_attestation_test.rb +44 -0
  41. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_hook_invocation_logging_attestation_test.rb +43 -0
  42. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_must_support_exposure_attestation_test.rb +43 -0
  43. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_must_support_use_attestation_test.rb +41 -0
  44. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_order_sign_support_attestation_test.rb +42 -0
  45. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_prefetch_key_omission_attestation_test.rb +42 -0
  46. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_resource_identifiers_attestation_test.rb +43 -0
  47. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_response_case_distinction_attestation_test.rb +43 -0
  48. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_security_and_privacy_attestation_test.rb +43 -0
  49. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_workflow_integration_attestation_test.rb +44 -0
  50. data/lib/davinci_crd_test_kit/client/v2.2.1/auth/decode_auth_token_test.rb +1 -1
  51. data/lib/davinci_crd_test_kit/client/v2.2.1/auth/retrieve_jwks_test.rb +29 -18
  52. data/lib/davinci_crd_test_kit/client/v2.2.1/auth/token_payload_test.rb +35 -22
  53. data/lib/davinci_crd_test_kit/client/v2.2.1/cds-services-prefetch-subset-v221.json +47 -47
  54. data/lib/davinci_crd_test_kit/client/v2.2.1/cds-services-v221.json +50 -50
  55. data/lib/davinci_crd_test_kit/client/v2.2.1/client_appointment_book_group.rb +16 -14
  56. data/lib/davinci_crd_test_kit/client/v2.2.1/client_attestations_group.rb +49 -0
  57. data/lib/davinci_crd_test_kit/client/v2.2.1/client_cross_hook_group.rb +22 -6
  58. data/lib/davinci_crd_test_kit/client/v2.2.1/client_cross_hook_interaction_group.rb +98 -0
  59. data/lib/davinci_crd_test_kit/client/v2.2.1/client_encounter_discharge_group.rb +12 -10
  60. data/lib/davinci_crd_test_kit/client/v2.2.1/client_encounter_start_group.rb +12 -10
  61. data/lib/davinci_crd_test_kit/client/v2.2.1/client_fhir_api_group.rb +14 -12
  62. data/lib/davinci_crd_test_kit/client/v2.2.1/client_hooks_group.rb +9 -9
  63. data/lib/davinci_crd_test_kit/client/v2.2.1/client_long_running_hook_group.rb +2 -1
  64. data/lib/davinci_crd_test_kit/client/v2.2.1/client_order_dispatch_group.rb +16 -14
  65. data/lib/davinci_crd_test_kit/client/v2.2.1/client_order_select_group.rb +12 -10
  66. data/lib/davinci_crd_test_kit/client/v2.2.1/client_order_sign_group.rb +16 -14
  67. data/lib/davinci_crd_test_kit/client/v2.2.1/client_scenarios_group.rb +26 -0
  68. data/lib/davinci_crd_test_kit/client/v2.2.1/client_self_pay_hook_group.rb +36 -0
  69. data/lib/davinci_crd_test_kit/client/v2.2.1/client_unknown_content_hook_group.rb +34 -0
  70. data/lib/davinci_crd_test_kit/client/v2.2.1/crd_client_suite.rb +19 -3
  71. data/lib/davinci_crd_test_kit/client/v2.2.1/cross_hook/client_card_must_support_coverage_information_test.rb +1 -1
  72. data/lib/davinci_crd_test_kit/client/v2.2.1/cross_hook/client_fhirpath_collection_as_comma_delimited_string_test.rb +4 -11
  73. data/lib/davinci_crd_test_kit/client/v2.2.1/cross_hook/client_hook_instances_unique_test.rb +1 -9
  74. data/lib/davinci_crd_test_kit/client/v2.2.1/cross_hook/client_location_address_propagation_test.rb +6 -2
  75. data/lib/davinci_crd_test_kit/client/v2.2.1/cross_hook/client_prefetch_complete_and_subset_test.rb +3 -12
  76. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/appointment_book_receive_request_test.rb +8 -89
  77. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/base_hook_invocation_receive_request_test.rb +163 -0
  78. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/cross_hooks_receive_request_test.rb +97 -0
  79. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/encounter_discharge_receive_request_test.rb +8 -90
  80. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/encounter_start_receive_request_test.rb +8 -90
  81. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/order_dispatch_receive_request_test.rb +8 -98
  82. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/order_select_receive_request_test.rb +8 -98
  83. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/order_sign_receive_request_test.rb +8 -98
  84. data/lib/davinci_crd_test_kit/client/v2.2.1/long_running/client_long_running_receive_request_test.rb +10 -2
  85. data/lib/davinci_crd_test_kit/client/v2.2.1/long_running/client_skip_long_running_attestation_test.rb +1 -1
  86. data/lib/davinci_crd_test_kit/client/v2.2.1/self_pay/client_self_pay_no_request_test.rb +30 -0
  87. data/lib/davinci_crd_test_kit/client/v2.2.1/self_pay/client_self_pay_workflow_test.rb +55 -0
  88. data/lib/davinci_crd_test_kit/client/v2.2.1/unknown_content/client_unknown_content_attestation_test.rb +99 -0
  89. data/lib/davinci_crd_test_kit/client/v2.2.1/unknown_content/client_unknown_content_receive_request_test.rb +62 -0
  90. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_conformance_test.rb +1 -1
  91. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_coverage_verification_test.rb +10 -2
  92. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_data_fetch_verification_test.rb +1 -1
  93. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_granted_scopes_test.rb +1 -1
  94. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_prefetch_complete_test.rb +1 -1
  95. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_prefetch_profiles_test.rb +1 -1
  96. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_requested_version_test.rb +1 -1
  97. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_secured_transport_test.rb +1 -1
  98. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_response/client_display_cards_attest.rb +52 -20
  99. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_response/hook_response_support_coverage_information_test.rb +1 -1
  100. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_response/inferno_response_validation.rb +6 -3
  101. data/lib/davinci_crd_test_kit/cross_suite/cards_identification.rb +9 -0
  102. data/lib/davinci_crd_test_kit/cross_suite/logical_models_override_helper.rb +30 -13
  103. data/lib/davinci_crd_test_kit/cross_suite/prefetch_profile_validation.rb +2 -0
  104. data/lib/davinci_crd_test_kit/cross_suite/requests_logical_model_validation.rb +26 -27
  105. data/lib/davinci_crd_test_kit/cross_suite/response_logical_model_validation.rb +48 -41
  106. data/lib/davinci_crd_test_kit/cross_suite/tags.rb +3 -0
  107. data/lib/davinci_crd_test_kit/requirements/cds-hooks_3.0.0-ballot_requirements.xlsx +0 -0
  108. data/lib/davinci_crd_test_kit/requirements/davinci_crd_test_kit_requirements.csv +6 -2
  109. data/lib/davinci_crd_test_kit/requirements/generated/crd_client_requirements_coverage.csv +107 -107
  110. data/lib/davinci_crd_test_kit/requirements/generated/crd_client_v221_requirements_coverage.csv +101 -97
  111. data/lib/davinci_crd_test_kit/requirements/generated/crd_server_requirements_coverage.csv +18 -18
  112. data/lib/davinci_crd_test_kit/requirements/generated/crd_server_v221_requirements_coverage.csv +36 -32
  113. data/lib/davinci_crd_test_kit/requirements/hl7.fhir.us.davinci-crd_2.2.1_requirements.xlsx +0 -0
  114. data/lib/davinci_crd_test_kit/server/v2.0.1/crd_server_suite.rb +1 -1
  115. data/lib/davinci_crd_test_kit/server/v2.0.1/server_hooks_group.rb +5 -5
  116. data/lib/davinci_crd_test_kit/server/v2.2.1/discovery/discovery_standard_prefetch_expressions_test.rb +1 -2
  117. data/lib/davinci_crd_test_kit/server/v2.2.1/server_demonstrate_hook_response_group.rb +2 -2
  118. data/lib/davinci_crd_test_kit/server/v2.2.1/server_hooks_group.rb +5 -5
  119. data/lib/davinci_crd_test_kit/version.rb +2 -2
  120. metadata +40 -12
  121. data/config/presets/inferno_crd_server_suite_v221.json.erb +0 -94
@@ -13,15 +13,10 @@ module DaVinciCRDTestKit
13
13
  'Practitioner', 'PractitionerRole'
14
14
  ].freeze
15
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
16
+ def validate_request_against_logical_model(request_body, request_index, ig_semver, validator: :default)
17
+ check_logical_model_conformance_no_resource_checks(request_body, request_index, ig_semver, validator:)
23
18
 
24
- perform_version_specific_additional_verification(request_body, request_index, ig_semver)
19
+ perform_version_specific_additional_verification(request_body, request_index, ig_semver, validator:)
25
20
  end
26
21
 
27
22
  private
@@ -30,84 +25,88 @@ module DaVinciCRDTestKit
30
25
  # Additional Validation to cover areas not checked or checked incorrectly by the logical models
31
26
  # -------------------------------------------------------------------------
32
27
 
33
- def perform_version_specific_additional_verification(request_body, request_index, ig_semver)
28
+ def perform_version_specific_additional_verification(request_body, request_index, ig_semver, validator: :default)
34
29
  case ig_semver
35
30
  when '2.2.1'
36
- perform_v221_additional_verification(request_body, request_index)
31
+ perform_v221_additional_verification(request_body, request_index, validator:)
37
32
  end
38
33
  end
39
34
 
40
- def perform_v221_additional_verification(request_body, request_index)
35
+ def perform_v221_additional_verification(request_body, request_index, validator: :default)
41
36
  check_context_resources_for_ids(request_body, request_index)
42
37
  check_relative_references(request_body, request_index)
43
- check_context_resource_profiles(request_body, request_index, '2.2.1')
38
+ check_context_resource_profiles(request_body, request_index, '2.2.1', validator:)
44
39
  end
45
40
 
46
41
  # -------------------------------------------------------------------------
47
42
  # Context resources profile check (not working in v2.2.1 logical models)
48
43
  # -------------------------------------------------------------------------
49
44
 
50
- def check_logical_model_conformance_no_resource_checks(request_body, request_index, ig_semver)
45
+ def check_logical_model_conformance_no_resource_checks(request_body, request_index, ig_semver, validator: :default)
51
46
  validation_issues = []
52
47
  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)
48
+ add_messages_to_runnable: false, validator_response_details: validation_issues,
49
+ validator:)
54
50
 
55
- reject_resource_issues(validation_issues).each do |issue|
51
+ filtered_issues = reject_resource_issues(validation_issues)
52
+ filter_logical_model_extension_issues(filtered_issues, validator)
53
+ filtered_issues.each do |issue|
56
54
  next if issue.filtered
57
55
 
58
56
  add_message(issue.severity, "(Request #{request_index + 1}) #{issue.message}")
59
57
  end
60
58
  end
61
59
 
62
- def check_context_resource_profiles(request_body, request_index, ig_semver)
60
+ def check_context_resource_profiles(request_body, request_index, ig_semver, validator: :default)
63
61
  case request_body['hook']
64
62
  when 'order-sign', 'order-select'
65
- draft_orders_conform_to_profiles?(request_body, request_index, ig_semver)
63
+ draft_orders_conform_to_profiles?(request_body, request_index, ig_semver, validator:)
66
64
  when 'order-dispatch'
67
65
  request_body.dig('context', 'fulfillmentTasks')&.each_with_index do |task, index|
68
66
  resource = FHIR.from_contents(task.to_json)
69
67
  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} - ")
68
+ validator:, message_prefix: "(Request #{request_index + 1}) " \
69
+ "context.fulfillmentTasks entry #{index + 1} - ")
72
70
  end
73
71
  when 'appointment-book'
74
- check_appointments_profiles(request_body, request_index, ig_semver)
72
+ check_appointments_profiles(request_body, request_index, ig_semver, validator:)
75
73
  end
76
74
  rescue JSON::ParserError
77
75
  nil # no resource to validate - error found elsewhere
78
76
  end
79
77
 
80
- def draft_orders_conform_to_profiles?(request_body, request_index, ig_semver)
78
+ def draft_orders_conform_to_profiles?(request_body, request_index, ig_semver, validator: :default)
81
79
  draft_orders = request_body.dig('context', 'draftOrders').to_json
82
80
  resource = FHIR.from_contents(draft_orders)
83
81
  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 - ")
82
+ message_prefix: "(Request #{request_index + 1}) context.draftOrders - ", validator:)
85
83
  end
86
84
 
87
85
  # -------------------------------------------------------------------------
88
86
  # Appointment conformance (requires extra help to decide profile and check profile-based slicing)
89
87
  # -------------------------------------------------------------------------
90
88
 
91
- def check_appointments_profiles(request_body, request_index, ig_semver)
89
+ def check_appointments_profiles(request_body, request_index, ig_semver, validator: :default)
92
90
  resource = FHIR.from_contents(request_body.dig('context', 'appointments')&.to_json)
93
91
  return unless resource.is_a?(FHIR::Bundle)
94
92
 
95
93
  check_bundle_non_entry_resource_conformance(resource,
96
94
  "(Request #{request_index + 1}) context.appointments - ",
97
- ig_semver)
95
+ ig_semver,
96
+ validator:)
98
97
 
99
98
  resource.entry.each_with_index do |entry, entry_index|
100
99
  next unless entry.resource.present? # error caught on Bundle validation
101
100
 
102
101
  error_prefix = "(Request #{request_index + 1}) context.appointments entry #{entry_index + 1} - "
103
- check_appointment_conformance(entry.resource, request_body, error_prefix, ig_semver)
102
+ check_appointment_conformance(entry.resource, request_body, error_prefix, ig_semver, validator:)
104
103
  end
105
104
  end
106
105
 
107
- def check_bundle_non_entry_resource_conformance(bundle, error_prefix, ig_semver)
106
+ def check_bundle_non_entry_resource_conformance(bundle, error_prefix, ig_semver, validator: :default)
108
107
  validation_issues = []
109
108
  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)
109
+ add_messages_to_runnable: false, validator_response_details: validation_issues, validator:)
111
110
 
112
111
  reject_entry_resource_issues(validation_issues).each do |issue|
113
112
  add_message(issue.severity, "#{error_prefix}#{issue.message}")
@@ -39,21 +39,24 @@ module DaVinciCRDTestKit
39
39
  "#{CRD_LOGICAL_MODEL_BASE}/#{profile_name}"
40
40
  end
41
41
 
42
- def perform_response_logical_model_validation(cards, system_actions, request_body, response_index, ig_semver)
42
+ def perform_response_logical_model_validation(cards, system_actions, request_body, response_index, ig_semver,
43
+ validator: :default)
43
44
  if cards.is_a?(Array)
44
45
  cards.each_with_index do |card, card_index|
45
- validate_card_against_logical_model(card, response_index, request_body, card_index, ig_semver)
46
+ validate_card_against_logical_model(card, response_index, request_body, card_index, ig_semver, validator:)
46
47
  end
47
48
  end
48
49
 
49
50
  return unless system_actions.is_a?(Array)
50
51
 
51
52
  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
+ validate_system_action_against_logical_model(action, response_index, request_body, action_index, ig_semver,
54
+ validator:)
53
55
  end
54
56
  end
55
57
 
56
- def validate_card_against_logical_model(card, response_index, request_body, card_index, ig_semver)
58
+ def validate_card_against_logical_model(card, response_index, request_body, card_index, ig_semver,
59
+ validator: :default)
57
60
  label = logical_model_entity_label(response_index, card_index, 'card')
58
61
  unless card.is_a?(Hash)
59
62
  add_message('error', "#{label} is not a JSON object; skipping logical model validation.")
@@ -71,7 +74,8 @@ module DaVinciCRDTestKit
71
74
 
72
75
  validation_issues = []
73
76
  conforms_to_logical_model?({ 'cards' => [card] }, logical_model_url(profile_name),
74
- add_messages_to_runnable: false, validator_response_details: validation_issues)
77
+ add_messages_to_runnable: false, validator_response_details: validation_issues,
78
+ validator:)
75
79
 
76
80
  validate_summary_length(card, label)
77
81
  validate_suggestion_uuid_presence(card, label) unless ['v201', '2.0.1'].include? ig_semver
@@ -79,7 +83,8 @@ module DaVinciCRDTestKit
79
83
 
80
84
  error_prefix = "#{label} (#{card_type || 'uncategorized'}): "
81
85
  filtered_issues = manually_check_card_specific_errors(card, validation_issues, card_type,
82
- request_body, error_prefix, ig_semver)
86
+ request_body, error_prefix, ig_semver, validator:)
87
+ filter_logical_model_extension_issues(filtered_issues, validator)
83
88
  add_messages_not_excluded(filtered_issues, error_prefix)
84
89
  end
85
90
 
@@ -123,7 +128,8 @@ module DaVinciCRDTestKit
123
128
  )
124
129
  end
125
130
 
126
- def validate_system_action_against_logical_model(action, response_index, request_body, action_index, ig_semver)
131
+ def validate_system_action_against_logical_model(action, response_index, request_body, action_index, ig_semver,
132
+ validator: :default)
127
133
  label = logical_model_entity_label(response_index, action_index, 'systemAction')
128
134
  unless action.is_a?(Hash)
129
135
  add_message('error', "#{label} is not a JSON object; skipping logical model validation.")
@@ -146,11 +152,12 @@ module DaVinciCRDTestKit
146
152
 
147
153
  validation_issues = []
148
154
  conforms_to_logical_model?({ 'systemActions' => [action] }, logical_model_url(profile_name),
149
- add_messages_to_runnable: false, validator_response_details: validation_issues)
155
+ add_messages_to_runnable: false, validator_response_details: validation_issues,
156
+ validator:)
150
157
 
151
158
  validation_issues
152
159
  .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
160
+ issue.message.match?(%r{CDSHooksResponse\.systemActions\[\d+\]\.extension: The extension definition http://hl7\.org/fhir/us/davinci-crd/StructureDefinition/CDSHookServiceResponseExtensionIfNoneExist\|2\.2\.1 defines the contexts of use as}) # rubocop:disable Layout/LineLength
154
161
  end
155
162
 
156
163
  if action_type == CardsIdentification::COVERAGE_INFORMATION_RESPONSE_TYPE
@@ -158,7 +165,8 @@ module DaVinciCRDTestKit
158
165
  end
159
166
  error_prefix = "#{label} (#{action_type || 'uncategorized'}): "
160
167
  filtered_issues = manually_check_action_specific_errors(action, validation_issues, action_type,
161
- request_body, error_prefix, ig_semver)
168
+ request_body, error_prefix, ig_semver, validator:)
169
+ filter_logical_model_extension_issues(filtered_issues, validator)
162
170
  add_messages_not_excluded(filtered_issues, error_prefix)
163
171
  end
164
172
 
@@ -253,7 +261,7 @@ module DaVinciCRDTestKit
253
261
 
254
262
  def add_messages_not_excluded(issues, error_prefix)
255
263
  issues.each do |issue|
256
- next if issue.filtered || logical_model_extension_issue?(issue)
264
+ next if issue.filtered
257
265
 
258
266
  add_message(issue.severity, "#{error_prefix}#{issue.message}")
259
267
  end
@@ -267,21 +275,17 @@ module DaVinciCRDTestKit
267
275
  # Validator Filtering and Manual Checks Depending on the Card Type
268
276
  # -------------------------------------------------------------------------
269
277
 
270
- def logical_model_extension_issue?(issue)
271
- issue.message.match(/\.extension: Unrecognized property/).present?
272
- end
273
-
274
278
  def manually_check_card_specific_errors(card, validation_issues, card_type, request_body, error_prefix,
275
- ig_semver)
279
+ ig_semver, validator: :default)
276
280
  case card_type
277
281
  when CardsIdentification::FORM_COMPLETION_RESPONSE_TYPE
278
- manually_check_form_completion_errors(card, validation_issues, error_prefix)
282
+ manually_check_form_completion_errors(card, validation_issues, error_prefix, validator:)
279
283
  when CardsIdentification::PROPOSE_ALTERNATIVE_REQUEST_RESPONSE_TYPE
280
284
  manually_check_propose_alternative_errors(card, validation_issues, request_body,
281
- error_prefix, ig_semver)
285
+ error_prefix, ig_semver, validator:)
282
286
  when CardsIdentification::ADDITIONAL_ORDERS_RESPONSE_TYPE
283
287
  manually_check_additional_orders_errors(card, validation_issues, request_body,
284
- error_prefix, ig_semver)
288
+ error_prefix, ig_semver, validator:)
285
289
  when CardsIdentification::LAUNCH_SMART_APP_RESPONSE_TYPE
286
290
  manually_check_launch_smart_app_errors(validation_issues)
287
291
  else
@@ -290,26 +294,26 @@ module DaVinciCRDTestKit
290
294
  end
291
295
 
292
296
  def manually_check_action_specific_errors(action, validation_issues, action_type, request_body,
293
- error_prefix, ig_semver)
297
+ error_prefix, ig_semver, validator: :default)
294
298
  case action_type
295
299
  when DaVinciCRDTestKit::CardsIdentification::COVERAGE_INFORMATION_RESPONSE_TYPE
296
300
  manually_check_coverage_information_errors(action, validation_issues, request_body,
297
- error_prefix, ig_semver)
301
+ error_prefix, ig_semver, validator:)
298
302
  when DaVinciCRDTestKit::CardsIdentification::CREATE_OR_UPDATE_COVERAGE_RESPONSE_TYPE
299
303
  manually_check_update_coverage_action_errors(action, validation_issues,
300
- error_prefix, ig_semver)
304
+ error_prefix, ig_semver, validator:)
301
305
  when DaVinciCRDTestKit::CardsIdentification::FORM_COMPLETION_RESPONSE_TYPE
302
306
  manually_check_form_completion_action_errors(action, validation_issues,
303
- error_prefix, ig_semver)
307
+ error_prefix, ig_semver, validator:)
304
308
  else
305
309
  validation_issues
306
310
  end
307
311
  end
308
312
 
309
- def manually_check_form_completion_errors(card, validation_issues, error_prefix)
313
+ def manually_check_form_completion_errors(card, validation_issues, error_prefix, validator: :default)
310
314
  validation_issues.reject do |issue|
311
315
  if issue.message.match?(/The type 'Questionnaire' is not valid - must be Task/)
312
- check_questionnaire_actions(card, issue.message, error_prefix)
316
+ check_questionnaire_actions(card, issue.message, error_prefix, validator:)
313
317
  true
314
318
  else
315
319
  false
@@ -317,7 +321,7 @@ module DaVinciCRDTestKit
317
321
  end
318
322
  end
319
323
 
320
- def check_questionnaire_actions(card, error_message, error_prefix)
324
+ def check_questionnaire_actions(card, error_message, error_prefix, validator: :default)
321
325
  extracted_indexes =
322
326
  error_message.match(/CDSHooksResponse\.cards\[0\]\.suggestions\[(\d+)\]\.actions\[(\d+)\]\.resource/)
323
327
  unless extracted_indexes
@@ -330,7 +334,7 @@ module DaVinciCRDTestKit
330
334
 
331
335
  message_prefix = "#{error_prefix} suggestion #{suggestion_index + 1}, action #{action_index + 1} - "
332
336
  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
337
+ resource_is_valid?(resource:, message_prefix:, validator:) # no questionnaire profile applied per CRD
334
338
 
335
339
  return if resource.id.present?
336
340
 
@@ -344,12 +348,13 @@ module DaVinciCRDTestKit
344
348
  end
345
349
 
346
350
  def manually_check_propose_alternative_errors(card, validation_issues, request_body,
347
- error_prefix, ig_semver)
351
+ error_prefix, ig_semver, validator: :default)
348
352
  no_resource_issues = manually_check_action_resources_for_order_profile_conformance(card,
349
353
  validation_issues,
350
354
  request_body,
351
355
  error_prefix,
352
- ig_semver)
356
+ ig_semver,
357
+ validator:)
353
358
 
354
359
  no_resource_issues.each do |issue|
355
360
  next unless issue.message.match?(/Constraint failed: crd-respar-1/)
@@ -368,23 +373,24 @@ module DaVinciCRDTestKit
368
373
  end
369
374
 
370
375
  def manually_check_additional_orders_errors(card, validation_issues, request_body,
371
- error_prefix, ig_semver)
376
+ error_prefix, ig_semver, validator: :default)
372
377
  manually_check_action_resources_for_order_profile_conformance(card,
373
378
  validation_issues,
374
379
  request_body,
375
380
  error_prefix,
376
- ig_semver)
381
+ ig_semver,
382
+ validator:)
377
383
  end
378
384
 
379
385
  def manually_check_action_resources_for_order_profile_conformance(card, validation_issues, request_body,
380
- error_prefix, ig_semver)
386
+ error_prefix, ig_semver, validator: :default)
381
387
  if card['suggestions'].present?
382
388
  card['suggestions'].each_with_index do |suggestion, suggestion_index|
383
389
  next unless suggestion['actions'].present?
384
390
 
385
391
  suggestion['actions'].each_with_index do |action, action_index|
386
392
  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)
393
+ check_action_target(action, request_body, action_error_prefix, ig_semver, validator:)
388
394
  end
389
395
  end
390
396
  end
@@ -392,36 +398,37 @@ module DaVinciCRDTestKit
392
398
  reject_resource_issues(validation_issues)
393
399
  end
394
400
 
395
- def check_action_target(action, request_body, error_prefix, ig_semver)
401
+ def check_action_target(action, request_body, error_prefix, ig_semver, validator: :default)
396
402
  local_reference?(action['resourceId'], error_prefix) if action['resourceId'].present?
397
403
  return unless action['resource'].present?
398
404
 
399
- check_resource_conformance_to_order_profile(action['resource'], request_body, error_prefix, ig_semver)
405
+ check_resource_conformance_to_order_profile(action['resource'], request_body, error_prefix, ig_semver, validator:)
400
406
  end
401
407
 
402
408
  def manually_check_coverage_information_errors(action, validation_issues, request_body,
403
- error_prefix, ig_semver)
409
+ error_prefix, ig_semver, validator: :default)
404
410
  if action['resource'].present?
405
411
  check_resource_conformance_to_order_or_encounter_profile(action['resource'], request_body,
406
- error_prefix, ig_semver)
412
+ error_prefix, ig_semver, validator:)
407
413
  end
408
414
  reject_resource_issues(validation_issues)
409
415
  end
410
416
 
411
417
  def manually_check_update_coverage_action_errors(action, validation_issues,
412
- error_prefix, ig_semver)
418
+ error_prefix, ig_semver, validator: :default)
413
419
  check_required_action_type(action, 'update', error_prefix, 'coverage update action response type')
414
420
  if action['resource'].present?
415
- check_resource_conformance_to_coverage_profile(action['resource'], error_prefix, ig_semver)
421
+ check_resource_conformance_to_coverage_profile(action['resource'], error_prefix, ig_semver, validator:)
416
422
  end
417
423
  reject_resource_issues(validation_issues)
418
424
  end
419
425
 
420
426
  def manually_check_form_completion_action_errors(action, validation_issues,
421
- error_prefix, ig_semver)
427
+ error_prefix, ig_semver, validator: :default)
422
428
  check_required_action_type(action, 'create', error_prefix, 'form completion action response type')
423
429
  if action['resource'].present?
424
- check_resource_conformance_to_questionnaire_task_profile(action['resource'], error_prefix, ig_semver)
430
+ check_resource_conformance_to_questionnaire_task_profile(action['resource'], error_prefix, ig_semver,
431
+ validator:)
425
432
  end
426
433
  reject_resource_issues(validation_issues)
427
434
  end
@@ -7,12 +7,15 @@ module DaVinciCRDTestKit
7
7
  ORDER_SIGN_TAG = 'order-sign'.freeze
8
8
  DISCOVERY_TAG = 'cds_discovery'.freeze
9
9
  ANY_HOOK_TAG = 'any_hook'.freeze
10
+ CROSS_HOOK_ANALYSIS_TAG = 'cross_hook'.freeze
10
11
  DATA_FETCH_TAG = 'data_fetch'.freeze
11
12
  PAYER_ORG_FETCH_TAG = 'payer_org'.freeze
12
13
  PARENT_LOCATION_FETCH_TAG = 'parent_location'.freeze
13
14
  HOOK_INSTANCE_TAG_PREFIX = 'hi_'.freeze
14
15
  HOOK_INSTANCE_DATA_FETCH_TAG_PREFIX = 'hi_data_fetch_'.freeze
15
16
  LONG_RUNNING_GROUP_TAG = 'long_running_request'.freeze
17
+ UNKNOWN_CONTENT_GROUP_TAG = 'unknown_content_request'.freeze
18
+ SELF_PAY_GROUP_TAG = 'self_pay_request'.freeze
16
19
  DUPLICATED_HOOK_INSTANCE_TAG = 'duplicate_hook_instance'.freeze
17
20
  COVERAGE_INFO_DISABLED_TAG = 'coverage-info-disabled'.freeze
18
21
  TECHNICAL_ISSUES_TAG = 'technical-issues'.freeze
@@ -1216,7 +1216,7 @@ hl7.fhir.us.davinci-crd_2.2.1,hook-35,https://hl7.org/fhir/us/davinci-crd/2.2.1/
1216
1216
  hl7.fhir.us.davinci-crd_2.2.1,hook-36,https://hl7.org/fhir/us/davinci-crd/2.2.1/en/hooks.html#ci-c-hook-36,"Where CRD Clients have an appropriate workflow and data capture mechanism, the order-select hook **MAY** be used in scenarios that do not involve creating a true order.",MAY,CRD Client,,true,,
1217
1217
  hl7.fhir.us.davinci-crd_2.2.1,hook-37,https://hl7.org/fhir/us/davinci-crd/2.2.1/en/hooks.html#ci-c-hook-37,The 'context' field in invocations of the order-sign hook **SHALL** conform to the logical model specified [here](https://hl7.org/fhir/us/davinci-crd/2.2.1/en/StructureDefinition-CRDOrderSignContext.html),SHALL,CRD Client,,true,,
1218
1218
  hl7.fhir.us.davinci-crd_2.2.1,hook-38,https://hl7.org/fhir/us/davinci-crd/2.2.1/en/hooks.html#ci-c-hook-38,CRD Servers **MAY** use the order-sign hook as a basis for associating a patient with a particular practitioner from a payer attribution perspective.,MAY,CRD Server,,false,,
1219
- hl7.fhir.us.davinci-crd_2.2.1,hook-39,https://hl7.org/fhir/us/davinci-crd/2.2.1/en/hooks.html#ci-c-hook-39,"CRD clients and servers **SHALL**, at minimum, support returning and processing the [Coverage Information](https://hl7.org/fhir/us/davinci-crd/2.2.1/en/StructureDefinition-ext-coverage-information.html) system action for all invocations of the order-select hook.",SHALL,"CRD Client, CRD Server",,false,,
1219
+ hl7.fhir.us.davinci-crd_2.2.1,hook-39,https://hl7.org/fhir/us/davinci-crd/2.2.1/en/hooks.html#ci-c-hook-39,"CRD clients and servers **SHALL**, at minimum, support returning and processing the [Coverage Information](https://hl7.org/fhir/us/davinci-crd/2.2.1/en/StructureDefinition-ext-coverage-information.html) system action for all invocations of the order-sign hook.",SHALL,"CRD Client, CRD Server",,false,,
1220
1220
  hl7.fhir.us.davinci-crd_2.2.1,impl-1,https://hl7.org/fhir/us/davinci-crd/2.2.1/en/implementation.html#ci-c-impl-1,Clients that suppress 'default presumption' coverage-information of messages **SHALL** mitigate the potential for misinterpretation in the event CRD is unavailable.,SHALL,CRD Client,,false,,
1221
1221
  hl7.fhir.us.davinci-crd_2.2.1,impl-2,https://hl7.org/fhir/us/davinci-crd/2.2.1/en/implementation.html#ci-c-impl-2,CRD servers **SHOULD** strive to achieve a minimum of 3-9s availability for their services and strive to provide at least some level of useful response to CRD clients even if some of their back-end systems are unavailable.,SHOULD,CRD Server,,false,,
1222
1222
  hl7.fhir.us.davinci-crd_2.2.1,impl-3,https://hl7.org/fhir/us/davinci-crd/2.2.1/en/implementation.html#ci-c-impl-3,"Even if implemented using multiple components, there is still a requirement that the exchanges between the provider HIT (including any ePA coordinators) and the payer HIT (including any ePA coordinators) **SHALL** replicate all of the defined exchanges between provider and payer.",SHALL,"CRD Client, CRD Server",,false,,
@@ -1311,7 +1311,11 @@ hl7.fhir.us.davinci-crd_2.2.1,resp-69,https://hl7.org/fhir/us/davinci-crd/2.2.1/
1311
1311
  hl7.fhir.us.davinci-crd_2.2.1,resp-70,https://hl7.org/fhir/us/davinci-crd/2.2.1/en/cards.html#ci-c-resp-70,"If returning a card rather than a system action, the ""Update Coverage Records"" response type **SHOULD NOT** be returned for hook types that are likely to be triggered by clinical users rather than administrative staff. Cards of this type would be appropriate for hooks such as encounter-start or appointment-book but would not be appropriate for order-select or order-sign.",SHOULD NOT,CRD Server,,true,,
1312
1312
  hl7.fhir.us.davinci-crd_2.2.1,resp-71,https://hl7.org/fhir/us/davinci-crd/2.2.1/en/cards.html#ci-c-resp-71,Adjust Coverage responses **SHALL** comply with the logical model profile found [here](https://hl7.org/fhir/us/davinci-crd/2.2.1/en/StructureDefinition-CRDHooksResponse-adjustCoverage.html).,SHALL,CRD Server,,false,,
1313
1313
  hl7.fhir.us.davinci-crd_2.2.1,resp-72,https://hl7.org/fhir/us/davinci-crd/2.2.1/en/cards.html#ci-c-resp-72,Launch SMART responses **SHALL** comply with the logical model profile found [here](https://hl7.org/fhir/us/davinci-crd/2.2.1/en/StructureDefinition-CRDHooksResponse-launchSMART.html).,SHALL,CRD Server,,false,,
1314
- hl7.fhir.us.davinci-crd_2.2.1,sec-1,https://hl7.org/fhir/us/davinci-crd/2.2.1/en/security.html#ci-c-sec-1,Implementers SHALL adhere to inherited security and privacy rules,SHALL,"CRD Client, CRD Server",,false,,
1314
+ hl7.fhir.us.davinci-crd_2.2.1,sec-1,https://hl7.org/fhir/us/davinci-crd/2.2.1/en/security.html#ci-c-sec-1,"Implementers SHALL adhere to any security and privacy rules defined by:
1315
+ - FHIR Core: [Security & Privacy Module](http://hl7.org/fhir/R4/secpriv-module.html), [Security Principles](http://hl7.org/fhir/R4/security.html) and [Implementer's Checklist](http://hl7.org/fhir/R4/safety.html)
1316
+ - HRex: [Privacy & Security](http://hl7.org/fhir/us/davinci-hrex/1.2.0/security.html)
1317
+ - CDS Hooks: [Security & Safety](http://cds-hooks.hl7.org/2026Jan/index.html#security-and-safety)
1318
+ - SMART on FHIR: [SMART App Launch](http://www.hl7.org/fhir/smart-app-launch)",SHALL,"CRD Client, CRD Server",,false,,
1315
1319
  hl7.fhir.us.davinci-crd_2.2.1,sec-2,https://hl7.org/fhir/us/davinci-crd/2.2.1/en/security.html#ci-c-sec-2,"As per the CDS Hooks specification, communications between CRD clients and CRD servers **SHALL** use TLS.",SHALL,"CRD Client, CRD Server",,false,,
1316
1320
  hl7.fhir.us.davinci-crd_2.2.1,sec-3,https://hl7.org/fhir/us/davinci-crd/2.2.1/en/security.html#ci-c-sec-3,CRD servers and CRD clients **SHOULD** enforce a minimum version and other TLS configuration requirements based on [HRex rules](http://hl7.org/fhir/us/davinci-hrex/1.2.0/security.html#exchange-security) for PHI exchange.,SHOULD,"CRD Client, CRD Server",,false,,
1317
1321
  hl7.fhir.us.davinci-crd_2.2.1,sec-4,https://hl7.org/fhir/us/davinci-crd/2.2.1/en/security.html#ci-c-sec-4,CRD clients that support the Launch SMART Application Response Type **SHALL** support running applications that adhere to the SMART on FHIR [confidential app](https://hl7.org/fhir/smart-app-launch/STU2.2/app-launch.html#support-for-public-and-confidential-apps) profile.,SHALL,CRD Client,,true,,