davinci_dtr_test_kit 0.11.1 → 0.13.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/auth_groups/token_request_test.rb +1 -1
- data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_full_ehr_dinner_questionnaire_package_request_test.rb +52 -0
- data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_full_ehr_dinner_static_questionnaire_response_conformance_test.rb +15 -0
- data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_full_ehr_dinner_static_questionnaire_response_correctness_test.rb +30 -0
- data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_full_ehr_launch_attestation_test.rb +28 -0
- data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_full_ehr_prepopulation_attestation_test.rb +30 -0
- data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_full_ehr_prepopulation_override_attestation_test.rb +27 -0
- data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_full_ehr_questionnaire_workflow_group.rb +91 -0
- data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_full_ehr_rendering_enabled_questions_attestation_test.rb +30 -0
- data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_full_ehr_store_attestation_test.rb +29 -0
- data/lib/davinci_dtr_test_kit/client_groups/dinner_static/{dtr_dinner_questionnaire_package_request_test.rb → dtr_smart_app_dinner_questionnaire_package_request_test.rb} +5 -5
- data/lib/davinci_dtr_test_kit/client_groups/dinner_static/{prepopulation_attestation_test.rb → dtr_smart_app_prepopulation_attestation_test.rb} +2 -2
- data/lib/davinci_dtr_test_kit/client_groups/dinner_static/{prepopulation_override_attestation_test.rb → dtr_smart_app_prepopulation_override_attestation_test.rb} +2 -2
- data/lib/davinci_dtr_test_kit/client_groups/dinner_static/{dtr_questionnaire_response_save_test.rb → dtr_smart_app_questionnaire_response_save_test.rb} +2 -2
- data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_smart_app_questionnaire_workflow_group.rb +13 -13
- data/lib/davinci_dtr_test_kit/client_groups/dinner_static/{rendering_enabled_questions_attestation_test.rb → dtr_smart_app_rendering_enabled_questions_attestation_test.rb} +2 -2
- data/lib/davinci_dtr_test_kit/client_groups/shared/dtr_questionnaire_package_request_validation_test.rb +1 -1
- data/lib/davinci_dtr_test_kit/client_groups/shared/dtr_questionnaire_response_basic_conformance_test.rb +3 -7
- data/lib/davinci_dtr_test_kit/client_groups/shared/dtr_questionnaire_response_pre_population_test.rb +9 -5
- data/lib/davinci_dtr_test_kit/cql_test.rb +182 -137
- data/lib/davinci_dtr_test_kit/docs/dtr_full_ehr_suite_description_v201.md +127 -0
- data/lib/davinci_dtr_test_kit/docs/dtr_light_ehr_suite_description_v201.md +29 -0
- data/lib/davinci_dtr_test_kit/dtr_full_ehr_suite.rb +4 -12
- data/lib/davinci_dtr_test_kit/dtr_light_ehr_suite.rb +38 -25
- data/lib/davinci_dtr_test_kit/dtr_options.rb +7 -0
- data/lib/davinci_dtr_test_kit/dtr_questionnaire_response_validation.rb +118 -75
- data/lib/davinci_dtr_test_kit/dtr_smart_app_suite.rb +6 -3
- data/lib/davinci_dtr_test_kit/fixture_loader.rb +6 -84
- data/lib/davinci_dtr_test_kit/fixtures.rb +43 -48
- data/lib/davinci_dtr_test_kit/mock_auth_server.rb +101 -18
- data/lib/davinci_dtr_test_kit/mock_ehr.rb +32 -24
- data/lib/davinci_dtr_test_kit/mock_payer.rb +41 -64
- data/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_next_questionnaire_expressions_test.rb +2 -2
- data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_request_validation_test.rb +11 -19
- data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_response_bundles_validation_test.rb +6 -6
- data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_response_search_validation_test.rb +6 -6
- data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_response_validation_test.rb +17 -18
- data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_next_request_validation_test.rb +6 -7
- data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_next_response_validation_test.rb +3 -1
- data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_request_validation_test.rb +10 -21
- data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_response_validation_test.rb +7 -13
- data/lib/davinci_dtr_test_kit/tags.rb +1 -0
- data/lib/davinci_dtr_test_kit/urls.rb +13 -10
- data/lib/davinci_dtr_test_kit/validation_test.rb +8 -9
- data/lib/davinci_dtr_test_kit/version.rb +1 -1
- data/lib/davinci_dtr_test_kit.rb +2 -2
- metadata +37 -12
- data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_full_ehr_questionnaire_workflow_group.rb +0 -19
- /data/lib/davinci_dtr_test_kit/fixtures/{pre_populated_questionnaire_response.json → respiratory_assist_device/pre_populated_questionnaire_response.json} +0 -0
- /data/lib/davinci_dtr_test_kit/fixtures/{questionnaire_package.json → respiratory_assist_device/questionnaire_package.json} +0 -0
@@ -1,18 +1,14 @@
|
|
1
|
+
require 'tls_test_kit'
|
1
2
|
require_relative 'version'
|
3
|
+
require_relative 'dtr_options'
|
4
|
+
require 'smart_app_launch/smart_stu1_suite'
|
5
|
+
require 'smart_app_launch/smart_stu2_suite'
|
2
6
|
|
3
7
|
module DaVinciDTRTestKit
|
4
8
|
class DTRLightEHRSuite < Inferno::TestSuite
|
5
9
|
id :dtr_light_ehr
|
6
10
|
title 'Da Vinci DTR Light EHR Test Suite'
|
7
|
-
description
|
8
|
-
# Da Vinci DTR Light EHR Test Suite
|
9
|
-
|
10
|
-
This suite validates that an EMR or other application
|
11
|
-
can act as a data source for a DTR SMART App. Inferno
|
12
|
-
will act as a DTR SMART App making requests for data
|
13
|
-
against the system under test and storing completed
|
14
|
-
questionnaire responses.
|
15
|
-
)
|
11
|
+
description File.read(File.join(__dir__, 'docs', 'dtr_light_ehr_suite_description_v201.md'))
|
16
12
|
|
17
13
|
version VERSION
|
18
14
|
|
@@ -35,28 +31,45 @@ module DaVinciDTRTestKit
|
|
35
31
|
}
|
36
32
|
]
|
37
33
|
|
38
|
-
# These inputs will be available to all tests in this suite
|
39
34
|
input :url,
|
40
|
-
title: 'FHIR
|
35
|
+
title: 'FHIR Endpoint',
|
36
|
+
description: 'URL of the DTR FHIR server'
|
41
37
|
|
42
|
-
|
43
|
-
|
44
|
-
type: :oauth_credentials,
|
45
|
-
optional: true
|
38
|
+
group do
|
39
|
+
title 'Authorization'
|
46
40
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
41
|
+
group from: :smart_discovery_stu2 do
|
42
|
+
required_suite_options DTROptions::SMART_2_REQUIREMENT
|
43
|
+
run_as_group
|
44
|
+
|
45
|
+
test from: :tls_version_test do
|
46
|
+
title 'DTR FHIR Server is secured by transport layer security'
|
47
|
+
description <<~DESCRIPTION
|
48
|
+
Under [Privacy, Security, and Safety](https://hl7.org/fhir/us/davinci-crd/STU2/security.html),
|
49
|
+
the DTR Implementation Guide imposes the following rule about TLS:
|
50
|
+
As per the [DTR Hook specification](https://cds-hooks.hl7.org/2.0/#security-and-safety),
|
51
|
+
communications between DTR Clients and DTR Servers SHALL
|
52
|
+
use TLS. Mutual TLS is not required by this specification but is permitted. DTR Servers and
|
53
|
+
DTR Clients SHOULD enforce a minimum version and other TLS configuration requirements based
|
54
|
+
on HRex rules for PHI exchange.
|
55
|
+
This test verifies that the FHIR server is using TLS 1.2 or higher.
|
56
|
+
DESCRIPTION
|
52
57
|
|
53
|
-
|
54
|
-
fhir_resource_validator do
|
55
|
-
igs 'hl7.fhir.us.davinci-dtr#2.0.1'
|
58
|
+
id :dtr_server_tls_version_stu2
|
56
59
|
|
57
|
-
|
58
|
-
|
60
|
+
config(
|
61
|
+
options: { minimum_allowed_version: OpenSSL::SSL::TLS1_2_VERSION }
|
62
|
+
)
|
63
|
+
end
|
59
64
|
end
|
65
|
+
|
66
|
+
group from: :smart_ehr_launch_stu2,
|
67
|
+
required_suite_options: DTROptions::SMART_2_REQUIREMENT,
|
68
|
+
run_as_group: true
|
69
|
+
|
70
|
+
group from: :smart_standalone_launch_stu2,
|
71
|
+
required_suite_options: DTROptions::SMART_2_REQUIREMENT,
|
72
|
+
run_as_group: true
|
60
73
|
end
|
61
74
|
end
|
62
75
|
end
|
@@ -1,30 +1,78 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module DaVinciDTRTestKit
|
4
4
|
module DTRQuestionnaireResponseValidation
|
5
|
-
include Fixtures
|
6
|
-
|
7
5
|
CQL_EXPRESSION_EXTENSIONS = [
|
8
6
|
'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression',
|
9
7
|
'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-calculatedExpression',
|
10
8
|
'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-candidateExpression'
|
11
9
|
].freeze
|
12
10
|
|
13
|
-
def
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
11
|
+
def check_is_questionnaire_response(questionnaire_response_json)
|
12
|
+
assert_valid_json(questionnaire_response_json)
|
13
|
+
questionnaire_response = begin
|
14
|
+
FHIR.from_contents(questionnaire_response_json)
|
15
|
+
rescue StandardError
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
|
19
|
+
assert questionnaire_response.present?, 'The QuestionnaireResponse is not a recognized FHIR object'
|
20
|
+
assert_resource_type(:questionnaire_response, resource: questionnaire_response)
|
21
|
+
end
|
22
|
+
|
23
|
+
def verify_basic_conformance(questionnaire_response_json)
|
24
|
+
check_is_questionnaire_response(questionnaire_response_json)
|
25
|
+
assert_valid_resource(resource: FHIR.from_contents(questionnaire_response_json),
|
26
|
+
profile_url: 'http://hl7.org/fhir/us/davinci-dtr/StructureDefinition/dtr-questionnaireresponse|2.0.1')
|
27
|
+
end
|
28
|
+
|
29
|
+
# This only checks answers in the questionnaire response, meaning it does not catch missing answers
|
30
|
+
def check_origin_sources(questionnaire_items, response_items, expected_overrides: [])
|
31
|
+
response_items&.each do |response_item|
|
32
|
+
check_origin_sources(questionnaire_items, response_item.item, expected_overrides:)
|
33
|
+
next unless response_item.answer&.any?
|
34
|
+
|
35
|
+
link_id = response_item.linkId
|
36
|
+
origin_source = find_origin_source(response_item)
|
37
|
+
questionnaire_item = find_item_by_link_id(questionnaire_items, link_id)
|
38
|
+
is_cql_expression = item_is_cql_expression?(questionnaire_item)
|
27
39
|
|
40
|
+
if origin_source.nil?
|
41
|
+
add_message('error', "Required `origin.source` extension not present on answer to item `#{link_id}`")
|
42
|
+
else
|
43
|
+
check_origin_source(origin_source, link_id, is_cql_expression, override: expected_overrides.include?(link_id))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def check_origin_source(origin_source, link_id, is_cql_expression, override: false)
|
49
|
+
if override
|
50
|
+
origin_source_error(link_id, ['override'], origin_source) unless origin_source == 'override'
|
51
|
+
elsif is_cql_expression && !['auto', 'override'].include?(origin_source)
|
52
|
+
origin_source_error(link_id, 'auto or override', origin_source)
|
53
|
+
elsif !is_cql_expression && origin_source != 'manual'
|
54
|
+
origin_source_error(link_id, 'manual', origin_source)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# This checks presence of all answers if link_ids is nil
|
59
|
+
def check_answer_presence(items, link_ids: nil)
|
60
|
+
items.each do |item|
|
61
|
+
check_answer_presence(item.item, link_ids:)
|
62
|
+
|
63
|
+
if !item.answer&.first&.value.present? && (link_ids.nil? || link_ids.include?(item.linkId))
|
64
|
+
add_message('error', "No answer for item #{item.linkId}")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Requirements:
|
70
|
+
# - Prior to exposing the draft QuestionnaireResponse to the user for completion and/or review, the DTR client
|
71
|
+
# SHALL execute all CQL necessary to resolve the initialExpression, candidateExpression and
|
72
|
+
# calculatedExpression extensions found in the Questionnaire for any enabled elements.
|
73
|
+
# - All items that are pre-populated (whether by the payer in the initial QuestionnaireResponse provided in the
|
74
|
+
# questionnaire package, or from data retrieved from the EHR) SHALL have their origin.source set to ‘auto’.
|
75
|
+
def validate_questionnaire_pre_population(questionnaire, template_questionnaire_response, questionnaire_response)
|
28
76
|
questionnaire_cql_expression_link_ids = collect_questionnaire_cql_expression_link_ids(questionnaire.item)
|
29
77
|
template_prepopulation_expectations = {}
|
30
78
|
template_override_expectations = {}
|
@@ -32,45 +80,42 @@ module DaVinciDTRTestKit
|
|
32
80
|
questionnaire_cql_expression_link_ids,
|
33
81
|
template_prepopulation_expectations,
|
34
82
|
template_override_expectations)
|
35
|
-
|
83
|
+
|
36
84
|
validate_cql_executed(questionnaire_response.item, questionnaire_cql_expression_link_ids,
|
37
|
-
template_prepopulation_expectations, template_override_expectations
|
85
|
+
template_prepopulation_expectations, template_override_expectations)
|
38
86
|
|
39
87
|
if template_prepopulation_expectations.size.positive?
|
40
|
-
|
41
|
-
|
88
|
+
add_message('error', %(Items expected to be pre-populated not found:
|
89
|
+
#{template_prepopulation_expectations.keys.join(', ')}))
|
42
90
|
end
|
43
91
|
|
44
92
|
if template_override_expectations.size.positive?
|
45
|
-
|
46
|
-
|
93
|
+
add_message('error', %(Items expected to be pre-poplated and overridden not found:
|
94
|
+
#{template_override_expectations.keys.join(', ')}))
|
47
95
|
end
|
48
96
|
|
49
|
-
|
50
|
-
|
97
|
+
assert(messages.none? { |m| m[:type] == 'error' },
|
98
|
+
'QuestionnaireResponse is not conformant. Check messages for issues found.')
|
51
99
|
end
|
52
100
|
|
53
101
|
def validate_cql_executed(actual_items, questionnaire_cql_expression_link_ids, template_prepopulation_expectations,
|
54
|
-
template_override_expectations
|
102
|
+
template_override_expectations)
|
55
103
|
|
56
104
|
actual_items&.each do |item_to_validate|
|
57
105
|
link_id = item_to_validate.linkId
|
58
106
|
if questionnaire_cql_expression_link_ids.include?(link_id)
|
59
107
|
if template_prepopulation_expectations.key?(link_id)
|
60
|
-
check_item_prepopulation(item_to_validate, template_prepopulation_expectations.delete(link_id),
|
61
|
-
error_messages, false)
|
108
|
+
check_item_prepopulation(item_to_validate, template_prepopulation_expectations.delete(link_id), false)
|
62
109
|
elsif template_override_expectations.include?(link_id)
|
63
|
-
check_item_prepopulation(item_to_validate, template_override_expectations.delete(link_id),
|
64
|
-
true)
|
110
|
+
check_item_prepopulation(item_to_validate, template_override_expectations.delete(link_id), true)
|
65
111
|
else
|
66
112
|
raise "template missing expectation for question `#{link_id}`"
|
67
113
|
end
|
68
114
|
end
|
69
115
|
|
70
116
|
validate_cql_executed(item_to_validate.item, questionnaire_cql_expression_link_ids,
|
71
|
-
template_prepopulation_expectations, template_override_expectations
|
117
|
+
template_prepopulation_expectations, template_override_expectations)
|
72
118
|
end
|
73
|
-
error_messages
|
74
119
|
end
|
75
120
|
|
76
121
|
def extract_expected_answers_from_template(template_questionnaire_response,
|
@@ -87,70 +132,60 @@ module DaVinciDTRTestKit
|
|
87
132
|
raise "Template QuestionnaireResponse missing an answer for item with link id `#{target_link_id}`"
|
88
133
|
end
|
89
134
|
|
90
|
-
|
91
|
-
'http://hl7.org/fhir/us/davinci-dtr/StructureDefinition/information-origin')
|
92
|
-
source_extension = find_extension(origin_extension, 'source')
|
135
|
+
origin_source = find_origin_source(target_item)
|
93
136
|
|
94
|
-
unless
|
137
|
+
unless origin_source.present?
|
95
138
|
raise "Template QuestionnaireResponse item `#{target_link_id}` missing the `origin.source` extension"
|
96
139
|
end
|
97
140
|
|
98
|
-
if
|
141
|
+
if origin_source == 'auto'
|
99
142
|
expected_prepopulated[target_link_id] = target_item_answer
|
100
|
-
elsif
|
143
|
+
elsif origin_source == 'override'
|
101
144
|
expected_overrides[target_link_id] = target_item_answer
|
102
145
|
else
|
103
|
-
raise "`origin.source` extension for item `#{target_link_id}` has unexpected value: #{
|
146
|
+
raise "`origin.source` extension for item `#{target_link_id}` has unexpected value: #{origin_source}"
|
104
147
|
end
|
105
148
|
end
|
106
149
|
end
|
107
150
|
|
108
|
-
def
|
109
|
-
|
110
|
-
|
111
|
-
DATA_REQUIREMENT_ANSWERS.each do |library_name, link_id|
|
112
|
-
expected = find_item_by_link_id(expected_questionnaire_response.item, link_id).answer.first.value
|
113
|
-
actual = find_item_by_link_id(questionnaire_response.item, link_id)&.answer&.first&.value
|
114
|
-
next if coding_equal?(expected, actual)
|
151
|
+
def check_item_prepopulation(item, expected_answer, override)
|
152
|
+
answer = item.answer.first
|
153
|
+
link_id = item.linkId
|
115
154
|
|
116
|
-
|
117
|
-
|
118
|
-
|
155
|
+
unless answer&.value&.present?
|
156
|
+
add_message('error', "No answer for item `#{link_id}`")
|
157
|
+
return
|
119
158
|
end
|
120
|
-
error_messages
|
121
|
-
end
|
122
159
|
|
123
|
-
|
124
|
-
answer = item.answer.first
|
125
|
-
if answer&.value&.present?
|
126
|
-
# check answer
|
127
|
-
if override && answer_value_equal?(expected_answer, answer)
|
128
|
-
error_list << "Answer to item `#{item.linkId}` was not overriden from the pre-populated value. " \
|
129
|
-
"Found #{expected_answer}, but should be different"
|
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)}"
|
133
|
-
end
|
160
|
+
check_answer(link_id, override, expected_answer, answer)
|
134
161
|
|
135
|
-
|
136
|
-
|
137
|
-
'http://hl7.org/fhir/us/davinci-dtr/StructureDefinition/information-origin')
|
138
|
-
source_extension = find_extension(origin_extension, 'source')
|
162
|
+
origin_source = find_origin_source(item)
|
163
|
+
expected_origin_source = override ? 'override' : 'auto'
|
139
164
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
error_list << "`origin.source` extension on item `#{item.linkId}` contains unexpected value. Expected: " \
|
144
|
-
"#{expected_source_value}. Found #{source_extension.value}"
|
145
|
-
end
|
146
|
-
else
|
147
|
-
error_list << "Required `origin.source` extension not present on answer to item `#{item.linkId}`"
|
165
|
+
if origin_source.present?
|
166
|
+
unless origin_source == expected_origin_source
|
167
|
+
origin_source_error(link_id, expected_origin_source, origin_source)
|
148
168
|
end
|
149
169
|
else
|
150
|
-
|
170
|
+
add_message('error', "Required `origin.source` extension not present on answer to item `#{item.linkId}`")
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def check_answer(link_id, override, expected_answer, answer)
|
175
|
+
if override && answer_value_equal?(expected_answer, answer)
|
176
|
+
add_message('error', %(Answer to item `#{link_id}` was not overriden from the pre-populated value.
|
177
|
+
Found #{expected_answer}, but should be different))
|
178
|
+
elsif !override && !answer_value_equal?(expected_answer, answer)
|
179
|
+
add_message('error', %(Answer to item `#{link_id}` contains unexpected value. Expected:
|
180
|
+
#{value_for_display(expected_answer)}. Found #{value_for_display(answer)}))
|
151
181
|
end
|
152
182
|
end
|
153
183
|
|
184
|
+
def origin_source_error(link_id, expected, actual)
|
185
|
+
add_message('error', %(`origin.source` extension on item `#{link_id}` contains unexpected value.
|
186
|
+
Expected: #{expected}. Found: #{actual}))
|
187
|
+
end
|
188
|
+
|
154
189
|
def find_item_by_link_id(items, link_id)
|
155
190
|
items.each do |item|
|
156
191
|
return item if item.linkId == link_id
|
@@ -161,6 +196,14 @@ module DaVinciDTRTestKit
|
|
161
196
|
nil
|
162
197
|
end
|
163
198
|
|
199
|
+
def find_origin_source(item)
|
200
|
+
origin_extension = find_extension(
|
201
|
+
item&.answer&.first,
|
202
|
+
'http://hl7.org/fhir/us/davinci-dtr/StructureDefinition/information-origin'
|
203
|
+
)
|
204
|
+
find_extension(origin_extension, 'source')&.value
|
205
|
+
end
|
206
|
+
|
164
207
|
def find_extension(element, url)
|
165
208
|
element&.extension&.find { |e| e.url == url }
|
166
209
|
end
|
@@ -50,18 +50,21 @@ module DaVinciDTRTestKit
|
|
50
50
|
end
|
51
51
|
|
52
52
|
allow_cors QUESTIONNAIRE_PACKAGE_PATH, QUESTIONNAIRE_RESPONSE_PATH, FHIR_RESOURCE_PATH, FHIR_SEARCH_PATH,
|
53
|
-
EHR_AUTHORIZE_PATH, EHR_TOKEN_PATH
|
53
|
+
EHR_AUTHORIZE_PATH, EHR_TOKEN_PATH, JKWS_PATH, OPENID_CONFIG_PATH
|
54
54
|
|
55
55
|
route(:get, '/fhir/metadata', method(:metadata_handler))
|
56
56
|
|
57
57
|
route(:get, SMART_CONFIG_PATH, method(:ehr_smart_config))
|
58
|
+
route(:get, OPENID_CONFIG_PATH, method(:ehr_openid_config))
|
58
59
|
|
59
|
-
|
60
|
+
route(:get, JKWS_PATH, method(:auth_server_jwks))
|
61
|
+
|
62
|
+
record_response_route :get, EHR_AUTHORIZE_PATH, EHR_AUTHORIZE_TAG, method(:ehr_authorize),
|
60
63
|
resumes: ->(_) { false } do |request|
|
61
64
|
DTRSmartAppSuite.extract_client_id_from_query_params(request)
|
62
65
|
end
|
63
66
|
|
64
|
-
record_response_route :post, EHR_AUTHORIZE_PATH,
|
67
|
+
record_response_route :post, EHR_AUTHORIZE_PATH, EHR_AUTHORIZE_TAG, method(:ehr_authorize),
|
65
68
|
resumes: ->(_) { false } do |request|
|
66
69
|
DTRSmartAppSuite.extract_client_id_from_form_params(request)
|
67
70
|
end
|
@@ -6,94 +6,16 @@ module DaVinciDTRTestKit
|
|
6
6
|
include Singleton
|
7
7
|
|
8
8
|
def initialize
|
9
|
-
|
10
|
-
# - first entry is the path to a file containing a Questionnaire Bundle (not Parameters)
|
11
|
-
# - second entry is the path to a file containing a Populated QuestionnaireResponse
|
12
|
-
# for use in validation.
|
13
|
-
# - third entry is a list of group id within which this questionnaire should be used and
|
14
|
-
# will be returned for any $questionnaire-package calls. This association of a questionnaire
|
15
|
-
# with a specific test is Inferno's simulated version of payer business logic.
|
16
|
-
questionnaires_to_load =
|
17
|
-
[
|
18
|
-
[
|
19
|
-
'fixtures/questionnaire_package.json',
|
20
|
-
'fixtures/pre_populated_questionnaire_response.json',
|
21
|
-
['dtr_smart_app_questionnaire_workflow', 'dtr_full_ehr_questionnaire_workflow']
|
22
|
-
],
|
23
|
-
[
|
24
|
-
'fixtures/dinner_static/questionnaire_dinner_order_static.json',
|
25
|
-
'fixtures/dinner_static/questionnaire_response_dinner_order_static.json',
|
26
|
-
['dtr_smart_app_static_dinner_questionnaire_workflow']
|
27
|
-
],
|
28
|
-
[
|
29
|
-
'fixtures/dinner_adaptive/questionnaire_dinner_order_adaptive.json',
|
30
|
-
'',
|
31
|
-
['dtr_smart_app_adaptive_dinner_questionnaire_workflow']
|
32
|
-
]
|
33
|
-
]
|
34
|
-
|
35
|
-
questionnaires_to_load.each do |questionnaire_details|
|
36
|
-
init_questionnaire_package_and_response(questionnaire_details[0], questionnaire_details[1],
|
37
|
-
questionnaire_details[2])
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
def canonical_to_questionnaire_package_map
|
42
|
-
@canonical_to_questionnaire_package_map ||= {}
|
9
|
+
@cache = {}
|
43
10
|
end
|
44
11
|
|
45
|
-
def
|
46
|
-
|
47
|
-
end
|
48
|
-
|
49
|
-
def group_id_to_questionnaire_canonical_map
|
50
|
-
@group_id_to_questionnaire_canonical_map ||= {}
|
51
|
-
end
|
52
|
-
|
53
|
-
def questionnaire_canonical_for_group_id(group_id)
|
54
|
-
group_id_to_questionnaire_canonical_map[group_id]
|
55
|
-
end
|
56
|
-
|
57
|
-
def questionnaire_package_for_group_id(group_id)
|
58
|
-
canonical = questionnaire_canonical_for_group_id(group_id)
|
59
|
-
return unless canonical
|
60
|
-
|
61
|
-
@canonical_to_questionnaire_package_map[canonical]
|
62
|
-
end
|
12
|
+
def [](path)
|
13
|
+
return unless path.present?
|
63
14
|
|
64
|
-
|
65
|
-
@group_id_to_questionnaire_response_map ||= {}
|
66
|
-
end
|
67
|
-
|
68
|
-
def questionnaire_response_for_group_id(group_id)
|
69
|
-
group_id_to_questionnaire_response_map[group_id]
|
70
|
-
end
|
71
|
-
|
72
|
-
def init_questionnaire_package_and_response(package_file, response_file = nil, covered_groups = nil)
|
73
|
-
package_json = File.read(File.join(__dir__, package_file))
|
74
|
-
package = FHIR.from_contents(package_json)
|
75
|
-
url = get_url_for_questionnaire(package)
|
76
|
-
|
77
|
-
canonical_to_questionnaire_package_map[url] = package if url
|
78
|
-
covered_groups&.each do |group|
|
79
|
-
group_id_to_questionnaire_canonical_map[group] = url
|
80
|
-
end
|
81
|
-
|
82
|
-
if response_file.present?
|
83
|
-
response_json = File.read(File.join(__dir__, response_file))
|
84
|
-
response = FHIR.from_contents(response_json)
|
85
|
-
|
86
|
-
covered_groups&.each do |group|
|
87
|
-
group_id_to_questionnaire_response_map[group] = response
|
88
|
-
end
|
89
|
-
|
90
|
-
end
|
91
|
-
|
92
|
-
package
|
93
|
-
end
|
15
|
+
return @cache[path] if @cache.key?(path)
|
94
16
|
|
95
|
-
|
96
|
-
|
17
|
+
fhir_instance = FHIR.from_contents(File.read(File.join(__dir__, 'fixtures', path)))
|
18
|
+
@cache[path] = fhir_instance
|
97
19
|
end
|
98
20
|
end
|
99
21
|
end
|
@@ -2,64 +2,59 @@ require_relative 'fixture_loader'
|
|
2
2
|
|
3
3
|
module DaVinciDTRTestKit
|
4
4
|
module Fixtures
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
# Ideally the groups and/or tests would define which fixtures they use and this module would simply be a provider
|
6
|
+
# of whatever fixture is requested. But the MockPayer needs to determine which questionnaire package belongs to
|
7
|
+
# each group, so this is a straighforward way to facilitate that.
|
8
|
+
FIXTURE_CONFIG = [
|
9
|
+
{
|
10
|
+
group_ids: [
|
11
|
+
'dtr_smart_app_questionnaire_workflow',
|
12
|
+
'dtr_full_ehr_questionnaire_workflow'
|
13
|
+
],
|
14
|
+
questionnaire_package: File.join('respiratory_assist_device', 'questionnaire_package.json'),
|
15
|
+
questionnaire_response: File.join('respiratory_assist_device', 'pre_populated_questionnaire_response.json')
|
16
|
+
},
|
17
|
+
{
|
18
|
+
group_ids: [
|
19
|
+
'dtr_smart_app_static_dinner_questionnaire_workflow',
|
20
|
+
'dtr_full_ehr_static_dinner_questionnaire_workflow'
|
21
|
+
],
|
22
|
+
questionnaire_package: File.join('dinner_static', 'questionnaire_dinner_order_static.json'),
|
23
|
+
questionnaire_response: File.join('dinner_static', 'questionnaire_response_dinner_order_static.json')
|
24
|
+
}
|
25
|
+
].freeze
|
26
|
+
|
27
|
+
extend self
|
28
|
+
|
29
|
+
# full_test_id needs to be the long inferno-generated ID that includes hyphenated ancestor IDs
|
30
|
+
def questionnaire_package_for_test(full_test_id)
|
31
|
+
get_fixture(full_test_id, :questionnaire_package)
|
9
32
|
end
|
10
33
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
package = get_questionnaire_packcage_for_canonical(canonical_url)
|
16
|
-
return unless package.present?
|
17
|
-
|
18
|
-
questionnaire = nil
|
19
|
-
package.entry.find do |entry|
|
20
|
-
questionnaire = entry.resource if entry.resource.is_a?(FHIR::Questionnaire)
|
21
|
-
end
|
22
|
-
questionnaire
|
34
|
+
# full_test_id needs to be the long inferno-generated ID that includes hyphenated ancestor IDs
|
35
|
+
def questionnaire_response_for_test(full_test_id)
|
36
|
+
get_fixture(full_test_id, :questionnaire_response)
|
23
37
|
end
|
24
38
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
# test_id is of the form [suite id]-[group id 1]-...-[group id n]-[test id]
|
29
|
-
groups = test_id.split('-')[1..-2] # first is suite, last is test, we want groups
|
30
|
-
groups.each do |one_group_id|
|
31
|
-
next if canonical_url.present?
|
32
|
-
|
33
|
-
canonical_url = get_questionnaire_canonical_for_group_id(one_group_id)
|
34
|
-
end
|
35
|
-
|
36
|
-
canonical_url
|
39
|
+
# full_test_id needs to be the long inferno-generated ID that includes hyphenated ancestor IDs
|
40
|
+
def questionnaire_for_test(full_test_id)
|
41
|
+
questionnaire_package_for_test(full_test_id)&.entry&.find { |e| e.resource.is_a?(FHIR::Questionnaire) }&.resource
|
37
42
|
end
|
38
43
|
|
39
|
-
|
40
|
-
FixtureLoader.instance.questionnaire_canonical_for_group_id(group_id)
|
41
|
-
end
|
42
|
-
|
43
|
-
def get_questionnaire_packcage_for_canonical(url)
|
44
|
-
FixtureLoader.instance.questionnaire_package_for_canonical(url)
|
45
|
-
end
|
46
|
-
|
47
|
-
def find_questionnaire_response_for_test_id(test_id)
|
48
|
-
questionnaire_response = nil
|
49
|
-
|
50
|
-
# test_id is of the form [suite id]-[group id 1]-...-[group id n]-[test id]
|
51
|
-
groups = test_id.split('-')[1..-2] # first is suite, last is test, we want groups
|
52
|
-
groups.each do |one_group_id|
|
53
|
-
next if questionnaire_response.present?
|
44
|
+
private
|
54
45
|
|
55
|
-
|
56
|
-
|
46
|
+
def get_fixture(full_test_id, fixture_type)
|
47
|
+
fixture_path = extract_group_ids(full_test_id).filter_map do |group_id|
|
48
|
+
FIXTURE_CONFIG.find { |fc| fc[:group_ids].include?(group_id) }&.dig(fixture_type)
|
49
|
+
end&.first
|
57
50
|
|
58
|
-
|
51
|
+
FixtureLoader.instance[fixture_path]
|
59
52
|
end
|
60
53
|
|
61
|
-
|
62
|
-
|
54
|
+
# full_test_id is of the form [suite id]-[group id 1]-...-[group id n]-[test id]
|
55
|
+
def extract_group_ids(full_test_id)
|
56
|
+
# First is suite, last is test, we want groups
|
57
|
+
full_test_id.split('-')[1...-1]&.reverse
|
63
58
|
end
|
64
59
|
end
|
65
60
|
end
|