davinci_dtr_test_kit 0.12.0 → 0.13.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 +4 -4
- data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_full_ehr_dinner_static_questionnaire_response_conformance_test.rb +15 -0
- data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_full_ehr_dinner_static_questionnaire_response_correctness_test.rb +30 -0
- data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_full_ehr_questionnaire_workflow_group.rb +15 -5
- data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_smart_app_dinner_questionnaire_package_request_test.rb +1 -1
- data/lib/davinci_dtr_test_kit/client_groups/shared/dtr_questionnaire_response_basic_conformance_test.rb +3 -7
- data/lib/davinci_dtr_test_kit/client_groups/shared/dtr_questionnaire_response_pre_population_test.rb +9 -5
- data/lib/davinci_dtr_test_kit/cql_test.rb +182 -137
- data/lib/davinci_dtr_test_kit/docs/dtr_light_ehr_suite_description_v201.md +29 -0
- data/lib/davinci_dtr_test_kit/dtr_light_ehr_suite.rb +38 -25
- data/lib/davinci_dtr_test_kit/dtr_options.rb +7 -0
- data/lib/davinci_dtr_test_kit/dtr_questionnaire_response_validation.rb +118 -75
- data/lib/davinci_dtr_test_kit/dtr_smart_app_suite.rb +6 -3
- data/lib/davinci_dtr_test_kit/fixture_loader.rb +6 -84
- data/lib/davinci_dtr_test_kit/fixtures.rb +43 -48
- data/lib/davinci_dtr_test_kit/mock_auth_server.rb +101 -18
- data/lib/davinci_dtr_test_kit/mock_ehr.rb +32 -24
- data/lib/davinci_dtr_test_kit/mock_payer.rb +41 -64
- data/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_next_questionnaire_expressions_test.rb +2 -2
- data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_request_validation_test.rb +2 -1
- data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_response_validation_test.rb +6 -5
- data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_next_request_validation_test.rb +1 -1
- data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_request_validation_test.rb +2 -1
- data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_response_validation_test.rb +6 -4
- data/lib/davinci_dtr_test_kit/tags.rb +1 -0
- data/lib/davinci_dtr_test_kit/urls.rb +13 -10
- data/lib/davinci_dtr_test_kit/validation_test.rb +2 -1
- data/lib/davinci_dtr_test_kit/version.rb +1 -1
- data/lib/davinci_dtr_test_kit.rb +1 -1
- metadata +24 -8
- data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_full_ehr_prepopulation_representation_attestation_test.rb +0 -33
- data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_full_ehr_questionnaire_workflow_group.rb +0 -19
- /data/lib/davinci_dtr_test_kit/fixtures/{pre_populated_questionnaire_response.json → respiratory_assist_device/pre_populated_questionnaire_response.json} +0 -0
- /data/lib/davinci_dtr_test_kit/fixtures/{questionnaire_package.json → respiratory_assist_device/questionnaire_package.json} +0 -0
@@ -1,18 +1,14 @@
|
|
1
|
+
require 'tls_test_kit'
|
1
2
|
require_relative 'version'
|
3
|
+
require_relative 'dtr_options'
|
4
|
+
require 'smart_app_launch/smart_stu1_suite'
|
5
|
+
require 'smart_app_launch/smart_stu2_suite'
|
2
6
|
|
3
7
|
module DaVinciDTRTestKit
|
4
8
|
class DTRLightEHRSuite < Inferno::TestSuite
|
5
9
|
id :dtr_light_ehr
|
6
10
|
title 'Da Vinci DTR Light EHR Test Suite'
|
7
|
-
description
|
8
|
-
# Da Vinci DTR Light EHR Test Suite
|
9
|
-
|
10
|
-
This suite validates that an EMR or other application
|
11
|
-
can act as a data source for a DTR SMART App. Inferno
|
12
|
-
will act as a DTR SMART App making requests for data
|
13
|
-
against the system under test and storing completed
|
14
|
-
questionnaire responses.
|
15
|
-
)
|
11
|
+
description File.read(File.join(__dir__, 'docs', 'dtr_light_ehr_suite_description_v201.md'))
|
16
12
|
|
17
13
|
version VERSION
|
18
14
|
|
@@ -35,28 +31,45 @@ module DaVinciDTRTestKit
|
|
35
31
|
}
|
36
32
|
]
|
37
33
|
|
38
|
-
# These inputs will be available to all tests in this suite
|
39
34
|
input :url,
|
40
|
-
title: 'FHIR
|
35
|
+
title: 'FHIR Endpoint',
|
36
|
+
description: 'URL of the DTR FHIR server'
|
41
37
|
|
42
|
-
|
43
|
-
|
44
|
-
type: :oauth_credentials,
|
45
|
-
optional: true
|
38
|
+
group do
|
39
|
+
title 'Authorization'
|
46
40
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
41
|
+
group from: :smart_discovery_stu2 do
|
42
|
+
required_suite_options DTROptions::SMART_2_REQUIREMENT
|
43
|
+
run_as_group
|
44
|
+
|
45
|
+
test from: :tls_version_test do
|
46
|
+
title 'DTR FHIR Server is secured by transport layer security'
|
47
|
+
description <<~DESCRIPTION
|
48
|
+
Under [Privacy, Security, and Safety](https://hl7.org/fhir/us/davinci-crd/STU2/security.html),
|
49
|
+
the DTR Implementation Guide imposes the following rule about TLS:
|
50
|
+
As per the [DTR Hook specification](https://cds-hooks.hl7.org/2.0/#security-and-safety),
|
51
|
+
communications between DTR Clients and DTR Servers SHALL
|
52
|
+
use TLS. Mutual TLS is not required by this specification but is permitted. DTR Servers and
|
53
|
+
DTR Clients SHOULD enforce a minimum version and other TLS configuration requirements based
|
54
|
+
on HRex rules for PHI exchange.
|
55
|
+
This test verifies that the FHIR server is using TLS 1.2 or higher.
|
56
|
+
DESCRIPTION
|
52
57
|
|
53
|
-
|
54
|
-
fhir_resource_validator do
|
55
|
-
igs 'hl7.fhir.us.davinci-dtr#2.0.1'
|
58
|
+
id :dtr_server_tls_version_stu2
|
56
59
|
|
57
|
-
|
58
|
-
|
60
|
+
config(
|
61
|
+
options: { minimum_allowed_version: OpenSSL::SSL::TLS1_2_VERSION }
|
62
|
+
)
|
63
|
+
end
|
59
64
|
end
|
65
|
+
|
66
|
+
group from: :smart_ehr_launch_stu2,
|
67
|
+
required_suite_options: DTROptions::SMART_2_REQUIREMENT,
|
68
|
+
run_as_group: true
|
69
|
+
|
70
|
+
group from: :smart_standalone_launch_stu2,
|
71
|
+
required_suite_options: DTROptions::SMART_2_REQUIREMENT,
|
72
|
+
run_as_group: true
|
60
73
|
end
|
61
74
|
end
|
62
75
|
end
|
@@ -1,30 +1,78 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module DaVinciDTRTestKit
|
4
4
|
module DTRQuestionnaireResponseValidation
|
5
|
-
include Fixtures
|
6
|
-
|
7
5
|
CQL_EXPRESSION_EXTENSIONS = [
|
8
6
|
'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression',
|
9
7
|
'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-calculatedExpression',
|
10
8
|
'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-candidateExpression'
|
11
9
|
].freeze
|
12
10
|
|
13
|
-
def
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
11
|
+
def check_is_questionnaire_response(questionnaire_response_json)
|
12
|
+
assert_valid_json(questionnaire_response_json)
|
13
|
+
questionnaire_response = begin
|
14
|
+
FHIR.from_contents(questionnaire_response_json)
|
15
|
+
rescue StandardError
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
|
19
|
+
assert questionnaire_response.present?, 'The QuestionnaireResponse is not a recognized FHIR object'
|
20
|
+
assert_resource_type(:questionnaire_response, resource: questionnaire_response)
|
21
|
+
end
|
22
|
+
|
23
|
+
def verify_basic_conformance(questionnaire_response_json)
|
24
|
+
check_is_questionnaire_response(questionnaire_response_json)
|
25
|
+
assert_valid_resource(resource: FHIR.from_contents(questionnaire_response_json),
|
26
|
+
profile_url: 'http://hl7.org/fhir/us/davinci-dtr/StructureDefinition/dtr-questionnaireresponse|2.0.1')
|
27
|
+
end
|
28
|
+
|
29
|
+
# This only checks answers in the questionnaire response, meaning it does not catch missing answers
|
30
|
+
def check_origin_sources(questionnaire_items, response_items, expected_overrides: [])
|
31
|
+
response_items&.each do |response_item|
|
32
|
+
check_origin_sources(questionnaire_items, response_item.item, expected_overrides:)
|
33
|
+
next unless response_item.answer&.any?
|
34
|
+
|
35
|
+
link_id = response_item.linkId
|
36
|
+
origin_source = find_origin_source(response_item)
|
37
|
+
questionnaire_item = find_item_by_link_id(questionnaire_items, link_id)
|
38
|
+
is_cql_expression = item_is_cql_expression?(questionnaire_item)
|
27
39
|
|
40
|
+
if origin_source.nil?
|
41
|
+
add_message('error', "Required `origin.source` extension not present on answer to item `#{link_id}`")
|
42
|
+
else
|
43
|
+
check_origin_source(origin_source, link_id, is_cql_expression, override: expected_overrides.include?(link_id))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def check_origin_source(origin_source, link_id, is_cql_expression, override: false)
|
49
|
+
if override
|
50
|
+
origin_source_error(link_id, ['override'], origin_source) unless origin_source == 'override'
|
51
|
+
elsif is_cql_expression && !['auto', 'override'].include?(origin_source)
|
52
|
+
origin_source_error(link_id, 'auto or override', origin_source)
|
53
|
+
elsif !is_cql_expression && origin_source != 'manual'
|
54
|
+
origin_source_error(link_id, 'manual', origin_source)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# This checks presence of all answers if link_ids is nil
|
59
|
+
def check_answer_presence(items, link_ids: nil)
|
60
|
+
items.each do |item|
|
61
|
+
check_answer_presence(item.item, link_ids:)
|
62
|
+
|
63
|
+
if !item.answer&.first&.value.present? && (link_ids.nil? || link_ids.include?(item.linkId))
|
64
|
+
add_message('error', "No answer for item #{item.linkId}")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Requirements:
|
70
|
+
# - Prior to exposing the draft QuestionnaireResponse to the user for completion and/or review, the DTR client
|
71
|
+
# SHALL execute all CQL necessary to resolve the initialExpression, candidateExpression and
|
72
|
+
# calculatedExpression extensions found in the Questionnaire for any enabled elements.
|
73
|
+
# - All items that are pre-populated (whether by the payer in the initial QuestionnaireResponse provided in the
|
74
|
+
# questionnaire package, or from data retrieved from the EHR) SHALL have their origin.source set to ‘auto’.
|
75
|
+
def validate_questionnaire_pre_population(questionnaire, template_questionnaire_response, questionnaire_response)
|
28
76
|
questionnaire_cql_expression_link_ids = collect_questionnaire_cql_expression_link_ids(questionnaire.item)
|
29
77
|
template_prepopulation_expectations = {}
|
30
78
|
template_override_expectations = {}
|
@@ -32,45 +80,42 @@ module DaVinciDTRTestKit
|
|
32
80
|
questionnaire_cql_expression_link_ids,
|
33
81
|
template_prepopulation_expectations,
|
34
82
|
template_override_expectations)
|
35
|
-
|
83
|
+
|
36
84
|
validate_cql_executed(questionnaire_response.item, questionnaire_cql_expression_link_ids,
|
37
|
-
template_prepopulation_expectations, template_override_expectations
|
85
|
+
template_prepopulation_expectations, template_override_expectations)
|
38
86
|
|
39
87
|
if template_prepopulation_expectations.size.positive?
|
40
|
-
|
41
|
-
|
88
|
+
add_message('error', %(Items expected to be pre-populated not found:
|
89
|
+
#{template_prepopulation_expectations.keys.join(', ')}))
|
42
90
|
end
|
43
91
|
|
44
92
|
if template_override_expectations.size.positive?
|
45
|
-
|
46
|
-
|
93
|
+
add_message('error', %(Items expected to be pre-poplated and overridden not found:
|
94
|
+
#{template_override_expectations.keys.join(', ')}))
|
47
95
|
end
|
48
96
|
|
49
|
-
|
50
|
-
|
97
|
+
assert(messages.none? { |m| m[:type] == 'error' },
|
98
|
+
'QuestionnaireResponse is not conformant. Check messages for issues found.')
|
51
99
|
end
|
52
100
|
|
53
101
|
def validate_cql_executed(actual_items, questionnaire_cql_expression_link_ids, template_prepopulation_expectations,
|
54
|
-
template_override_expectations
|
102
|
+
template_override_expectations)
|
55
103
|
|
56
104
|
actual_items&.each do |item_to_validate|
|
57
105
|
link_id = item_to_validate.linkId
|
58
106
|
if questionnaire_cql_expression_link_ids.include?(link_id)
|
59
107
|
if template_prepopulation_expectations.key?(link_id)
|
60
|
-
check_item_prepopulation(item_to_validate, template_prepopulation_expectations.delete(link_id),
|
61
|
-
error_messages, false)
|
108
|
+
check_item_prepopulation(item_to_validate, template_prepopulation_expectations.delete(link_id), false)
|
62
109
|
elsif template_override_expectations.include?(link_id)
|
63
|
-
check_item_prepopulation(item_to_validate, template_override_expectations.delete(link_id),
|
64
|
-
true)
|
110
|
+
check_item_prepopulation(item_to_validate, template_override_expectations.delete(link_id), true)
|
65
111
|
else
|
66
112
|
raise "template missing expectation for question `#{link_id}`"
|
67
113
|
end
|
68
114
|
end
|
69
115
|
|
70
116
|
validate_cql_executed(item_to_validate.item, questionnaire_cql_expression_link_ids,
|
71
|
-
template_prepopulation_expectations, template_override_expectations
|
117
|
+
template_prepopulation_expectations, template_override_expectations)
|
72
118
|
end
|
73
|
-
error_messages
|
74
119
|
end
|
75
120
|
|
76
121
|
def extract_expected_answers_from_template(template_questionnaire_response,
|
@@ -87,70 +132,60 @@ module DaVinciDTRTestKit
|
|
87
132
|
raise "Template QuestionnaireResponse missing an answer for item with link id `#{target_link_id}`"
|
88
133
|
end
|
89
134
|
|
90
|
-
|
91
|
-
'http://hl7.org/fhir/us/davinci-dtr/StructureDefinition/information-origin|2.0.1')
|
92
|
-
source_extension = find_extension(origin_extension, 'source')
|
135
|
+
origin_source = find_origin_source(target_item)
|
93
136
|
|
94
|
-
unless
|
137
|
+
unless origin_source.present?
|
95
138
|
raise "Template QuestionnaireResponse item `#{target_link_id}` missing the `origin.source` extension"
|
96
139
|
end
|
97
140
|
|
98
|
-
if
|
141
|
+
if origin_source == 'auto'
|
99
142
|
expected_prepopulated[target_link_id] = target_item_answer
|
100
|
-
elsif
|
143
|
+
elsif origin_source == 'override'
|
101
144
|
expected_overrides[target_link_id] = target_item_answer
|
102
145
|
else
|
103
|
-
raise "`origin.source` extension for item `#{target_link_id}` has unexpected value: #{
|
146
|
+
raise "`origin.source` extension for item `#{target_link_id}` has unexpected value: #{origin_source}"
|
104
147
|
end
|
105
148
|
end
|
106
149
|
end
|
107
150
|
|
108
|
-
def
|
109
|
-
|
110
|
-
|
111
|
-
DATA_REQUIREMENT_ANSWERS.each do |library_name, link_id|
|
112
|
-
expected = find_item_by_link_id(expected_questionnaire_response.item, link_id).answer.first.value
|
113
|
-
actual = find_item_by_link_id(questionnaire_response.item, link_id)&.answer&.first&.value
|
114
|
-
next if coding_equal?(expected, actual)
|
151
|
+
def check_item_prepopulation(item, expected_answer, override)
|
152
|
+
answer = item.answer.first
|
153
|
+
link_id = item.linkId
|
115
154
|
|
116
|
-
|
117
|
-
|
118
|
-
|
155
|
+
unless answer&.value&.present?
|
156
|
+
add_message('error', "No answer for item `#{link_id}`")
|
157
|
+
return
|
119
158
|
end
|
120
|
-
error_messages
|
121
|
-
end
|
122
159
|
|
123
|
-
|
124
|
-
answer = item.answer.first
|
125
|
-
if answer&.value&.present?
|
126
|
-
# check answer
|
127
|
-
if override && answer_value_equal?(expected_answer, answer)
|
128
|
-
error_list << "Answer to item `#{item.linkId}` was not overriden from the pre-populated value. " \
|
129
|
-
"Found #{expected_answer}, but should be different"
|
130
|
-
elsif !override && !answer_value_equal?(expected_answer, answer)
|
131
|
-
error_list << "answer to item `#{item.linkId}` contains unexpected value. Expected: \
|
132
|
-
#{value_for_display(expected_answer)}. Found #{value_for_display(answer)}"
|
133
|
-
end
|
160
|
+
check_answer(link_id, override, expected_answer, answer)
|
134
161
|
|
135
|
-
|
136
|
-
|
137
|
-
'http://hl7.org/fhir/us/davinci-dtr/StructureDefinition/information-origin|2.0.1')
|
138
|
-
source_extension = find_extension(origin_extension, 'source')
|
162
|
+
origin_source = find_origin_source(item)
|
163
|
+
expected_origin_source = override ? 'override' : 'auto'
|
139
164
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
error_list << "`origin.source` extension on item `#{item.linkId}` contains unexpected value. Expected: " \
|
144
|
-
"#{expected_source_value}. Found #{source_extension.value}"
|
145
|
-
end
|
146
|
-
else
|
147
|
-
error_list << "Required `origin.source` extension not present on answer to item `#{item.linkId}`"
|
165
|
+
if origin_source.present?
|
166
|
+
unless origin_source == expected_origin_source
|
167
|
+
origin_source_error(link_id, expected_origin_source, origin_source)
|
148
168
|
end
|
149
169
|
else
|
150
|
-
|
170
|
+
add_message('error', "Required `origin.source` extension not present on answer to item `#{item.linkId}`")
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def check_answer(link_id, override, expected_answer, answer)
|
175
|
+
if override && answer_value_equal?(expected_answer, answer)
|
176
|
+
add_message('error', %(Answer to item `#{link_id}` was not overriden from the pre-populated value.
|
177
|
+
Found #{expected_answer}, but should be different))
|
178
|
+
elsif !override && !answer_value_equal?(expected_answer, answer)
|
179
|
+
add_message('error', %(Answer to item `#{link_id}` contains unexpected value. Expected:
|
180
|
+
#{value_for_display(expected_answer)}. Found #{value_for_display(answer)}))
|
151
181
|
end
|
152
182
|
end
|
153
183
|
|
184
|
+
def origin_source_error(link_id, expected, actual)
|
185
|
+
add_message('error', %(`origin.source` extension on item `#{link_id}` contains unexpected value.
|
186
|
+
Expected: #{expected}. Found: #{actual}))
|
187
|
+
end
|
188
|
+
|
154
189
|
def find_item_by_link_id(items, link_id)
|
155
190
|
items.each do |item|
|
156
191
|
return item if item.linkId == link_id
|
@@ -161,6 +196,14 @@ module DaVinciDTRTestKit
|
|
161
196
|
nil
|
162
197
|
end
|
163
198
|
|
199
|
+
def find_origin_source(item)
|
200
|
+
origin_extension = find_extension(
|
201
|
+
item&.answer&.first,
|
202
|
+
'http://hl7.org/fhir/us/davinci-dtr/StructureDefinition/information-origin'
|
203
|
+
)
|
204
|
+
find_extension(origin_extension, 'source')&.value
|
205
|
+
end
|
206
|
+
|
164
207
|
def find_extension(element, url)
|
165
208
|
element&.extension&.find { |e| e.url == url }
|
166
209
|
end
|
@@ -50,18 +50,21 @@ module DaVinciDTRTestKit
|
|
50
50
|
end
|
51
51
|
|
52
52
|
allow_cors QUESTIONNAIRE_PACKAGE_PATH, QUESTIONNAIRE_RESPONSE_PATH, FHIR_RESOURCE_PATH, FHIR_SEARCH_PATH,
|
53
|
-
EHR_AUTHORIZE_PATH, EHR_TOKEN_PATH
|
53
|
+
EHR_AUTHORIZE_PATH, EHR_TOKEN_PATH, JKWS_PATH, OPENID_CONFIG_PATH
|
54
54
|
|
55
55
|
route(:get, '/fhir/metadata', method(:metadata_handler))
|
56
56
|
|
57
57
|
route(:get, SMART_CONFIG_PATH, method(:ehr_smart_config))
|
58
|
+
route(:get, OPENID_CONFIG_PATH, method(:ehr_openid_config))
|
58
59
|
|
59
|
-
|
60
|
+
route(:get, JKWS_PATH, method(:auth_server_jwks))
|
61
|
+
|
62
|
+
record_response_route :get, EHR_AUTHORIZE_PATH, EHR_AUTHORIZE_TAG, method(:ehr_authorize),
|
60
63
|
resumes: ->(_) { false } do |request|
|
61
64
|
DTRSmartAppSuite.extract_client_id_from_query_params(request)
|
62
65
|
end
|
63
66
|
|
64
|
-
record_response_route :post, EHR_AUTHORIZE_PATH,
|
67
|
+
record_response_route :post, EHR_AUTHORIZE_PATH, EHR_AUTHORIZE_TAG, method(:ehr_authorize),
|
65
68
|
resumes: ->(_) { false } do |request|
|
66
69
|
DTRSmartAppSuite.extract_client_id_from_form_params(request)
|
67
70
|
end
|
@@ -6,94 +6,16 @@ module DaVinciDTRTestKit
|
|
6
6
|
include Singleton
|
7
7
|
|
8
8
|
def initialize
|
9
|
-
|
10
|
-
# - first entry is the path to a file containing a Questionnaire Bundle (not Parameters)
|
11
|
-
# - second entry is the path to a file containing a Populated QuestionnaireResponse
|
12
|
-
# for use in validation.
|
13
|
-
# - third entry is a list of group id within which this questionnaire should be used and
|
14
|
-
# will be returned for any $questionnaire-package calls. This association of a questionnaire
|
15
|
-
# with a specific test is Inferno's simulated version of payer business logic.
|
16
|
-
questionnaires_to_load =
|
17
|
-
[
|
18
|
-
[
|
19
|
-
'fixtures/questionnaire_package.json',
|
20
|
-
'fixtures/pre_populated_questionnaire_response.json',
|
21
|
-
['dtr_smart_app_questionnaire_workflow', 'dtr_full_ehr_questionnaire_workflow']
|
22
|
-
],
|
23
|
-
[
|
24
|
-
'fixtures/dinner_static/questionnaire_dinner_order_static.json',
|
25
|
-
'fixtures/dinner_static/questionnaire_response_dinner_order_static.json',
|
26
|
-
['dtr_smart_app_static_dinner_questionnaire_workflow', 'dtr_full_ehr_static_dinner_questionnaire_workflow']
|
27
|
-
],
|
28
|
-
[
|
29
|
-
'fixtures/dinner_adaptive/questionnaire_dinner_order_adaptive.json',
|
30
|
-
'',
|
31
|
-
['dtr_smart_app_adaptive_dinner_questionnaire_workflow']
|
32
|
-
]
|
33
|
-
]
|
34
|
-
|
35
|
-
questionnaires_to_load.each do |questionnaire_details|
|
36
|
-
init_questionnaire_package_and_response(questionnaire_details[0], questionnaire_details[1],
|
37
|
-
questionnaire_details[2])
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
def canonical_to_questionnaire_package_map
|
42
|
-
@canonical_to_questionnaire_package_map ||= {}
|
9
|
+
@cache = {}
|
43
10
|
end
|
44
11
|
|
45
|
-
def
|
46
|
-
|
47
|
-
end
|
48
|
-
|
49
|
-
def group_id_to_questionnaire_canonical_map
|
50
|
-
@group_id_to_questionnaire_canonical_map ||= {}
|
51
|
-
end
|
52
|
-
|
53
|
-
def questionnaire_canonical_for_group_id(group_id)
|
54
|
-
group_id_to_questionnaire_canonical_map[group_id]
|
55
|
-
end
|
56
|
-
|
57
|
-
def questionnaire_package_for_group_id(group_id)
|
58
|
-
canonical = questionnaire_canonical_for_group_id(group_id)
|
59
|
-
return unless canonical
|
60
|
-
|
61
|
-
@canonical_to_questionnaire_package_map[canonical]
|
62
|
-
end
|
12
|
+
def [](path)
|
13
|
+
return unless path.present?
|
63
14
|
|
64
|
-
|
65
|
-
@group_id_to_questionnaire_response_map ||= {}
|
66
|
-
end
|
67
|
-
|
68
|
-
def questionnaire_response_for_group_id(group_id)
|
69
|
-
group_id_to_questionnaire_response_map[group_id]
|
70
|
-
end
|
71
|
-
|
72
|
-
def init_questionnaire_package_and_response(package_file, response_file = nil, covered_groups = nil)
|
73
|
-
package_json = File.read(File.join(__dir__, package_file))
|
74
|
-
package = FHIR.from_contents(package_json)
|
75
|
-
url = get_url_for_questionnaire(package)
|
76
|
-
|
77
|
-
canonical_to_questionnaire_package_map[url] = package if url
|
78
|
-
covered_groups&.each do |group|
|
79
|
-
group_id_to_questionnaire_canonical_map[group] = url
|
80
|
-
end
|
81
|
-
|
82
|
-
if response_file.present?
|
83
|
-
response_json = File.read(File.join(__dir__, response_file))
|
84
|
-
response = FHIR.from_contents(response_json)
|
85
|
-
|
86
|
-
covered_groups&.each do |group|
|
87
|
-
group_id_to_questionnaire_response_map[group] = response
|
88
|
-
end
|
89
|
-
|
90
|
-
end
|
91
|
-
|
92
|
-
package
|
93
|
-
end
|
15
|
+
return @cache[path] if @cache.key?(path)
|
94
16
|
|
95
|
-
|
96
|
-
|
17
|
+
fhir_instance = FHIR.from_contents(File.read(File.join(__dir__, 'fixtures', path)))
|
18
|
+
@cache[path] = fhir_instance
|
97
19
|
end
|
98
20
|
end
|
99
21
|
end
|
@@ -2,64 +2,59 @@ require_relative 'fixture_loader'
|
|
2
2
|
|
3
3
|
module DaVinciDTRTestKit
|
4
4
|
module Fixtures
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
# Ideally the groups and/or tests would define which fixtures they use and this module would simply be a provider
|
6
|
+
# of whatever fixture is requested. But the MockPayer needs to determine which questionnaire package belongs to
|
7
|
+
# each group, so this is a straighforward way to facilitate that.
|
8
|
+
FIXTURE_CONFIG = [
|
9
|
+
{
|
10
|
+
group_ids: [
|
11
|
+
'dtr_smart_app_questionnaire_workflow',
|
12
|
+
'dtr_full_ehr_questionnaire_workflow'
|
13
|
+
],
|
14
|
+
questionnaire_package: File.join('respiratory_assist_device', 'questionnaire_package.json'),
|
15
|
+
questionnaire_response: File.join('respiratory_assist_device', 'pre_populated_questionnaire_response.json')
|
16
|
+
},
|
17
|
+
{
|
18
|
+
group_ids: [
|
19
|
+
'dtr_smart_app_static_dinner_questionnaire_workflow',
|
20
|
+
'dtr_full_ehr_static_dinner_questionnaire_workflow'
|
21
|
+
],
|
22
|
+
questionnaire_package: File.join('dinner_static', 'questionnaire_dinner_order_static.json'),
|
23
|
+
questionnaire_response: File.join('dinner_static', 'questionnaire_response_dinner_order_static.json')
|
24
|
+
}
|
25
|
+
].freeze
|
26
|
+
|
27
|
+
extend self
|
28
|
+
|
29
|
+
# full_test_id needs to be the long inferno-generated ID that includes hyphenated ancestor IDs
|
30
|
+
def questionnaire_package_for_test(full_test_id)
|
31
|
+
get_fixture(full_test_id, :questionnaire_package)
|
9
32
|
end
|
10
33
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
package = get_questionnaire_packcage_for_canonical(canonical_url)
|
16
|
-
return unless package.present?
|
17
|
-
|
18
|
-
questionnaire = nil
|
19
|
-
package.entry.find do |entry|
|
20
|
-
questionnaire = entry.resource if entry.resource.is_a?(FHIR::Questionnaire)
|
21
|
-
end
|
22
|
-
questionnaire
|
34
|
+
# full_test_id needs to be the long inferno-generated ID that includes hyphenated ancestor IDs
|
35
|
+
def questionnaire_response_for_test(full_test_id)
|
36
|
+
get_fixture(full_test_id, :questionnaire_response)
|
23
37
|
end
|
24
38
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
# test_id is of the form [suite id]-[group id 1]-...-[group id n]-[test id]
|
29
|
-
groups = test_id.split('-')[1..-2] # first is suite, last is test, we want groups
|
30
|
-
groups.each do |one_group_id|
|
31
|
-
next if canonical_url.present?
|
32
|
-
|
33
|
-
canonical_url = get_questionnaire_canonical_for_group_id(one_group_id)
|
34
|
-
end
|
35
|
-
|
36
|
-
canonical_url
|
39
|
+
# full_test_id needs to be the long inferno-generated ID that includes hyphenated ancestor IDs
|
40
|
+
def questionnaire_for_test(full_test_id)
|
41
|
+
questionnaire_package_for_test(full_test_id)&.entry&.find { |e| e.resource.is_a?(FHIR::Questionnaire) }&.resource
|
37
42
|
end
|
38
43
|
|
39
|
-
|
40
|
-
FixtureLoader.instance.questionnaire_canonical_for_group_id(group_id)
|
41
|
-
end
|
42
|
-
|
43
|
-
def get_questionnaire_packcage_for_canonical(url)
|
44
|
-
FixtureLoader.instance.questionnaire_package_for_canonical(url)
|
45
|
-
end
|
46
|
-
|
47
|
-
def find_questionnaire_response_for_test_id(test_id)
|
48
|
-
questionnaire_response = nil
|
49
|
-
|
50
|
-
# test_id is of the form [suite id]-[group id 1]-...-[group id n]-[test id]
|
51
|
-
groups = test_id.split('-')[1..-2] # first is suite, last is test, we want groups
|
52
|
-
groups.each do |one_group_id|
|
53
|
-
next if questionnaire_response.present?
|
44
|
+
private
|
54
45
|
|
55
|
-
|
56
|
-
|
46
|
+
def get_fixture(full_test_id, fixture_type)
|
47
|
+
fixture_path = extract_group_ids(full_test_id).filter_map do |group_id|
|
48
|
+
FIXTURE_CONFIG.find { |fc| fc[:group_ids].include?(group_id) }&.dig(fixture_type)
|
49
|
+
end&.first
|
57
50
|
|
58
|
-
|
51
|
+
FixtureLoader.instance[fixture_path]
|
59
52
|
end
|
60
53
|
|
61
|
-
|
62
|
-
|
54
|
+
# full_test_id is of the form [suite id]-[group id 1]-...-[group id n]-[test id]
|
55
|
+
def extract_group_ids(full_test_id)
|
56
|
+
# First is suite, last is test, we want groups
|
57
|
+
full_test_id.split('-')[1...-1]&.reverse
|
63
58
|
end
|
64
59
|
end
|
65
60
|
end
|