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,68 @@
|
|
1
|
+
require_relative '../urls'
|
2
|
+
|
3
|
+
module DaVinciCRDTestKit
|
4
|
+
class EncounterStartReceiveRequestTest < Inferno::Test
|
5
|
+
include URLs
|
6
|
+
|
7
|
+
id :crd_encounter_start_request
|
8
|
+
title 'Request received for encounter-start hook'
|
9
|
+
description %(
|
10
|
+
This test waits for an incoming [encounter-start](https://hl7.org/fhir/us/davinci-crd/STU2/hooks.html#encounter-start)
|
11
|
+
hook request and responds to the client with the response types selected as an input.
|
12
|
+
)
|
13
|
+
receives_request :encounter_start
|
14
|
+
|
15
|
+
input :iss
|
16
|
+
input :encounter_start_selected_response_types,
|
17
|
+
title: 'Response types to return from encounter-start hook requests',
|
18
|
+
description: %(
|
19
|
+
Select the cards/action response types that the Inferno hook request endpoints will return. The default
|
20
|
+
response type that will be returned for this hook is the `Instructions` card type.
|
21
|
+
),
|
22
|
+
type: 'checkbox',
|
23
|
+
default: ['instructions'],
|
24
|
+
options: {
|
25
|
+
list_options: [
|
26
|
+
{
|
27
|
+
label: 'External Reference',
|
28
|
+
value: 'external_reference'
|
29
|
+
},
|
30
|
+
{
|
31
|
+
label: 'Instructions',
|
32
|
+
value: 'instructions'
|
33
|
+
},
|
34
|
+
{
|
35
|
+
label: 'Coverage Information',
|
36
|
+
value: 'coverage_information'
|
37
|
+
},
|
38
|
+
{
|
39
|
+
label: 'Request Form Completion',
|
40
|
+
value: 'request_form_completion'
|
41
|
+
},
|
42
|
+
{
|
43
|
+
label: 'Create/Update Coverage Information',
|
44
|
+
value: 'create_update_coverage_info'
|
45
|
+
},
|
46
|
+
{
|
47
|
+
label: 'Launch SMART Application',
|
48
|
+
value: 'launch_smart_app'
|
49
|
+
}
|
50
|
+
]
|
51
|
+
}
|
52
|
+
|
53
|
+
run do
|
54
|
+
wait(
|
55
|
+
identifier: "encounter-start #{iss}",
|
56
|
+
message: %(
|
57
|
+
**Encounter Start CDS Service Test**:
|
58
|
+
|
59
|
+
Invoke the encounter-start hook and send a request to:
|
60
|
+
|
61
|
+
`#{encounter_start_url}`
|
62
|
+
|
63
|
+
Inferno will process the request and return CDS cards if successful.
|
64
|
+
)
|
65
|
+
)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require_relative '../client_hook_request_validation'
|
2
|
+
|
3
|
+
module DaVinciCRDTestKit
|
4
|
+
class HookRequestOptionalFieldsTest < Inferno::Test
|
5
|
+
include DaVinciCRDTestKit::ClientHookRequestValidation
|
6
|
+
|
7
|
+
id :crd_hook_request_optional_fields
|
8
|
+
title 'Hook request contains optional fields'
|
9
|
+
description %(
|
10
|
+
Under the [CDS hooks HTTP Request section](https://cds-hooks.hl7.org/2.0/#http-request_1), the specification
|
11
|
+
requires that a CDS service request SHALL include a JSON POST body which MAY contain the following optional input
|
12
|
+
fields:
|
13
|
+
* `fhirServer` - *URL*
|
14
|
+
* `fhirAuthorization` - *object*
|
15
|
+
* `prefetch` - *object*
|
16
|
+
|
17
|
+
This test checks for the precense of these fields and if they are of the correct type. This test is optional and
|
18
|
+
will not fail if the hook request does not contain an optional field, it only produces an informational message.
|
19
|
+
If the client provides its FHIR server URL in the `fhirServer` field, and it's authorization token in the
|
20
|
+
`fhirAuthorization` field object, they will be produced as an output from this test to be used in
|
21
|
+
subsequent tests.
|
22
|
+
)
|
23
|
+
optional
|
24
|
+
|
25
|
+
output :client_fhir_server
|
26
|
+
output :client_access_token,
|
27
|
+
optional: true
|
28
|
+
|
29
|
+
uses_request :hook_request
|
30
|
+
|
31
|
+
run do
|
32
|
+
assert_valid_json(request.request_body)
|
33
|
+
request_body = JSON.parse(request.request_body)
|
34
|
+
|
35
|
+
client_fhir_server = hook_request_optional_fields_check(request_body)
|
36
|
+
|
37
|
+
output client_fhir_server: client_fhir_server[:fhir_server_uri],
|
38
|
+
client_access_token: client_fhir_server[:fhir_access_token]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require_relative '../client_hook_request_validation'
|
2
|
+
|
3
|
+
module DaVinciCRDTestKit
|
4
|
+
class HookRequestRequiredFieldsTest < Inferno::Test
|
5
|
+
include DaVinciCRDTestKit::ClientHookRequestValidation
|
6
|
+
include URLs
|
7
|
+
|
8
|
+
id :crd_hook_request_required_fields
|
9
|
+
title 'Hook request contains required fields'
|
10
|
+
description %(
|
11
|
+
Under the [CDS hooks HTTP Request section](https://cds-hooks.hl7.org/2.0/#http-request_1), the specification
|
12
|
+
requires that a CDS service request SHALL include a JSON POST body with the following input fields:
|
13
|
+
* `hook` - *string*
|
14
|
+
* `hookInstance` - *string*
|
15
|
+
* `context` - *object*
|
16
|
+
|
17
|
+
Additionally, if the optional `fhirAuthorization` field is present, then the `fhirServer` field is required.
|
18
|
+
|
19
|
+
This test also checks that the `hook` field contains the correct CDS service name that the CDS client is sending
|
20
|
+
a request for
|
21
|
+
)
|
22
|
+
|
23
|
+
uses_request :hook_request
|
24
|
+
|
25
|
+
def hook_url
|
26
|
+
base_url + config.options[:hook_path]
|
27
|
+
end
|
28
|
+
|
29
|
+
def hook_name
|
30
|
+
config.options[:hook_name]
|
31
|
+
end
|
32
|
+
|
33
|
+
run do
|
34
|
+
assert_valid_json(request.request_body)
|
35
|
+
request_body = JSON.parse(request.request_body)
|
36
|
+
|
37
|
+
hook_request_required_fields_check(request_body, hook_name)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require_relative '../client_hook_request_validation'
|
2
|
+
module DaVinciCRDTestKit
|
3
|
+
class HookRequestValidContextTest < Inferno::Test
|
4
|
+
include URLs
|
5
|
+
include ClientHookRequestValidation
|
6
|
+
|
7
|
+
id :crd_hook_request_valid_context
|
8
|
+
title 'Hook contains valid context'
|
9
|
+
description %(
|
10
|
+
As stated in the [CDS hooks specification](https://cds-hooks.hl7.org/2.0#http-request), a CDS service request's
|
11
|
+
`context` field contains hook-specific contextual data that the CDS service will need. The context is specified
|
12
|
+
in the hook definition to guide developers on the information available at the point in the workflow when the hook
|
13
|
+
is triggered.
|
14
|
+
|
15
|
+
The `context` requirements for each [hook specified in the CRD IG](https://hl7.org/fhir/us/davinci-crd/STU2/hooks.html)
|
16
|
+
can be found below:
|
17
|
+
* [appointment-book](https://cds-hooks.hl7.org/hooks/appointment-book/2023SepSTU1Ballot/appointment-book/)
|
18
|
+
* [encounter-start](https://cds-hooks.hl7.org/hooks/encounter-start/2023SepSTU1Ballot/encounter-start/)
|
19
|
+
* [encounter-discharge](https://cds-hooks.hl7.org/hooks/encounter-discharge/2023SepSTU1Ballot/encounter-discharge/)
|
20
|
+
* [order-select](https://cds-hooks.hl7.org/hooks/order-select/2023SepSTU1Ballot/order-select/)
|
21
|
+
* [order-dispatch](https://cds-hooks.hl7.org/hooks/order-dispatch/2023SepSTU1Ballot/order-dispatch/)
|
22
|
+
* [order-sign](https://cds-hooks.org/hooks/order-sign/)
|
23
|
+
|
24
|
+
This test performs the following:
|
25
|
+
* Verifies that the incoming hook request's `context` field contains the fields required by each hook and
|
26
|
+
that they are in the correct format
|
27
|
+
* Checks the optional fields and ensures they are in the correct format
|
28
|
+
* Validates any resources contained in a `context` field that contains a Bundle or FHIR resource
|
29
|
+
* Makes FHIR requests for any `context` fields that contain an id or reference and validates each resource
|
30
|
+
response against its corresponding CRD resource profile
|
31
|
+
* Check some specific `context` requirements for hooks that have special requirements for certain fields
|
32
|
+
|
33
|
+
The client must provide its FHIR server URL and access token in the hook request in order to run
|
34
|
+
this test.
|
35
|
+
)
|
36
|
+
uses_request :hook_request
|
37
|
+
|
38
|
+
input :client_fhir_server
|
39
|
+
input :client_access_token,
|
40
|
+
optional: true
|
41
|
+
|
42
|
+
fhir_client do
|
43
|
+
url :client_fhir_server
|
44
|
+
bearer_token :client_access_token
|
45
|
+
end
|
46
|
+
|
47
|
+
def hook_name
|
48
|
+
config.options[:hook_name]
|
49
|
+
end
|
50
|
+
|
51
|
+
run do
|
52
|
+
assert_valid_json(request.request_body)
|
53
|
+
request_body = JSON.parse(request.request_body)
|
54
|
+
|
55
|
+
hook_context = request_body['context']
|
56
|
+
|
57
|
+
assert(hook_context, 'Hook request does not contain required `context` field')
|
58
|
+
|
59
|
+
hook_request_context_check(hook_context, hook_name)
|
60
|
+
no_error_validation('Context is not valid.')
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require_relative '../urls'
|
2
|
+
|
3
|
+
module DaVinciCRDTestKit
|
4
|
+
class HookRequestValidPrefetchTest < Inferno::Test
|
5
|
+
include URLs
|
6
|
+
|
7
|
+
id :crd_hook_request_valid_prefetch
|
8
|
+
title 'Hook contains valid prefetch response'
|
9
|
+
description %(
|
10
|
+
As stated in the [CDS hooks specification](https://cds-hooks.hl7.org/2.0#http-request), a CDS service request's
|
11
|
+
`prefetch` field is an optional field that contains key/value pairs of FHIR queries that the service is requesting
|
12
|
+
the CDS Client to perform and provide on each service call. The key is a string that describes the type of data
|
13
|
+
being requested and the value is a string representing the FHIR query. See [Prefetch Template](https://cds-hooks.hl7.org/2.0#prefetch-template)
|
14
|
+
for more information about how the `prefetch` formatting works.
|
15
|
+
|
16
|
+
This test verifies that the incoming hook request's `prefetch` field is in a valid JSON format and validates each
|
17
|
+
contained resource against its corresponding CRD resource profile. This test is optional and will be skipped if no
|
18
|
+
`prefetch` field is contained in the hook request.
|
19
|
+
)
|
20
|
+
optional
|
21
|
+
|
22
|
+
uses_request :hook_request
|
23
|
+
|
24
|
+
def hook_name
|
25
|
+
config.options[:hook_name]
|
26
|
+
end
|
27
|
+
|
28
|
+
def cds_services_json
|
29
|
+
JSON.parse(File.read(File.join(
|
30
|
+
__dir__, '..', 'routes', 'cds-services.json'
|
31
|
+
)))['services']
|
32
|
+
end
|
33
|
+
|
34
|
+
def structure_definition_map
|
35
|
+
{
|
36
|
+
'Practitioner' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-practitioner',
|
37
|
+
'PractitionerRole' => 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitionerrole',
|
38
|
+
'Patient' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-patient',
|
39
|
+
'Coverage' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-coverage',
|
40
|
+
'RelatedPerson' => 'http://hl7.org/fhir/StructureDefinition/RelatedPerson',
|
41
|
+
'Encounter' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-encounter',
|
42
|
+
'DeviceRequest' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-devicerequest',
|
43
|
+
'MedicationRequest' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-medicationrequest',
|
44
|
+
'NutritionOrder' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-nutritionorder',
|
45
|
+
'ServiceRequest' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-servicerequest',
|
46
|
+
'VisionPrescription' => 'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-visionprescription'
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
def validate_prefetch_coverage(received_resource, advertised_prefetch_key,
|
51
|
+
received_context_patient_id, advertised_status)
|
52
|
+
assert_resource_type('Bundle', resource: received_resource)
|
53
|
+
assert(received_resource.entry.any?, 'Bundle of coverage resources received from prefetch is empty')
|
54
|
+
coverage_resource = received_resource.entry.first.resource
|
55
|
+
assert_resource_type('Coverage', resource: coverage_resource)
|
56
|
+
assert_valid_resource(resource: coverage_resource,
|
57
|
+
profile_url: structure_definition_map['Coverage'])
|
58
|
+
|
59
|
+
coverage_beneficiary_reference = coverage_resource.beneficiary
|
60
|
+
coverage_beneficiary_patient_id = coverage_beneficiary_reference.reference_id
|
61
|
+
assert(coverage_beneficiary_patient_id.present?,
|
62
|
+
"Could not get beneficiary reference id from `#{advertised_prefetch_key}` field's Coverage resource")
|
63
|
+
|
64
|
+
assert(coverage_beneficiary_patient_id == received_context_patient_id,
|
65
|
+
%(Expected `#{advertised_prefetch_key}` field's Coverage resource to have a `beneficiary` reference id of
|
66
|
+
'#{received_context_patient_id}', instead was '#{coverage_beneficiary_patient_id}'))
|
67
|
+
|
68
|
+
coverage_status = coverage_resource.status
|
69
|
+
assert(coverage_status == advertised_status,
|
70
|
+
%(Expected `#{advertised_prefetch_key}` field's Coverage resource to have a `status` of
|
71
|
+
'#{advertised_status}', instead was '#{coverage_status}'))
|
72
|
+
end
|
73
|
+
|
74
|
+
def validate_prefetch_resource(received_resource, advertised_prefetch_key, context_field_resource_type,
|
75
|
+
context_field_id)
|
76
|
+
assert_resource_type(context_field_resource_type, resource: received_resource)
|
77
|
+
|
78
|
+
if hook_name == 'order-dispatch'
|
79
|
+
assert_valid_resource(resource: received_resource)
|
80
|
+
else
|
81
|
+
assert_valid_resource(resource: received_resource,
|
82
|
+
profile_url: structure_definition_map[context_field_resource_type])
|
83
|
+
end
|
84
|
+
|
85
|
+
received_prefetch_resource_id = received_resource.id
|
86
|
+
assert(received_prefetch_resource_id.present?,
|
87
|
+
"`#{advertised_prefetch_key}` field's FHIR resource does not contain the `id` field")
|
88
|
+
assert(received_prefetch_resource_id == context_field_id,
|
89
|
+
%(Expected `#{advertised_prefetch_key}` field's FHIR resource to have an `id` of '#{context_field_id}',
|
90
|
+
instead was '#{received_prefetch_resource_id}'))
|
91
|
+
end
|
92
|
+
|
93
|
+
run do
|
94
|
+
assert_valid_json(request.request_body)
|
95
|
+
request_body = JSON.parse(request.request_body)
|
96
|
+
|
97
|
+
received_prefetch = request_body['prefetch']
|
98
|
+
received_context = request_body['context']
|
99
|
+
|
100
|
+
skip_if received_prefetch.blank?, 'Received hook request does not contain the `prefetch` field.'
|
101
|
+
skip_if received_context.blank?,
|
102
|
+
%(Received hook request does not contain the `context` field which is needed to validate the `prefetch`
|
103
|
+
field)
|
104
|
+
|
105
|
+
advertised_hook_service = cds_services_json.find { |service| service['hook'] == hook_name }
|
106
|
+
|
107
|
+
advertised_prefetch_fields = advertised_hook_service['prefetch']
|
108
|
+
|
109
|
+
advertised_prefetch_fields.each do |advertised_prefetch_key, advertised_prefetch_template|
|
110
|
+
next unless received_prefetch[advertised_prefetch_key].present?
|
111
|
+
|
112
|
+
assert(received_prefetch[advertised_prefetch_key].is_a?(Hash),
|
113
|
+
"Prefetch field `#{advertised_prefetch_key}` is not of type `Hash`.")
|
114
|
+
|
115
|
+
received_prefetch_resource = FHIR.from_contents(received_prefetch[advertised_prefetch_key].to_json)
|
116
|
+
|
117
|
+
if advertised_prefetch_template.include?('?')
|
118
|
+
advertised_prefetch_fhir_search = advertised_prefetch_template.gsub(/{|}/, '').split('?')
|
119
|
+
advertised_prefetch_resource_type = advertised_prefetch_fhir_search.first
|
120
|
+
|
121
|
+
if advertised_prefetch_resource_type == 'Coverage'
|
122
|
+
advertised_coverage_query_params = Rack::Utils.parse_nested_query(advertised_prefetch_fhir_search.last)
|
123
|
+
|
124
|
+
advertised_patient_token = advertised_coverage_query_params['patient']
|
125
|
+
advertised_context_patient_id_key = advertised_patient_token.split('.').last
|
126
|
+
received_context_patient_id = received_context[advertised_context_patient_id_key]
|
127
|
+
|
128
|
+
advertised_status_param = advertised_coverage_query_params['status']
|
129
|
+
|
130
|
+
validate_prefetch_coverage(received_prefetch_resource, advertised_prefetch_key, received_context_patient_id,
|
131
|
+
advertised_status_param)
|
132
|
+
end
|
133
|
+
else
|
134
|
+
advertised_prefetch_token = advertised_prefetch_template.gsub(/{|}/, '').split('/')
|
135
|
+
advertised_context_id = advertised_prefetch_token.last.split('.').last
|
136
|
+
|
137
|
+
if advertised_prefetch_token.length == 1
|
138
|
+
received_context_reference = FHIR::Reference.new(reference: received_context[advertised_context_id])
|
139
|
+
received_context_resource_type = received_context_reference.resource_type
|
140
|
+
received_context_id = received_context_reference.reference_id
|
141
|
+
else
|
142
|
+
received_context_id = received_context[advertised_context_id]
|
143
|
+
received_context_resource_type = advertised_prefetch_token.first
|
144
|
+
end
|
145
|
+
validate_prefetch_resource(received_prefetch_resource, advertised_prefetch_key,
|
146
|
+
received_context_resource_type, received_context_id)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require_relative '../urls'
|
2
|
+
|
3
|
+
module DaVinciCRDTestKit
|
4
|
+
class OrderDispatchReceiveRequestTest < Inferno::Test
|
5
|
+
include URLs
|
6
|
+
|
7
|
+
id :crd_order_dispatch_request
|
8
|
+
title 'Request received for order-dispatch hook'
|
9
|
+
description %(
|
10
|
+
This test waits for an incoming [order-dispatch](https://hl7.org/fhir/us/davinci-crd/STU2/hooks.html#order-dispatch)
|
11
|
+
hook request and responds to the client with the response types selected as an input. This hook is a 'primary'
|
12
|
+
hook, meaning that CRD Servers SHALL, at minimum, return a [Coverage Information](https://hl7.org/fhir/us/davinci-crd/STU2/StructureDefinition-ext-coverage-information.html)
|
13
|
+
system action for these hooks, even if the response indicates that further information is needed or that the
|
14
|
+
level of detail provided is insufficient to determine coverage.
|
15
|
+
)
|
16
|
+
receives_request :order_dispatch
|
17
|
+
|
18
|
+
input :iss
|
19
|
+
input :order_dispatch_selected_response_types,
|
20
|
+
title: 'Response types to return from order-dispatch hook requests',
|
21
|
+
description: %(
|
22
|
+
Select the cards/action response types that the Inferno hook request endpoints will return. The default
|
23
|
+
response type that will be returned for this hook is the `Coverage Information` card type.
|
24
|
+
),
|
25
|
+
type: 'checkbox',
|
26
|
+
default: ['coverage_information'],
|
27
|
+
options: {
|
28
|
+
list_options: [
|
29
|
+
{
|
30
|
+
label: 'External Reference',
|
31
|
+
value: 'external_reference'
|
32
|
+
},
|
33
|
+
{
|
34
|
+
label: 'Instructions',
|
35
|
+
value: 'instructions'
|
36
|
+
},
|
37
|
+
{
|
38
|
+
label: 'Coverage Information',
|
39
|
+
value: 'coverage_information'
|
40
|
+
},
|
41
|
+
{
|
42
|
+
label: 'Request Form Completion',
|
43
|
+
value: 'request_form_completion'
|
44
|
+
},
|
45
|
+
{
|
46
|
+
label: 'Create/Update Coverage Information',
|
47
|
+
value: 'create_update_coverage_info'
|
48
|
+
},
|
49
|
+
{
|
50
|
+
label: 'Launch SMART Application',
|
51
|
+
value: 'launch_smart_app'
|
52
|
+
},
|
53
|
+
{
|
54
|
+
label: 'Propose Alternate Request',
|
55
|
+
value: 'propose_alternate_request'
|
56
|
+
},
|
57
|
+
{
|
58
|
+
label: 'Additional Orders as Companions/Prerequisites',
|
59
|
+
value: 'companions_prerequisites'
|
60
|
+
}
|
61
|
+
]
|
62
|
+
}
|
63
|
+
|
64
|
+
run do
|
65
|
+
wait(
|
66
|
+
identifier: "order-dispatch #{iss}",
|
67
|
+
message: %(
|
68
|
+
**Order Dispatch CDS Service Test**:
|
69
|
+
|
70
|
+
Invoke the order-dispatch hook and send a request to:
|
71
|
+
|
72
|
+
`#{order_dispatch_url}`
|
73
|
+
|
74
|
+
Inferno will process the request and return CDS cards if successful.
|
75
|
+
)
|
76
|
+
)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require_relative '../urls'
|
2
|
+
|
3
|
+
module DaVinciCRDTestKit
|
4
|
+
class OrderSelectReceiveRequestTest < Inferno::Test
|
5
|
+
include URLs
|
6
|
+
|
7
|
+
id :crd_order_select_request
|
8
|
+
title 'Request received for order-select hook'
|
9
|
+
description %(
|
10
|
+
This test waits for an incoming [order-select](https://hl7.org/fhir/us/davinci-crd/STU2/hooks.html#order-select)
|
11
|
+
hook request and responds to the client with the response types selected as an input.
|
12
|
+
)
|
13
|
+
receives_request :order_select
|
14
|
+
|
15
|
+
input :iss
|
16
|
+
input :order_select_selected_response_types,
|
17
|
+
title: 'Response types to return from order-select hook requests',
|
18
|
+
description: %(
|
19
|
+
Select the cards/action response types that the Inferno hook request endpoints will return. The default
|
20
|
+
response type that will be returned for this hook is the `Instructions` card type.
|
21
|
+
),
|
22
|
+
type: 'checkbox',
|
23
|
+
default: ['instructions'],
|
24
|
+
options: {
|
25
|
+
list_options: [
|
26
|
+
{
|
27
|
+
label: 'External Reference',
|
28
|
+
value: 'external_reference'
|
29
|
+
},
|
30
|
+
{
|
31
|
+
label: 'Instructions',
|
32
|
+
value: 'instructions'
|
33
|
+
},
|
34
|
+
{
|
35
|
+
label: 'Coverage Information',
|
36
|
+
value: 'coverage_information'
|
37
|
+
},
|
38
|
+
{
|
39
|
+
label: 'Request Form Completion',
|
40
|
+
value: 'request_form_completion'
|
41
|
+
},
|
42
|
+
{
|
43
|
+
label: 'Create/Update Coverage Information',
|
44
|
+
value: 'create_update_coverage_info'
|
45
|
+
},
|
46
|
+
{
|
47
|
+
label: 'Launch SMART Application',
|
48
|
+
value: 'launch_smart_app'
|
49
|
+
},
|
50
|
+
{
|
51
|
+
label: 'Propose Alternate Request',
|
52
|
+
value: 'propose_alternate_request'
|
53
|
+
},
|
54
|
+
{
|
55
|
+
label: 'Additional Orders as Companions/Prerequisites',
|
56
|
+
value: 'companions_prerequisites'
|
57
|
+
}
|
58
|
+
]
|
59
|
+
}
|
60
|
+
|
61
|
+
run do
|
62
|
+
wait(
|
63
|
+
identifier: "order-select #{iss}",
|
64
|
+
message: %(
|
65
|
+
**Order Select CDS Service Test**:
|
66
|
+
|
67
|
+
Invoke the order-select hook and send a request to:
|
68
|
+
|
69
|
+
`#{order_select_url}`
|
70
|
+
|
71
|
+
Inferno will process the request and return CDS cards if successful.
|
72
|
+
)
|
73
|
+
)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require_relative '../urls'
|
2
|
+
|
3
|
+
module DaVinciCRDTestKit
|
4
|
+
class OrderSignReceiveRequestTest < Inferno::Test
|
5
|
+
include URLs
|
6
|
+
|
7
|
+
id :crd_order_sign_request
|
8
|
+
title 'Request received for order-sign hook'
|
9
|
+
description %(
|
10
|
+
This test waits for an incoming [order-sign](https://hl7.org/fhir/us/davinci-crd/STU2/hooks.html#order-sign)
|
11
|
+
hook request and responds to the client with the response types selected as an input. This hook is a 'primary'
|
12
|
+
hook, meaning that CRD Servers SHALL, at minimum, return a [Coverage Information](https://hl7.org/fhir/us/davinci-crd/STU2/StructureDefinition-ext-coverage-information.html)
|
13
|
+
system action for these hooks, even if the response indicates that further information is needed or that the
|
14
|
+
level of detail provided is insufficient to determine coverage.
|
15
|
+
)
|
16
|
+
receives_request :order_sign
|
17
|
+
|
18
|
+
input :iss
|
19
|
+
input :order_sign_selected_response_types,
|
20
|
+
title: 'Response types to return from order-sign hook requests',
|
21
|
+
description: %(
|
22
|
+
Select the cards/action response types that the Inferno hook request endpoints will return. The default
|
23
|
+
response type that will be returned for this hook is the `Coverage Information` card type.
|
24
|
+
),
|
25
|
+
type: 'checkbox',
|
26
|
+
default: ['coverage_information'],
|
27
|
+
options: {
|
28
|
+
list_options: [
|
29
|
+
{
|
30
|
+
label: 'External Reference',
|
31
|
+
value: 'external_reference'
|
32
|
+
},
|
33
|
+
{
|
34
|
+
label: 'Instructions',
|
35
|
+
value: 'instructions'
|
36
|
+
},
|
37
|
+
{
|
38
|
+
label: 'Coverage Information',
|
39
|
+
value: 'coverage_information'
|
40
|
+
},
|
41
|
+
{
|
42
|
+
label: 'Request Form Completion',
|
43
|
+
value: 'request_form_completion'
|
44
|
+
},
|
45
|
+
{
|
46
|
+
label: 'Create/Update Coverage Information',
|
47
|
+
value: 'create_update_coverage_info'
|
48
|
+
},
|
49
|
+
{
|
50
|
+
label: 'Launch SMART Application',
|
51
|
+
value: 'launch_smart_app'
|
52
|
+
},
|
53
|
+
{
|
54
|
+
label: 'Propose Alternate Request',
|
55
|
+
value: 'propose_alternate_request'
|
56
|
+
},
|
57
|
+
{
|
58
|
+
label: 'Additional Orders as Companions/Prerequisites',
|
59
|
+
value: 'companions_prerequisites'
|
60
|
+
}
|
61
|
+
]
|
62
|
+
}
|
63
|
+
|
64
|
+
run do
|
65
|
+
wait(
|
66
|
+
identifier: "order-sign #{iss}",
|
67
|
+
message: %(
|
68
|
+
**Order Sign CDS Service Test**:
|
69
|
+
|
70
|
+
Invoke the order-sign hook and send a request to:
|
71
|
+
|
72
|
+
`#{order_sign_url}`
|
73
|
+
|
74
|
+
Inferno will process the request and return CDS cards if successful.
|
75
|
+
)
|
76
|
+
)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module DaVinciCRDTestKit
|
2
|
+
class RetrieveJWKSTest < Inferno::Test
|
3
|
+
id :crd_retrieve_jwks
|
4
|
+
title 'JWKS can be retrieved'
|
5
|
+
description %(
|
6
|
+
Verify that the JWKS can be retrieved from the JWKS uri if it is present in the `jku` field within the JWT token
|
7
|
+
header. As per the [CDS hooks specification](https://cds-hooks.hl7.org/2.0#trusting-cds-clients), if the jku
|
8
|
+
header field is ommitted, the CDS Client and CDS Service SHALL communicate the JWK Set out-of-band. Therefore,
|
9
|
+
if the client does not make their keys publicly available via a uri in the `jku` field, the user must
|
10
|
+
submit the jwk_set as an input to the test.
|
11
|
+
)
|
12
|
+
|
13
|
+
input :auth_token_header_json
|
14
|
+
input :jwk_set,
|
15
|
+
title: "The Client's JWK Set containing it's public key",
|
16
|
+
description: %(
|
17
|
+
Must supply if you do not make your keys publicly available via a uri in the authorization JWT header `jku`
|
18
|
+
field'
|
19
|
+
),
|
20
|
+
type: 'textarea',
|
21
|
+
optional: true
|
22
|
+
output :crd_jwks_json, :crd_jwks_keys_json
|
23
|
+
makes_request :crd_client_jwks
|
24
|
+
|
25
|
+
run do
|
26
|
+
token_header = JSON.parse(auth_token_header_json)
|
27
|
+
jku = token_header['jku']
|
28
|
+
|
29
|
+
if jku.present?
|
30
|
+
get(jku, name: :crd_client_jwks)
|
31
|
+
|
32
|
+
assert_response_status(200)
|
33
|
+
assert_valid_json(response[:body])
|
34
|
+
output crd_jwks_json: response[:body]
|
35
|
+
|
36
|
+
jwks = JSON.parse(response[:body])
|
37
|
+
else
|
38
|
+
skip_if jwk_set.blank?,
|
39
|
+
%(JWK Set must be inputted if Client's JWK Set is not available via a URL identified by the jku header
|
40
|
+
field)
|
41
|
+
|
42
|
+
jwks = JSON.parse(jwk_set)
|
43
|
+
end
|
44
|
+
|
45
|
+
keys = jwks['keys']
|
46
|
+
assert keys.is_a?(Array), 'JWKS `keys` field must be an array'
|
47
|
+
|
48
|
+
assert keys.present?, 'The JWK set returned contains no public keys'
|
49
|
+
|
50
|
+
keys.each do |jwk|
|
51
|
+
JWT::JWK.import(jwk.deep_symbolize_keys)
|
52
|
+
rescue StandardError
|
53
|
+
assert false, "Invalid JWK: #{jwk.to_json}"
|
54
|
+
end
|
55
|
+
|
56
|
+
kid_presence = keys.all? { |key| key['kid'].present? }
|
57
|
+
assert kid_presence, '`kid` field must be present in each key if JWKS contains multiple keys'
|
58
|
+
|
59
|
+
kid_uniqueness = keys.map { |key| key['kid'] }.uniq.length == keys.length
|
60
|
+
assert kid_uniqueness, '`kid` must be unique within the client\' JWK Set.'
|
61
|
+
|
62
|
+
output crd_jwks_keys_json: keys.to_json
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|