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,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
|