davinci_crd_test_kit 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +201 -0
- data/lib/davinci_crd_test_kit/card_responses/companions_prerequisites.json +58 -0
- data/lib/davinci_crd_test_kit/card_responses/create_update_coverage_information.json +20 -0
- data/lib/davinci_crd_test_kit/card_responses/external_reference.json +21 -0
- data/lib/davinci_crd_test_kit/card_responses/instructions.json +14 -0
- data/lib/davinci_crd_test_kit/card_responses/launch_smart_app.json +21 -0
- data/lib/davinci_crd_test_kit/card_responses/propose_alternate_request.json +71 -0
- data/lib/davinci_crd_test_kit/card_responses/request_form_completion.json +227 -0
- data/lib/davinci_crd_test_kit/cards_validation.rb +234 -0
- data/lib/davinci_crd_test_kit/client_fhir_api_group.rb +762 -0
- data/lib/davinci_crd_test_kit/client_hook_request_validation.rb +15 -0
- data/lib/davinci_crd_test_kit/client_hooks_group.rb +706 -0
- data/lib/davinci_crd_test_kit/client_tests/appointment_book_receive_request_test.rb +71 -0
- data/lib/davinci_crd_test_kit/client_tests/client_display_cards_attest.rb +48 -0
- data/lib/davinci_crd_test_kit/client_tests/client_fhir_api_create_test.rb +40 -0
- data/lib/davinci_crd_test_kit/client_tests/client_fhir_api_read_test.rb +39 -0
- data/lib/davinci_crd_test_kit/client_tests/client_fhir_api_search_test.rb +232 -0
- data/lib/davinci_crd_test_kit/client_tests/client_fhir_api_update_test.rb +40 -0
- data/lib/davinci_crd_test_kit/client_tests/client_fhir_api_validation_test.rb +60 -0
- data/lib/davinci_crd_test_kit/client_tests/decode_auth_token_test.rb +40 -0
- data/lib/davinci_crd_test_kit/client_tests/encounter_discharge_receive_request_test.rb +68 -0
- data/lib/davinci_crd_test_kit/client_tests/encounter_start_receive_request_test.rb +68 -0
- data/lib/davinci_crd_test_kit/client_tests/hook_request_optional_fields_test.rb +41 -0
- data/lib/davinci_crd_test_kit/client_tests/hook_request_required_fields_test.rb +40 -0
- data/lib/davinci_crd_test_kit/client_tests/hook_request_valid_context_test.rb +63 -0
- data/lib/davinci_crd_test_kit/client_tests/hook_request_valid_prefetch_test.rb +151 -0
- data/lib/davinci_crd_test_kit/client_tests/order_dispatch_receive_request_test.rb +79 -0
- data/lib/davinci_crd_test_kit/client_tests/order_select_receive_request_test.rb +76 -0
- data/lib/davinci_crd_test_kit/client_tests/order_sign_receive_request_test.rb +79 -0
- data/lib/davinci_crd_test_kit/client_tests/retrieve_jwks_test.rb +65 -0
- data/lib/davinci_crd_test_kit/client_tests/token_header_test.rb +34 -0
- data/lib/davinci_crd_test_kit/client_tests/token_payload_test.rb +61 -0
- data/lib/davinci_crd_test_kit/crd_client_suite.rb +156 -0
- data/lib/davinci_crd_test_kit/crd_jwks.json +59 -0
- data/lib/davinci_crd_test_kit/crd_options.rb +9 -0
- data/lib/davinci_crd_test_kit/crd_server_suite.rb +115 -0
- data/lib/davinci_crd_test_kit/ext/inferno_core/runnable.rb +22 -0
- data/lib/davinci_crd_test_kit/hook_request_field_validation.rb +410 -0
- data/lib/davinci_crd_test_kit/jwks.rb +25 -0
- data/lib/davinci_crd_test_kit/jwt_helper.rb +74 -0
- data/lib/davinci_crd_test_kit/mock_service_response.rb +421 -0
- data/lib/davinci_crd_test_kit/routes/cds-services.json +74 -0
- data/lib/davinci_crd_test_kit/routes/cds_services_discovery_handler.rb +18 -0
- data/lib/davinci_crd_test_kit/routes/hook_request_endpoint.rb +93 -0
- data/lib/davinci_crd_test_kit/routes/jwk_set_endpoint_handler.rb +15 -0
- data/lib/davinci_crd_test_kit/server_appointment_book_group.rb +173 -0
- data/lib/davinci_crd_test_kit/server_discovery_group.rb +59 -0
- data/lib/davinci_crd_test_kit/server_encounter_discharge_group.rb +144 -0
- data/lib/davinci_crd_test_kit/server_encounter_start_group.rb +144 -0
- data/lib/davinci_crd_test_kit/server_hook_request_validation.rb +15 -0
- data/lib/davinci_crd_test_kit/server_hooks_group.rb +69 -0
- data/lib/davinci_crd_test_kit/server_order_dispatch_group.rb +173 -0
- data/lib/davinci_crd_test_kit/server_order_select_group.rb +169 -0
- data/lib/davinci_crd_test_kit/server_order_sign_group.rb +198 -0
- data/lib/davinci_crd_test_kit/server_required_card_response_validation_group.rb +23 -0
- data/lib/davinci_crd_test_kit/server_tests/additional_orders_validation_test.rb +70 -0
- data/lib/davinci_crd_test_kit/server_tests/card_optional_fields_validation_test.rb +47 -0
- data/lib/davinci_crd_test_kit/server_tests/coverage_information_system_action_across_hooks_validation_test.rb +32 -0
- data/lib/davinci_crd_test_kit/server_tests/coverage_information_system_action_received_test.rb +58 -0
- data/lib/davinci_crd_test_kit/server_tests/coverage_information_system_action_validation_test.rb +121 -0
- data/lib/davinci_crd_test_kit/server_tests/create_or_update_coverage_info_response_validation_test.rb +72 -0
- data/lib/davinci_crd_test_kit/server_tests/discovery_endpoint_test.rb +88 -0
- data/lib/davinci_crd_test_kit/server_tests/discovery_services_validation_test.rb +65 -0
- data/lib/davinci_crd_test_kit/server_tests/external_reference_card_across_hooks_validation_test.rb +28 -0
- data/lib/davinci_crd_test_kit/server_tests/external_reference_card_validation_test.rb +36 -0
- data/lib/davinci_crd_test_kit/server_tests/form_completion_response_validation_test.rb +79 -0
- data/lib/davinci_crd_test_kit/server_tests/instructions_card_received_across_hooks_test.rb +25 -0
- data/lib/davinci_crd_test_kit/server_tests/instructions_card_received_test.rb +28 -0
- data/lib/davinci_crd_test_kit/server_tests/launch_smart_app_card_validation_test.rb +38 -0
- data/lib/davinci_crd_test_kit/server_tests/propose_alternate_request_card_validation_test.rb +65 -0
- data/lib/davinci_crd_test_kit/server_tests/service_call_test.rb +86 -0
- data/lib/davinci_crd_test_kit/server_tests/service_request_context_validation_test.rb +30 -0
- data/lib/davinci_crd_test_kit/server_tests/service_request_optional_fields_validation_test.rb +41 -0
- data/lib/davinci_crd_test_kit/server_tests/service_request_required_fields_validation_test.rb +43 -0
- data/lib/davinci_crd_test_kit/server_tests/service_response_validation_test.rb +82 -0
- data/lib/davinci_crd_test_kit/suggestion_actions_validation.rb +123 -0
- data/lib/davinci_crd_test_kit/tags.rb +8 -0
- data/lib/davinci_crd_test_kit/test_helper.rb +23 -0
- data/lib/davinci_crd_test_kit/urls.rb +52 -0
- data/lib/davinci_crd_test_kit/version.rb +3 -0
- data/lib/davinci_crd_test_kit.rb +2 -0
- metadata +170 -0
@@ -0,0 +1,32 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
|
3
|
+
module DaVinciCRDTestKit
|
4
|
+
class CoverageInformationSystemActionAcrossHooksValidationTest < Inferno::Test
|
5
|
+
include DaVinciCRDTestKit::TestHelper
|
6
|
+
|
7
|
+
title 'Valid Coverage Information system actions received across all hooks'
|
8
|
+
id :crd_coverage_info_system_action_across_hooks_validation
|
9
|
+
description %(
|
10
|
+
This test verifies the presence of valid [Coverage Information](https://hl7.org/fhir/us/davinci-crd/STU2/cards.html#coverage-information)
|
11
|
+
system action returned by CRD services across all hooks invoked. It verifies the following for each action:
|
12
|
+
- The action type is `update`.
|
13
|
+
- The resource within the action conforms its respective FHIR profile.
|
14
|
+
|
15
|
+
Additionally, the test examines the `coverage-info` extensions within the resource to ensure that:
|
16
|
+
- Entries referencing differing coverage have distinct `coverage-assertion-ids` and `satisfied-pa-ids`
|
17
|
+
(if present).
|
18
|
+
- Entries referencing the same coverage have the same `coverage-assertion-ids` and `satisfied-pa-ids`
|
19
|
+
(if present).
|
20
|
+
|
21
|
+
The test will be skipped if no valid Coverage Information system actions are returned across all hooks.
|
22
|
+
)
|
23
|
+
|
24
|
+
run do
|
25
|
+
verify_at_least_one_test_passes(
|
26
|
+
self.class.parent.parent.groups,
|
27
|
+
'crd_coverage_info_system_action_validation',
|
28
|
+
'None of the hooks invoked returned valid Coverage Info system actions.'
|
29
|
+
)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/davinci_crd_test_kit/server_tests/coverage_information_system_action_received_test.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
module DaVinciCRDTestKit
|
3
|
+
class CoverageInformationSystemActionReceivedTest < Inferno::Test
|
4
|
+
include DaVinciCRDTestKit::TestHelper
|
5
|
+
|
6
|
+
title 'Coverage Information system action was received'
|
7
|
+
id :crd_coverage_info_system_action_received
|
8
|
+
description %(
|
9
|
+
This test validates that a [Coverage Information](https://hl7.org/fhir/us/davinci-crd/STU2/cards.html#coverage-information)
|
10
|
+
system action was returned. It does so by:
|
11
|
+
- First checking for the presence of actions with a `resource` element of the following types:
|
12
|
+
- For `appointment-book`: Appointment
|
13
|
+
- For `order-sign` or `order-dispatch`: DeviceRequest, MedicationRequest, NutritionOrder,
|
14
|
+
ServiceRequest, or VisionPrescription
|
15
|
+
- Then, among the target actions, checking if their resource has the [coverage-information extension](http://hl7.org/fhir/us/davinci-crd/StructureDefinition/ext-coverage-information).
|
16
|
+
)
|
17
|
+
|
18
|
+
input :valid_system_actions
|
19
|
+
output :coverage_info
|
20
|
+
|
21
|
+
def hook_name
|
22
|
+
config.options[:hook_name]
|
23
|
+
end
|
24
|
+
|
25
|
+
def resources_by_hook
|
26
|
+
shared_resources = [
|
27
|
+
'DeviceRequest', 'MedicationRequest', 'NutritionOrder',
|
28
|
+
'ServiceRequest', 'VisionPrescription'
|
29
|
+
]
|
30
|
+
{
|
31
|
+
'appointment-book' => ['Appointment'],
|
32
|
+
'order-sign' => shared_resources,
|
33
|
+
'order-dispatch' => shared_resources
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
run do
|
38
|
+
parsed_actions = parse_json(valid_system_actions)
|
39
|
+
target_resources = resources_by_hook[hook_name]
|
40
|
+
|
41
|
+
target_actions = parsed_actions.select do |action|
|
42
|
+
resource = FHIR.from_contents(action['resource'].to_json)
|
43
|
+
target_resources.include?(resource&.resourceType)
|
44
|
+
end
|
45
|
+
|
46
|
+
coverage_info_system_actions = target_actions.select do |action|
|
47
|
+
resource = FHIR.from_contents(action['resource'].to_json)
|
48
|
+
coverage_info_ext_url = 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/ext-coverage-information'
|
49
|
+
resource.extension.any? { |extension| extension.url == coverage_info_ext_url }
|
50
|
+
end
|
51
|
+
|
52
|
+
assert coverage_info_system_actions.present?,
|
53
|
+
"Coverage Information system action was not returned in the #{hook_name} hook response."
|
54
|
+
|
55
|
+
output coverage_info: coverage_info_system_actions.to_json
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/davinci_crd_test_kit/server_tests/coverage_information_system_action_validation_test.rb
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
require_relative '../server_hook_request_validation'
|
2
|
+
require_relative '../test_helper'
|
3
|
+
|
4
|
+
module DaVinciCRDTestKit
|
5
|
+
class CoverageInformationSystemActionValidationTest < Inferno::Test
|
6
|
+
include DaVinciCRDTestKit::ServerHookRequestValidation
|
7
|
+
include DaVinciCRDTestKit::TestHelper
|
8
|
+
|
9
|
+
title 'All Coverage Information system actions received are valid'
|
10
|
+
id :crd_coverage_info_system_action_validation
|
11
|
+
description %(
|
12
|
+
This test validates all [Coverage Information](https://hl7.org/fhir/us/davinci-crd/STU2/cards.html#coverage-information)
|
13
|
+
system actions received. It verifies the following for each action:
|
14
|
+
- The action type is `update`.
|
15
|
+
- The resource within the action conforms its respective FHIR profile.
|
16
|
+
|
17
|
+
Additionally, the test examines the `coverage-info` extensions within the resource to ensure that:
|
18
|
+
- Entries referencing differing coverage have distinct `coverage-assertion-ids` and `satisfied-pa-ids`
|
19
|
+
(if present).
|
20
|
+
- Entries referencing the same coverage have the same `coverage-assertion-ids` and `satisfied-pa-ids`
|
21
|
+
(if present).
|
22
|
+
)
|
23
|
+
input :coverage_info
|
24
|
+
|
25
|
+
def hook_name
|
26
|
+
config.options[:hook_name]
|
27
|
+
end
|
28
|
+
|
29
|
+
def find_extension_value(extension, url, *properties)
|
30
|
+
found_extension = extension.extension.find { |ext| ext.url == url }
|
31
|
+
return nil unless found_extension
|
32
|
+
|
33
|
+
properties.reduce(found_extension) do |current, prop|
|
34
|
+
return current unless current.respond_to?(prop)
|
35
|
+
|
36
|
+
current.send(prop)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def extract_and_group_coverage_info(resource)
|
41
|
+
resource.extension.each_with_object({}) do |extension, grouped_extensions|
|
42
|
+
next unless extension.url == 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/ext-coverage-information'
|
43
|
+
|
44
|
+
coverage_key = find_extension_value(extension, 'coverage', 'valueReference', 'reference')
|
45
|
+
grouped_extensions[coverage_key] ||= []
|
46
|
+
grouped_extensions[coverage_key] << extension
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# For the same coverage, ensure coverage-assertion-ids and satisfied-pa-ids are the same.
|
51
|
+
# For different coverages, ensure coverage-assertion-ids and satisfied-pa-ids are distinct.
|
52
|
+
def multiple_extensions_conformance_check(grouped_coverage_info, resource)
|
53
|
+
resource_ref = "#{resource.resourceType}/#{resource.id}"
|
54
|
+
assertion_ids_across_coverages = Set.new
|
55
|
+
pa_ids_across_coverages = Set.new
|
56
|
+
|
57
|
+
grouped_coverage_info.each do |coverage, extensions|
|
58
|
+
coverage_assertion_ids = collect_extensions_id(extensions, 'coverage-assertion-id', 'valueString').uniq
|
59
|
+
satisfied_pa_ids = collect_extensions_id(extensions, 'satisfied-pa-id', 'valueString').uniq.compact
|
60
|
+
assert coverage_assertion_ids.length == 1,
|
61
|
+
same_coverage_conformance_error_msg(resource_ref, coverage, 'coverage-assertion-ids')
|
62
|
+
|
63
|
+
assert satisfied_pa_ids.length <= 1,
|
64
|
+
same_coverage_conformance_error_msg(resource_ref, coverage, 'satisfied-pa-ids')
|
65
|
+
|
66
|
+
assertion_id = coverage_assertion_ids.first
|
67
|
+
assert !assertion_ids_across_coverages.include?(assertion_id),
|
68
|
+
different_coverage_conformance_error_msg(resource_ref, 'coverage-assertion-ids')
|
69
|
+
assertion_ids_across_coverages.add(assertion_id)
|
70
|
+
pa_id = satisfied_pa_ids.first
|
71
|
+
next unless pa_id
|
72
|
+
|
73
|
+
assert !pa_ids_across_coverages.include?(pa_id),
|
74
|
+
different_coverage_conformance_error_msg(resource_ref, 'satisfied-pa-ids')
|
75
|
+
pa_ids_across_coverages.add(pa_id)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def collect_extensions_id(extensions, url, *properties)
|
80
|
+
extensions.map do |extension|
|
81
|
+
find_extension_value(extension, url, *properties)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def same_coverage_conformance_error_msg(resource_ref, coverage, id_name)
|
86
|
+
"#{resource_ref}: extension has multiple repetitions of coverage `#{coverage}` with different #{id_name}."
|
87
|
+
end
|
88
|
+
|
89
|
+
def different_coverage_conformance_error_msg(resource_ref, id_name)
|
90
|
+
"#{resource_ref}: extensions referencing differing coverage SHALL have distinct #{id_name}."
|
91
|
+
end
|
92
|
+
|
93
|
+
def coverage_info_system_action_check(coverage_info_system_action)
|
94
|
+
type = coverage_info_system_action['type']
|
95
|
+
assert type, '`type` field is missing.'
|
96
|
+
assert type == 'update', "`type` must be `update`, but was `#{type}`"
|
97
|
+
|
98
|
+
resource = FHIR.from_contents(coverage_info_system_action['resource'].to_json)
|
99
|
+
profile_url = structure_definition_map[resource.resourceType]
|
100
|
+
assert_valid_resource(resource:, profile_url:)
|
101
|
+
|
102
|
+
grouped_coverage_info = extract_and_group_coverage_info(resource)
|
103
|
+
multiple_extensions_conformance_check(grouped_coverage_info, resource)
|
104
|
+
end
|
105
|
+
|
106
|
+
run do
|
107
|
+
parsed_coverage_info = parse_json(coverage_info)
|
108
|
+
error_messages = []
|
109
|
+
parsed_coverage_info.each do |action|
|
110
|
+
coverage_info_system_action_check(action)
|
111
|
+
rescue Inferno::Exceptions::AssertionException => e
|
112
|
+
error_messages << "Coverage Info system action `#{action}`: #{e.message}"
|
113
|
+
end
|
114
|
+
|
115
|
+
error_messages.each do |msg|
|
116
|
+
messages << { type: 'error', message: msg }
|
117
|
+
end
|
118
|
+
assert error_messages.empty?, 'Some Coverage Info system actions are not valid.'
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
require_relative '../suggestion_actions_validation'
|
3
|
+
|
4
|
+
module DaVinciCRDTestKit
|
5
|
+
class CreateOrUpdateCoverageInfoResponseValidationTest < Inferno::Test
|
6
|
+
include DaVinciCRDTestKit::TestHelper
|
7
|
+
include DaVinciCRDTestKit::SuggestionActionsValidation
|
8
|
+
|
9
|
+
title 'Valid Create or Update Coverage Information cards or system actions received'
|
10
|
+
id :crd_create_or_update_coverage_info_response_validation
|
11
|
+
description %(
|
12
|
+
This test validates the Create or Update Coverage Information cards or system actions received from the
|
13
|
+
CRD service, as per the specifications outlined in the [Da Vinci CRD Implementation Guide](https://hl7.org/fhir/us/davinci-crd/STU2/cards.html#create-or-update-coverage-information).
|
14
|
+
|
15
|
+
- **Checking for Presence:**
|
16
|
+
The test first checks if any Create or Update Coverage Information cards or system actions are present in
|
17
|
+
the returned valid cards or valid system actions.
|
18
|
+
- **For cards**: it ensures there are cards with a `suggestions` array containing a single suggestion,
|
19
|
+
and the `actions` array of that suggestion has one `create` or `update` action for the `Coverage` resource.
|
20
|
+
- **For system actions**: it checks for the presence of `create` or `update` actions for the `Coverage`
|
21
|
+
resource.
|
22
|
+
|
23
|
+
- **Validating:**
|
24
|
+
If any Create or Update Coverage Information cards or system actions are found, each `Coverage` resource is
|
25
|
+
validated against the base FHIR Coverage resource.
|
26
|
+
|
27
|
+
If no Create or Update Coverage Information cards or system actions are received, the test is skipped.
|
28
|
+
)
|
29
|
+
optional
|
30
|
+
input :valid_cards_with_suggestions, :valid_system_actions
|
31
|
+
|
32
|
+
def hook_name
|
33
|
+
config.options[:hook_name]
|
34
|
+
end
|
35
|
+
|
36
|
+
def coverage_actions(actions)
|
37
|
+
return [] if actions.nil?
|
38
|
+
|
39
|
+
valid_types = ['create', 'update']
|
40
|
+
actions.filter do |action|
|
41
|
+
valid_types.include?(action['type']) && action_resource_type_check(action, ['Coverage'])
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def create_or_update_coverage_info_card?(card)
|
46
|
+
card['suggestions'].one? && coverage_actions(card['suggestions'].first['actions']).one?
|
47
|
+
end
|
48
|
+
|
49
|
+
run do
|
50
|
+
parsed_cards = parse_json(valid_cards_with_suggestions)
|
51
|
+
parsed_actions = parse_json(valid_system_actions)
|
52
|
+
|
53
|
+
create_or_update_coverage_info_cards = parsed_cards.filter { |card| create_or_update_coverage_info_card?(card) }
|
54
|
+
create_or_update_coverage_info_actions = coverage_actions(parsed_actions)
|
55
|
+
|
56
|
+
skip_msg = "#{hook_name} hook response does not contain any Create or Update Coverage Information " \
|
57
|
+
'cards or system actions.'
|
58
|
+
skip_if create_or_update_coverage_info_cards.blank? && create_or_update_coverage_info_actions.blank?, skip_msg
|
59
|
+
|
60
|
+
actions_check(create_or_update_coverage_info_actions) if create_or_update_coverage_info_actions.present?
|
61
|
+
|
62
|
+
if create_or_update_coverage_info_cards.present?
|
63
|
+
create_or_update_coverage_info_cards.each do |card|
|
64
|
+
actions = card['suggestions'].first['actions']
|
65
|
+
actions_check(coverage_actions(actions))
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
no_error_validation('Some Create or Update Coverage Information received are not valid.')
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module DaVinciCRDTestKit
|
2
|
+
class DiscoveryEndpointTest < Inferno::Test
|
3
|
+
title 'Server returns a discovery response'
|
4
|
+
id :crd_discovery_endpoint_test
|
5
|
+
description %(
|
6
|
+
A CDS Service provider must expose its discovery endpoint at `{baseURL}/cds-services`
|
7
|
+
as specified in the [CDS Hooks Specification](https://cds-hooks.hl7.org/2.0/#discovery).
|
8
|
+
|
9
|
+
This test checks that the server responds to a GET request at the following endpoint:
|
10
|
+
|
11
|
+
`GET {baseURL}/cds-services`
|
12
|
+
|
13
|
+
It does this by checking that the server responds with an HTTP OK 200 status code
|
14
|
+
and that the body of the response is a valid JSON object. This test does not
|
15
|
+
inspect the structure and content of the response body to see if it contains the required information.
|
16
|
+
It only checks to see if the RESTful interaction is supported and returns a valid JSON object.
|
17
|
+
)
|
18
|
+
|
19
|
+
input_order :base_url, :authentication_required, :encryption_method, :jwks_kid
|
20
|
+
input :base_url
|
21
|
+
input :authentication_required,
|
22
|
+
title: 'Discovery endpoint requires authentication?',
|
23
|
+
type: 'radio',
|
24
|
+
default: 'no',
|
25
|
+
options: {
|
26
|
+
list_options: [
|
27
|
+
{
|
28
|
+
label: 'No',
|
29
|
+
value: 'no'
|
30
|
+
},
|
31
|
+
{
|
32
|
+
label: 'Yes',
|
33
|
+
value: 'yes'
|
34
|
+
}
|
35
|
+
]
|
36
|
+
}
|
37
|
+
input :encryption_method,
|
38
|
+
title: 'JWT Signing Algorithm',
|
39
|
+
description: <<~DESCRIPTION,
|
40
|
+
CDS Hooks recommends ES384 and RS384 for JWT signature verification.
|
41
|
+
Select which method to use.
|
42
|
+
DESCRIPTION
|
43
|
+
type: 'radio',
|
44
|
+
default: 'ES384',
|
45
|
+
options: {
|
46
|
+
list_options: [
|
47
|
+
{
|
48
|
+
label: 'ES384',
|
49
|
+
value: 'ES384'
|
50
|
+
},
|
51
|
+
{
|
52
|
+
label: 'RS384',
|
53
|
+
value: 'RS384'
|
54
|
+
}
|
55
|
+
]
|
56
|
+
}
|
57
|
+
input :jwks_kid,
|
58
|
+
title: 'CDS Services JWKS kid',
|
59
|
+
description: <<~DESCRIPTION,
|
60
|
+
The key ID of the JWKS private key to use for signing the JWTs when invoking a CDS service endpoint
|
61
|
+
requiring authentication.
|
62
|
+
Defaults to the first JWK in the list if no kid is supplied.
|
63
|
+
DESCRIPTION
|
64
|
+
optional: true
|
65
|
+
output :cds_services
|
66
|
+
|
67
|
+
run do
|
68
|
+
discovery_url = "#{base_url.chomp('/')}/cds-services"
|
69
|
+
headers = { 'Accept' => 'application/json' }
|
70
|
+
|
71
|
+
if authentication_required == 'yes'
|
72
|
+
token = JwtHelper.build(
|
73
|
+
aud: discovery_url,
|
74
|
+
iss: inferno_base_url,
|
75
|
+
jku: "#{inferno_base_url}/jwks.json",
|
76
|
+
kid: jwks_kid,
|
77
|
+
encryption_method:
|
78
|
+
)
|
79
|
+
headers['Authorization'] = "Bearer #{token}"
|
80
|
+
end
|
81
|
+
get(discovery_url, headers:)
|
82
|
+
assert_response_status(200)
|
83
|
+
assert_valid_json(request.response_body)
|
84
|
+
|
85
|
+
output cds_services: request.response_body
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
|
3
|
+
module DaVinciCRDTestKit
|
4
|
+
class DiscoveryServicesValidationTest < Inferno::Test
|
5
|
+
include DaVinciCRDTestKit::TestHelper
|
6
|
+
|
7
|
+
title 'Discovery response contains valid services'
|
8
|
+
id :crd_discovery_services_validation
|
9
|
+
description %(
|
10
|
+
As per the [CDS Hooks Spec](https://cds-hooks.hl7.org/2.0/#response),
|
11
|
+
the response to the discovery endpoint SHALL be an object containing
|
12
|
+
a list of CDS services. If your CDS server hosts no CDS services,
|
13
|
+
the discovery endpoint should return a 200 HTTP response with
|
14
|
+
an empty array of services.
|
15
|
+
|
16
|
+
Each CDS service must contain the following required fields:
|
17
|
+
`hook`, `description`, and `id`.
|
18
|
+
|
19
|
+
This test checks for the presence of the required fields and
|
20
|
+
validates that they are of the correct type.
|
21
|
+
|
22
|
+
The test will be skipped if the server hosts no CDS services.
|
23
|
+
)
|
24
|
+
|
25
|
+
input :cds_services
|
26
|
+
output :appointment_book_service_ids, :encounter_start_service_ids, :encounter_discharge_service_ids,
|
27
|
+
:order_dispatch_service_ids, :order_select_service_ids, :order_sign_service_ids
|
28
|
+
|
29
|
+
def required_fields
|
30
|
+
{
|
31
|
+
'hook' => String,
|
32
|
+
'description' => String,
|
33
|
+
'id' => String
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
run do
|
38
|
+
object = parse_json(cds_services)
|
39
|
+
assert object['services'], 'Discovery response did not contain `services`'
|
40
|
+
|
41
|
+
services = object['services']
|
42
|
+
assert services.is_a?(Array), 'Services field of the CDS Discovery response object is not an array.'
|
43
|
+
skip_if services.empty?, 'Server hosts no CDS Services.'
|
44
|
+
|
45
|
+
service_hooks_to_ids = services.each_with_object({}) do |service, hash|
|
46
|
+
hash[service['hook']] ||= []
|
47
|
+
hash[service['hook']] << service['id'] if service['id']
|
48
|
+
end
|
49
|
+
|
50
|
+
output appointment_book_service_ids: service_hooks_to_ids['appointment-book']&.join(', '),
|
51
|
+
encounter_start_service_ids: service_hooks_to_ids['encounter-start']&.join(', '),
|
52
|
+
encounter_discharge_service_ids: service_hooks_to_ids['encounter-discharge']&.join(', '),
|
53
|
+
order_dispatch_service_ids: service_hooks_to_ids['order-dispatch']&.join(', '),
|
54
|
+
order_select_service_ids: service_hooks_to_ids['order-select']&.join(', '),
|
55
|
+
order_sign_service_ids: service_hooks_to_ids['order-sign']&.join(', ')
|
56
|
+
|
57
|
+
services.each do |service|
|
58
|
+
required_fields.each do |field, type|
|
59
|
+
assert(service[field], "Service `#{service}` did not contain required field: `#{field}`")
|
60
|
+
assert(service[field].is_a?(type), "Service `#{service}`: field `#{field}` is not of type #{type}")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/davinci_crd_test_kit/server_tests/external_reference_card_across_hooks_validation_test.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
|
3
|
+
module DaVinciCRDTestKit
|
4
|
+
class ExternalReferenceCardAcrossHooksValidationTest < Inferno::Test
|
5
|
+
include DaVinciCRDTestKit::TestHelper
|
6
|
+
|
7
|
+
title 'Valid External Reference cards received across all hooks'
|
8
|
+
id :crd_external_reference_card_across_hooks_validation
|
9
|
+
description %(
|
10
|
+
This test verifies the presence of valid External Reference returned by CRD services across all hooks invoked.
|
11
|
+
As per the [Da Vinci CRD Implementation Guide](https://hl7.org/fhir/us/davinci-crd/STU2/cards.html#external-reference),
|
12
|
+
External Reference cards must contain links with the type set to `absolute`.
|
13
|
+
This test checks for the presence of any External Reference cards by verifying:
|
14
|
+
- The presence of a `links` array within each card.
|
15
|
+
- That every link in the `links` array of a card is of type `absolute`.
|
16
|
+
|
17
|
+
The test will be skipped if no valid External Reference cards are returned across all hooks.
|
18
|
+
)
|
19
|
+
|
20
|
+
run do
|
21
|
+
verify_at_least_one_test_passes(
|
22
|
+
self.class.parent.parent.groups,
|
23
|
+
'crd_external_reference_card_validation',
|
24
|
+
'None of the hooks invoked returned an External Reference card.'
|
25
|
+
)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
|
3
|
+
module DaVinciCRDTestKit
|
4
|
+
class ExternalReferenceCardValidationTest < Inferno::Test
|
5
|
+
include DaVinciCRDTestKit::TestHelper
|
6
|
+
|
7
|
+
title 'Valid External Reference cards received'
|
8
|
+
id :crd_external_reference_card_validation
|
9
|
+
description %(
|
10
|
+
This test verifies the presence of valid External Reference cards within the list of valid cards
|
11
|
+
returned by the CRD service.
|
12
|
+
As per the [Da Vinci CRD Implementation Guide](https://hl7.org/fhir/us/davinci-crd/STU2/cards.html#external-reference),
|
13
|
+
External Reference cards must contain links with the type set to `absolute`.
|
14
|
+
This test checks for the presence of any External Reference cards by verifying:
|
15
|
+
- The presence of a `links` array within each card.
|
16
|
+
- That every link in the `links` array of a card is of type `absolute`.
|
17
|
+
)
|
18
|
+
|
19
|
+
input :valid_cards_with_links
|
20
|
+
optional
|
21
|
+
|
22
|
+
def hook_name
|
23
|
+
config.options[:hook_name]
|
24
|
+
end
|
25
|
+
|
26
|
+
run do
|
27
|
+
parsed_cards = parse_json(valid_cards_with_links)
|
28
|
+
external_reference_cards = parsed_cards.select do |card|
|
29
|
+
links = card['links']
|
30
|
+
links.present? && links.all? { |link| link['type'] == 'absolute' }
|
31
|
+
end
|
32
|
+
|
33
|
+
assert external_reference_cards.present?, "#{hook_name} hook response did not contain an External Reference card."
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
require_relative '../suggestion_actions_validation'
|
3
|
+
|
4
|
+
module DaVinciCRDTestKit
|
5
|
+
class FormCompletionResponseValidationTest < Inferno::Test
|
6
|
+
include DaVinciCRDTestKit::TestHelper
|
7
|
+
include DaVinciCRDTestKit::SuggestionActionsValidation
|
8
|
+
|
9
|
+
title 'Valid Request Form Completion cards or system actions received'
|
10
|
+
id :crd_request_form_completion_response_validation
|
11
|
+
description %(
|
12
|
+
This test validates the Request Form Completion cards or system actions received from the CRD service,
|
13
|
+
as per the specifications outlined in the [Da Vinci CRD Implementation Guide](https://hl7.org/fhir/us/davinci-crd/STU2/cards.html#request-form-completion).
|
14
|
+
|
15
|
+
- **Checking for Presence:**
|
16
|
+
The test begins by verifying whether any Request Form Completion cards or system actions are present.
|
17
|
+
- **For cards:** It ensures that there are cards with `suggestions` containing `create` actions
|
18
|
+
for the `Task` resource, specifically:
|
19
|
+
- The `Task` must have a `code` of `complete-questionnaire`.
|
20
|
+
- The `Task` should include an input of type `text` (`Task.input.type.text`) labeled as `questionnaire`
|
21
|
+
and associated with a valid canonical URL (`Task.input.valueCanonical`).
|
22
|
+
- **For system actions:** It checks for the presence of `create` actions for the `Task` resource with
|
23
|
+
the characteristics described above.
|
24
|
+
|
25
|
+
- **Validating:**
|
26
|
+
If any Request Form Completion cards or system actions are found, the test proceeds to validate them.
|
27
|
+
Each `Task` resource is validated against the [CRD Questionnaire Task profile](http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-taskquestionnaire).
|
28
|
+
|
29
|
+
If no Request Form Completion cards or system actions are received, the test is skipped.
|
30
|
+
)
|
31
|
+
optional
|
32
|
+
input :valid_cards_with_suggestions, :valid_system_actions
|
33
|
+
|
34
|
+
def hook_name
|
35
|
+
config.options[:hook_name]
|
36
|
+
end
|
37
|
+
|
38
|
+
def task_actions(actions)
|
39
|
+
actions&.select { |action| action['type'] == 'create' && action_resource_type_check(action, ['Task']) }
|
40
|
+
end
|
41
|
+
|
42
|
+
def task_questionnaire?(task_action)
|
43
|
+
task = FHIR.from_contents(task_action['resource'].to_json)
|
44
|
+
task.code.coding.any? { |code| code.code == 'complete-questionnaire' } &&
|
45
|
+
task.input.any? { |input| input.type.text == 'questionnaire' && valid_url?(input.valueCanonical) }
|
46
|
+
end
|
47
|
+
|
48
|
+
def request_form_completion_card?(card)
|
49
|
+
card['suggestions'].all? do |suggestion|
|
50
|
+
actions = suggestion['actions']
|
51
|
+
task_actions = task_actions(actions)
|
52
|
+
task_actions.present? && task_actions.all? { |action| task_questionnaire?(action) }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
run do
|
57
|
+
parsed_cards = parse_json(valid_cards_with_suggestions)
|
58
|
+
parsed_actions = parse_json(valid_system_actions)
|
59
|
+
|
60
|
+
form_completion_cards = parsed_cards.filter { |card| request_form_completion_card?(card) }
|
61
|
+
form_completion_actions = task_actions(parsed_actions).select { |action| task_questionnaire?(action) }
|
62
|
+
|
63
|
+
skip_if form_completion_cards.blank? && form_completion_actions.blank?,
|
64
|
+
"#{hook_name} hook response does not contain any Request Form Completion cards or system actions."
|
65
|
+
|
66
|
+
actions_check(form_completion_actions) if form_completion_actions.present?
|
67
|
+
|
68
|
+
if form_completion_cards.present?
|
69
|
+
form_completion_cards.each do |card|
|
70
|
+
card['suggestions'].each do |suggestion|
|
71
|
+
actions_check(task_actions(suggestion['actions']))
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
no_error_validation('Some Request Form Completion received are not valid.')
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
|
3
|
+
module DaVinciCRDTestKit
|
4
|
+
class InstructionsCardReceivedAcrossHooksTest < Inferno::Test
|
5
|
+
include DaVinciCRDTestKit::TestHelper
|
6
|
+
|
7
|
+
title 'Valid Instructions cards received across all hooks'
|
8
|
+
id :crd_valid_instructions_card_received_across_hooks
|
9
|
+
description %(
|
10
|
+
This test validates that a valid [Instructions card](https://hl7.org/fhir/us/davinci-crd/STU2/cards.html#instructions)
|
11
|
+
was received across all hooks responses.
|
12
|
+
|
13
|
+
The test will be skipped if no valid Instructions cards are returned across all hooks.
|
14
|
+
)
|
15
|
+
|
16
|
+
run do
|
17
|
+
verify_at_least_one_test_passes(
|
18
|
+
self.class.parent.parent.groups,
|
19
|
+
'crd_valid_instructions_card_received',
|
20
|
+
'None of the hooks invoked returned a valid Instructions card.',
|
21
|
+
'across_hooks'
|
22
|
+
)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
|
3
|
+
module DaVinciCRDTestKit
|
4
|
+
class InstructionsCardReceivedTest < Inferno::Test
|
5
|
+
include DaVinciCRDTestKit::TestHelper
|
6
|
+
|
7
|
+
title 'Valid Instructions cards received'
|
8
|
+
id :crd_valid_instructions_card_received
|
9
|
+
description %(
|
10
|
+
This test validates that an [Instructions](https://hl7.org/fhir/us/davinci-crd/STU2/cards.html#instructions)
|
11
|
+
card was received. It does so by:
|
12
|
+
- Checking for the presence of a valid card that does not contain the `links` field and the `suggestions` field.
|
13
|
+
)
|
14
|
+
|
15
|
+
input :valid_cards
|
16
|
+
optional
|
17
|
+
|
18
|
+
def hook_name
|
19
|
+
config.options[:hook_name]
|
20
|
+
end
|
21
|
+
|
22
|
+
run do
|
23
|
+
parsed_cards = parse_json(valid_cards)
|
24
|
+
instructions_card = parsed_cards.find { |card| card['links'].blank? && card['suggestions'].blank? }
|
25
|
+
assert instructions_card, 'Hook response did not contain an Instructions card.'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|