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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_full_ehr_dinner_static_questionnaire_response_conformance_test.rb +15 -0
  3. data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_full_ehr_dinner_static_questionnaire_response_correctness_test.rb +30 -0
  4. data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_full_ehr_questionnaire_workflow_group.rb +15 -5
  5. data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_smart_app_dinner_questionnaire_package_request_test.rb +1 -1
  6. data/lib/davinci_dtr_test_kit/client_groups/shared/dtr_questionnaire_response_basic_conformance_test.rb +3 -7
  7. data/lib/davinci_dtr_test_kit/client_groups/shared/dtr_questionnaire_response_pre_population_test.rb +9 -5
  8. data/lib/davinci_dtr_test_kit/cql_test.rb +182 -137
  9. data/lib/davinci_dtr_test_kit/docs/dtr_light_ehr_suite_description_v201.md +29 -0
  10. data/lib/davinci_dtr_test_kit/dtr_light_ehr_suite.rb +38 -25
  11. data/lib/davinci_dtr_test_kit/dtr_options.rb +7 -0
  12. data/lib/davinci_dtr_test_kit/dtr_questionnaire_response_validation.rb +118 -75
  13. data/lib/davinci_dtr_test_kit/dtr_smart_app_suite.rb +6 -3
  14. data/lib/davinci_dtr_test_kit/fixture_loader.rb +6 -84
  15. data/lib/davinci_dtr_test_kit/fixtures.rb +43 -48
  16. data/lib/davinci_dtr_test_kit/mock_auth_server.rb +101 -18
  17. data/lib/davinci_dtr_test_kit/mock_ehr.rb +32 -24
  18. data/lib/davinci_dtr_test_kit/mock_payer.rb +41 -64
  19. data/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_next_questionnaire_expressions_test.rb +2 -2
  20. data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_request_validation_test.rb +2 -1
  21. data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_response_validation_test.rb +6 -5
  22. data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_next_request_validation_test.rb +1 -1
  23. data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_request_validation_test.rb +2 -1
  24. data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_response_validation_test.rb +6 -4
  25. data/lib/davinci_dtr_test_kit/tags.rb +1 -0
  26. data/lib/davinci_dtr_test_kit/urls.rb +13 -10
  27. data/lib/davinci_dtr_test_kit/validation_test.rb +2 -1
  28. data/lib/davinci_dtr_test_kit/version.rb +1 -1
  29. data/lib/davinci_dtr_test_kit.rb +1 -1
  30. metadata +24 -8
  31. data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_full_ehr_prepopulation_representation_attestation_test.rb +0 -33
  32. data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_full_ehr_questionnaire_workflow_group.rb +0 -19
  33. /data/lib/davinci_dtr_test_kit/fixtures/{pre_populated_questionnaire_response.json → respiratory_assist_device/pre_populated_questionnaire_response.json} +0 -0
  34. /data/lib/davinci_dtr_test_kit/fixtures/{questionnaire_package.json → respiratory_assist_device/questionnaire_package.json} +0 -0
@@ -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 Server Base Url'
35
+ title: 'FHIR Endpoint',
36
+ description: 'URL of the DTR FHIR server'
41
37
 
42
- input :credentials,
43
- title: 'OAuth Credentials',
44
- type: :oauth_credentials,
45
- optional: true
38
+ group do
39
+ title 'Authorization'
46
40
 
