davinci_dtr_test_kit 0.9.0 → 0.11.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 +4 -4
- data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_dinner_questionnaire_package_request_test.rb +97 -0
- data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_questionnaire_response_save_test.rb +2 -2
- data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_smart_app_questionnaire_workflow_group.rb +3 -2
- data/lib/davinci_dtr_test_kit/client_groups/dinner_static/prepopulation_attestation_test.rb +4 -4
- data/lib/davinci_dtr_test_kit/client_groups/dinner_static/prepopulation_override_attestation_test.rb +4 -4
- data/lib/davinci_dtr_test_kit/client_groups/dinner_static/rendering_enabled_questions_attestation_test.rb +4 -4
- data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_questionnaire_package_group.rb +2 -2
- data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_questionnaire_rendering_attestation_test.rb +4 -4
- data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_questionnaire_response_save_test.rb +2 -2
- data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_resp_questionnaire_package_request_test.rb +95 -0
- data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_smart_app_questionnaire_workflow_group.rb +4 -3
- data/lib/davinci_dtr_test_kit/docs/dtr_smart_app_suite_description_v201.md +107 -43
- data/lib/davinci_dtr_test_kit/dtr_full_ehr_suite.rb +30 -6
- data/lib/davinci_dtr_test_kit/dtr_light_ehr_suite.rb +26 -3
- data/lib/davinci_dtr_test_kit/dtr_payer_server_suite.rb +33 -7
- data/lib/davinci_dtr_test_kit/dtr_questionnaire_response_validation.rb +26 -10
- data/lib/davinci_dtr_test_kit/dtr_smart_app_suite.rb +59 -14
- data/lib/davinci_dtr_test_kit/fixtures/dinner_static/questionnaire_dinner_order_static.json +1 -1
- data/lib/davinci_dtr_test_kit/fixtures/questionnaire_package.json +1 -1
- data/lib/davinci_dtr_test_kit/mock_auth_server.rb +135 -0
- data/lib/davinci_dtr_test_kit/mock_ehr.rb +32 -12
- data/lib/davinci_dtr_test_kit/mock_payer.rb +2 -21
- data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_static_group.rb +4 -2
- data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_libraries_test.rb +2 -2
- data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_questionnaire_expressions_test.rb +3 -3
- data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_questionnaire_extensions_test.rb +2 -2
- data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_request_test.rb +1 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_request_validation_test.rb +5 -3
- data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_response_validation_test.rb +15 -11
- data/lib/davinci_dtr_test_kit/urls.rb +14 -3
- data/lib/davinci_dtr_test_kit/version.rb +1 -1
- metadata +19 -3
- data/lib/davinci_dtr_test_kit/client_groups/shared/dtr_questionnaire_package_request_test.rb +0 -36
@@ -16,6 +16,25 @@ module DaVinciDTRTestKit
|
|
16
16
|
|
17
17
|
version VERSION
|
18
18
|
|
19
|
+
links [
|
20
|
+
{
|
21
|
+
label: 'Report Issue',
|
22
|
+
url: 'https://github.com/inferno-framework/davinci-dtr-test-kit/issues'
|
23
|
+
},
|
24
|
+
{
|
25
|
+
label: 'Open Source',
|
26
|
+
url: 'https://github.com/inferno-framework/davinci-dtr-test-kit'
|
27
|
+
},
|
28
|
+
{
|
29
|
+
label: 'Download',
|
30
|
+
url: 'https://github.com/inferno-framework/davinci-dtr-test-kit/releases'
|
31
|
+
},
|
32
|
+
{
|
33
|
+
label: 'Implementation Guide',
|
34
|
+
url: 'https://hl7.org/fhir/us/davinci-dtr/STU2/'
|
35
|
+
}
|
36
|
+
]
|
37
|
+
|
19
38
|
# These inputs will be available to all tests in this suite
|
20
39
|
input :url,
|
21
40
|
title: 'FHIR Server Base Url'
|
@@ -31,9 +50,13 @@ module DaVinciDTRTestKit
|
|
31
50
|
oauth_credentials :credentials
|
32
51
|
end
|
33
52
|
|
34
|
-
#
|
35
|
-
|
36
|
-
|
53
|
+
# Hl7 Validator Wrapper:
|
54
|
+
fhir_resource_validator do
|
55
|
+
igs 'hl7.fhir.us.davinci-dtr#2.0.1'
|
56
|
+
|
57
|
+
exclude_message do |message|
|
58
|
+
message.message.match?(/\A\S+: \S+: URL value '.*' does not resolve/)
|
59
|
+
end
|
37
60
|
end
|
38
61
|
end
|
39
62
|
end
|
@@ -5,10 +5,12 @@ require_relative 'payer_server_groups/payer_server_static_group'
|
|
5
5
|
require_relative 'payer_server_groups/payer_server_adaptive_group'
|
6
6
|
require_relative 'tags'
|
7
7
|
require_relative 'mock_payer'
|
8
|
+
require_relative 'mock_auth_server'
|
8
9
|
require_relative 'version'
|
9
10
|
|
10
11
|
module DaVinciDTRTestKit
|
11
12
|
class DTRPayerServerSuite < Inferno::TestSuite
|
13
|
+
extend MockAuthServer
|
12
14
|
extend MockPayer
|
13
15
|
|
14
16
|
id :dtr_payer_server
|
@@ -16,6 +18,26 @@ module DaVinciDTRTestKit
|
|
16
18
|
description File.read(File.join(__dir__, 'docs', 'dtr_payer_server_suite_description_v201.md'))
|
17
19
|
|
18
20
|
version VERSION
|
21
|
+
|
22
|
+
links [
|
23
|
+
{
|
24
|
+
label: 'Report Issue',
|
25
|
+
url: 'https://github.com/inferno-framework/davinci-dtr-test-kit/issues'
|
26
|
+
},
|
27
|
+
{
|
28
|
+
label: 'Open Source',
|
29
|
+
url: 'https://github.com/inferno-framework/davinci-dtr-test-kit'
|
30
|
+
},
|
31
|
+
{
|
32
|
+
label: 'Download',
|
33
|
+
url: 'https://github.com/inferno-framework/davinci-dtr-test-kit/releases'
|
34
|
+
},
|
35
|
+
{
|
36
|
+
label: 'Implementation Guide',
|
37
|
+
url: 'https://hl7.org/fhir/us/davinci-dtr/STU2/'
|
38
|
+
}
|
39
|
+
]
|
40
|
+
|
19
41
|
# These inputs will be available to all tests in this suite
|
20
42
|
|
21
43
|
input :retrieval_method,
|
@@ -73,23 +95,27 @@ module DaVinciDTRTestKit
|
|
73
95
|
oauth_credentials :credentials
|
74
96
|
end
|
75
97
|
|
76
|
-
#
|
77
|
-
|
78
|
-
|
98
|
+
# Hl7 Validator Wrapper:
|
99
|
+
fhir_resource_validator do
|
100
|
+
igs 'hl7.fhir.us.davinci-dtr#2.0.1'
|
101
|
+
|
102
|
+
exclude_message do |message|
|
103
|
+
message.message.match?(/\A\S+: \S+: URL value '.*' does not resolve/)
|
104
|
+
end
|
79
105
|
end
|
80
106
|
|
81
107
|
allow_cors QUESTIONNAIRE_PACKAGE_PATH, NEXT_PATH
|
82
108
|
|
83
|
-
record_response_route :post,
|
84
|
-
DTRPayerServerSuite.
|
109
|
+
record_response_route :post, PAYER_TOKEN_PATH, 'dtr_payer_auth', method(:payer_token_response) do |request|
|
110
|
+
DTRPayerServerSuite.extract_client_id_from_form_params(request)
|
85
111
|
end
|
86
112
|
|
87
|
-
record_response_route :post,
|
113
|
+
record_response_route :post, QUESTIONNAIRE_PACKAGE_PATH, QUESTIONNAIRE_TAG,
|
88
114
|
method(:payer_questionnaire_response), resumes: method(:test_resumes?) do |request|
|
89
115
|
DTRPayerServerSuite.extract_bearer_token(request)
|
90
116
|
end
|
91
117
|
|
92
|
-
record_response_route :post,
|
118
|
+
record_response_route :post, NEXT_PATH, NEXT_TAG,
|
93
119
|
method(:questionnaire_next_response), resumes: method(:test_resumes?) do |request|
|
94
120
|
DTRPayerServerSuite.extract_bearer_token(request)
|
95
121
|
end
|
@@ -36,6 +36,16 @@ module DaVinciDTRTestKit
|
|
36
36
|
validate_cql_executed(questionnaire_response.item, questionnaire_cql_expression_link_ids,
|
37
37
|
template_prepopulation_expectations, template_override_expectations, validation_errors)
|
38
38
|
|
39
|
+
if template_prepopulation_expectations.size.positive?
|
40
|
+
validation_errors << 'Items expected to be pre-populated not found: ' \
|
41
|
+
"#{template_prepopulation_expectations.keys.join(', ')}"
|
42
|
+
end
|
43
|
+
|
44
|
+
if template_override_expectations.size.positive?
|
45
|
+
validation_errors << 'Items expected to be pre-poplated and overridden not found: ' \
|
46
|
+
"#{template_override_expectations.keys.join(', ')}"
|
47
|
+
end
|
48
|
+
|
39
49
|
validation_errors.each { |msg| messages << { type: 'error', message: msg } }
|
40
50
|
assert validation_errors.blank?, 'QuestionnaireResponse is not conformant. Check messages for issues found.'
|
41
51
|
end
|
@@ -47,10 +57,11 @@ module DaVinciDTRTestKit
|
|
47
57
|
link_id = item_to_validate.linkId
|
48
58
|
if questionnaire_cql_expression_link_ids.include?(link_id)
|
49
59
|
if template_prepopulation_expectations.key?(link_id)
|
50
|
-
check_item_prepopulation(item_to_validate, template_prepopulation_expectations
|
51
|
-
false)
|
60
|
+
check_item_prepopulation(item_to_validate, template_prepopulation_expectations.delete(link_id),
|
61
|
+
error_messages, false)
|
52
62
|
elsif template_override_expectations.include?(link_id)
|
53
|
-
check_item_prepopulation(item_to_validate, template_override_expectations
|
63
|
+
check_item_prepopulation(item_to_validate, template_override_expectations.delete(link_id), error_messages,
|
64
|
+
true)
|
54
65
|
else
|
55
66
|
raise "template missing expectation for question `#{link_id}`"
|
56
67
|
end
|
@@ -84,11 +95,10 @@ module DaVinciDTRTestKit
|
|
84
95
|
raise "Template QuestionnaireResponse item `#{target_link_id}` missing the `origin.source` extension"
|
85
96
|
end
|
86
97
|
|
87
|
-
# TODO: handle other data types
|
88
98
|
if source_extension.value == 'auto'
|
89
|
-
expected_prepopulated[target_link_id] = target_item_answer
|
99
|
+
expected_prepopulated[target_link_id] = target_item_answer
|
90
100
|
elsif source_extension.value == 'override'
|
91
|
-
expected_overrides[target_link_id] = target_item_answer
|
101
|
+
expected_overrides[target_link_id] = target_item_answer
|
92
102
|
else
|
93
103
|
raise "`origin.source` extension for item `#{target_link_id}` has unexpected value: #{source_extension.value}"
|
94
104
|
end
|
@@ -114,12 +124,12 @@ module DaVinciDTRTestKit
|
|
114
124
|
answer = item.answer.first
|
115
125
|
if answer&.value&.present?
|
116
126
|
# check answer
|
117
|
-
if override && answer
|
127
|
+
if override && answer_value_equal?(expected_answer, answer)
|
118
128
|
error_list << "Answer to item `#{item.linkId}` was not overriden from the pre-populated value. " \
|
119
129
|
"Found #{expected_answer}, but should be different"
|
120
|
-
elsif !override && answer
|
121
|
-
error_list << "answer to item `#{item.linkId}` contains unexpected value. Expected:
|
122
|
-
|
130
|
+
elsif !override && !answer_value_equal?(expected_answer, answer)
|
131
|
+
error_list << "answer to item `#{item.linkId}` contains unexpected value. Expected: \
|
132
|
+
#{value_for_display(expected_answer)}. Found #{value_for_display(answer)}"
|
123
133
|
end
|
124
134
|
|
125
135
|
# check origin.source extension
|
@@ -176,5 +186,11 @@ module DaVinciDTRTestKit
|
|
176
186
|
def coding_equal?(expected, actual)
|
177
187
|
expected.system == actual&.system && expected.code == actual&.code
|
178
188
|
end
|
189
|
+
|
190
|
+
def value_for_display(answer)
|
191
|
+
return "#{answer.value&.system}|#{answer.value&.code}" if answer.valueCoding.present?
|
192
|
+
|
193
|
+
answer.value
|
194
|
+
end
|
179
195
|
end
|
180
196
|
end
|
@@ -11,8 +11,9 @@ require_relative 'version'
|
|
11
11
|
|
12
12
|
module DaVinciDTRTestKit
|
13
13
|
class DTRSmartAppSuite < Inferno::TestSuite
|
14
|
-
extend
|
14
|
+
extend MockAuthServer
|
15
15
|
extend MockEHR
|
16
|
+
extend MockPayer
|
16
17
|
|
17
18
|
id :dtr_smart_app
|
18
19
|
title 'Da Vinci DTR SMART App Test Suite'
|
@@ -20,48 +21,92 @@ module DaVinciDTRTestKit
|
|
20
21
|
|
21
22
|
version VERSION
|
22
23
|
|
23
|
-
|
24
|
-
|
25
|
-
|
24
|
+
links [
|
25
|
+
{
|
26
|
+
label: 'Report Issue',
|
27
|
+
url: 'https://github.com/inferno-framework/davinci-dtr-test-kit/issues'
|
28
|
+
},
|
29
|
+
{
|
30
|
+
label: 'Open Source',
|
31
|
+
url: 'https://github.com/inferno-framework/davinci-dtr-test-kit'
|
32
|
+
},
|
33
|
+
{
|
34
|
+
label: 'Download',
|
35
|
+
url: 'https://github.com/inferno-framework/davinci-dtr-test-kit/releases'
|
36
|
+
},
|
37
|
+
{
|
38
|
+
label: 'Implementation Guide',
|
39
|
+
url: 'https://hl7.org/fhir/us/davinci-dtr/STU2/'
|
40
|
+
}
|
41
|
+
]
|
42
|
+
|
43
|
+
# Hl7 Validator Wrapper:
|
44
|
+
fhir_resource_validator do
|
45
|
+
igs 'hl7.fhir.us.davinci-dtr#2.0.1'
|
46
|
+
|
47
|
+
exclude_message do |message|
|
48
|
+
message.message.match?(/\A\S+: \S+: URL value '.*' does not resolve/)
|
49
|
+
end
|
26
50
|
end
|
27
51
|
|
28
|
-
allow_cors QUESTIONNAIRE_PACKAGE_PATH, QUESTIONNAIRE_RESPONSE_PATH, FHIR_RESOURCE_PATH, FHIR_SEARCH_PATH
|
52
|
+
allow_cors QUESTIONNAIRE_PACKAGE_PATH, QUESTIONNAIRE_RESPONSE_PATH, FHIR_RESOURCE_PATH, FHIR_SEARCH_PATH,
|
53
|
+
EHR_AUTHORIZE_PATH, EHR_TOKEN_PATH
|
29
54
|
|
30
55
|
route(:get, '/fhir/metadata', method(:metadata_handler))
|
31
56
|
|
32
|
-
|
33
|
-
|
57
|
+
route(:get, SMART_CONFIG_PATH, method(:ehr_smart_config))
|
58
|
+
|
59
|
+
record_response_route :get, EHR_AUTHORIZE_PATH, 'dtr_smart_app_ehr_authorize', method(:ehr_authorize),
|
60
|
+
resumes: ->(_) { false } do |request|
|
61
|
+
DTRSmartAppSuite.extract_client_id_from_query_params(request)
|
62
|
+
end
|
63
|
+
|
64
|
+
record_response_route :post, EHR_AUTHORIZE_PATH, 'dtr_smart_app_authorize', method(:ehr_authorize),
|
65
|
+
resumes: ->(_) { false } do |request|
|
66
|
+
DTRSmartAppSuite.extract_client_id_from_form_params(request)
|
67
|
+
end
|
68
|
+
|
69
|
+
record_response_route :post, EHR_TOKEN_PATH, 'dtr_smart_app_ehr_token', method(:ehr_token_response),
|
70
|
+
resumes: ->(_) { false } do |request|
|
71
|
+
DTRSmartAppSuite.extract_client_id_from_token_request(request)
|
72
|
+
end
|
73
|
+
|
74
|
+
record_response_route :post, PAYER_TOKEN_PATH, 'dtr_smart_app_payer_token',
|
75
|
+
method(:payer_token_response) do |request|
|
76
|
+
DTRSmartAppSuite.extract_client_id_from_client_assertion(request)
|
34
77
|
end
|
35
78
|
|
36
79
|
record_response_route :post, QUESTIONNAIRE_PACKAGE_PATH, QUESTIONNAIRE_PACKAGE_TAG,
|
37
80
|
method(:questionnaire_package_response), resumes: ->(_) { false } do |request|
|
38
|
-
DTRSmartAppSuite.
|
81
|
+
DTRSmartAppSuite.extract_client_id_from_bearer_token(request)
|
39
82
|
end
|
40
83
|
|
41
84
|
record_response_route :post, QUESTIONNAIRE_RESPONSE_PATH, 'dtr_smart_app_questionnaire_response',
|
42
85
|
method(:questionnaire_response_response) do |request|
|
43
|
-
DTRSmartAppSuite.
|
86
|
+
DTRSmartAppSuite.extract_client_id_from_bearer_token(request)
|
44
87
|
end
|
45
88
|
|
46
89
|
record_response_route :get, FHIR_RESOURCE_PATH, SMART_APP_EHR_REQUEST_TAG, method(:get_fhir_resource),
|
47
90
|
resumes: ->(_) { false } do |request|
|
48
|
-
DTRSmartAppSuite.
|
91
|
+
DTRSmartAppSuite.extract_client_id_from_bearer_token(request)
|
49
92
|
end
|
50
93
|
|
51
94
|
record_response_route :get, FHIR_SEARCH_PATH, SMART_APP_EHR_REQUEST_TAG, method(:get_fhir_resource),
|
52
95
|
resumes: ->(_) { false } do |request|
|
53
|
-
DTRSmartAppSuite.
|
96
|
+
DTRSmartAppSuite.extract_client_id_from_bearer_token(request)
|
54
97
|
end
|
55
98
|
|
56
99
|
resume_test_route :get, RESUME_PASS_PATH do |request|
|
57
|
-
DTRSmartAppSuite.
|
100
|
+
DTRSmartAppSuite.extract_client_id_from_query_params(request)
|
58
101
|
end
|
59
102
|
|
60
103
|
resume_test_route :get, RESUME_FAIL_PATH, result: 'fail' do |request|
|
61
|
-
DTRSmartAppSuite.
|
104
|
+
DTRSmartAppSuite.extract_client_id_from_query_params(request)
|
62
105
|
end
|
63
106
|
|
64
|
-
|
107
|
+
# TODO: Update based on SMART Launch changes. Do we even want to have this group now?
|
108
|
+
# group from: :oauth2_authentication
|
109
|
+
|
65
110
|
group do
|
66
111
|
id :dtr_smart_app_basic_workflows
|
67
112
|
title 'Basic Workflows'
|
@@ -7,6 +7,7 @@
|
|
7
7
|
"resource": {
|
8
8
|
"resourceType": "Questionnaire",
|
9
9
|
"id": "DinnerOrderStatic",
|
10
|
+
"url": "urn:inferno:dtr-test-kit:dinner-order-static",
|
10
11
|
"meta": {
|
11
12
|
"profile": [
|
12
13
|
"http://hl7.org/fhir/StructureDefinition/cqf-questionnaire",
|
@@ -21,7 +22,6 @@
|
|
21
22
|
],
|
22
23
|
"name": "DinnerOrderStatic",
|
23
24
|
"title": "Dinner Order (Static)",
|
24
|
-
"url": "urn:inferno:dtr-test-kit:dinner-order-static",
|
25
25
|
"status": "draft",
|
26
26
|
"subjectType": [
|
27
27
|
"Patient"
|
@@ -5,6 +5,7 @@
|
|
5
5
|
"resource": {
|
6
6
|
"resourceType": "Questionnaire",
|
7
7
|
"id": "RespiratoryAssistDevices",
|
8
|
+
"url": "urn:inferno:dtr-test-kit:respiratory-assist-devices",
|
8
9
|
"meta": {
|
9
10
|
"profile": [
|
10
11
|
"http://hl7.org/fhir/StructureDefinition/cqf-questionnaire",
|
@@ -453,7 +454,6 @@
|
|
453
454
|
],
|
454
455
|
"name": "RespiratoryAssistDevices",
|
455
456
|
"title": "Respiratory Assist Device Questionnaire",
|
456
|
-
"url": "urn:fake",
|
457
457
|
"status": "draft",
|
458
458
|
"subjectType": [
|
459
459
|
"Patient"
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require_relative 'urls'
|
2
|
+
require_relative 'fixtures'
|
3
|
+
|
4
|
+
module DaVinciDTRTestKit
|
5
|
+
module MockAuthServer
|
6
|
+
include Fixtures
|
7
|
+
|
8
|
+
def ehr_smart_config(env)
|
9
|
+
protocol = env['rack.url_scheme']
|
10
|
+
host = env['HTTP_HOST']
|
11
|
+
path = env['REQUEST_PATH'] || env['PATH_INFO']
|
12
|
+
path.gsub!(%r{#{SMART_CONFIG_PATH}(/)?}, '')
|
13
|
+
base_url = "#{protocol}://#{host + path}"
|
14
|
+
response_body =
|
15
|
+
{
|
16
|
+
authorization_endpoint: base_url + EHR_AUTHORIZE_PATH,
|
17
|
+
token_endpoint: base_url + EHR_TOKEN_PATH,
|
18
|
+
token_endpoint_auth_methods_supported: ['private_key_jwt'],
|
19
|
+
token_endpoint_auth_signing_alg_values_supported: ['RS256'],
|
20
|
+
grant_types_supported: ['authorization_code'],
|
21
|
+
scopes_supported: ['launch', 'patient/*.rs', 'user/*.rs', 'offline_access'],
|
22
|
+
response_types_supported: ['code'],
|
23
|
+
code_challenge_methods_supported: ['S256'],
|
24
|
+
capabilities: [
|
25
|
+
'launch-ehr',
|
26
|
+
'permission-patient',
|
27
|
+
'permission-user',
|
28
|
+
'client-public',
|
29
|
+
'client-confidential-symmetric',
|
30
|
+
'client-confidential-asymmetric'
|
31
|
+
]
|
32
|
+
}.to_json
|
33
|
+
|
34
|
+
[200, { 'Content-Type' => 'application/json', 'Access-Control-Allow-Origin' => '*' }, [response_body]]
|
35
|
+
end
|
36
|
+
|
37
|
+
def ehr_authorize(request, _test = nil, _test_result = nil)
|
38
|
+
# Authorization requests can bet GET or POST
|
39
|
+
params = request.verb == 'get' ? request.query_parameters : URI.decode_www_form(request.request_body)&.to_h
|
40
|
+
if params['redirect_uri'].present?
|
41
|
+
redirect_uri = "#{params['redirect_uri']}?" \
|
42
|
+
"code=#{SecureRandom.hex}&" \
|
43
|
+
"state=#{params['state']}"
|
44
|
+
request.status = 302
|
45
|
+
request.response_headers = { 'Location' => redirect_uri }
|
46
|
+
else
|
47
|
+
request.status = 400
|
48
|
+
request.response_headers = { 'Content-Type': 'application/json' }
|
49
|
+
request.response_body = FHIR::OperationOutcome.new(
|
50
|
+
issue: FHIR::OperationOutcome::Issue.new(severity: 'fatal', code: 'required',
|
51
|
+
details: FHIR::CodeableConcept.new(
|
52
|
+
text: 'No redirect_uri provided'
|
53
|
+
))
|
54
|
+
).to_json
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def ehr_token_response(request, _test = nil, test_result = nil)
|
59
|
+
client_id = extract_client_id_from_token_request(request)
|
60
|
+
token = JWT.encode({ inferno_client_id: client_id }, nil, 'none')
|
61
|
+
response = { access_token: token, token_type: 'bearer', expires_in: 3600 }
|
62
|
+
test_input = JSON.parse(test_result.input_json)
|
63
|
+
smart_app_launch_input = test_input.find { |input| input['name'] == 'smart_app_launch' }
|
64
|
+
|
65
|
+
if smart_app_launch_input.present? && smart_app_launch_input['value'] == 'inferno'
|
66
|
+
fhir_context_input = test_input.find { |input| input['name'] == 'smart_fhir_context' }
|
67
|
+
fhir_context_input_value = fhir_context_input['value'] if fhir_context_input.present?
|
68
|
+
fhir_context = fhir_context_input_value || [
|
69
|
+
{ reference: 'Coverage/cov015' },
|
70
|
+
{ reference: 'DeviceRequest/devreqe0470' }
|
71
|
+
]
|
72
|
+
|
73
|
+
smart_patient_input = test_input.find { |input| input['name'] == 'smart_patient_id' }
|
74
|
+
smart_patient_input_value = smart_patient_input['value'] if smart_patient_input.present?
|
75
|
+
smart_patient = smart_patient_input_value || 'pat015'
|
76
|
+
|
77
|
+
response.merge!({ patient: smart_patient, fhirContext: fhir_context })
|
78
|
+
end
|
79
|
+
request.response_body = response.to_json
|
80
|
+
request.response_headers = { 'Access-Control-Allow-Origin' => '*' }
|
81
|
+
request.status = 200
|
82
|
+
end
|
83
|
+
|
84
|
+
def payer_token_response(request, _test = nil, _test_result = nil)
|
85
|
+
# Placeholder for a more complete mock token endpoint
|
86
|
+
request.response_body = { access_token: SecureRandom.hex, token_type: 'bearer', expires_in: 3600 }.to_json
|
87
|
+
request.status = 200
|
88
|
+
end
|
89
|
+
|
90
|
+
def extract_client_id_from_token_request(request)
|
91
|
+
# Public client || confidential client asymmetric || confidential client symmetric
|
92
|
+
extract_client_id_from_form_params(request) ||
|
93
|
+
extract_client_id_from_client_assertion(request) ||
|
94
|
+
extract_client_id_from_basic_auth(request)
|
95
|
+
end
|
96
|
+
|
97
|
+
def extract_client_id_from_form_params(request)
|
98
|
+
URI.decode_www_form(request.request_body).to_h['client_id']
|
99
|
+
end
|
100
|
+
|
101
|
+
def extract_client_id_from_client_assertion(request)
|
102
|
+
encoded_jwt = URI.decode_www_form(request.request_body).to_h['client_assertion']
|
103
|
+
return unless encoded_jwt.present?
|
104
|
+
|
105
|
+
jwt_payload = JWT.decode(encoded_jwt, nil, false)&.first # skip signature verification
|
106
|
+
jwt_payload['iss'] || jwt_payload['sub'] if jwt_payload.present?
|
107
|
+
end
|
108
|
+
|
109
|
+
def extract_client_id_from_basic_auth(request)
|
110
|
+
encoded_credentials = request.request_header('Authorization')&.value&.split&.last
|
111
|
+
return unless encoded_credentials.present?
|
112
|
+
|
113
|
+
decoded_credentials = Base64.decode64(encoded_credentials)
|
114
|
+
decoded_credentials&.split(':')&.first
|
115
|
+
end
|
116
|
+
|
117
|
+
def extract_client_id_from_query_params(request)
|
118
|
+
request.query_parameters['client_id']
|
119
|
+
end
|
120
|
+
|
121
|
+
def extract_client_id_from_bearer_token(request)
|
122
|
+
token = extract_bearer_token(request)
|
123
|
+
JWT.decode(token, nil, false)&.first&.dig('inferno_client_id')
|
124
|
+
end
|
125
|
+
|
126
|
+
# Header expected to be a bearer token of the form "Bearer: <token>"
|
127
|
+
def extract_bearer_token(request)
|
128
|
+
request.request_header('Authorization')&.value&.split&.last
|
129
|
+
end
|
130
|
+
|
131
|
+
def extract_token_from_query_params(request)
|
132
|
+
request.query_parameters['token']
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -1,11 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'urls'
|
4
|
+
|
3
5
|
module DaVinciDTRTestKit
|
4
6
|
module MockEHR
|
5
7
|
RESOURCE_SERVER_BASE = ENV.fetch('FHIR_REFERENCE_SERVER')
|
6
8
|
RESOURCE_SERVER_BEARER_TOKEN = 'SAMPLE_TOKEN'
|
7
9
|
|
8
|
-
RESPONSE_HEADERS = { 'Content-Type'
|
10
|
+
RESPONSE_HEADERS = { 'Content-Type' => 'application/json', 'Access-Control-Allow-Origin' => '*' }.freeze
|
9
11
|
|
10
12
|
def resource_server_client
|
11
13
|
return @resource_server_client if @resource_server_client
|
@@ -18,13 +20,13 @@ module DaVinciDTRTestKit
|
|
18
20
|
def metadata_handler(_env)
|
19
21
|
cs = resource_server_client.capability_statement
|
20
22
|
if cs.present?
|
21
|
-
[200,
|
23
|
+
[200, RESPONSE_HEADERS, [cs.to_json]]
|
22
24
|
else
|
23
25
|
[500, {}, ['Unexpected error occurred while fetching metadata']]
|
24
26
|
end
|
25
27
|
end
|
26
28
|
|
27
|
-
def get_fhir_resource(request, _test = nil,
|
29
|
+
def get_fhir_resource(request, _test = nil, test_result = nil)
|
28
30
|
resource_type, id = resource_type_and_id_from_url(request.url)
|
29
31
|
request.response_headers = RESPONSE_HEADERS
|
30
32
|
|
@@ -34,15 +36,7 @@ module DaVinciDTRTestKit
|
|
34
36
|
resource_type = nil
|
35
37
|
end
|
36
38
|
|
37
|
-
if resource_type.
|
38
|
-
response = if id.present?
|
39
|
-
resource_server_client.read(fhir_class, id)
|
40
|
-
else
|
41
|
-
resource_server_client.search(fhir_class, search: { parameters: request.query_parameters })
|
42
|
-
end
|
43
|
-
request.status = response.code
|
44
|
-
request.response_body = response.body
|
45
|
-
else
|
39
|
+
if resource_type.blank?
|
46
40
|
request.status = 400
|
47
41
|
request.response_headers = { 'Content-Type': 'application/json' }
|
48
42
|
request.response_body = FHIR::OperationOutcome.new(
|
@@ -51,7 +45,33 @@ module DaVinciDTRTestKit
|
|
51
45
|
text: 'No recognized resource type in URL'
|
52
46
|
))
|
53
47
|
).to_json
|
48
|
+
return
|
54
49
|
end
|
50
|
+
|
51
|
+
# Respond with user-inputted resource if there is one that matches the request
|
52
|
+
ehr_bundle_input = JSON.parse(test_result.input_json).find { |input| input['name'] == 'ehr_bundle' }
|
53
|
+
ehr_bundle_input_value = ehr_bundle_input_value = ehr_bundle_input['value'] if ehr_bundle_input.present?
|
54
|
+
ehr_bundle = FHIR.from_contents(ehr_bundle_input_value) if ehr_bundle_input_value.present?
|
55
|
+
if id.present? && ehr_bundle.present? && ehr_bundle.is_a?(FHIR::Bundle)
|
56
|
+
matching_resource = ehr_bundle.entry&.find do |entry|
|
57
|
+
entry.resource.is_a?(fhir_class) && entry.resource&.id == id
|
58
|
+
end&.resource
|
59
|
+
if matching_resource.present?
|
60
|
+
request.status = 200
|
61
|
+
request.response_headers = { 'Content-Type': 'application/json' }
|
62
|
+
request.response_body = matching_resource.to_json
|
63
|
+
return
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Proxy resource request to the reference server
|
68
|
+
response = if id.present?
|
69
|
+
resource_server_client.read(fhir_class, id)
|
70
|
+
else
|
71
|
+
resource_server_client.search(fhir_class, search: { parameters: request.query_parameters })
|
72
|
+
end
|
73
|
+
request.status = response.code
|
74
|
+
request.response_body = response.body
|
55
75
|
end
|
56
76
|
|
57
77
|
def questionnaire_response_response(request, _test = nil, _test_result = nil)
|
@@ -6,13 +6,7 @@ module DaVinciDTRTestKit
|
|
6
6
|
module MockPayer
|
7
7
|
include Fixtures
|
8
8
|
|
9
|
-
RESPONSE_HEADERS = { 'Content-Type'
|
10
|
-
|
11
|
-
def token_response(request, _test = nil, _test_result = nil)
|
12
|
-
# Placeholder for a more complete mock token endpoint
|
13
|
-
request.response_body = { access_token: SecureRandom.hex, token_type: 'bearer', expires_in: 300 }.to_json
|
14
|
-
request.status = 200
|
15
|
-
end
|
9
|
+
RESPONSE_HEADERS = { 'Content-Type' => 'application/json', 'Access-Control-Allow-Origin' => '*' }.freeze
|
16
10
|
|
17
11
|
def questionnaire_package_response(request, _test = nil, test_result = nil)
|
18
12
|
request.status = 200
|
@@ -25,7 +19,7 @@ module DaVinciDTRTestKit
|
|
25
19
|
url_input = JSON.parse(test_result.input_json).find { |input| input['name'] == 'url' }
|
26
20
|
client = FHIR::Client.new(url_input['value'])
|
27
21
|
client.default_json
|
28
|
-
endpoint = endpoint_input['value'].nil? ? '/Questionnaire/$questionnaire-package' : endpoint_input['value']
|
22
|
+
endpoint = endpoint_input.to_h['value'].nil? ? '/Questionnaire/$questionnaire-package' : endpoint_input['value']
|
29
23
|
payer_response = client.send(:post, endpoint, JSON.parse(request.request_body),
|
30
24
|
{ 'Content-Type' => 'application/json' })
|
31
25
|
|
@@ -47,19 +41,6 @@ module DaVinciDTRTestKit
|
|
47
41
|
request.response_body = payer_response.response[:body]
|
48
42
|
end
|
49
43
|
|
50
|
-
def extract_client_id(request)
|
51
|
-
URI.decode_www_form(request.request_body).to_h['client_id']
|
52
|
-
end
|
53
|
-
|
54
|
-
# Header expected to be a bearer token of the form "Bearer: <token>"
|
55
|
-
def extract_bearer_token(request)
|
56
|
-
request.request_header('Authorization')&.value&.split&.last
|
57
|
-
end
|
58
|
-
|
59
|
-
def extract_token_from_query_params(request)
|
60
|
-
request.query_parameters['token']
|
61
|
-
end
|
62
|
-
|
63
44
|
def test_resumes?(test)
|
64
45
|
!test.config.options[:accepts_multiple_requests]
|
65
46
|
end
|
@@ -37,11 +37,13 @@ module DaVinciDTRTestKit
|
|
37
37
|
description: 'Manual Flow',
|
38
38
|
type: 'textarea'
|
39
39
|
|
40
|
+
input :access_token, :retrieval_method
|
41
|
+
|
40
42
|
input_order :retrieval_method,
|
41
|
-
:
|
43
|
+
:access_token,
|
42
44
|
:initial_static_questionnaire_request
|
43
45
|
|
44
|
-
test from: :dtr_v201_payer_static_questionnaire_request_test, receives_request: :
|
46
|
+
test from: :dtr_v201_payer_static_questionnaire_request_test, receives_request: :static_questionnaire_request
|
45
47
|
test from: :dtr_v201_payer_static_form_request_validation_test
|
46
48
|
test from: :dtr_v201_payer_static_form_response_test
|
47
49
|
test from: :dtr_v201_payer_static_form_libraries_test
|
@@ -12,8 +12,8 @@ module DaVinciDTRTestKit
|
|
12
12
|
|
13
13
|
run do
|
14
14
|
skip_if retrieval_method == 'Adaptive', 'Performing only adaptive flow tests - only one flow is required.'
|
15
|
-
skip_if scratch[:
|
16
|
-
check_libraries(scratch[:
|
15
|
+
skip_if scratch[:output_parameters].nil?, 'No questionnaire bundle returned.'
|
16
|
+
check_libraries(scratch[:output_parameters])
|
17
17
|
end
|
18
18
|
end
|
19
19
|
end
|
data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_questionnaire_expressions_test.rb
CHANGED
@@ -12,9 +12,9 @@ module DaVinciDTRTestKit
|
|
12
12
|
|
13
13
|
run do
|
14
14
|
skip_if retrieval_method == 'Adaptive', 'Performing only adaptive flow tests - only one flow is required.'
|
15
|
-
skip_if scratch[:
|
16
|
-
questionnaire_items_test(scratch[:
|
17
|
-
scratch[:
|
15
|
+
skip_if scratch[:output_parameters].nil?, 'No questionnaire bundle returned.'
|
16
|
+
questionnaire_items_test(scratch[:output_parameters], final_cql_test: true)
|
17
|
+
scratch[:output_parameters] = nil
|
18
18
|
end
|
19
19
|
end
|
20
20
|
end
|
data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_questionnaire_extensions_test.rb
CHANGED
@@ -12,8 +12,8 @@ module DaVinciDTRTestKit
|
|
12
12
|
|
13
13
|
run do
|
14
14
|
skip_if retrieval_method == 'Adaptive', 'Performing only adaptive flow tests - only one flow is required.'
|
15
|
-
skip_if scratch[:
|
16
|
-
questionnaire_extensions_test(scratch[:
|
15
|
+
skip_if scratch[:output_parameters].nil?, 'No questionnaire bundle returned.'
|
16
|
+
questionnaire_extensions_test(scratch[:output_parameters])
|
17
17
|
end
|
18
18
|
end
|
19
19
|
end
|
@@ -9,6 +9,7 @@ module DaVinciDTRTestKit
|
|
9
9
|
)
|
10
10
|
id :dtr_v201_payer_static_questionnaire_request_test
|
11
11
|
config options: { accepts_multiple_requests: false }
|
12
|
+
input :initial_static_questionnaire_request, :access_token, :retrieval_method, :url
|
12
13
|
|
13
14
|
run do
|
14
15
|
skip_if retrieval_method == 'Adaptive', 'Performing only adaptive flow tests - only one flow is required.'
|