davinci_dtr_test_kit 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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