davinci_dtr_test_kit 0.12.0 → 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/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_questionnaire_workflow_group.rb +15 -5
- data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_smart_app_dinner_questionnaire_package_request_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_light_ehr_suite_description_v201.md +29 -0
- 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 +2 -1
- data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_response_validation_test.rb +6 -5
- data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_next_request_validation_test.rb +1 -1
- data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_request_validation_test.rb +2 -1
- data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_response_validation_test.rb +6 -4
- 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 +2 -1
- data/lib/davinci_dtr_test_kit/version.rb +1 -1
- data/lib/davinci_dtr_test_kit.rb +1 -1
- metadata +24 -8
- data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_full_ehr_prepopulation_representation_attestation_test.rb +0 -33
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0a072f135a4057afa53b8836a3436fab457fc8c1ab15199e19608732b16d16cb
|
4
|
+
data.tar.gz: e4942f1af9755743eab89b7a0cc8b559929ad8b4be4bed00e7e1a33b4de1b0bd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aae323b007b88a564283b1fc946490e3c2330d6b3081de4783c0611c25afd732327e631d7e782743bab1fe1cab797dad84bcd9403accedcdb21f464278d05f0f
|
7
|
+
data.tar.gz: e68f011efaa3f0785d94c7a1f683a9986474dc887f3b99492b62ccdaf5f3ecb92387be39c909872a6f6448dfc6cb8dc1520de971557d8325cf725b3fe6853771
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require_relative '../../dtr_questionnaire_response_validation'
|
2
|
+
|
3
|
+
module DaVinciDTRTestKit
|
4
|
+
class DTRFullEHRDinnerStaticQuestionnaireResponseConformanceTest < Inferno::Test
|
5
|
+
include DTRQuestionnaireResponseValidation
|
6
|
+
|
7
|
+
id :dtr_full_ehr_dinner_static_questionnaire_response_conformance
|
8
|
+
title 'QuestionnaireResponse is conformant'
|
9
|
+
|
10
|
+
run do
|
11
|
+
skip_if questionnaire_response.nil?, 'Completed QuestionnaireResponse input was blank'
|
12
|
+
verify_basic_conformance(questionnaire_response)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require_relative '../../dtr_questionnaire_response_validation'
|
2
|
+
|
3
|
+
module DaVinciDTRTestKit
|
4
|
+
class DTRFullEHRDinnerStaticQuestionnaireResponseCorrectnessTest < Inferno::Test
|
5
|
+
include DTRQuestionnaireResponseValidation
|
6
|
+
|
7
|
+
id :dtr_full_ehr_dinner_static_questionnaire_response_correctness
|
8
|
+
title 'QuestionnaireResponse is correct for the workflow'
|
9
|
+
description %(
|
10
|
+
The QuestionnaireResponse aligns with the following expectations for the workflow. This includes checks for
|
11
|
+
the presence of the following answers and their appropriate origin source extensions:
|
12
|
+
|
13
|
+
- PBD.1 (Last Name): `auto`
|
14
|
+
- PBD.2 (First Name): `override`
|
15
|
+
- 3.1 (dinner choice): `manual`
|
16
|
+
)
|
17
|
+
|
18
|
+
run do
|
19
|
+
skip_if questionnaire_response.nil?, 'Completed QuestionnaireResponse input was blank'
|
20
|
+
check_is_questionnaire_response(questionnaire_response)
|
21
|
+
|
22
|
+
questionnaire = Fixtures.questionnaire_for_test(id)
|
23
|
+
# questionnaire = Fixtures.find_questionnaire('DinnerOrderStatic')
|
24
|
+
qr = FHIR.from_contents(questionnaire_response)
|
25
|
+
check_origin_sources(questionnaire.item, qr.item, expected_overrides: ['PBD.2'])
|
26
|
+
check_answer_presence(qr.item, link_ids: ['PBD.1', 'PBD.2', 'LOC.1'])
|
27
|
+
assert(messages.none? { |m| m[:type] == 'error' }, 'QuestionnaireResponse is not correct, see error message(s)')
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -5,7 +5,8 @@ require_relative 'dtr_full_ehr_prepopulation_attestation_test'
|
|
5
5
|
require_relative 'dtr_full_ehr_prepopulation_override_attestation_test'
|
6
6
|
require_relative 'dtr_full_ehr_rendering_enabled_questions_attestation_test'
|
7
7
|
require_relative 'dtr_full_ehr_store_attestation_test'
|
8
|
-
require_relative '
|
8
|
+
require_relative 'dtr_full_ehr_dinner_static_questionnaire_response_conformance_test'
|
9
|
+
require_relative 'dtr_full_ehr_dinner_static_questionnaire_response_correctness_test'
|
9
10
|
|
10
11
|
module DaVinciDTRTestKit
|
11
12
|
class DTRFullEHRStaticDinnerQuestionnaireWorkflowGroup < Inferno::TestGroup
|
@@ -68,14 +69,23 @@ module DaVinciDTRTestKit
|
|
68
69
|
The tester will attest to the completion of the questionnaire such that
|
69
70
|
the results are stored for later use.
|
70
71
|
)
|
72
|
+
input :questionnaire_response,
|
73
|
+
type: 'textarea',
|
74
|
+
title: 'Completed QuestionnaireResponse',
|
75
|
+
optional: true,
|
76
|
+
description: %(
|
77
|
+
The QuestionnaireResponse as exported from the EHR after completion of the Questionnaire. IMPORTANT: If
|
78
|
+
you have not yet run the 'Filling Out the Static Questionnaire' group, leave this blank until you have
|
79
|
+
done so. Then, run just the 'Saving the QuestionnaireResponse' group and populate this input.
|
80
|
+
)
|
71
81
|
run_as_group
|
72
82
|
|
73
83
|
# Test 1: attest QuestionnaireResponse saved
|
74
84
|
test from: :dtr_full_ehr_dinner_static_store_attestation
|
75
|
-
# Test 2:
|
76
|
-
|
77
|
-
# Test 3:
|
78
|
-
test from: :
|
85
|
+
# Test 2: verify basic conformance of the QuestionnaireResponse
|
86
|
+
test from: :dtr_full_ehr_dinner_static_questionnaire_response_conformance
|
87
|
+
# Test 3: check workflow-specific details such as pre-population and overrides
|
88
|
+
test from: :dtr_full_ehr_dinner_static_questionnaire_response_correctness
|
79
89
|
end
|
80
90
|
end
|
81
91
|
end
|
@@ -83,7 +83,7 @@ module DaVinciDTRTestKit
|
|
83
83
|
launch_prompt = if smart_app_launch == 'ehr'
|
84
84
|
%(Launch the DTR SMART App from Inferno by right clicking
|
85
85
|
[this link](#{launch_uri}?iss=#{fhir_base_url}&launch=#{launch_uri})
|
86
|
-
and selecting
|
86
|
+
and selecting "Open in new window" or "Open in new tab".)
|
87
87
|
else
|
88
88
|
%(Launch the SMART App from your EHR.)
|
89
89
|
end
|
@@ -1,8 +1,10 @@
|
|
1
1
|
require_relative '../../urls'
|
2
|
+
require_relative '../../dtr_questionnaire_response_validation'
|
2
3
|
|
3
4
|
module DaVinciDTRTestKit
|
4
5
|
class DTRQuestionnaireResponseBasicConformanceTest < Inferno::Test
|
5
6
|
include URLs
|
7
|
+
include DTRQuestionnaireResponseValidation
|
6
8
|
|
7
9
|
id :dtr_questionnaire_response_basic_conformance
|
8
10
|
title 'QuestionnaireResponse is conformant'
|
@@ -16,13 +18,7 @@ module DaVinciDTRTestKit
|
|
16
18
|
assert request.url == questionnaire_response_url,
|
17
19
|
"Request made to wrong URL: #{request.url}. Should instead be to #{questionnaire_response_url}"
|
18
20
|
|
19
|
-
|
20
|
-
questionnaire_response = FHIR.from_contents(request.request_body)
|
21
|
-
assert questionnaire_response.present?, 'Request does not contain a recognized FHIR object'
|
22
|
-
assert_resource_type(:questionnaire_response, resource: questionnaire_response)
|
23
|
-
|
24
|
-
assert_valid_resource(resource: questionnaire_response,
|
25
|
-
profile_url: 'http://hl7.org/fhir/us/davinci-dtr/StructureDefinition/dtr-questionnaireresponse|2.0.1')
|
21
|
+
verify_basic_conformance(request.request_body)
|
26
22
|
end
|
27
23
|
end
|
28
24
|
end
|
data/lib/davinci_dtr_test_kit/client_groups/shared/dtr_questionnaire_response_pre_population_test.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative '../../dtr_questionnaire_response_validation'
|
4
|
+
require_relative '../../fixtures'
|
2
5
|
|
3
6
|
module DaVinciDTRTestKit
|
4
7
|
class DTRQuestionnaireResponsePrePopulationTest < Inferno::Test
|
@@ -21,11 +24,12 @@ module DaVinciDTRTestKit
|
|
21
24
|
)
|
22
25
|
|
23
26
|
run do
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
27
|
+
questionnaire_response_json = request.request_body
|
28
|
+
check_is_questionnaire_response(questionnaire_response_json)
|
29
|
+
questionnaire_response = FHIR.from_contents(questionnaire_response_json)
|
30
|
+
questionnaire = Fixtures.questionnaire_for_test(id)
|
31
|
+
response_template = Fixtures.questionnaire_response_for_test(id)
|
32
|
+
validate_questionnaire_pre_population(questionnaire, response_template, questionnaire_response)
|
29
33
|
end
|
30
34
|
end
|
31
35
|
end
|
@@ -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,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.
|