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.
- checksums.yaml +7 -0
- data/LICENSE +201 -0
- data/lib/davinci_dtr_test_kit/auth_groups/oauth2_authentication_group.rb +51 -0
- data/lib/davinci_dtr_test_kit/auth_groups/token_request_test.rb +25 -0
- data/lib/davinci_dtr_test_kit/auth_groups/token_validation_test.rb +13 -0
- data/lib/davinci_dtr_test_kit/client_groups/dinner_adaptive/dtr_smart_app_questionnaire_workflow_group.rb +20 -0
- data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_questionnaire_response_save_test.rb +31 -0
- data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_smart_app_questionnaire_workflow_group.rb +89 -0
- data/lib/davinci_dtr_test_kit/client_groups/dinner_static/prepopulation_attestation_test.rb +29 -0
- data/lib/davinci_dtr_test_kit/client_groups/dinner_static/prepopulation_override_attestation_test.rb +30 -0
- data/lib/davinci_dtr_test_kit/client_groups/dinner_static/rendering_enabled_questions_attestation_test.rb +30 -0
- data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_full_ehr_questionnaire_workflow_group.rb +19 -0
- data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_questionnaire_package_group.rb +16 -0
- data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_questionnaire_rendering_attestation_test.rb +32 -0
- data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_questionnaire_rendering_group.rb +14 -0
- data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_questionnaire_response_group.rb +23 -0
- data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_questionnaire_response_save_test.rb +31 -0
- data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_smart_app_questionnaire_workflow_group.rb +22 -0
- data/lib/davinci_dtr_test_kit/client_groups/shared/dtr_questionnaire_package_request_test.rb +36 -0
- data/lib/davinci_dtr_test_kit/client_groups/shared/dtr_questionnaire_package_request_validation_test.rb +35 -0
- data/lib/davinci_dtr_test_kit/client_groups/shared/dtr_questionnaire_response_basic_conformance_test.rb +28 -0
- data/lib/davinci_dtr_test_kit/client_groups/shared/dtr_questionnaire_response_pre_population_test.rb +30 -0
- data/lib/davinci_dtr_test_kit/cql_test.rb +387 -0
- data/lib/davinci_dtr_test_kit/docs/dtr_payer_server_suite_description_v201.md +127 -0
- data/lib/davinci_dtr_test_kit/docs/dtr_smart_app_suite_description_v201.md +118 -0
- data/lib/davinci_dtr_test_kit/dtr_full_ehr_suite.rb +55 -0
- data/lib/davinci_dtr_test_kit/dtr_light_ehr_suite.rb +39 -0
- data/lib/davinci_dtr_test_kit/dtr_payer_server_suite.rb +104 -0
- data/lib/davinci_dtr_test_kit/dtr_questionnaire_response_validation.rb +180 -0
- data/lib/davinci_dtr_test_kit/dtr_smart_app_suite.rb +85 -0
- data/lib/davinci_dtr_test_kit/ext/inferno_core/record_response_route.rb +98 -0
- data/lib/davinci_dtr_test_kit/ext/inferno_core/request.rb +19 -0
- data/lib/davinci_dtr_test_kit/ext/inferno_core/runnable.rb +35 -0
- data/lib/davinci_dtr_test_kit/fixture_loader.rb +99 -0
- data/lib/davinci_dtr_test_kit/fixtures/dinner_adaptive/dinner_order_adaptive_next_question_burrito.json +170 -0
- data/lib/davinci_dtr_test_kit/fixtures/dinner_adaptive/dinner_order_adaptive_next_question_hamburger.json +175 -0
- data/lib/davinci_dtr_test_kit/fixtures/dinner_adaptive/dinner_order_adaptive_next_question_initial.json +140 -0
- data/lib/davinci_dtr_test_kit/fixtures/dinner_adaptive/questionnaire_dinner_order_adaptive.json +95 -0
- data/lib/davinci_dtr_test_kit/fixtures/dinner_static/questionnaire_dinner_order_static.json +283 -0
- data/lib/davinci_dtr_test_kit/fixtures/dinner_static/questionnaire_response_dinner_order_static.json +170 -0
- data/lib/davinci_dtr_test_kit/fixtures/pre_populated_questionnaire_response.json +581 -0
- data/lib/davinci_dtr_test_kit/fixtures/questionnaire_package.json +2121 -0
- data/lib/davinci_dtr_test_kit/fixtures.rb +65 -0
- data/lib/davinci_dtr_test_kit/mock_ehr.rb +72 -0
- data/lib/davinci_dtr_test_kit/mock_payer.rb +142 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_form_libraries_test.rb +19 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_form_questionnaire_expressions_test.rb +20 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_form_questionnaire_extensions_test.rb +19 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_next_questionnaire_expressions_test.rb +20 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_next_questionnaire_extensions_test.rb +19 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_group.rb +88 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_request_test.rb +41 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_request_validation_test.rb +44 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_response_bundles_validation_test.rb +40 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_response_search_validation_test.rb +42 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_response_validation_test.rb +49 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_next_request_validation_test.rb +61 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_next_response_complete_test.rb +17 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_next_response_validation_test.rb +43 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_static_group.rb +51 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_libraries_test.rb +19 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_questionnaire_expressions_test.rb +20 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_questionnaire_extensions_test.rb +19 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_request_test.rb +33 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_request_validation_test.rb +46 -0
- data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_response_validation_test.rb +50 -0
- data/lib/davinci_dtr_test_kit/tags.rb +8 -0
- data/lib/davinci_dtr_test_kit/urls.rb +50 -0
- data/lib/davinci_dtr_test_kit/validation_test.rb +72 -0
- data/lib/davinci_dtr_test_kit/version.rb +5 -0
- data/lib/davinci_dtr_test_kit.rb +4 -0
- 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
|
data/lib/davinci_dtr_test_kit/client_groups/shared/dtr_questionnaire_response_pre_population_test.rb
ADDED
|
@@ -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
|