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
@@ -44,8 +44,16 @@ module DaVinciCRDTestKit
44
44
  message: %(
45
45
  **Long Running Hook Request Test**:
46
46
 
47
- Invoke any supported hook. This test will
48
- automatically continue once Inferno has received a request, paused
47
+ Invoke any supported hook on one of the two Inferno simulated
48
+ CRD servers discoverable at the following endpoints:
49
+
50
+ - Complete Prefetch: `#{discovery_url}`
51
+ - Subset Prefetch: `#{prefetch_subset_discovery_url}`
52
+
53
+ For Inferno to recognize these requests and associate them with this session,
54
+ the authentication JWT sent as a Bearer token in the Authorization header
55
+ must have `#{cds_jwt_iss}` as the `iss` claim in the JWT payload. This test
56
+ will automatically continue once Inferno has received a request, paused
49
57
  for #{long_running_pause_time.to_i} seconds, and returned a response.
50
58
 
51
59
  Users must have the option to continue
@@ -26,7 +26,7 @@ module DaVinciCRDTestKit
26
26
  output :attest_false_url
27
27
 
28
28
  run do
29
- long_running_requests = load_hook_requests
29
+ long_running_requests = load_interaction_group_requests
30
30
  skip_if long_running_requests.blank?, 'Long-running requests not demonstrated: ' \
31
31
  'no hook requests sent during the previous wait.'
32
32
 
@@ -0,0 +1,30 @@
1
+ require_relative '../../tagged_request_load_helper'
2
+
3
+ module DaVinciCRDTestKit
4
+ module V221
5
+ class ClientSelfPayNoRequestTest < Inferno::Test
6
+ include DaVinciCRDTestKit::TaggedRequestLoadHelper
7
+
8
+ id :crd_v221_client_self_pay_no_request
9
+ title 'Client does not invoke hooks during self-pay workflows'
10
+ description %(
11
+ The CRD IG [requires](https://hl7.org/fhir/us/davinci-crd/2.2.1/en/deviations.html#ci-c-dev-26)
12
+ that client systems only invoke hooks on payer services where the patient record indicates
13
+ active coverage with the payer associated with the service and where there is no recorded
14
+ indication the patient intends to bypass insurance coverage, i.e., the service or product
15
+ is not flagged as 'self-pay'. During this test, Inferno verifies that the client did not
16
+ make any hook requests when the tester performed a self-pay workflow during the
17
+ previous test.
18
+ )
19
+
20
+ verifies_requirements 'hl7.fhir.us.davinci-crd_2.2.1@dev-26'
21
+
22
+ run do
23
+ self_pay_requests = load_interaction_group_requests
24
+
25
+ assert self_pay_requests.blank?,
26
+ 'Inferno incorrectly received a hook request for a service marked as self-pay.'
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,55 @@
1
+ require_relative '../client_urls'
2
+
3
+ module DaVinciCRDTestKit
4
+ module V221
5
+ class ClientSelfPayWorkflowTest < Inferno::Test
6
+ include ClientURLs
7
+
8
+ id :crd_v221_client_self_pay_workflow
9
+ title 'Client performs a self-pay workflow'
10
+ description %(
11
+ During this test, the tester will perform a self-pay scenario in which a patient has indicated
12
+ that they will pay for a service instead of asking their insurance to cover it. Because there
13
+ is a recorded indication that the patient intends to bypass insurance coverage, the client
14
+ is required to not invoke any hooks that would send details of the service to the payer.
15
+ The scenario must include an action related to the service that would normally trigger a
16
+ hook request and Inferno will verify that no hook requests are received.
17
+ )
18
+
19
+ input :cds_jwt_iss,
20
+ title: 'CRD JWT Issuer',
21
+ description: %(
22
+ Value of the `iss` claim that must be present in the JWT used to authorize the client's hook
23
+ request sent as the Bearer token in the `Authorization` header.
24
+ Run or re-run the "Registration" group to set or change this value.
25
+ ),
26
+ locked: true
27
+ output :continuation_url
28
+
29
+ run do
30
+ identifier = cds_jwt_iss
31
+ continuation_url = "#{resume_pass_url}?token=#{identifier}"
32
+ output(continuation_url:)
33
+
34
+ wait(
35
+ identifier:,
36
+ message: %(
37
+ **Self-Pay Workflow Test**:
38
+
39
+ Perform a workflow that would normally trigger a hook request, such as one
40
+ performed during previous tests, but for a service or product where the patient
41
+ record contains an indication that the patient intends to self-pay. The client must
42
+ not send any hook requests to Inferno during the workflow.
43
+
44
+ By [clicking here](#{continuation_url}) I attest that a user completed a workflow
45
+ in the client system that would normally trigger a hook request for a service that
46
+ has been marked as self-pay.
47
+
48
+ If Inferno receives a hook request, this test will automatically continue
49
+ and the next test will fail.
50
+ )
51
+ )
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,99 @@
1
+ require_relative '../client_urls'
2
+ require_relative '../../../cross_suite/cards_identification'
3
+ require_relative '../../tagged_request_load_helper'
4
+
5
+ module DaVinciCRDTestKit
6
+ module V221
7
+ class ClientUnknownContentAttestationTest < Inferno::Test
8
+ include ClientURLs
9
+ include CardsIdentification
10
+ include DaVinciCRDTestKit::TaggedRequestLoadHelper
11
+
12
+ KNOWN_ACTION_ELEMENTS = ['type', 'description', 'resource', 'resourceId', 'extension'].freeze
13
+
14
+ id :crd_v221_client_unknown_content_attestation_test
15
+ title 'Client processes coverage information returned with unknown content'
16
+ description %(
17
+ The CRD IG [requires](https://hl7.org/fhir/us/davinci-crd/2.2.1/en/foundation.html#ci-c-found-33)
18
+ that client systems ignore unexpected elements within CDS Hooks structures when processing
19
+ instances. During this test, the tester will confirm that the unknown element and custom extension
20
+ included in the CDS section of the previous response did not prevent the client system from
21
+ processing the returned coverage information and making it available to the user.
22
+ )
23
+ attestation
24
+
25
+ verifies_requirements 'hl7.fhir.us.davinci-crd_2.2.1@found-33'
26
+
27
+ output :attest_true_url
28
+ output :attest_false_url
29
+
30
+ def response_bodies(hook_requests)
31
+ hook_requests.filter_map do |request|
32
+ body = JSON.parse(request.response_body)
33
+ body if body.is_a?(Hash)
34
+ rescue JSON::ParserError
35
+ nil
36
+ end
37
+ end
38
+
39
+ def unknown_action_elements(bodies)
40
+ bodies.flat_map do |body|
41
+ body['systemActions'].to_a.flat_map { |action| action.keys - KNOWN_ACTION_ELEMENTS }
42
+ end.uniq
43
+ end
44
+
45
+ def unknown_extensions(bodies)
46
+ bodies.flat_map { |body| body['extension'].is_a?(Hash) ? body['extension'].keys : [] }.uniq
47
+ end
48
+
49
+ def format_unknown_content(bodies)
50
+ [
51
+ format_unknown_names('System action element name', unknown_action_elements(bodies)),
52
+ format_unknown_names('Response extension name', unknown_extensions(bodies))
53
+ ].compact.join("\n")
54
+ end
55
+
56
+ def format_unknown_names(label, names)
57
+ return if names.blank?
58
+
59
+ "- #{label}: #{names.map { |name| "`#{name}`" }.join(', ')}"
60
+ end
61
+
62
+ run do
63
+ hook_requests = load_interaction_group_requests
64
+ skip_if hook_requests.blank?, 'Unknown response content not demonstrated: ' \
65
+ 'no hook requests sent during the previous wait.'
66
+
67
+ bodies = response_bodies(hook_requests)
68
+ skip_if bodies.none? { |body| coverage_info_system_action_response?(body) },
69
+ 'Unknown response content not demonstrated: ' \
70
+ 'no coverage information system action was returned.'
71
+
72
+ identifier = SecureRandom.hex(32)
73
+ attest_true_url = "#{resume_pass_url}?token=#{identifier}"
74
+ attest_false_url = "#{resume_fail_url}?token=#{identifier}"
75
+ output(attest_true_url:)
76
+ output(attest_false_url:)
77
+ wait(
78
+ identifier:,
79
+ message: <<~MESSAGE
80
+ **Unknown Response Content Attestation**:
81
+
82
+ Inferno returned coverage information along with the following content that is
83
+ not defined by CRD or CDS Hooks:
84
+
85
+ #{format_unknown_content(bodies)}
86
+
87
+ I attest that this unexpected content did not prevent the client system from
88
+ processing the coverage information returned in the same response and that the
89
+ coverage information was displayed or otherwise made available to the user:
90
+
91
+ [Click here](#{attest_true_url}) if the above statement is **true**.
92
+
93
+ [Click here](#{attest_false_url}) if the above statement is **false**.
94
+ MESSAGE
95
+ )
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,62 @@
1
+ require_relative '../client_urls'
2
+
3
+ module DaVinciCRDTestKit
4
+ module V221
5
+ class ClientUnknownContentReceiveRequestTest < Inferno::Test
6
+ include ClientURLs
7
+
8
+ id :crd_v221_client_unknown_content_receive_request
9
+ title 'Client invokes any hook'
10
+ description %(
11
+ During this test, Inferno will wait while the client makes a single hook request of any type.
12
+ Inferno will return a fixed [mocked response](https://github.com/inferno-framework/davinci-crd-test-kit/wiki/Controlling-Simulated-Responses#mocked-responses)
13
+ containing a coverage information system action along with content that the client is not
14
+ expected to recognize: an element added to the system action and a custom extension added to
15
+ the response, both with randomly generated names. This content is added at the CRD response
16
+ level rather than within the FHIR resources carried in the response, and testers cannot change
17
+ the response that is returned. The details of the request and its response do not matter for
18
+ the purposes of this test and they will not be evaluated, checked for conformance, or included
19
+ in cross-hook evaluations. The test will automatically continue after Inferno receives a hook
20
+ request and returns a response.
21
+ )
22
+ config options: { accepts_multiple_requests: true }
23
+
24
+ input :cds_jwt_iss,
25
+ title: 'CRD JWT Issuer',
26
+ description: %(
27
+ Value of the `iss` claim that must be present in the JWT used to authorize the client's hook
28
+ request sent as the Bearer token in the `Authorization` header.
29
+ Run or re-run the "Registration" group to set or change this value.
30
+ ),
31
+ locked: true
32
+
33
+ run do
34
+ identifier = cds_jwt_iss
35
+ wait(
36
+ identifier:,
37
+ message: %(
38
+ **Unknown Response Content Test**:
39
+
40
+ Invoke any supported hook on one of the two Inferno simulated
41
+ CRD servers discoverable at the following endpoints:
42
+
43
+ - Complete Prefetch: `#{discovery_url}`
44
+ - Subset Prefetch: `#{prefetch_subset_discovery_url}`
45
+
46
+ For Inferno to recognize these requests and associate them with this session,
47
+ the authentication JWT sent as a Bearer token in the Authorization header
48
+ must have `#{cds_jwt_iss}` as the `iss` claim in the JWT payload. The test
49
+ will automatically continue once Inferno has received a request and returned
50
+ a response.
51
+
52
+ The response will include coverage information alongside an element and a
53
+ custom extension whose names are not defined by CRD or CDS Hooks. Clients
54
+ must ignore the content they do not recognize and process the coverage
55
+ information as usual. Testers will be asked to attest that this was
56
+ demonstrated in the next test.
57
+ )
58
+ )
59
+ end
60
+ end
61
+ end
62
+ end
@@ -24,7 +24,7 @@ module DaVinciCRDTestKit
24
24
  'hl7.fhir.us.davinci-crd_2.2.1@hook-20', 'hl7.fhir.us.davinci-crd_2.2.1@hook-21'
25
25
 
26
26
  run do
27
- hook_requests = load_hook_requests
27
+ hook_requests = load_interaction_group_requests
28
28
 
29
29
  skip_if hook_requests.blank?, "No #{hook_name} hook requests received."
30
30
 
@@ -97,15 +97,23 @@ module DaVinciCRDTestKit
97
97
  request_index)
98
98
  end
99
99
 
100
+ check_payer_resource_conformance(payer_resource, request_index)
101
+ end
102
+
103
+ def check_payer_resource_conformance(payer_resource, request_index)
100
104
  validator_response_details = []
101
105
  resource_is_valid?(resource: payer_resource, profile_url: 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-organization|2.2.1',
102
106
  add_messages_to_runnable: false, validator_response_details:)
103
107
 
104
- validator_response_details.each { |issue| add_request_message(issue.severity, issue.message, request_index) }
108
+ validator_response_details.each do |issue|
109
+ next if issue.filtered
110
+
111
+ add_request_message(issue.severity, issue.message, request_index)
112
+ end
105
113
  end
106
114
 
107
115
  run do
108
- hook_requests = load_hook_requests
116
+ hook_requests = load_interaction_group_requests
109
117
 
110
118
  skip_if hook_requests.blank?, "No #{hook_name} hook requests received."
111
119
 
@@ -33,7 +33,7 @@ module DaVinciCRDTestKit
33
33
  end
34
34
 
35
35
  run do
36
- hook_requests = load_hook_requests
36
+ hook_requests = load_interaction_group_requests
37
37
 
38
38
  skip_if hook_requests.blank?, "No #{hook_name} hook requests received."
39
39
 
@@ -29,7 +29,7 @@ module DaVinciCRDTestKit
29
29
  verifies_requirements 'hl7.fhir.us.davinci-crd_2.2.1@found-21'
30
30
 
31
31
  run do
32
- hook_requests = load_hook_requests
32
+ hook_requests = load_interaction_group_requests
33
33
 
34
34
  skip_if hook_requests.blank?, "No #{hook_name} hook requests received."
35
35
 
@@ -89,7 +89,7 @@ module DaVinciCRDTestKit
89
89
  end
90
90
 
91
91
  run do
92
- hook_requests = load_hook_requests
92
+ hook_requests = load_interaction_group_requests
93
93
 
94
94
  skip_if hook_requests.blank?, "No #{hook_name} hook requests received."
95
95
 
@@ -34,7 +34,7 @@ module DaVinciCRDTestKit
34
34
  'hl7.fhir.us.davinci-crd_2.2.1@prof-10', 'hl7.fhir.us.davinci-crd_2.2.1@prof-11',
35
35
  'hl7.fhir.us.davinci-crd_2.2.1@prof-12'
36
36
  run do
37
- hook_requests = load_hook_requests
37
+ hook_requests = load_interaction_group_requests
38
38
 
39
39
  skip_if hook_requests.blank?, "No #{hook_name} hook requests received."
40
40
 
@@ -21,7 +21,7 @@ module DaVinciCRDTestKit
21
21
  'hl7.fhir.us.davinci-crd_2.2.1@dev-12'
22
22
 
23
23
  run do
24
- hook_requests = load_hook_requests
24
+ hook_requests = load_interaction_group_requests
25
25
 
26
26
  skip_if hook_requests.blank?, "No #{hook_name} hook requests received."
27
27
 
@@ -18,7 +18,7 @@ module DaVinciCRDTestKit
18
18
  'hl7.fhir.us.davinci-crd_2.2.1@sec-2'
19
19
 
20
20
  run do
21
- hook_requests = load_hook_requests
21
+ hook_requests = load_interaction_group_requests
22
22
 
23
23
  skip_if hook_requests.blank?, "No #{hook_name} hook requests received."
24
24
 
@@ -18,33 +18,65 @@ module DaVinciCRDTestKit
18
18
  )
19
19
  attestation
20
20
 
21
+ verifies_requirements 'hl7.fhir.us.davinci-crd_2.2.1@resp-49'
22
+
21
23
  def responded_card_types
22
- list_card_types_in_requests(requests)
24
+ @responded_card_types ||= list_card_types_in_requests(requests)
23
25
  end
24
26
 
25
- def format_responded_response_types
26
- responded_card_types
27
+ def format_responded_response_types(response_types = responded_card_types)
28
+ response_types
27
29
  .map do |response_type|
28
- response_type_string =
29
- response_type.split('_')
30
- .map(&:capitalize)
31
- .join(' ')
30
+ DaVinciCRDTestKit::CardsIdentification.humanize_response_type(response_type)
32
31
  .prepend('- ')
33
- .sub('Smart', 'SMART')
34
- .sub('Create Update', 'Create/Update')
35
- .sub('Companions Prerequisites', 'Companions/Prerequisites')
36
- .sub('Card', '(card)')
37
- .sub('Action', '(systemAction)')
38
- response_type_string
32
+ .sub(' Card', '')
33
+ .sub(' Action', '')
34
+ end.join("\n")
35
+ end
36
+
37
+ def format_responded_card_types
38
+ @format_responded_card_types ||=
39
+ format_responded_response_types(responded_card_types.select do |type|
40
+ type.end_with?('_card')
41
+ end)
42
+ end
43
+
44
+ def format_responded_system_action_types
45
+ @format_responded_system_action_types ||=
46
+ format_responded_response_types(responded_card_types.select do |type|
47
+ type.end_with?('_action')
48
+ end)
49
+ end
50
+
51
+ CARD_AND_SYSTEM_ACTION_DISPLAY_PREFIX = "\n\n By attesting true, ".freeze
52
+ CARD_AND_SYSTEM_ACTION_DISPLAY_CLAUSE_PREFIX = 'you confirm that the following '.freeze
53
+ CARD_AND_SYSTEM_ACTION_DISPLAY_CLAUSE_CONNECTOR = "\n\n Additionally, ".freeze
54
+ CARD_AND_SYSTEM_ACTION_DISPLAY_CARD_CLAUSE =
55
+ 'card types observed in Inferno\'s responses were displayed to users:'.freeze
56
+ CARD_AND_SYSTEM_ACTION_DISPLAY_SYSTEM_ACTION_CLAUSE =
57
+ 'system action types observed in Inferno\'s responses were displayed to users ' \
58
+ 'through standard card display, icons, flyovers, or another alternate mechanism:'.freeze
59
+
60
+ def card_and_system_action_details_display
61
+ if format_responded_card_types.present? && format_responded_system_action_types.present?
62
+ "#{CARD_AND_SYSTEM_ACTION_DISPLAY_PREFIX}#{CARD_AND_SYSTEM_ACTION_DISPLAY_CLAUSE_PREFIX}" \
63
+ "#{CARD_AND_SYSTEM_ACTION_DISPLAY_CARD_CLAUSE}\n#{format_responded_card_types}" \
64
+ "#{CARD_AND_SYSTEM_ACTION_DISPLAY_CLAUSE_CONNECTOR}#{CARD_AND_SYSTEM_ACTION_DISPLAY_CLAUSE_PREFIX}" \
65
+ "#{CARD_AND_SYSTEM_ACTION_DISPLAY_SYSTEM_ACTION_CLAUSE}\n#{format_responded_system_action_types}"
66
+ elsif format_responded_card_types.present?
67
+ "#{CARD_AND_SYSTEM_ACTION_DISPLAY_PREFIX}#{CARD_AND_SYSTEM_ACTION_DISPLAY_CLAUSE_PREFIX}" \
68
+ "#{CARD_AND_SYSTEM_ACTION_DISPLAY_CARD_CLAUSE}\n#{format_responded_card_types}"
69
+ else
70
+ "#{CARD_AND_SYSTEM_ACTION_DISPLAY_PREFIX}#{CARD_AND_SYSTEM_ACTION_DISPLAY_CLAUSE_PREFIX}" \
71
+ "#{CARD_AND_SYSTEM_ACTION_DISPLAY_SYSTEM_ACTION_CLAUSE}\n#{format_responded_system_action_types}"
39
72
  end
40
- .join("\n")
41
73
  end
42
74
 
43
75
  output :attest_true_url
44
76
  output :attest_false_url
45
77
 
46
78
  run do
47
- load_hook_requests
79
+ load_interaction_group_requests
48
80
  skip_if responded_card_types.blank?, 'No responses sent to the client.'
49
81
 
50
82
  identifier = SecureRandom.hex(32)
@@ -57,12 +89,12 @@ module DaVinciCRDTestKit
57
89
  message: <<~MESSAGE
58
90
  **Card Display Attestation**:
59
91
 
60
- I attest that the following CRD response types returned by Inferno's simulated
61
- CRD servers were processed by the client system and displayed or otherwise made
62
- available to users of the client system in an appropriate way that allows for
63
- consideration and action if warranted:
92
+ I attest that the simulated CRD responses returned by Inferno during this
93
+ group were successfully processed by the client system and that they were
94
+ made available to users of the system in an appropriate way that allowed for
95
+ user review and action if warranted.
64
96
 
65
- #{format_responded_response_types}
97
+ #{card_and_system_action_details_display}
66
98
 
67
99
  [Click here](#{attest_true_url}) if the above statement is **true**.
68
100
 
@@ -18,7 +18,7 @@ module DaVinciCRDTestKit
18
18
  DESCRIPTION
19
19
 
20
20
  run do
21
- hook_requests = load_hook_requests
21
+ hook_requests = load_interaction_group_requests
22
22
  skip_if hook_requests.blank?, "No #{hook_name} hook requests received."
23
23
 
24
24
  sorted_cards = sorted_cards_from_requests(hook_requests)
@@ -14,6 +14,8 @@ module DaVinciCRDTestKit
14
14
  During this test, Inferno will verify that each hook response built by Inferno's simulated CRD servers
15
15
  and returned to the client conforms to CDS Hooks and CRD requirements. These responses must be conformant
16
16
  for the client to demonstrate its ability to accept and process valid CRD responses.
17
+ To prevent the injection of non-standard information into Inferno responses, these validations will
18
+ strictly check that all extensions are allowed at the locations they appear by the CRD specification.
17
19
 
18
20
  If this test fails when the tester provided a custom response template, adjust the
19
21
  provided template and the submitted requests so that the response built by Inferno
@@ -29,7 +31,7 @@ module DaVinciCRDTestKit
29
31
  simulation_verification
30
32
 
31
33
  verifies_requirements 'cds-hooks_3.0.0-ballot@3', 'cds-hooks_3.0.0-ballot@222', 'cds-hooks_3.0.0-ballot@223',
32
- 'cds-hooks_3.0.0-ballot@224'
34
+ 'cds-hooks_3.0.0-ballot@224', 'hl7.fhir.us.davinci-crd_2.2.1@conf-10'
33
35
 
34
36
  input :custom_response_template, optional: true
35
37
 
@@ -45,7 +47,7 @@ module DaVinciCRDTestKit
45
47
  end
46
48
 
47
49
  run do
48
- load_hook_requests
50
+ load_interaction_group_requests
49
51
 
50
52
  skip_if request.blank?, "No #{response_label.downcase}s to verify."
51
53
 
@@ -62,7 +64,8 @@ module DaVinciCRDTestKit
62
64
  response_hash['systemActions'],
63
65
  request_hash,
64
66
  index,
65
- '2.2.1')
67
+ '2.2.1',
68
+ validator: :no_custom_extensions)
66
69
  rescue JSON::ParserError
67
70
  next
68
71
  end
@@ -35,6 +35,15 @@ module DaVinciCRDTestKit
35
35
  NutritionOrder ServiceRequest VisionPrescription
36
36
  ].freeze
37
37
 
38
+ def self.humanize_response_type(response_type)
39
+ response_type.split('_')
40
+ .map(&:capitalize)
41
+ .join(' ')
42
+ .sub('Smart', 'SMART')
43
+ .sub('Create Update', 'Create/Update')
44
+ .sub('Companions Prerequisites', 'Companions/Prerequisites')
45
+ end
46
+
38
47
  def identify_card_type(card) # rubocop:disable Metrics/CyclomaticComplexity
39
48
  return nil if card['type'].present? # action, not a card
40
49
  return ADDITIONAL_ORDERS_RESPONSE_TYPE if additional_orders_response_type?(card)
@@ -13,27 +13,44 @@ module DaVinciCRDTestKit
13
13
  end
14
14
  end
15
15
 
16
+ def filter_logical_model_extension_issues(issues, validator)
17
+ return if validator == :no_custom_extensions
18
+
19
+ issues.each { |issue| issue.filtered = issue.filtered || logical_model_extension_issue?(issue) }
20
+ end
21
+
22
+ # Logical models validation doesn't currently respect the `"extensions": ["any"]`
23
+ # validator flag
24
+ def logical_model_extension_issue?(issue)
25
+ issue.message.match(/\.extension: Unrecognized property/).present?
26
+ end
27
+
16
28
  # -------------------------------------------------------------------------
17
29
  # Check resource conformance outside the logical models
18
30
  # -------------------------------------------------------------------------
19
31
 
20
- def check_resource_conformance_to_coverage_profile(resource_hash, error_prefix, ig_semver)
21
- check_resource_type_and_validate(resource_hash, error_prefix, ig_semver, FHIR::Coverage)
32
+ def check_resource_conformance_to_coverage_profile(resource_hash, error_prefix, ig_semver, validator: :default)
33
+ check_resource_type_and_validate(resource_hash, error_prefix, ig_semver, FHIR::Coverage, validator:)
22
34
  end
23
35
 
24
- def check_resource_conformance_to_questionnaire_task_profile(resource_hash, error_prefix, ig_semver)
25
- check_resource_type_and_validate(resource_hash, error_prefix, ig_semver, FHIR::Task)
36
+ def check_resource_conformance_to_questionnaire_task_profile(resource_hash, error_prefix, ig_semver,
37
+ validator: :default)
38
+ check_resource_type_and_validate(resource_hash, error_prefix, ig_semver, FHIR::Task, validator:)
26
39
  end
27
40
 
28
- def check_resource_conformance_to_order_or_encounter_profile(resource_hash, request_body, error_prefix, ig_semver)
41
+ def check_resource_conformance_to_order_or_encounter_profile(resource_hash, request_body, error_prefix, ig_semver,
42
+ validator: :default)
29
43
  check_order_like_resource_conformance(resource_hash, request_body, error_prefix, ig_semver,
44
+ validator:,
30
45
  allowed_types: ProfilesAndResourceTypes::ORDER_OR_ENCOUNTER_RESOURCE_CLASSES,
31
46
  disallowed_message: 'is not allowed as a target ' \
32
47
  'for a coverage-information action')
33
48
  end
34
49
 
35
- def check_resource_conformance_to_order_profile(resource_hash, request_body, error_prefix, ig_semver)
50
+ def check_resource_conformance_to_order_profile(resource_hash, request_body, error_prefix, ig_semver,
51
+ validator: :default)
36
52
  check_order_like_resource_conformance(resource_hash, request_body, error_prefix, ig_semver,
53
+ validator:,
37
54
  allowed_types: ProfilesAndResourceTypes::ORDER_RESOURCE_CLASSES,
38
55
  disallowed_message: 'is not allowed for CRD orders')
39
56
  end
@@ -51,11 +68,11 @@ module DaVinciCRDTestKit
51
68
  yield resource
52
69
  end
53
70
 
54
- def check_resource_type_and_validate(resource_hash, error_prefix, ig_semver, expected_class)
71
+ def check_resource_type_and_validate(resource_hash, error_prefix, ig_semver, expected_class, validator: :default)
55
72
  parse_action_resource(resource_hash, error_prefix) do |resource|
56
73
  if resource.is_a?(expected_class)
57
74
  resource_is_valid?(resource:, profile_url: structure_definition_map(ig_semver)[resource.resourceType],
58
- message_prefix: error_prefix)
75
+ message_prefix: error_prefix, validator:)
59
76
  else
60
77
  add_message('error', "#{error_prefix}found resource type '#{resource.resourceType}' " \
61
78
  "expected '#{expected_class.name.split('::').last}'.")
@@ -64,14 +81,14 @@ module DaVinciCRDTestKit
64
81
  end
65
82
 
66
83
  def check_order_like_resource_conformance(resource_hash, request_body, error_prefix, ig_semver,
67
- allowed_types:, disallowed_message:)
84
+ allowed_types:, disallowed_message:, validator: :default)
68
85
  parse_action_resource(resource_hash, error_prefix) do |resource|
69
86
  case resource
70
87
  when FHIR::Appointment
71
- check_appointment_conformance(resource, request_body, error_prefix, ig_semver)
88
+ check_appointment_conformance(resource, request_body, error_prefix, ig_semver, validator:)
72
89
  when *allowed_types
73
90
  resource_is_valid?(resource:, profile_url: structure_definition_map(ig_semver)[resource.resourceType],
74
- message_prefix: error_prefix)
91
+ message_prefix: error_prefix, validator:)
75
92
  else
