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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_full_ehr_dinner_static_questionnaire_response_conformance_test.rb +15 -0
  3. data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_full_ehr_dinner_static_questionnaire_response_correctness_test.rb +30 -0
  4. data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_full_ehr_questionnaire_workflow_group.rb +15 -5
  5. data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_smart_app_dinner_questionnaire_package_request_test.rb +1 -1
  6. data/lib/davinci_dtr_test_kit/client_groups/shared/dtr_questionnaire_response_basic_conformance_test.rb +3 -7
  7. data/lib/davinci_dtr_test_kit/client_groups/shared/dtr_questionnaire_response_pre_population_test.rb +9 -5
  8. data/lib/davinci_dtr_test_kit/cql_test.rb +182 -137
  9. data/lib/davinci_dtr_test_kit/docs/dtr_light_ehr_suite_description_v201.md +29 -0
  10. data/lib/davinci_dtr_test_kit/dtr_light_ehr_suite.rb +38 -25
  11. data/lib/davinci_dtr_test_kit/dtr_options.rb +7 -0
  12. data/lib/davinci_dtr_test_kit/dtr_questionnaire_response_validation.rb +118 -75
  13. data/lib/davinci_dtr_test_kit/dtr_smart_app_suite.rb +6 -3
  14. data/lib/davinci_dtr_test_kit/fixture_loader.rb +6 -84
  15. data/lib/davinci_dtr_test_kit/fixtures.rb +43 -48
  16. data/lib/davinci_dtr_test_kit/mock_auth_server.rb +101 -18
  17. data/lib/davinci_dtr_test_kit/mock_ehr.rb +32 -24
  18. data/lib/davinci_dtr_test_kit/mock_payer.rb +41 -64
  19. data/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_next_questionnaire_expressions_test.rb +2 -2
  20. data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_request_validation_test.rb +2 -1
  21. data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_response_validation_test.rb +6 -5
  22. data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_next_request_validation_test.rb +1 -1
  23. data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_request_validation_test.rb +2 -1
  24. data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_response_validation_test.rb +6 -4
  25. data/lib/davinci_dtr_test_kit/tags.rb +1 -0
  26. data/lib/davinci_dtr_test_kit/urls.rb +13 -10
  27. data/lib/davinci_dtr_test_kit/validation_test.rb +2 -1
  28. data/lib/davinci_dtr_test_kit/version.rb +1 -1
  29. data/lib/davinci_dtr_test_kit.rb +1 -1
  30. metadata +24 -8
  31. data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_full_ehr_prepopulation_representation_attestation_test.rb +0 -33
  32. data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_full_ehr_questionnaire_workflow_group.rb +0 -19
  33. /data/lib/davinci_dtr_test_kit/fixtures/{pre_populated_questionnaire_response.json → respiratory_assist_device/pre_populated_questionnaire_response.json} +0 -0
  34. /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: 5120a6592cc4d31933d917e3214c89c8d505cdf6f8c3d5065d000588988052a8
4
- data.tar.gz: c850d248ab0ae414fe399d03749d781e9f8c186525d67ba76c0d9cbad509aa17
3
+ metadata.gz: 0a072f135a4057afa53b8836a3436fab457fc8c1ab15199e19608732b16d16cb
4
+ data.tar.gz: e4942f1af9755743eab89b7a0cc8b559929ad8b4be4bed00e7e1a33b4de1b0bd
5
5
  SHA512:
6
- metadata.gz: 852190bb650b3f15d3fe25e79a08ba78778aeb85f836f32f6ed5e925859f14196cfb9d5719246a625c85803db6a606526ee2e735669bed0d574adaac3b23b8d9
7
- data.tar.gz: 30e796f34e0b3e6b9088a2883af8bda32f8ac26d945c66cc6d4070b925c0eb40b362b9f76036d62b07aef6cc89d41e8c133efbb95d1bfb01233aa0289bd5889b
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 'dtr_full_ehr_prepopulation_representation_attestation_test'
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: validate basic conformance of the QuestionnaireResponse
76
- # - not using currently
77
- # Test 3: validate workflow-specific details such as pre-population and overrides
78
- test from: :dtr_full_ehr_dinner_static_prepopulation_representation_attestation
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 or "Open in new window" or "Open in new tab".)
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
- assert_valid_json(request.request_body)
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
@@ -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
- assert_valid_json(request.request_body)
25
- questionnaire_response = FHIR.from_contents(request.request_body)
26
- skip_if !questionnaire_response.present?, 'QuestionnaireResponse not received'
27
-
28
- validate_questionnaire_pre_population(questionnaire_response, id)
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
- @cqf_reference_libraries ||= Set.new
17
+ scratch[:cqf_reference_libraries] ||= Set.new
18
18
  end
19
19
 
20
20
  def library_urls
21
- @@library_urls ||= Set.new
21
+ scratch[:library_urls] ||= Set.new
22
22
  end
23
23
 
24
24
  def library_names
25
- @@library_names ||= Set.new
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 questionnaire_extensions_test(response)
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
- if resource.instance_of? Array
60
- resource.each do |individual_resource|
61
- next unless individual_resource.resourceType == 'QuestionnaireResponse'
53
+ resource.each do |individual_resource|
54
+ next unless individual_resource.resourceType == 'QuestionnaireResponse'
62
55
 
63
- individual_resource.contained.each_with_index do |questionnaire, q_index|
64
- # Do out put parameters have a bundle?
65
- next unless questionnaire.resourceType == 'Questionnaire'
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
- # check the libraries first so references in questionnaires can be checked after
68
- found_questionnaire = true
69
- check_questionnaire_extensions(questionnaire, q_index)
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
- elsif resource.resourceType == 'Parameters'
73
- resource.parameter.each do |param|
74
- # Do out put parameters have a bundle?
75
- next unless param.resource.resourceType == 'Bundle'
64
+ end
65
+ found_questionnaire
66
+ end
76
67
 
77
- param.resource.entry.each_with_index do |entry, q_index|
78
- # check questionnaire extensions
79
- next unless entry.resource.resourceType == 'Questionnaire'
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
- found_questionnaire = true
82
- check_questionnaire_extensions(entry.resource, q_index)
83
- ## NEED TO FIGURE OUT HOW TO FAIL TEST WHEN POORLY FORMATTED EXPRESSIONS FOUND
84
- end
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
- check_library_references
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
- def questionnaire_items_test(response, final_cql_test)
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
- found_bundle = found_questionnaire = false
99
- # are extensions present in any questionnaire?
90
+ found_questionnaire = false
100
91
  if resource.instance_of? Array
101
- resource.each_with_index do |individual_resource, q_index|
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
- resource.parameter.each do |param|
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 = 0
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
- unless found_cqf_lib
190
- messages << { type: 'info',
191
- message: format_markdown("[questionnaire #{q_index + 1}]
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
- end
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
- assert misformatted_expressions.compact.empty?, 'Expression in questionnaire misformatted.'
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 = 0
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
- unless found_item_expressions['found_candidate_expression']
226
- messages << { type: 'info',
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.resource.content.each do |content|
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
- questionnaire_responses = []
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.