davinci_dtr_test_kit 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +201 -0
  3. data/lib/davinci_dtr_test_kit/auth_groups/oauth2_authentication_group.rb +51 -0
  4. data/lib/davinci_dtr_test_kit/auth_groups/token_request_test.rb +25 -0
  5. data/lib/davinci_dtr_test_kit/auth_groups/token_validation_test.rb +13 -0
  6. data/lib/davinci_dtr_test_kit/client_groups/dinner_adaptive/dtr_smart_app_questionnaire_workflow_group.rb +20 -0
  7. data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_questionnaire_response_save_test.rb +31 -0
  8. data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_smart_app_questionnaire_workflow_group.rb +89 -0
  9. data/lib/davinci_dtr_test_kit/client_groups/dinner_static/prepopulation_attestation_test.rb +29 -0
  10. data/lib/davinci_dtr_test_kit/client_groups/dinner_static/prepopulation_override_attestation_test.rb +30 -0
  11. data/lib/davinci_dtr_test_kit/client_groups/dinner_static/rendering_enabled_questions_attestation_test.rb +30 -0
  12. data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_full_ehr_questionnaire_workflow_group.rb +19 -0
  13. data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_questionnaire_package_group.rb +16 -0
  14. data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_questionnaire_rendering_attestation_test.rb +32 -0
  15. data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_questionnaire_rendering_group.rb +14 -0
  16. data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_questionnaire_response_group.rb +23 -0
  17. data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_questionnaire_response_save_test.rb +31 -0
  18. data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_smart_app_questionnaire_workflow_group.rb +22 -0
  19. data/lib/davinci_dtr_test_kit/client_groups/shared/dtr_questionnaire_package_request_test.rb +36 -0
  20. data/lib/davinci_dtr_test_kit/client_groups/shared/dtr_questionnaire_package_request_validation_test.rb +35 -0
  21. data/lib/davinci_dtr_test_kit/client_groups/shared/dtr_questionnaire_response_basic_conformance_test.rb +28 -0
  22. data/lib/davinci_dtr_test_kit/client_groups/shared/dtr_questionnaire_response_pre_population_test.rb +30 -0
  23. data/lib/davinci_dtr_test_kit/cql_test.rb +387 -0
  24. data/lib/davinci_dtr_test_kit/docs/dtr_payer_server_suite_description_v201.md +127 -0
  25. data/lib/davinci_dtr_test_kit/docs/dtr_smart_app_suite_description_v201.md +118 -0
  26. data/lib/davinci_dtr_test_kit/dtr_full_ehr_suite.rb +55 -0
  27. data/lib/davinci_dtr_test_kit/dtr_light_ehr_suite.rb +39 -0
  28. data/lib/davinci_dtr_test_kit/dtr_payer_server_suite.rb +104 -0
  29. data/lib/davinci_dtr_test_kit/dtr_questionnaire_response_validation.rb +180 -0
  30. data/lib/davinci_dtr_test_kit/dtr_smart_app_suite.rb +85 -0
  31. data/lib/davinci_dtr_test_kit/ext/inferno_core/record_response_route.rb +98 -0
  32. data/lib/davinci_dtr_test_kit/ext/inferno_core/request.rb +19 -0
  33. data/lib/davinci_dtr_test_kit/ext/inferno_core/runnable.rb +35 -0
  34. data/lib/davinci_dtr_test_kit/fixture_loader.rb +99 -0
  35. data/lib/davinci_dtr_test_kit/fixtures/dinner_adaptive/dinner_order_adaptive_next_question_burrito.json +170 -0
  36. data/lib/davinci_dtr_test_kit/fixtures/dinner_adaptive/dinner_order_adaptive_next_question_hamburger.json +175 -0
  37. data/lib/davinci_dtr_test_kit/fixtures/dinner_adaptive/dinner_order_adaptive_next_question_initial.json +140 -0
  38. data/lib/davinci_dtr_test_kit/fixtures/dinner_adaptive/questionnaire_dinner_order_adaptive.json +95 -0
  39. data/lib/davinci_dtr_test_kit/fixtures/dinner_static/questionnaire_dinner_order_static.json +283 -0
  40. data/lib/davinci_dtr_test_kit/fixtures/dinner_static/questionnaire_response_dinner_order_static.json +170 -0
  41. data/lib/davinci_dtr_test_kit/fixtures/pre_populated_questionnaire_response.json +581 -0
  42. data/lib/davinci_dtr_test_kit/fixtures/questionnaire_package.json +2121 -0
  43. data/lib/davinci_dtr_test_kit/fixtures.rb +65 -0
  44. data/lib/davinci_dtr_test_kit/mock_ehr.rb +72 -0
  45. data/lib/davinci_dtr_test_kit/mock_payer.rb +142 -0
  46. data/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_form_libraries_test.rb +19 -0
  47. data/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_form_questionnaire_expressions_test.rb +20 -0
  48. data/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_form_questionnaire_extensions_test.rb +19 -0
  49. data/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_next_questionnaire_expressions_test.rb +20 -0
  50. data/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_next_questionnaire_extensions_test.rb +19 -0
  51. data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_group.rb +88 -0
  52. data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_request_test.rb +41 -0
  53. data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_request_validation_test.rb +44 -0
  54. data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_response_bundles_validation_test.rb +40 -0
  55. data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_response_search_validation_test.rb +42 -0
  56. data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_response_validation_test.rb +49 -0
  57. data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_next_request_validation_test.rb +61 -0
  58. data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_next_response_complete_test.rb +17 -0
  59. data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_next_response_validation_test.rb +43 -0
  60. data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_static_group.rb +51 -0
  61. data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_libraries_test.rb +19 -0
  62. data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_questionnaire_expressions_test.rb +20 -0
  63. data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_questionnaire_extensions_test.rb +19 -0
  64. data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_request_test.rb +33 -0
  65. data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_request_validation_test.rb +46 -0
  66. data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_response_validation_test.rb +50 -0
  67. data/lib/davinci_dtr_test_kit/tags.rb +8 -0
  68. data/lib/davinci_dtr_test_kit/urls.rb +50 -0
  69. data/lib/davinci_dtr_test_kit/validation_test.rb +72 -0
  70. data/lib/davinci_dtr_test_kit/version.rb +5 -0
  71. data/lib/davinci_dtr_test_kit.rb +4 -0
  72. metadata +132 -0
