davinci_dtr_test_kit 0.9.0 → 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- 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.'
|