76
93
  add_message('error', "#{error_prefix}resource type '#{resource.resourceType}' #{disallowed_message}.")
77
94
  end
@@ -82,7 +99,7 @@ module DaVinciCRDTestKit
82
99
  # Appointment conformance (requires extra help to decide profile and check profile-based slicing)
83
100
  # -------------------------------------------------------------------------
84
101
 
85
- def check_appointment_conformance(appointment, request_body, error_prefix, ig_semver)
102
+ def check_appointment_conformance(appointment, request_body, error_prefix, ig_semver, validator: :default)
86
103
  target_appointment_profile =
87
104
  if appointment.basedOn.present?
88
105
  'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-appointment-with-order'
@@ -92,7 +109,7 @@ module DaVinciCRDTestKit
92
109
 
93
110
  validation_issues = []
94
111
  resource_is_valid?(resource: appointment, profile_url: "#{target_appointment_profile}|#{ig_semver}",
95
- add_messages_to_runnable: false, validator_response_details: validation_issues)
112
+ add_messages_to_runnable: false, validator_response_details: validation_issues, validator:)
96
113
 
97
114
  manually_check_appointment_validation_errors(validation_issues, appointment, request_body)
98
115
  .each do |issue|
@@ -34,6 +34,8 @@ module DaVinciCRDTestKit
34
34
  profile_url: target_crd_profile,
35
35
  validator_response_details: validation_details, add_messages_to_runnable: false)
36
36
  validation_details.each do |issue|
37
+ next if issue.filtered
38
+
37
39
  prefix = prefetch_profile_error_prefix(request_index, bundle_entry_index)
38
40
  add_message(issue.severity, "#{prefix}#{issue.message}")
39
41
  end