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,34 @@
|
|
|
1
|
+
module DaVinciCRDTestKit
|
|
2
|
+
class TokenHeaderTest < Inferno::Test
|
|
3
|
+
id :crd_token_header
|
|
4
|
+
title 'Authorization token header contains required information'
|
|
5
|
+
description %(
|
|
6
|
+
Verify that the JWT header contains the header fields required by the [CDS hooks spec](https://cds-hooks.hl7.org/2.0#trusting-cds-clients).
|
|
7
|
+
The `alg`, `kid`, and `typ` fields are required. This test also verifies that the `typ` field is set to `JWT` and
|
|
8
|
+
that the key used to sign the token can be identified in the JWKS.
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
input :auth_token_header_json, :crd_jwks_keys_json
|
|
12
|
+
output :auth_token_jwk_json
|
|
13
|
+
|
|
14
|
+
run do
|
|
15
|
+
header = JSON.parse(auth_token_header_json)
|
|
16
|
+
|
|
17
|
+
algorithm = header['alg']
|
|
18
|
+
assert algorithm.present?, 'Token header must have the `alg` field'
|
|
19
|
+
assert algorithm != 'none', 'Token header `alg` field cannot be set to none'
|
|
20
|
+
|
|
21
|
+
assert header['typ'].present?, 'Token header must have the `typ` field'
|
|
22
|
+
assert header['typ'] == 'JWT', "Token header `typ` field must be set to 'JWT', instead was #{header['typ']}"
|
|
23
|
+
|
|
24
|
+
assert header['kid'].present?, 'Token header must have the `kid` field'
|
|
25
|
+
kid = header['kid']
|
|
26
|
+
keys = JSON.parse(crd_jwks_keys_json)
|
|
27
|
+
|
|
28
|
+
jwk = keys.find { |key| key['kid'] == kid }
|
|
29
|
+
assert jwk.present?, "JWKS did not contain a public key with an id of `#{kid}`"
|
|
30
|
+
|
|
31
|
+
output auth_token_jwk_json: jwk.to_json
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module DaVinciCRDTestKit
|
|
2
|
+
class TokenPayloadTest < Inferno::Test
|
|
3
|
+
include URLs
|
|
4
|
+
id :crd_token_payload
|
|
5
|
+
title 'Authorization token payload has required claims and a valid signature'
|
|
6
|
+
description %(
|
|
7
|
+
Verify that the JWT payload contains the payload fields required by the
|
|
8
|
+
[CDS hooks spec](https://cds-hooks.hl7.org/2.0#trusting-cds-clients).
|
|
9
|
+
The `iss`, `aud`, `exp`, `iat`, and `jti` claims are required.
|
|
10
|
+
Additionally:
|
|
11
|
+
|
|
12
|
+
- `iss` must match the `issuer` from the `iss` input
|
|
13
|
+
- `aud` must match the URL of the CDS Service endpoint being invoked
|
|
14
|
+
- `exp` must represent a time in the future
|
|
15
|
+
- `jti` must be a non-blank string that uniquely identifies this authentication JWT
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
REQUIRED_CLAIMS = ['iss', 'aud', 'exp', 'iat', 'jti'].freeze
|
|
19
|
+
|
|
20
|
+
def required_claims
|
|
21
|
+
REQUIRED_CLAIMS.dup
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def hook_url
|
|
25
|
+
base_url + config.options[:hook_path]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
input :auth_token,
|
|
29
|
+
:auth_token_jwk_json,
|
|
30
|
+
:iss
|
|
31
|
+
|
|
32
|
+
run do
|
|
33
|
+
begin
|
|
34
|
+
jwk = JSON.parse(auth_token_jwk_json).deep_symbolize_keys
|
|
35
|
+
|
|
36
|
+
payload, =
|
|
37
|
+
JWT.decode(
|
|
38
|
+
auth_token,
|
|
39
|
+
JWT::JWK.import(jwk).public_key,
|
|
40
|
+
true,
|
|
41
|
+
algorithms: [jwk[:alg]],
|
|
42
|
+
exp_leeway: 60,
|
|
43
|
+
iss:,
|
|
44
|
+
aud: hook_url,
|
|
45
|
+
verify_not_before: false,
|
|
46
|
+
verify_iat: false,
|
|
47
|
+
verify_jti: true,
|
|
48
|
+
verify_iss: true,
|
|
49
|
+
verify_aud: true
|
|
50
|
+
)
|
|
51
|
+
rescue StandardError => e
|
|
52
|
+
assert false, "Token validation error: #{e.message}"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
missing_claims = required_claims - payload.keys
|
|
56
|
+
missing_claims_string = missing_claims.map { |claim| "`#{claim}`" }.join(', ')
|
|
57
|
+
|
|
58
|
+
assert missing_claims.empty?, "JWT payload missing required claims: #{missing_claims_string}"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
require_relative 'client_fhir_api_group'
|
|
2
|
+
require_relative 'client_hooks_group'
|
|
3
|
+
require_relative 'routes/cds_services_discovery_handler'
|
|
4
|
+
require_relative 'tags'
|
|
5
|
+
require_relative 'urls'
|
|
6
|
+
require_relative 'crd_options'
|
|
7
|
+
require_relative 'routes/hook_request_endpoint'
|
|
8
|
+
require_relative 'ext/inferno_core/runnable'
|
|
9
|
+
require_relative 'version'
|
|
10
|
+
|
|
11
|
+
module DaVinciCRDTestKit
|
|
12
|
+
class CRDClientSuite < Inferno::TestSuite
|
|
13
|
+
id :crd_client
|
|
14
|
+
title 'Da Vinci CRD Client Test Suite'
|
|
15
|
+
description <<~DESCRIPTION
|
|
16
|
+
The Da Vinci CRD Client Test Suite tests the conformance of client systems
|
|
17
|
+
to [version 2.0.1 of the Da Vinci Coverage Requirements Discovery (CRD)
|
|
18
|
+
Implementation Guide](https://hl7.org/fhir/us/davinci-crd/STU2).
|
|
19
|
+
|
|
20
|
+
## Overview
|
|
21
|
+
This suite contains two groups of tests. The Hooks group receives and
|
|
22
|
+
responds to incoming CDS Hooks requests from CRD clients. The FHIR API
|
|
23
|
+
group makes FHIR requests to CRD Clients to verify that they support the
|
|
24
|
+
FHIR interactions defined in the implementation guide.
|
|
25
|
+
|
|
26
|
+
## CDS Services
|
|
27
|
+
This suite provides basic CDS services for [the six hooks contained in the
|
|
28
|
+
implementation
|
|
29
|
+
guide](https://hl7.org/fhir/us/davinci-crd/STU2/hooks.html). The discovery
|
|
30
|
+
endpoint is located at:
|
|
31
|
+
|
|
32
|
+
* `#{Inferno::Application['base_url']}/custom/#{id}/cds-services`
|
|
33
|
+
|
|
34
|
+
## SMART App Launch
|
|
35
|
+
Use this information when registering Inferno as a SMART App:
|
|
36
|
+
|
|
37
|
+
* Launch URI: `#{SMARTAppLaunch::AppLaunchTest.config.options[:launch_uri]}`
|
|
38
|
+
* Redirect URI: `#{SMARTAppLaunch::AppRedirectTest.config.options[:redirect_uri]}`
|
|
39
|
+
|
|
40
|
+
If a client receives a SMART App Launch card in a response and would like
|
|
41
|
+
to test their ability to launch Inferno as a SMART App, first run the
|
|
42
|
+
SMART on FHIR Discovery and SMART EHR Launch groups under FHIR API >
|
|
43
|
+
Authorization. When running the SMART EHR Launch group, Inferno will wait
|
|
44
|
+
for the incoming SMART App Launch request, and this is the time to perform
|
|
45
|
+
the launch from the client being tested.
|
|
46
|
+
|
|
47
|
+
## Running the Tests
|
|
48
|
+
If you would like to try out the tests against [the public CRD reference
|
|
49
|
+
client](https://crd-request-generator.davinci.hl7.org/), you can do so by:
|
|
50
|
+
1. Selecting the *CRD Request Generator RI* option from the Preset
|
|
51
|
+
dropdown in the upper left.
|
|
52
|
+
2. Selecting the *order-sign* hook group on the left menu.
|
|
53
|
+
3. Clicking on the *RUN TESTS* button in the upper right.
|
|
54
|
+
4. Clicking the *Submit* button at the bottom of the input dialog.
|
|
55
|
+
5. Follow the instructions in the wait dialog.
|
|
56
|
+
6. Open the reference client in another tab/browser.
|
|
57
|
+
7. Update the *CRD Server* field in the client configuration to point to
|
|
58
|
+
the discovery endpoint of this suite provided above, and the *Order
|
|
59
|
+
Sign Rest End Point*
|
|
60
|
+
to the service id provided in the wait dialog.
|
|
61
|
+
8. Select the patient data to be used to form the request, then submit the
|
|
62
|
+
request.
|
|
63
|
+
|
|
64
|
+
You can run these tests using your own client by updating the inputs with
|
|
65
|
+
your own data.
|
|
66
|
+
|
|
67
|
+
Note that:
|
|
68
|
+
- You can only sequentially *RUN ALL TESTS* if your system supports all
|
|
69
|
+
hooks.
|
|
70
|
+
- Systems are not expected to pass the *FHIR RESTful Capabilities* tests
|
|
71
|
+
based on the provided inputs, as the resource might not exist on the
|
|
72
|
+
client's FHIR server.
|
|
73
|
+
|
|
74
|
+
## Limitations
|
|
75
|
+
The test suite does not implement any sort of payer business logic, so the
|
|
76
|
+
responses to hook calls are simple hard-coded responses. Hook
|
|
77
|
+
configuration is not tested.
|
|
78
|
+
DESCRIPTION
|
|
79
|
+
|
|
80
|
+
suite_summary <<~SUMMARY
|
|
81
|
+
The Da Vinci CRD Client Test Suite tests the conformance of client systems
|
|
82
|
+
to [version 2.0.1 of the Da Vinci Coverage Requirements Discovery (CRD)
|
|
83
|
+
Implementation Guide](https://hl7.org/fhir/us/davinci-crd/STU2).
|
|
84
|
+
SUMMARY
|
|
85
|
+
|
|
86
|
+
version VERSION
|
|
87
|
+
|
|
88
|
+
links [
|
|
89
|
+
{
|
|
90
|
+
label: 'Report Issue',
|
|
91
|
+
url: 'https://github.com/inferno-framework/davinci-crd-test-kit/issues'
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
label: 'Open Source',
|
|
95
|
+
url: 'https://github.com/inferno-framework/davinci-crd-test-kit'
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
label: 'Download',
|
|
99
|
+
url: 'https://github.com/inferno-framework/davinci-crd-test-kit/releases'
|
|
100
|
+
}
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
fhir_resource_validator do
|
|
104
|
+
igs('hl7.fhir.us.davinci-crd', 'hl7.fhir.us.core')
|
|
105
|
+
|
|
106
|
+
exclude_message do |message|
|
|
107
|
+
message.message.match?(/\A\S+: \S+: URL value '.*' does not resolve/)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
suite_option :smart_app_launch_version,
|
|
112
|
+
title: 'SMART App Launch Version',
|
|
113
|
+
list_options: [
|
|
114
|
+
{
|
|
115
|
+
label: 'SMART App Launch 1.0.0',
|
|
116
|
+
value: CRDOptions::SMART_1
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
label: 'SMART App Launch 2.0.0',
|
|
120
|
+
value: CRDOptions::SMART_2
|
|
121
|
+
}
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
def self.test_resumes?(test)
|
|
125
|
+
!test.config.options[:accepts_multiple_requests]
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def self.extract_token_from_query_params(request)
|
|
129
|
+
request.query_parameters['token']
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
route :get, '/cds-services', Routes::CDSServicesDiscoveryHandler
|
|
133
|
+
# TODO
|
|
134
|
+
# route :post, '/cds-services/:cds-service_id', cds_service_handler
|
|
135
|
+
|
|
136
|
+
allow_cors APPOINTMENT_BOOK_PATH, ENCOUNTER_START_PATH, ENCOUNTER_DISCHARGE_PATH, ORDER_DISPATCH_PATH,
|
|
137
|
+
ORDER_SELECT_PATH, ORDER_SIGN_PATH
|
|
138
|
+
suite_endpoint :post, APPOINTMENT_BOOK_PATH, HookRequestEndpoint
|
|
139
|
+
suite_endpoint :post, ENCOUNTER_START_PATH, HookRequestEndpoint
|
|
140
|
+
suite_endpoint :post, ENCOUNTER_DISCHARGE_PATH, HookRequestEndpoint
|
|
141
|
+
suite_endpoint :post, ORDER_DISPATCH_PATH, HookRequestEndpoint
|
|
142
|
+
suite_endpoint :post, ORDER_SELECT_PATH, HookRequestEndpoint
|
|
143
|
+
suite_endpoint :post, ORDER_SIGN_PATH, HookRequestEndpoint
|
|
144
|
+
|
|
145
|
+
resume_test_route :get, RESUME_PASS_PATH do |request|
|
|
146
|
+
CRDClientSuite.extract_token_from_query_params(request)
|
|
147
|
+
end
|
|
148
|
+
resume_test_route :get, RESUME_FAIL_PATH, result: 'fail' do |request|
|
|
149
|
+
CRDClientSuite.extract_token_from_query_params(request)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
group from: :crd_client_hooks
|
|
153
|
+
|
|
154
|
+
group from: :crd_client_fhir_api
|
|
155
|
+
end
|
|
156
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"keys": [
|
|
3
|
+
{
|
|
4
|
+
"kty": "EC",
|
|
5
|
+
"crv": "P-384",
|
|
6
|
+
"x": "JQKTsV6PT5Szf4QtDA1qrs0EJ1pbimQmM2SKvzOlIAqlph3h1OHmZ2i7MXahIF2C",
|
|
7
|
+
"y": "bRWWQRJBgDa6CTgwofYrHjVGcO-A7WNEnu4oJA5OUJPPPpczgx1g2NsfinK-D2Rw",
|
|
8
|
+
"use": "sig",
|
|
9
|
+
"key_ops": [
|
|
10
|
+
"verify"
|
|
11
|
+
],
|
|
12
|
+
"ext": true,
|
|
13
|
+
"kid": "4b49a739d1eb115b3225f4cf9beb6d1b",
|
|
14
|
+
"alg": "ES384"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"kty": "EC",
|
|
18
|
+
"crv": "P-384",
|
|
19
|
+
"d": "kDkn55p7gryKk2tj6z2ij7ExUnhi0ngxXosvqa73y7epwgthFqaJwApmiXXU2yhK",
|
|
20
|
+
"x": "JQKTsV6PT5Szf4QtDA1qrs0EJ1pbimQmM2SKvzOlIAqlph3h1OHmZ2i7MXahIF2C",
|
|
21
|
+
"y": "bRWWQRJBgDa6CTgwofYrHjVGcO-A7WNEnu4oJA5OUJPPPpczgx1g2NsfinK-D2Rw",
|
|
22
|
+
"key_ops": [
|
|
23
|
+
"sign"
|
|
24
|
+
],
|
|
25
|
+
"ext": true,
|
|
26
|
+
"kid": "4b49a739d1eb115b3225f4cf9beb6d1b",
|
|
27
|
+
"alg": "ES384"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"kty": "RSA",
|
|
31
|
+
"alg": "RS384",
|
|
32
|
+
"n": "vjbIzTqiY8K8zApeNng5ekNNIxJfXAue9BjoMrZ9Qy9m7yIA-tf6muEupEXWhq70tC7vIGLqJJ4O8m7yiH8H2qklX2mCAMg3xG3nbykY2X7JXtW9P8VIdG0sAMt5aZQnUGCgSS3n0qaooGn2LUlTGIR88Qi-4Nrao9_3Ki3UCiICeCiAE224jGCg0OlQU6qj2gEB3o-DWJFlG_dz1y-Mxo5ivaeM0vWuodjDrp-aiabJcSF_dx26sdC9dZdBKXFDq0t19I9S9AyGpGDJwzGRtWHY6LsskNHLvo8Zb5AsJ9eRZKpnh30SYBZI9WHtzU85M9WQqdScR69Vyp-6Uhfbvw",
|
|
33
|
+
"e": "AQAB",
|
|
34
|
+
"use": "sig",
|
|
35
|
+
"key_ops": [
|
|
36
|
+
"verify"
|
|
37
|
+
],
|
|
38
|
+
"ext": true,
|
|
39
|
+
"kid": "b41528b6f37a9500edb8a905a595bdd7"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"kty": "RSA",
|
|
43
|
+
"alg": "RS384",
|
|
44
|
+
"n": "vjbIzTqiY8K8zApeNng5ekNNIxJfXAue9BjoMrZ9Qy9m7yIA-tf6muEupEXWhq70tC7vIGLqJJ4O8m7yiH8H2qklX2mCAMg3xG3nbykY2X7JXtW9P8VIdG0sAMt5aZQnUGCgSS3n0qaooGn2LUlTGIR88Qi-4Nrao9_3Ki3UCiICeCiAE224jGCg0OlQU6qj2gEB3o-DWJFlG_dz1y-Mxo5ivaeM0vWuodjDrp-aiabJcSF_dx26sdC9dZdBKXFDq0t19I9S9AyGpGDJwzGRtWHY6LsskNHLvo8Zb5AsJ9eRZKpnh30SYBZI9WHtzU85M9WQqdScR69Vyp-6Uhfbvw",
|
|
45
|
+
"e": "AQAB",
|
|
46
|
+
"d": "rriV9GYimi5by7TOW4xNh6_gYBHVRDBsft2OFF8qapdVHt2GNuRDDxc_B6ga6TY2Enh2MLKLTr1dD3W4FIdTCJiMerrorp07FJS7nJEMgWQDxrfgkX4_EqrhW42L5d4vypYnRXEEW6u4gzkx5uFOkdvJBIK7CsIdSaBFYhochnynNgvbKWasi4rl2hayEH8tdf3B7Z6OIH9alspBTaq3j_zJt_KkrpYEzIUb4UgALB5NTWn5YKr0Avk_asOg8YfjViQwO9ASGaWjQeJ2Rx8OEQwBMQHSDMCSWNiWmYOu9PcwSZFc1vLxqzyIM8QrQSJHCCMo_wGYgke_r0CLeONHEQ",
|
|
47
|
+
"p": "5hH_QApWGeobRi1n7XbMfJYohB8K3JDPa0MspfplHpJ-17JiGG2sNoBdBcpaPRf9OX48P8VqO0qrSSRAk-I-uO6OO9BHbIukXJILqnY2JmurYzbcYbt5FVbknlHRJojkF6-7sFBazpueUlOnXCw7X7Z_SkfNE4QX5Ejm2Zm5mek",
|
|
48
|
+
"q": "06bZz7c7K9s1-aEZsxYnLJ9eTpKlt1tIBDA_LwIh5W3w259pes2kUtimbnkyOf-V2ZIERsFCh5s-S9IOEMvAIa6M5j9GW1ILNT7AcHIUfcyFcH-FF8BU_KJdRP5PXnIXFdYcylvsdoIdchy1AaUIzyiKRCU3HBYI75hez0l_F2c",
|
|
49
|
+
"dp": "h_sVIXW6hCCRND48EedIX06k7conMkxIu_39ErDXOWWeoMAnKIcR5TijQnviL__QxD1vQMXezuKIMHfDz2RGbClbWdD1lhtG7wvG515tDPJQXxia0wzqOQmdoFF9S8hXAAT26vPjaAAkaEZXQaxG_4Au5elgNWu6b0wDXZN1Vpk",
|
|
50
|
+
"dq": "GqS0YpuUTU8JGmWXUJ4HTGy7eHSpe8134V8ZdRd1oOYYHe2RX64nc25mdR24nuh3uq3Q7_9AGsYGL5E_yAl-JD9O6WUpvDE1y_wcSYty3Os0GRdUb8r8Z9kgmKDS6Pa_xTXw5eBwgfKbNlQ6zPwzgbB-x1lP-K8lbNPni3ybDR0",
|
|
51
|
+
"qi": "cqQfoi0sM5Su8ZOhznmdWrDIQB28H6fBKiabgaIKkbWZV4e0nwFvLquHjPOvv4Ao8iEGU5dyhvg0n5BKYPi-4mp6M6OA1sy0NrTr7EsKSYGyu2pBq9rw4oAYTM2LXKg6K-awgUUlkc451IwxHBAe15PWCBM3kvLQeijNid0Vz5I",
|
|
52
|
+
"key_ops": [
|
|
53
|
+
"sign"
|
|
54
|
+
],
|
|
55
|
+
"ext": true,
|
|
56
|
+
"kid": "b41528b6f37a9500edb8a905a595bdd7"
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
module DaVinciCRDTestKit
|
|
2
|
+
module CRDOptions
|
|
3
|
+
SMART_1 = 'smart_app_launch_1'.freeze
|
|
4
|
+
SMART_2 = 'smart_app_launch_2'.freeze
|
|
5
|
+
|
|
6
|
+
SMART_1_REQUIREMENT = { smart_app_launch_version: SMART_1 }.freeze
|
|
7
|
+
SMART_2_REQUIREMENT = { smart_app_launch_version: SMART_2 }.freeze
|
|
8
|
+
end
|
|
9
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
require_relative 'jwt_helper'
|
|
2
|
+
require_relative 'routes/jwk_set_endpoint_handler'
|
|
3
|
+
require_relative 'server_discovery_group'
|
|
4
|
+
require_relative 'server_hooks_group'
|
|
5
|
+
require_relative 'version'
|
|
6
|
+
|
|
7
|
+
module DaVinciCRDTestKit
|
|
8
|
+
class CRDServerSuite < Inferno::TestSuite
|
|
9
|
+
id :crd_server
|
|
10
|
+
title 'Da Vinci CRD Server Test Suite'
|
|
11
|
+
description <<~DESCRIPTION
|
|
12
|
+
The Da Vinci CRD Server Test Suite tests the conformance of server systems
|
|
13
|
+
to [version 2.0.1 of the Da Vinci Coverage Requirements Discovery (CRD)
|
|
14
|
+
Implementation Guide](https://hl7.org/fhir/us/davinci-crd/STU2).
|
|
15
|
+
|
|
16
|
+
## Overview
|
|
17
|
+
This suite contains two groups of tests. The Discovery group validates a
|
|
18
|
+
CRD server's discovery response. The Hooks group makes CDS Hooks calls to
|
|
19
|
+
the server and validates the responses. By default, the hook requests are
|
|
20
|
+
based on examples from the CDS Hooks specification, but testers can
|
|
21
|
+
replace these with request bodies to elicit a particular response from
|
|
22
|
+
their system.
|
|
23
|
+
|
|
24
|
+
## Trusting CDS Clients
|
|
25
|
+
As specified in the [CDS Hooks Spec](https://cds-hooks.hl7.org/2.0/#trusting-cds-clients),
|
|
26
|
+
Each time a CDS Client transmits a request to a CDS Service which
|
|
27
|
+
requires authentication, the request MUST include an Authorization
|
|
28
|
+
header presenting the JWT as a “Bearer” token:
|
|
29
|
+
`Authorization: Bearer {{JWT}}`
|
|
30
|
+
|
|
31
|
+
Inferno self-issues the JWT for each CDS Service call. The following
|
|
32
|
+
info is needed to register Inferno:
|
|
33
|
+
|
|
34
|
+
- **ISS**: `#{Inferno::Application[:base_url]}/custom/crd_server`
|
|
35
|
+
- **JWK Set Url**:
|
|
36
|
+
`#{Inferno::Application[:base_url]}/custom/crd_server/jwks.json`
|
|
37
|
+
|
|
38
|
+
## Running the Tests
|
|
39
|
+
Execution of these tests require a significant amount of tester input in
|
|
40
|
+
the form of requests that Inferno will make against the server under test.
|
|
41
|
+
|
|
42
|
+
If you would like to try out the tests using examples from the IG and the
|
|
43
|
+
[CDS Hooks spec](https://cds-hooks.hl7.org/2.0/) against [the public CRD
|
|
44
|
+
reference server endpoint](https://crd.davinci.hl7.org/), you can do so
|
|
45
|
+
by:
|
|
46
|
+
1. Selecting the *CRD Server RI* option from the
|
|
47
|
+
Preset dropdown in the upper left
|
|
48
|
+
2. Clicking the *Run All Tests* button in the upper right
|
|
49
|
+
3. Clicking the *Submit* button at the bottom of the input dialog
|
|
50
|
+
|
|
51
|
+
You can run these tests using your own server by updating the "CRD server
|
|
52
|
+
base URL" and, if needed, providing requests inputs you wish to use for
|
|
53
|
+
each hook your server supports.
|
|
54
|
+
|
|
55
|
+
Note that the provided inputs for these tests are not complete and systems
|
|
56
|
+
are not expected to pass the tests based on them.
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
## Limitations
|
|
60
|
+
Inferno is unable to determine what requests will result in specific kinds
|
|
61
|
+
of responses from the server under test (e.g., what will result in
|
|
62
|
+
Instructions being returned vs. Coverage Information). As a result, the
|
|
63
|
+
tester must supply the request bodies which will cause the system under
|
|
64
|
+
test to return the desired response types.
|
|
65
|
+
|
|
66
|
+
The ability of a CRD Server to request additional FHIR resources is not
|
|
67
|
+
tested. Hook configuration is not tested.
|
|
68
|
+
DESCRIPTION
|
|
69
|
+
|
|
70
|
+
suite_summary <<~SUMMARY
|
|
71
|
+
The Da Vinci CRD Server Test Suite tests the conformance of server systems
|
|
72
|
+
to [version 2.0.1 of the Da Vinci Coverage Requirements Discovery (CRD)
|
|
73
|
+
Implementation Guide](https://hl7.org/fhir/us/davinci-crd/STU2).
|
|
74
|
+
SUMMARY
|
|
75
|
+
|
|
76
|
+
version VERSION
|
|
77
|
+
|
|
78
|
+
links [
|
|
79
|
+
{
|
|
80
|
+
label: 'Report Issue',
|
|
81
|
+
url: 'https://github.com/inferno-framework/davinci-crd-test-kit/issues'
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
label: 'Open Source',
|
|
85
|
+
url: 'https://github.com/inferno-framework/davinci-crd-test-kit'
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
label: 'Download',
|
|
89
|
+
url: 'https://github.com/inferno-framework/davinci-crd-test-kit/releases'
|
|
90
|
+
}
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
input :base_url,
|
|
94
|
+
title: 'CRD server base URL'
|
|
95
|
+
|
|
96
|
+
fhir_resource_validator do
|
|
97
|
+
igs('hl7.fhir.us.davinci-crd', 'hl7.fhir.us.core')
|
|
98
|
+
|
|
99
|
+
exclude_message do |message|
|
|
100
|
+
message.message.match?(/\A\S+: \S+: URL value '.*' does not resolve/)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def inferno_base_url
|
|
105
|
+
suite_id = self.class.suite.id
|
|
106
|
+
@inferno_base_url ||= "#{Inferno::Application['base_url']}/custom/#{suite_id}"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
route :get, '/jwks.json', Routes::JWKSetEndpointHandler
|
|
110
|
+
|
|
111
|
+
group from: :crd_server_discovery_group
|
|
112
|
+
|
|
113
|
+
group from: :crd_server_hooks
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Inferno
|
|
2
|
+
module DSL
|
|
3
|
+
module Runnable
|
|
4
|
+
PRE_FLIGHT_HANDLER = proc do
|
|
5
|
+
[
|
|
6
|
+
200,
|
|
7
|
+
{
|
|
8
|
+
'Access-Control-Allow-Origin' => '*',
|
|
9
|
+
'Access-Control-Allow-Headers' => 'Content-Type, Authorization'
|
|
10
|
+
},
|
|
11
|
+
['']
|
|
12
|
+
]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def allow_cors(*paths)
|
|
16
|
+
paths.each do |path|
|
|
17
|
+
route(:options, path, PRE_FLIGHT_HANDLER)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|