@@ -0,0 +1,31 @@
1
+ require_relative '../../urls'
2
+
3
+ module DaVinciDTRTestKit
4
+ class DTRRespAssistQuestionnaireResponseSaveTest < Inferno::Test
5
+ include URLs
6
+
7
+ id :dtr_resp_assist_questionnaire_response_save
8
+ title 'Save the QuestionnaireResponse after completing it'
9
+ description %(
10
+ Inferno, acting as the EHR, will wait for a request to save the QuestionnaireResponse from the client.
11
+ )
12
+ input :access_token
13
+
14
+ run do
15
+ wait(
16
+ identifier: access_token,
17
+ message: %(
18
+ Complete the questionnaire, leaving the following items unmodified, because a subsequent test will expect
19
+ their pre-populated values:
20
+
21
+ - Last Name
22
+ - Patient diagnoses for order
23
+
24
+ Inferno will wait for a POST request at:
25
+
26
+ `#{questionnaire_response_url}`
27
+ )
28
+ )
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,22 @@
1
+ require_relative 'dtr_questionnaire_package_group'
2
+ require_relative 'dtr_questionnaire_rendering_group'
3
+ require_relative 'dtr_questionnaire_response_group'
4
+
5
+ module DaVinciDTRTestKit
6
+ class DTRSmartAppQuestionnaireWorkflowGroup < Inferno::TestGroup
7
+ id :dtr_smart_app_questionnaire_workflow
8
+ title 'Respiratory Assist Device Questionnaire Workflow'
9
+ description %(
10
+ This workflow validates that a DTR SMART App can perform a full DTR Questionnaire workflow using a canned
11
+ Questionnaire for a respiratory assist device order:
12
+
13
+ 1. Fetch the questionnaire package
14
+ 2. Render the questionnaire
15
+ 3. Pre-populate the questionnaire response
16
+ )
17
+
18
+ group from: :dtr_questionnaire_package
19
+ group from: :dtr_questionnaire_rendering
20
+ group from: :dtr_questionnaire_response
21
+ end
22
+ end
@@ -0,0 +1,36 @@
1
+ require_relative '../../urls'
2
+
3
+ module DaVinciDTRTestKit
4
+ class DTRQuestionnairePackageRequestTest < Inferno::Test
5
+ include URLs
6
+
7
+ id :dtr_questionnaire_package_request
8
+ title 'Invoke the DTR Questionnaire Package operation'
9
+ description %(
10
+ Inferno will wait for a DTR questionnaire package request from the client. Upon receipt, Inferno will generate and
11
+ send a response.
12
+ )
13
+ input :access_token
14
+
15
+ run do
16
+ wait(
17
+ identifier: access_token,
18
+ message: %(
19
+ Invoke the DTR Questionnaire Package operation by sending a POST request to
20
+
21
+ `#{questionnaire_package_url}`
22
+
23
+ A questionnaire package generated by Inferno will be returned.
24
+
25
+ Inferno will wait for the client to complete Questionnaire pre-population. The client should make FHIR GET
26
+ requests using service base path:
27
+
28
+ `#{fhir_base_url}`
29
+
30
+ When the DTR application has finished loading the Questionnaire,
31
+ [Click here](#{resume_pass_url}?token=#{access_token}) to continue.
32
+ )
33
+ )
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,35 @@
1
+ require_relative '../../urls'
2
+
3
+ module DaVinciDTRTestKit
4
+ class DTRQuestionnairePackageValidationTest < Inferno::Test
5
+ include URLs
6
+
7
+ id :dtr_questionnaire_package_request_validation
8
+ title 'Questionnaire Package request is valid'
9
+ description %(
10
+ This test validates the conformance of the client's request to the
11
+ [DTR Questionnaire Package Input Parameters](http://hl7.org/fhir/us/davinci-dtr/StructureDefinition/dtr-qpackage-input-parameters)
12
+ structure.
13
+
14
+ It verifies the presence of mandatory elements and that elements with required bindings contain appropriate
15
+ values. CodeableConcept element bindings will fail if none of their codings have a code/system belonging
16
+ to the bound ValueSet. Quantity, Coding, and code element bindings will fail if their code/system are not found in
17
+ the valueset.
18
+ )
19
+
20
+ run do
21
+ load_tagged_requests QUESTIONNAIRE_PACKAGE_TAG
22
+ skip_if request.blank?, 'A Questionnaire Package request must be made prior to running this test'
23
+
24
+ assert request.url == questionnaire_package_url,
25
+ "Request made to wrong URL: #{request.url}. Should instead be to #{questionnaire_package_url}"
26
+
27
+ assert_valid_json(request.request_body)
28
+ input_params = FHIR.from_contents(request.request_body)
29
+ assert input_params.present?, 'Request does not contain a recognized FHIR object'
30
+ assert_resource_type(:parameters, resource: input_params)
31
+ assert_valid_resource(resource: input_params,
32
+ profile_url: 'http://hl7.org/fhir/us/davinci-dtr/StructureDefinition/dtr-qpackage-input-parameters')
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,28 @@
1
+ require_relative '../../urls'
2
+
3
+ module DaVinciDTRTestKit
4
+ class DTRQuestionnaireResponseBasicConformanceTest < Inferno::Test
5
+ include URLs
6
+
7
+ id :dtr_questionnaire_response_basic_conformance
8
+ title 'QuestionnaireResponse is conformant'
9
+ description %(
10
+ This test validates the conformance of QuestionnaireResponse representing the
11
+ completed questionnaire. It verifies that the QuestionnaireResponse conforms
12
+ to the DTR Questionnaire Response resource profile.
13
+ )
14
+
15
+ run do
16
+ assert request.url == questionnaire_response_url,
17
+ "Request made to wrong URL: #{request.url}. Should instead be to #{questionnaire_response_url}"
18
+
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')
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,30 @@
1
+ require_relative '../../dtr_questionnaire_response_validation'
2
+
3
+ module DaVinciDTRTestKit
4
+ class DTRQuestionnaireResponsePrePopulationTest < Inferno::Test
5
+ include DTRQuestionnaireResponseValidation
6
+
7
+ id :dtr_questionnaire_response_pre_population
8
+ title 'QuestionnaireResponse pre-population and user overrides are conformant'
9
+ description %(
10
+ This test validates the conformance of the client's pre-population of the QuestionnaireResponse.
11
+
12
+ It verifies:
13
+
14
+ 1. All items that should be pre-populated by CQL execution have an answer
15
+ 2. Pre-populated answers the tester was not directed to override have
16
+ the origin.source extension set to 'auto' and an answer equivalent to
17
+ from the expected result from execution of the CQL on Inferno's data.
18
+ 3. Pre-populated answers the tester was directed to override have
19
+ the origin.source extension set to 'override' and an answer different
20
+ from the expected result from execution of the CQL on Inferno's data.
21
+ )
22
+
23
+ run do
24
+ questionnaire_response = FHIR.from_contents(request.request_body)
25
+ skip_if !questionnaire_response.present?, 'QuestionnaireResponse not received'
26
+
27
+ validate_questionnaire_pre_population(questionnaire_response, id)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,387 @@
1
+ module DaVinciDTRTestKit
2
+ module CQLTest
3
+ def extension_presence
4
+ @extension_presence ||= { 'found_min_launch_context' => false, 'found_min_variable' => false,
5
+ 'found_min_pop_context' => false, 'found_min_init_expression' => false,
6
+ 'found_min_candidate_expression' => false, 'found_min_context_expression' => false,
7
+ 'found_min_cqf_lib' => false }
8
+ end
9
+
10
+ def cql_presence
11
+ @cql_presence ||= { 'launch_context' => true, 'variable' => true,
12
+ 'pop_context' => true, 'init_expression' => true,
13
+ 'candidate_expression' => true, 'context_expression' => true }
14
+ end
15
+
16
+ def cqf_reference_libraries
17
+ @cqf_reference_libraries ||= Set.new
18
+ end
19
+
20
+ def library_urls
21
+ @@library_urls ||= Set.new
22
+ end
23
+
24
+ def library_names
25
+ @@library_names ||= Set.new
26
+ end
27
+
28
+ def found_questionnaire
29
+ @found_questionnaire ||= false
30
+ end
31
+
32
+ def found_bad_library_reference
33
+ @@found_bad_library_reference ||= false
34
+ end
35
+
36
+ def found_duplicate_library_name
37
+ @found_duplicate_library_name ||= false
38
+ end
39
+
40
+ def found_non_cql_elm_library
41
+ @found_non_cql_elm_library ||= false
42
+ end
43
+
44
+ def found_non_cql_expression
45
+ @found_non_cql_expression ||= false
46
+ end
47
+
48
+ def reset_cql_tests
49
+ library_names.clear
50
+ library_urls.clear
51
+ cqf_reference_libraries.clear
52
+ extension_presence.each_key { |k| extension_presence[k] = false }
53
+ end
54
+
55
+ def questionnaire_extensions_test(response)
56
+ resource = process_response(response)
57
+ assert !resource.nil?, 'Response is null or not a valid type.'
58
+ found_questionnaire = false
59
+ if resource.instance_of? Array
60
+ resource.each do |individual_resource|
61
+ next unless individual_resource.resourceType == 'QuestionnaireResponse'
62
+
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'
66
+
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
71
+ 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'
76
+
77
+ param.resource.entry.each_with_index do |entry, q_index|
78
+ # check questionnaire extensions
79
+ next unless entry.resource.resourceType == 'Questionnaire'
80
+
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
85
+ end
86
+ 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.'
93
+ end
94
+
95
+ def questionnaire_items_test(response, final_cql_test)
96
+ resource = process_response(response)
97
+ assert !resource.nil?, 'Response is null or not a valid type.'
98
+ found_bundle = found_questionnaire = false
99
+ # are extensions present in any questionnaire?
100
+ 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
113
+ 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
139
+ end
140
+ end
141
+
142
+ def check_questionnaire_extensions(questionnaire, q_index)
143
+ # are extensions present in this questionnaire?
144
+ found_launch_context = found_variable = found_pop_context = found_cqf_lib = false
145
+ cqf_count = 0
146
+ misformatted_expressions = []
147
+ questionnaire.extension.each_with_index do |extension, index|
148
+ if extension.url == 'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-launchContext'
149
+ found_launch_context = true
150
+ extension_presence['found_min_launch_context'] = true
151
+ check_for_cql(extension, 'launch_context', index, q_index, extension.url)
152
+ misformatted_expressions << check_expression_format(extension, index)
153
+ end
154
+ if extension.url == 'http://hl7.org/fhir/StructureDefinition/variable'
155
+ found_variable = true
156
+ extension_presence['found_min_variable'] = true
157
+ check_for_cql(extension, 'variable', index, q_index, extension.url)
158
+ misformatted_expressions << check_expression_format(extension, index)
159
+ end
160
+ if extension.url == 'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemPopulationContext'
161
+ found_pop_context = true
162
+ extension_presence['found_min_pop_context'] = true
163
+ check_for_cql(extension, 'pop_context', index, q_index, extension.url)
164
+ misformatted_expressions << check_expression_format(extension, index)
165
+ end
166
+ next unless extension.url == 'http://hl7.org/fhir/StructureDefinition/cqf-library'
167
+
168
+ cqf_count += 1
169
+ cqf_reference_libraries.add(extension.valueCanonical)
170
+ found_cqf_lib = true
171
+ extension_presence['found_min_cqf_lib'] = true
172
+
173
+ check_for_cql(extension, '', index, q_index, extension.url)
174
+ end
175
+ unless found_launch_context
176
+ messages << { type: 'info',
177
+ message: format_markdown("[questionnaire #{q_index + 1}] included no launch context.") }
178
+ end
179
+ unless found_variable
180
+ messages << { type: 'info',
181
+ message: format_markdown("[questionnaire #{q_index + 1}]
182
+ included no variable to query for additional data.") }
183
+ end
184
+ unless found_pop_context
185
+ messages << { type: 'info',
186
+ message: format_markdown("[questionnaire #{q_index + 1}]
187
+ included no item population context.") }
188
+ end
189
+ unless found_cqf_lib
190
+ messages << { type: 'info',
191
+ message: format_markdown("[questionnaire #{q_index + 1}]
192
+ included no cqf library.") }
193
+ end
194
+ return if cqf_count < 1
195
+
196
+ misformatted_expressions.compact.each do |idx|
197
+ messages << { type: 'info',
198
+ message: format_markdown("[expression #{idx + 1}] in [questionnaire #{q_index + 1}]
199
+ does not begin with a reference to an included library name.") }
200
+ end
201
+ assert misformatted_expressions.compact.empty?, 'Expression in questionnaire misformatted.'
202
+ end
203
+
204
+ def check_questionnaire_items(questionnaire, q_index)
205
+ # are expressions present in this questionnaire?
206
+ found_item_expressions = { 'found_init_expression' => false,
207
+ 'found_candidate_expression' => false,
208
+ 'found_context_expression' => false }
209
+ cqf_count = 0
210
+ misformatted_expressions = []
211
+ questionnaire.extension.each do |extension|
212
+ next unless extension.url == 'http://hl7.org/fhir/StructureDefinition/cqf-library'
213
+
214
+ cqf_count += 1
215
+ end
216
+ # check questionnaire items
217
+ questionnaire.item.each_with_index do |item, index|
218
+ misformatted_expressions.concat(check_nested_items(item, index, q_index, found_item_expressions, item.linkId))
219
+ # check extensions on items
220
+ item.extension.each do |item_ext|
221
+ misformatted_expressions << check_item_extension(item_ext,
222
+ index, q_index, found_item_expressions, item.linkId)
223
+ end
224
+ 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
237
+ return if cqf_count < 1
238
+
239
+ misformatted_expressions.compact.to_set.each do |idx|
240
+ messages << { type: 'info',
241
+ message: format_markdown("[item #{idx + 1}] in [questionnaire #{q_index + 1}]
242
+ contains expression that does not begin with a reference to an included library name.") }
243
+ end
244
+ assert misformatted_expressions.compact.to_set.empty?, 'Expression in questionnaire misformatted.'
245
+ end
246
+
247
+ def check_libraries(payer_response)
248
+ resource = process_response(payer_response)
249
+ assert !resource.nil?, 'Response is null or not a valid type.'
250
+ found_bundle = found_libraries = false
251
+ # are extensions present in any questionnaire?
252
+ resource.parameter.each do |param|
253
+ # Do out put parameters have a bundle?
254
+ next unless param.resource.resourceType == 'Bundle'
255
+
256
+ found_bundle = true
257
+ # check the libraries first so references in questionnaires can be checked after
258
+ param.resource.entry.each_with_index do |entry, index|
259
+ next unless entry.resource.resourceType == 'Library'
260
+
261
+ found_libraries = true
262
+ found_cql = found_elm = false
263
+ 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
285
+ 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
+ end
289
+ assert found_libraries, 'No Libraries found.'
290
+ end
291
+ assert found_bundle, 'No questionnaire bundles found.'
292
+ end
293
+
294
+ def check_library_references
295
+ missing_references = cqf_reference_libraries.select do |url|
296
+ library_urls.exclude? url
297
+ end
298
+ assert missing_references.empty?,
299
+ "Some libraries referenced by cqf-libraries were not found: #{missing_references.join(', ')}"
300
+ end
301
+
302
+ def check_item_extension(item_ext, index, q_index, found_item_expressions, link_id)
303
+ if item_ext.url == 'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-candidateExpression'
304
+ found_item_expressions['found_candidate_expression'] = true
305
+ extension_presence['found_min_candidate_expression'] = true
306
+ check_for_cql(item_ext, 'candidate_expression', index, q_index, item_ext.url, link_id)
307
+ return check_expression_format(item_ext, index)
308
+ end
309
+ if item_ext.url == 'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression'
310
+ found_item_expressions['found_init_expression'] = true
311
+ extension_presence['found_min_init_expression'] = true
312
+ check_for_cql(item_ext, 'init_expression', index, q_index, item_ext.url, link_id)
313
+ return check_expression_format(item_ext, index)
314
+ end
315
+ if item_ext.url == 'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-contextExpression'
316
+ found_item_expressions['found_context_expression'] = true
317
+ extension_presence['found_min_context_expression'] = true
318
+ check_for_cql(item_ext, 'context_expression', index, q_index, item_ext.url, link_id)
319
+ return check_expression_format(item_ext, index)
320
+ end
321
+ check_for_cql(item_ext, '', index, q_index, item_ext.url, link_id)
322
+ end
323
+
324
+ def check_nested_items(item, index, q_index, found_item_expressions, link_id)
325
+ misformatted_nested_expressions = []
326
+ item.item.each do |nested_item|
327
+ check_nested_items(nested_item, index, q_index, found_item_expressions, nested_item.linkId)
328
+ nested_item.extension.each do |item_ext|
329
+ misformatted_nested_expressions << check_item_extension(item_ext, index, q_index, found_item_expressions,
330
+ link_id)
331
+ end
332
+ end
333
+ misformatted_nested_expressions.compact
334
+ end
335
+
336
+ def check_expression_format(item_ext, index)
337
+ return unless library_names.none?
338
+
339
+ expression_passes = false
340
+ library_names.each do |name|
341
+ if item_ext.valueExpression.expression.start_with? "\"#{name}\""
342
+ expression_passes = true
343
+ break
344
+ end
345
+ end
346
+ index unless expression_passes
347
+ end
348
+
349
+ def check_for_cql(extension, extension_name, index, q_index, url, link_id = '')
350
+ return if extension.valueExpression.nil?
351
+ return if extension.valueExpression.language == 'text/cql'
352
+
353
+ cql_presence[extension_name] = false unless extension_name.blank?
354
+ messages << if link_id.blank?
355
+ { type: 'info',
356
+ message: format_markdown("[extension #{index + 1}] in [questionnaire #{q_index + 1}]
357
+ contains expression that does not have content type of cql
358
+ (URL: #{url}).") }
359
+ else
360
+ { type: 'info',
361
+ message: format_markdown("[item #{index + 1}] in [questionnaire #{q_index + 1}]
362
+ contains expression that does not have content type of cql
363
+ (linkId: #{link_id}, URL: #{url}).") }
364
+ end
365
+ end
366
+
367
+ def process_response(response)
368
+ if response.instance_of?(FHIR::Parameters) || response.instance_of?(FHIR::QuestionnaireResponse)
369
+ return response
370
+ 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
382
+ end
383
+
384
+ nil
385
+ end
386
+ end
387
+ end