47
- # All FHIR requests in this suite will use this FHIR client
48
- fhir_client do
49
- url :url
50
- oauth_credentials :credentials
51
- end
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
- # Hl7 Validator Wrapper:
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
- exclude_message do |message|
58
- message.message.match?(/\A\S+: \S+: URL value '.*' does not resolve/)
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
@@ -0,0 +1,7 @@
1
+ module DaVinciDTRTestKit
2
+ module DTROptions
3
+ SMART_2 = 'smart_app_launch_2'.freeze
4
+
5
+ SMART_2_REQUIREMENT = { smart_app_launch_version: SMART_2 }.freeze
6
+ end
7
+ end
@@ -1,30 +1,78 @@
1
- require_relative 'fixtures'
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 validate_questionnaire_pre_population(questionnaire_response, test_id)
14
- # Requirements:
15
- # - Prior to exposing the draft QuestionnaireResponse to the user for completion and/or review, the DTR client
16
- # SHALL execute all CQL necessary to resolve the initialExpression, candidateExpression and
17
- # calculatedExpression extensions found in the Questionnaire for any enabled elements.
18
- # - All items that are pre-populated (whether by the payer in the initial QuestionnaireResponse provided in the
19
- # questionnaire package, or from data retrieved from the EHR) SHALL have their origin.source set to ‘auto’.
20
- #
21
- # Note that in the questionnaire fixture, all cql expression elements are enabled, so we don't filter
22
- template_questionnaire_response = find_questionnaire_response_for_test_id(test_id)
23
- raise "missing QuestionnaireResponse template for test #{test_id}" unless template_questionnaire_response.present?
24
-
25
- questionnaire = find_questionnaire_instance_for_test_id(test_id)
26
- raise "missing Questionnaire for test #{test_id}" unless questionnaire.present?
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
- validation_errors = []
83
+
36
84
  validate_cql_executed(questionnaire_response.item, questionnaire_cql_expression_link_ids,
37
- template_prepopulation_expectations, template_override_expectations, validation_errors)
85
+ template_prepopulation_expectations, template_override_expectations)
38
86
 
39
87
  if template_prepopulation_expectations.size.positive?
40
- validation_errors << 'Items expected to be pre-populated not found: ' \
41
- "#{template_prepopulation_expectations.keys.join(', ')}"
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
- validation_errors << 'Items expected to be pre-poplated and overridden not found: ' \
46
- "#{template_override_expectations.keys.join(', ')}"
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
- validation_errors.each { |msg| messages << { type: 'error', message: msg } }
50
- assert validation_errors.blank?, 'QuestionnaireResponse is not conformant. Check messages for issues found.'
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, error_messages)
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), error_messages,
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, error_messages)
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
- origin_extension = find_extension(target_item_answer,
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 source_extension.present?
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 source_extension.value == 'auto'
141
+ if origin_source == 'auto'
99
142
  expected_prepopulated[target_link_id] = target_item_answer
100
- elsif source_extension.value == 'override'
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: #{source_extension.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 validate_data_requirements_retrieved(expected_questionnaire_response, questionnaire_response)
109
- error_messages = []
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
- error_messages << "dataRequirement not satisfied for Library '#{library_name}'. Expected answer to " \
117
- "question with linkId `#{link_id}` to have coding with system: '`#{expected.system}`' " \
118
- "and value: '`#{expected.code}`'"
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
- def check_item_prepopulation(item, expected_answer, error_list, override)
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
- # check origin.source extension
136
- origin_extension = find_extension(answer,
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
- if source_extension.present?
141
- expected_source_value = override ? 'override' : 'auto'
142
- if source_extension.value != expected_source_value
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
- error_list << "No answer for item `#{item.linkId}`"
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
- record_response_route :get, EHR_AUTHORIZE_PATH, 'dtr_smart_app_ehr_authorize', method(:ehr_authorize),
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, 'dtr_smart_app_ehr_authorize', method(:ehr_authorize),
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
- # list of lists
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 questionnaire_package_for_canonical(url)
46
- @canonical_to_questionnaire_package_map[url]
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
- def group_id_to_questionnaire_response_map
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
- def get_url_for_questionnaire(questionnaire_bundle)
96
- questionnaire_bundle.entry[0].resource.url
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
- DATA_REQUIREMENT_ANSWERS = { 'RAD Prepopulation' => '3.1' }.freeze
6
-
7
- def get_questionnaire_package_for_group_id(group_id)
8
- FixtureLoader.instance.questionnaire_package_for_group_id(group_id)
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
- def find_questionnaire_instance_for_test_id(test_id)
12
- canonical_url = find_questionnaire_canonical_for_test_id(test_id)
13
- return unless canonical_url.present?
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
- def find_questionnaire_canonical_for_test_id(test_id)
26
- canonical_url = nil
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
- def get_questionnaire_canonical_for_group_id(group_id)
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
- questionnaire_response = get_questionnaire_response_for_group_id(one_group_id)
56
- end
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
- questionnaire_response
51
+ FixtureLoader.instance[fixture_path]
59
52
  end
60
53
 
61
- def get_questionnaire_response_for_group_id(group_id)
62
- FixtureLoader.instance.questionnaire_response_for_group_id(group_id)
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