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
@@ -14,25 +14,21 @@ module DaVinciDTRTestKit
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def cqf_reference_libraries
|
17
|
-
|
17
|
+
scratch[:cqf_reference_libraries] ||= Set.new
|
18
18
|
end
|
19
19
|
|
20
20
|
def library_urls
|
21
|
-
|
21
|
+
scratch[:library_urls] ||= Set.new
|
22
22
|
end
|
23
23
|
|
24
24
|
def library_names
|
25
|
-
|
25
|
+
scratch[:library_names] ||= Set.new
|
26
26
|
end
|
27
27
|
|
28
28
|
def found_questionnaire
|
29
29
|
@found_questionnaire ||= false
|
30
30
|
end
|
31
31
|
|
32
|
-
def found_bad_library_reference
|
33
|
-
@@found_bad_library_reference ||= false
|
34
|
-
end
|
35
|
-
|
36
32
|
def found_duplicate_library_name
|
37
33
|
@found_duplicate_library_name ||= false
|
38
34
|
end
|
@@ -52,97 +48,63 @@ module DaVinciDTRTestKit
|
|
52
48
|
extension_presence.each_key { |k| extension_presence[k] = false }
|
53
49
|
end
|
54
50
|
|
55
|
-
def
|
56
|
-
resource = process_response(response)
|
57
|
-
assert !resource.nil?, 'Response is null or not a valid type.'
|
51
|
+
def evaluate_responses_extensions(resource)
|
58
52
|
found_questionnaire = false
|
59
|
-
|
60
|
-
|
61
|
-
next unless individual_resource.resourceType == 'QuestionnaireResponse'
|
53
|
+
resource.each do |individual_resource|
|
54
|
+
next unless individual_resource.resourceType == 'QuestionnaireResponse'
|
62
55
|
|
63
|
-
|
64
|
-
|
65
|
-
|
56
|
+
individual_resource.contained.each_with_index do |questionnaire, q_index|
|
57
|
+
# Do out put parameters have a bundle?
|
58
|
+
next unless questionnaire.resourceType == 'Questionnaire'
|
66
59
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
end
|
60
|
+
# check the libraries first so references in questionnaires can be checked after
|
61
|
+
found_questionnaire = true
|
62
|
+
check_questionnaire_extensions(questionnaire, q_index)
|
71
63
|
end
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
next unless param.resource.resourceType == 'Bundle'
|
64
|
+
end
|
65
|
+
found_questionnaire
|
66
|
+
end
|
76
67
|
|
77
|
-
|
78
|
-
|
79
|
-
|
68
|
+
def evaluate_parameters_extensions(resource)
|
69
|
+
found_questionnaire = false
|
70
|
+
resource.parameter.each do |param|
|
71
|
+
# Do out put parameters have a bundle?
|
72
|
+
next unless param.resource.resourceType == 'Bundle'
|
80
73
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
74
|
+
param.resource.entry.each_with_index do |entry, q_index|
|
75
|
+
# check questionnaire extensions
|
76
|
+
next unless entry.resource.resourceType == 'Questionnaire'
|
77
|
+
|
78
|
+
found_questionnaire = true
|
79
|
+
check_questionnaire_extensions(entry.resource, q_index)
|
80
|
+
## NEED TO FIGURE OUT HOW TO FAIL TEST WHEN POORLY FORMATTED EXPRESSIONS FOUND
|
85
81
|
end
|
86
82
|
end
|
87
|
-
|
88
|
-
assert found_questionnaire, 'No questionnaires found.'
|
89
|
-
assert extension_presence.value?(true), 'No extensions found. Questionnaire must demonstrate prepopulation.'
|
90
|
-
assert cql_presence['variable'], 'Variable expression logic not written in CQL.'
|
91
|
-
assert cql_presence['launch_context'], 'Launch context expression logic not written in CQL.'
|
92
|
-
assert cql_presence['pop_context'], 'Population context expression logic not written in CQL.'
|
83
|
+
found_questionnaire
|
93
84
|
end
|
94
85
|
|
95
|
-
|
86
|
+
# extensions
|
87
|
+
def questionnaire_extensions_test(response)
|
96
88
|
resource = process_response(response)
|
97
89
|
assert !resource.nil?, 'Response is null or not a valid type.'
|
98
|
-
|
99
|
-
# are extensions present in any questionnaire?
|
90
|
+
found_questionnaire = false
|
100
91
|
if resource.instance_of? Array
|
101
|
-
|
102
|
-
next unless individual_resource.resourceType == 'QuestionnaireResponse'
|
103
|
-
|
104
|
-
individual_resource.contained.each do |questionnaire|
|
105
|
-
# Do out put parameters have a bundle?
|
106
|
-
next unless questionnaire.resourceType == 'Questionnaire'
|
107
|
-
|
108
|
-
# check the libraries first so references in questionnaires can be checked after
|
109
|
-
found_questionnaire = true
|
110
|
-
check_questionnaire_items(questionnaire, q_index)
|
111
|
-
end
|
112
|
-
end
|
92
|
+
found_questionnaire = evaluate_responses_extensions(resource)
|
113
93
|
elsif resource.resourceType == 'Parameters'
|
114
|
-
|
115
|
-
# Do out put parameters have a bundle?
|
116
|
-
next unless param.resource.resourceType == 'Bundle'
|
117
|
-
|
118
|
-
found_bundle = true
|
119
|
-
# check the libraries first so references in questionnaires can be checked after
|
120
|
-
param.resource.entry.each_with_index do |entry, q_index|
|
121
|
-
if entry.resource.resourceType == 'Questionnaire'
|
122
|
-
found_questionnaire = true
|
123
|
-
check_questionnaire_items(entry.resource, q_index)
|
124
|
-
end
|
125
|
-
end
|
126
|
-
end
|
127
|
-
assert found_bundle, 'No questionnaire bundles found.'
|
128
|
-
end
|
129
|
-
begin
|
130
|
-
assert found_questionnaire, 'No questionnaires found.'
|
131
|
-
assert !found_non_cql_expression, 'Found non-cql expression.'
|
132
|
-
assert !found_bad_library_reference, 'Found expression with no or incorrect reference to library name.'
|
133
|
-
assert extension_presence.value?(true), 'No extensions found. Questionnaire must demonstrate prepopulation.'
|
134
|
-
assert cql_presence['init_expression'], 'Initial expression logic not written in CQL.'
|
135
|
-
assert cql_presence['candidate_expression'], 'Candidate expression logic not written in CQL.'
|
136
|
-
assert cql_presence['context_expression'], 'Context expression logic not written in CQL.'
|
137
|
-
ensure
|
138
|
-
reset_cql_tests if final_cql_test
|
94
|
+
found_questionnaire = evaluate_parameters_extensions(resource)
|
139
95
|
end
|
96
|
+
check_library_references
|
97
|
+
assert found_questionnaire, 'No questionnaires found.'
|
98
|
+
assert extension_presence.value?(true), 'No extensions found. Questionnaire must demonstrate prepopulation.'
|
99
|
+
assert cql_presence['variable'], 'Variable expression logic not written in CQL.'
|
100
|
+
assert cql_presence['launch_context'], 'Launch context expression logic not written in CQL.'
|
101
|
+
assert cql_presence['pop_context'], 'Population context expression logic not written in CQL.'
|
140
102
|
end
|
141
103
|
|
142
104
|
def check_questionnaire_extensions(questionnaire, q_index)
|
143
105
|
# are extensions present in this questionnaire?
|
144
106
|
found_launch_context = found_variable = found_pop_context = found_cqf_lib = false
|
145
|
-
cqf_count =
|
107
|
+
cqf_count = total_cqf_libs(questionnaire.extension)
|
146
108
|
misformatted_expressions = []
|
147
109
|
questionnaire.extension.each_with_index do |extension, index|
|
148
110
|
if extension.url == 'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-launchContext'
|
@@ -165,13 +127,77 @@ module DaVinciDTRTestKit
|
|
165
127
|
end
|
166
128
|
next unless extension.url == 'http://hl7.org/fhir/StructureDefinition/cqf-library'
|
167
129
|
|
168
|
-
cqf_count += 1
|
169
130
|
cqf_reference_libraries.add(extension.valueCanonical)
|
170
131
|
found_cqf_lib = true
|
171
132
|
extension_presence['found_min_cqf_lib'] = true
|
172
133
|
|
173
134
|
check_for_cql(extension, '', index, q_index, extension.url)
|
174
135
|
end
|
136
|
+
add_launch_context_messages(found_launch_context, found_variable, found_pop_context, found_cqf_lib, q_index)
|
137
|
+
return if cqf_count < 1
|
138
|
+
|
139
|
+
add_formatting_messages(misformatted_expressions, q_index)
|
140
|
+
assert misformatted_expressions.compact.empty?, 'Expression in questionnaire misformatted.'
|
141
|
+
end
|
142
|
+
|
143
|
+
# items
|
144
|
+
def evaluate_responses_items(resource)
|
145
|
+
found_questionnaire = false
|
146
|
+
resource.each_with_index do |individual_resource, q_index|
|
147
|
+
next unless individual_resource.resourceType == 'QuestionnaireResponse'
|
148
|
+
|
149
|
+
individual_resource.contained.each do |questionnaire|
|
150
|
+
next unless questionnaire.resourceType == 'Questionnaire'
|
151
|
+
|
152
|
+
# check the libraries first so references in questionnaires can be checked after
|
153
|
+
found_questionnaire = true
|
154
|
+
check_questionnaire_items(questionnaire, q_index)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
found_questionnaire
|
158
|
+
end
|
159
|
+
|
160
|
+
def evaluate_parameters_items(resource)
|
161
|
+
found_bundle = found_questionnaire = false
|
162
|
+
resource.parameter.each do |param|
|
163
|
+
next unless param.resource.resourceType == 'Bundle'
|
164
|
+
|
165
|
+
found_bundle = true
|
166
|
+
# check the libraries first so references in questionnaires can be checked after
|
167
|
+
param.resource.entry.each_with_index do |entry, q_index|
|
168
|
+
if entry.resource.resourceType == 'Questionnaire'
|
169
|
+
found_questionnaire = true
|
170
|
+
check_questionnaire_items(entry.resource, q_index)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
[found_bundle, found_questionnaire]
|
175
|
+
end
|
176
|
+
|
177
|
+
def questionnaire_items_test(response, final_cql_test)
|
178
|
+
resource = process_response(response)
|
179
|
+
assert !resource.nil?, 'Response is null or not a valid type.'
|
180
|
+
found_questionnaire = false
|
181
|
+
# are extensions present in any questionnaire?
|
182
|
+
if resource.instance_of? Array
|
183
|
+
found_questionnaire = evaluate_responses_items(resource)
|
184
|
+
elsif resource.resourceType == 'Parameters'
|
185
|
+
found_bundle, found_questionnaire = evaluate_parameters_items(resource)
|
186
|
+
assert found_bundle, 'No questionnaire bundles found.'
|
187
|
+
end
|
188
|
+
begin
|
189
|
+
assert found_questionnaire, 'No questionnaires found.'
|
190
|
+
assert !found_non_cql_expression, 'Found non-cql expression.'
|
191
|
+
assert extension_presence.value?(true), 'No extensions found. Questionnaire must demonstrate prepopulation.'
|
192
|
+
assert cql_presence['init_expression'], 'Initial expression logic not written in CQL.'
|
193
|
+
assert cql_presence['candidate_expression'], 'Candidate expression logic not written in CQL.'
|
194
|
+
assert cql_presence['context_expression'], 'Context expression logic not written in CQL.'
|
195
|
+
ensure
|
196
|
+
reset_cql_tests if final_cql_test
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def add_launch_context_messages(found_launch_context, found_variable, found_pop_context, found_cqf_lib, q_index)
|
175
201
|
unless found_launch_context
|
176
202
|
messages << { type: 'info',
|
177
203
|
message: format_markdown("[questionnaire #{q_index + 1}] included no launch context.") }
|
@@ -186,19 +212,44 @@ module DaVinciDTRTestKit
|
|
186
212
|
message: format_markdown("[questionnaire #{q_index + 1}]
|
187
213
|
included no item population context.") }
|
188
214
|
end
|
189
|
-
|
190
|
-
|
191
|
-
|
215
|
+
return if found_cqf_lib
|
216
|
+
|
217
|
+
messages << { type: 'info',
|
218
|
+
message: format_markdown("[questionnaire #{q_index + 1}]
|
192
219
|
included no cqf library.") }
|
193
|
-
|
194
|
-
return if cqf_count < 1
|
220
|
+
end
|
195
221
|
|
222
|
+
def add_formatting_messages(misformatted_expressions, q_index)
|
196
223
|
misformatted_expressions.compact.each do |idx|
|
197
224
|
messages << { type: 'info',
|
198
225
|
message: format_markdown("[expression #{idx + 1}] in [questionnaire #{q_index + 1}]
|
199
226
|
does not begin with a reference to an included library name.") }
|
200
227
|
end
|
201
|
-
|
228
|
+
end
|
229
|
+
|
230
|
+
def total_cqf_libs(extensions)
|
231
|
+
cqf_count = 0
|
232
|
+
extensions.each do |extension|
|
233
|
+
next unless extension.url == 'http://hl7.org/fhir/StructureDefinition/cqf-library'
|
234
|
+
|
235
|
+
cqf_count += 1
|
236
|
+
end
|
237
|
+
cqf_count
|
238
|
+
end
|
239
|
+
|
240
|
+
def add_item_messages(found_item_expressions, q_index)
|
241
|
+
unless found_item_expressions['found_candidate_expression']
|
242
|
+
messages << { type: 'info',
|
243
|
+
message: format_markdown("[questionnaire #{q_index + 1}] included no candidate expression.") }
|
244
|
+
end
|
245
|
+
unless found_item_expressions['found_init_expression']
|
246
|
+
messages << { type: 'info',
|
247
|
+
message: format_markdown("[questionnaire #{q_index + 1}] included no initial expression.") }
|
248
|
+
end
|
249
|
+
return if found_item_expressions['found_context_expression']
|
250
|
+
|
251
|
+
messages << { type: 'info',
|
252
|
+
message: format_markdown("[questionnaire #{q_index + 1}] included no context expression.") }
|
202
253
|
end
|
203
254
|
|
204
255
|
def check_questionnaire_items(questionnaire, q_index)
|
@@ -206,13 +257,9 @@ module DaVinciDTRTestKit
|
|
206
257
|
found_item_expressions = { 'found_init_expression' => false,
|
207
258
|
'found_candidate_expression' => false,
|
208
259
|
'found_context_expression' => false }
|
209
|
-
cqf_count =
|
260
|
+
cqf_count = total_cqf_libs(questionnaire.extension)
|
210
261
|
misformatted_expressions = []
|
211
|
-
questionnaire.extension.each do |extension|
|
212
|
-
next unless extension.url == 'http://hl7.org/fhir/StructureDefinition/cqf-library'
|
213
262
|
|
214
|
-
cqf_count += 1
|
215
|
-
end
|
216
263
|
# check questionnaire items
|
217
264
|
questionnaire.item.each_with_index do |item, index|
|
218
265
|
misformatted_expressions.concat(check_nested_items(item, index, q_index, found_item_expressions, item.linkId))
|
@@ -222,18 +269,8 @@ module DaVinciDTRTestKit
|
|
222
269
|
index, q_index, found_item_expressions, item.linkId)
|
223
270
|
end
|
224
271
|
end
|
225
|
-
|
226
|
-
|
227
|
-
message: format_markdown("[questionnaire #{q_index + 1}] included no candidate expression.") }
|
228
|
-
end
|
229
|
-
unless found_item_expressions['found_init_expression']
|
230
|
-
messages << { type: 'info',
|
231
|
-
message: format_markdown("[questionnaire #{q_index + 1}] included no initial expression.") }
|
232
|
-
end
|
233
|
-
unless found_item_expressions['found_context_expression']
|
234
|
-
messages << { type: 'info',
|
235
|
-
message: format_markdown("[questionnaire #{q_index + 1}] included no context expression.") }
|
236
|
-
end
|
272
|
+
add_item_messages(found_item_expressions, q_index)
|
273
|
+
# only care about formatting when there are multiple cqf libs
|
237
274
|
return if cqf_count < 1
|
238
275
|
|
239
276
|
misformatted_expressions.compact.to_set.each do |idx|
|
@@ -244,6 +281,33 @@ module DaVinciDTRTestKit
|
|
244
281
|
assert misformatted_expressions.compact.to_set.empty?, 'Expression in questionnaire misformatted.'
|
245
282
|
end
|
246
283
|
|
284
|
+
def evaluate_library(entry, lib_index)
|
285
|
+
found_cql = found_elm = false
|
286
|
+
entry.resource.content.each do |content|
|
287
|
+
if content.data.nil?
|
288
|
+
messages << { type: 'info',
|
289
|
+
message: format_markdown("[library #{lib_index + 1}] content element included no data.") }
|
290
|
+
end
|
291
|
+
if content.contentType == 'text/cql'
|
292
|
+
found_cql = true
|
293
|
+
elsif content.contentType == 'application/elm+json'
|
294
|
+
found_elm = true
|
295
|
+
else
|
296
|
+
messages << { type: 'info',
|
297
|
+
message: format_markdown("[library #{lib_index + 1}] has non-cql/elm content.") }
|
298
|
+
true
|
299
|
+
end
|
300
|
+
next unless library_names.include? entry.resource.name
|
301
|
+
|
302
|
+
found_duplicate_library_name = true
|
303
|
+
messages << { type: 'info', message: format_markdown("[library #{lib_index + 1}] has a name,
|
304
|
+
#{entry.resource.name}, that is already included in the bundle.") }
|
305
|
+
assert !found_duplicate_library_name, 'Found duplicate library names - all names must be unique.'
|
306
|
+
end
|
307
|
+
assert found_cql, "[library #{lib_index + 1}] does not include CQL."
|
308
|
+
assert found_elm, "[library #{lib_index + 1}] does not include ELM."
|
309
|
+
end
|
310
|
+
|
247
311
|
def check_libraries(payer_response)
|
248
312
|
resource = process_response(payer_response)
|
249
313
|
assert !resource.nil?, 'Response is null or not a valid type.'
|
@@ -259,32 +323,9 @@ module DaVinciDTRTestKit
|
|
259
323
|
next unless entry.resource.resourceType == 'Library'
|
260
324
|
|
261
325
|
found_libraries = true
|
262
|
-
found_cql = found_elm = false
|
263
326
|
library_urls.add(entry.resource.url) unless entry.resource.url.nil?
|
264
|
-
entry
|
265
|
-
if content.data.nil?
|
266
|
-
messages << { type: 'info',
|
267
|
-
message: format_markdown("[library #{index + 1}] content element included no data.") }
|
268
|
-
end
|
269
|
-
if content.contentType == 'text/cql'
|
270
|
-
found_cql = true
|
271
|
-
elsif content.contentType == 'application/elm+json'
|
272
|
-
found_elm = true
|
273
|
-
else
|
274
|
-
messages << { type: 'info',
|
275
|
-
message: format_markdown("[library #{index + 1}] has non-cql/elm content.") }
|
276
|
-
true
|
277
|
-
end
|
278
|
-
next unless library_names.include? entry.resource.name
|
279
|
-
|
280
|
-
found_duplicate_library_name = true
|
281
|
-
messages << { type: 'info', message: format_markdown("[library #{index + 1}] has a name,
|
282
|
-
#{entry.resource.name}, that is already included in the bundle.") }
|
283
|
-
assert !found_duplicate_library_name, 'Found duplicate library names - all names must be unique.'
|
284
|
-
end
|
327
|
+
evaluate_library(entry, index)
|
285
328
|
library_names.add(entry.resource.name)
|
286
|
-
assert found_cql, "[library #{index + 1}] does not include CQL."
|
287
|
-
assert found_elm, "[library #{index + 1}] does not include ELM."
|
288
329
|
end
|
289
330
|
assert found_libraries, 'No Libraries found.'
|
290
331
|
end
|
@@ -364,21 +405,25 @@ module DaVinciDTRTestKit
|
|
364
405
|
end
|
365
406
|
end
|
366
407
|
|
408
|
+
def q_responses(response)
|
409
|
+
questionnaire_responses = []
|
410
|
+
response.each do |resource|
|
411
|
+
fhir_resource = FHIR.from_contents(resource.response_body)
|
412
|
+
questionnaire_responses << fhir_resource if fhir_resource.resourceType == 'QuestionnaireResponse'
|
413
|
+
next unless resource.instance_of? Inferno::Entities::Request
|
414
|
+
|
415
|
+
if fhir_resource.resourceType == 'Questionnaire' || fhir_resource.resourceType == 'Parameters'
|
416
|
+
return fhir_resource
|
417
|
+
end
|
418
|
+
end
|
419
|
+
questionnaire_responses
|
420
|
+
end
|
421
|
+
|
367
422
|
def process_response(response)
|
368
423
|
if response.instance_of?(FHIR::Parameters) || response.instance_of?(FHIR::QuestionnaireResponse)
|
369
424
|
return response
|
370
425
|
elsif response.instance_of? Array
|
371
|
-
|
372
|
-
response.each do |resource|
|
373
|
-
fhir_resource = FHIR.from_contents(resource.response_body)
|
374
|
-
questionnaire_responses << fhir_resource if fhir_resource.resourceType == 'QuestionnaireResponse'
|
375
|
-
next unless resource.instance_of? Inferno::Entities::Request
|
376
|
-
|
377
|
-
if fhir_resource.resourceType == 'Questionnaire' || fhir_resource.resourceType == 'Parameters'
|
378
|
-
return fhir_resource
|
379
|
-
end
|
380
|
-
end
|
381
|
-
return questionnaire_responses
|
426
|
+
return q_responses(response)
|
382
427
|
end
|
383
428
|
|
384
429
|
nil
|
@@ -0,0 +1,127 @@
|
|
1
|
+
The Da Vinci DTR Test Kit Full EHR Suite validates the conformance of SMART apps
|
2
|
+
to the STU 2 version of the HL7® FHIR®
|
3
|
+
[Da Vinci Documentation Templates and Rules (DTR) Implementation Guide](https://hl7.org/fhir/us/davinci-dtr/STU2/).
|
4
|
+
|
5
|
+
## Scope
|
6
|
+
|
7
|
+
These tests are a **DRAFT** intended to allow app implementers to perform
|
8
|
+
preliminary checks of their systems against DTR IG requirements and [provide
|
9
|
+
feedback](https://github.com/inferno-framework/davinci-dtr-test-kit/issues)
|
10
|
+
on the tests. Future versions of these tests may validate other
|
11
|
+
requirements and may change the test validation logic.
|
12
|
+
|
13
|
+
## Test Methodology
|
14
|
+
|
15
|
+
Inferno will simulate a DTR payer server that will response to
|
16
|
+
requests for questionnaires for the EHR under test to interact with.
|
17
|
+
The EHR will be expected to initiate requests to Inferno to elicit responses. Over the
|
18
|
+
course of these interactions, Inferno will seek to observe conformant handling of
|
19
|
+
DTR workflows and requirements around the retrieval, completion, and storage of
|
20
|
+
questionnaires.
|
21
|
+
|
22
|
+
Tests within this suite are associated with specific questionnaires that the EHR will
|
23
|
+
demonstrate completion of. In each case, the EHR under test will initiate a request to
|
24
|
+
the payer server simulated by Inferno for a questionnaire using the
|
25
|
+
`$questionnaire-package` operation. Inferno will always return the specific questionnaire
|
26
|
+
for the test being executed regardless of the input provided by the EHR, though it must
|
27
|
+
be conformant. The EHR will then be asked to complete the questionnaire, including
|
28
|
+
- Pre-populating answers based on directives in the questionnaire
|
29
|
+
- Rendering the questionnaire for users and allowing them to make additional updates.
|
30
|
+
These tests can include specific directions on details to include in the completed
|
31
|
+
questionnaire.
|
32
|
+
- Storing the completed questionnaire for future use as a FHIR QuestionnaireResponse.
|
33
|
+
|
34
|
+
EHRs will be required to complete all questionnaires in the suite, which in aggregate
|
35
|
+
contain all questionnaire features that apps must support. Currently, the suite includes
|
36
|
+
one questionnaire:
|
37
|
+
1. A fictious "dinner" questionnaire created for these tests. It tests basic
|
38
|
+
item rendering and pre-population.
|
39
|
+
Additional questionnaires will be added in the future.
|
40
|
+
|
41
|
+
All requests sent by the app will be checked
|
42
|
+
for conformance to the DTR IG requirements individually and used in aggregate to determine
|
43
|
+
whether required features and functionality are present. HL7® FHIR® resources are
|
44
|
+
validated with the Java validator using `tx.fhir.org` as the terminology server.
|
45
|
+
|
46
|
+
## Running the Tests
|
47
|
+
|
48
|
+
### Quick Start
|
49
|
+
|
50
|
+
In order to run these tests, EHRs must be configured to interact with Inferno's simulated
|
51
|
+
payer server endpoint. The endpoint will be `[URL prefix]/custom/dtr_full_ehr/fhir` where
|
52
|
+
`[URL prefix]` can be inferred from the URL of the test session which will be of the form
|
53
|
+
`[URL prefix]/dtr_full_ehr/[session id]`.
|
54
|
+
|
55
|
+
In order for Inferno to associate requests sent to locations under these base URLs with this session,
|
56
|
+
it needs to know the bearer token that the EHR will send on requests, for which
|
57
|
+
there are two options.
|
58
|
+
|
59
|
+
1. If you want to choose your own bearer token, then
|
60
|
+
1. Select the "2. Basic Workflows" test from the list on the left (or other target test).
|
61
|
+
2. Click the '*Run All Tests*' button on the right.
|
62
|
+
3. In the "access_token" field, enter the bearer token that will be sent by the client
|
63
|
+
under test (as part of the Authorization header - `Bearer <provided value>`).
|
64
|
+
4. Click the '*Submit*' button at the bottom of the dialog.
|
65
|
+
2. If you want to use a client_id to obtain an access token, then
|
66
|
+
1. Click the '*Run All Tests*' button on the right.
|
67
|
+
2. Provide the EHR's registered id "client_id" field of the input (NOTE, Inferno
|
68
|
+
doesn't support the registration API, so this must be obtained from another
|
69
|
+
system or configured manually).
|
70
|
+
3. Click the '*Submit*' button at the bottom of the dialog.
|
71
|
+
4. Make a token request that includes the specified client id to the
|
72
|
+
`[URL prefix]/custom/dtr_full_ehr/mock_auth/token` endpoint to get
|
73
|
+
an access token to use on the request of the requests.
|
74
|
+
|
75
|
+
In either case, the tests will continue from that point. Further executions of tests under
|
76
|
+
this session will also use the selected bearer token.
|
77
|
+
|
78
|
+
Note: authentication options for these tests have not been finalized and are subject to change.
|
79
|
+
|
80
|
+
### Postman-based Demo
|
81
|
+
|
82
|
+
If you do not have a DTR Full EHR but would like to try the tests out, you can use
|
83
|
+
[this Postman collection](https://github.com/inferno-framework/davinci-dtr-test-kit/blob/main/config/DTR%20Full%20EHR%20Tests%20Postman%20Demo.postman_collection.json)
|
84
|
+
to make requests against Inferno. This does not include the capability to render and complete the
|
85
|
+
questionnaires, but does have samples of correctly and incorrectly completed QuestionnaireResponses.
|
86
|
+
To run the tests using this approach:
|
87
|
+
|
88
|
+
1. Install [postman](https://www.postman.com/downloads/).
|
89
|
+
1. Import [this Postman collection](https://github.com/inferno-framework/davinci-dtr-test-kit/blob/main/config/DTR%20Full%20EHR%20Tests%20Postman%20Demo.postman_collection.json).
|
90
|
+
1. Start a Da Vinci DTR Full EHR Test Suite Session.
|
91
|
+
1. Update the postman collection configuration variables found by opening the "DTR Full EHR
|
92
|
+
Tests Postman Demo" collection and selecting the "Variables" tab.
|
93
|
+
- **base_url**: corresponds to the where the test suite session is running. Defaults to
|
94
|
+
`inferno.healthit.gov`. If running in another location, see guidance on the "Overview" tab
|
95
|
+
of the postman collection.
|
96
|
+
- **access_token**: note the "Current value" (update if desired) for use later.
|
97
|
+
1. Return to Inferno and in the test list at the left, select *2 Static Questionnaire Workflow*.
|
98
|
+
1. Click the "Run All Tests" button in the upper right.
|
99
|
+
1. Add the **access_token** configured in postman to the Inferno input with the same name
|
100
|
+
1. Click the "Submit" button in Inferno.
|
101
|
+
1. Attest that the EHR has launched its DTR workflow in Inferno by clicking the link for the **true** response.
|
102
|
+
1. Once the next wait dialog has appeared within Inferno asking for a `$questionnaire-package`
|
103
|
+
request, use postman to submit the "Questionnaire Package for Dinner (Static)" request. Confirm
|
104
|
+
that the response that looks similar to the "Example Working Response" in postman
|
105
|
+
and click the link to continue the tests.
|
106
|
+
1. Attest to the remainder of the tests as desired to get a sense for what is involved in testing
|
107
|
+
with an actual EHR implementation. To see what a valid QuestionnaireResponse looks like, see
|
108
|
+
the "Sample QuestionnaireResponse for Dinner (Static) ..." request in postman.
|
109
|
+
|
110
|
+
## Limitations
|
111
|
+
|
112
|
+
The DTR IG is a complex specification and these tests currently validate conformance to only
|
113
|
+
a subset of IG requirements. Future versions of the test suite will test further
|
114
|
+
features. A few specific features of interest are listed below.
|
115
|
+
|
116
|
+
### Heavy Reliance on Attestations
|
117
|
+
|
118
|
+
Currently, these test kits do not have access to the QuestionnaireResponse and so validation
|
119
|
+
that the EHR performed CQL calculations and generated a conformant QuestionnaireResponse
|
120
|
+
based on pre-population and manual answers is left to a user attestation rather than a
|
121
|
+
mechanical check. Some level of mechanical checks are expected to be added in the future.
|
122
|
+
|
123
|
+
### Questionnaire Feature Coverage
|
124
|
+
|
125
|
+
Not all questionnaire features that are must support within the DTR IG are currently represented
|
126
|
+
in questionnaires tested by the IG. Adaptive questionnaires are a notable omission.
|
127
|
+
Additional questionnaires testing additional features will be added in the future.
|
@@ -0,0 +1,29 @@
|
|
1
|
+
The Da Vinci DTR Test Kit Light EHR Suite validates the conformance of SMART apps
|
2
|
+
to the STU 2 version of the HL7® FHIR®
|
3
|
+
[Da Vinci Documentation Templates and Rules (DTR) Implementation Guide](https://hl7.org/fhir/us/davinci-dtr/STU2/).
|
4
|
+
|
5
|
+
## Scope
|
6
|
+
|
7
|
+
These tests are a **DRAFT** intended to allow app implementers to perform
|
8
|
+
preliminary checks of their systems against DTR IG requirements and [provide
|
9
|
+
feedback](https://github.com/inferno-framework/davinci-dtr-test-kit/issues)
|
10
|
+
on the tests. Future versions of these tests may validate other
|
11
|
+
requirements and may change the test validation logic.
|
12
|
+
|
13
|
+
## Test Methodology
|
14
|
+
|
15
|
+
Inferno will simulate a DTR SMART App that will connect to the DTR Light EHR system under test. The tester will need to launch Inferno using either an EHR launch or a Standalone launch.
|
16
|
+
|
17
|
+
Once the connection between the DTR SMART App and the DTR Light EHR is established, tests within this suite check that the DTR Light EHR API is conformant to US Core and any other requirements outlined in the [Light DTR EHR Capability Statement](https://hl7.org/fhir/us/davinci-dtr/STU2/CapabilityStatement-light-dtr-ehr.html#root).
|
18
|
+
|
19
|
+
## Running the Tests
|
20
|
+
|
21
|
+
If you would like to try out the tests but don't have a DTR payer server implementation, you can run these tests against the [public instance of the Inferno Reference Server](https://inferno.healthit.gov/reference-server/r4/) by using the Inferno Reference Server preset in the test suite.
|
22
|
+
|
23
|
+
In order to get the Inferno QA Reference Server to do an EHR launch, navigate to https://inferno.healthit.gov/reference-server/app/app-launch and use https://inferno.healthit.gov/custom/smart/launch as the App Launch URL.
|
24
|
+
|
25
|
+
## Limitations
|
26
|
+
|
27
|
+
The DTR IG is a complex specification and these tests currently validate conformance to only
|
28
|
+
a subset of IG requirements. Future versions of the test suite will test further
|
29
|
+
features.
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require_relative 'ext/inferno_core/runnable'
|
2
2
|
require_relative 'ext/inferno_core/record_response_route'
|
3
3
|
require_relative 'ext/inferno_core/request'
|
4
|
-
require_relative 'client_groups/
|
4
|
+
require_relative 'client_groups/dinner_static/dtr_full_ehr_questionnaire_workflow_group'
|
5
5
|
require_relative 'auth_groups/oauth2_authentication_group'
|
6
6
|
require_relative 'mock_payer'
|
7
7
|
require_relative 'version'
|
@@ -9,19 +9,11 @@ require_relative 'version'
|
|
9
9
|
module DaVinciDTRTestKit
|
10
10
|
class DTRFullEHRSuite < Inferno::TestSuite
|
11
11
|
extend MockPayer
|
12
|
+
extend MockAuthServer
|
12
13
|
|
13
14
|
id :dtr_full_ehr
|
14
15
|
title 'Da Vinci DTR Full EHR Test Suite'
|
15
|
-
description
|
16
|
-
# Da Vinci DTR Full EHR Test Suite
|
17
|
-
|
18
|
-
This suite validates that an EHR or other application can act
|
19
|
-
as a full DTR application requesting questionnaires from a
|
20
|
-
payer server and using local data to complete and store them.
|
21
|
-
Inferno will act as payer server returning questionnaires
|
22
|
-
in response to queries from the system under test and validating
|
23
|
-
that they can be completed as expected.
|
24
|
-
)
|
16
|
+
description File.read(File.join(__dir__, 'docs', 'dtr_full_ehr_suite_description_v201.md'))
|
25
17
|
|
26
18
|
version VERSION
|
27
19
|
|
@@ -74,6 +66,6 @@ module DaVinciDTRTestKit
|
|
74
66
|
end
|
75
67
|
|
76
68
|
group from: :oauth2_authentication
|
77
|
-
group from: :
|
69
|
+
group from: :dtr_full_ehr_static_dinner_questionnaire_workflow
|
78
70
|
end
|
79
71
|
end
|