davinci_crd_test_kit 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|