davinci_dtr_test_kit 0.11.1 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/lib/davinci_dtr_test_kit/auth_groups/token_request_test.rb +1 -1
  3. data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_full_ehr_dinner_questionnaire_package_request_test.rb +52 -0
  4. data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_full_ehr_dinner_static_questionnaire_response_conformance_test.rb +15 -0
  5. data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_full_ehr_dinner_static_questionnaire_response_correctness_test.rb +30 -0
  6. data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_full_ehr_launch_attestation_test.rb +28 -0
  7. data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_full_ehr_prepopulation_attestation_test.rb +30 -0
  8. data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_full_ehr_prepopulation_override_attestation_test.rb +27 -0
  9. data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_full_ehr_questionnaire_workflow_group.rb +91 -0
  10. data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_full_ehr_rendering_enabled_questions_attestation_test.rb +30 -0
  11. data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_full_ehr_store_attestation_test.rb +29 -0
  12. data/lib/davinci_dtr_test_kit/client_groups/dinner_static/{dtr_dinner_questionnaire_package_request_test.rb → dtr_smart_app_dinner_questionnaire_package_request_test.rb} +5 -5
  13. data/lib/davinci_dtr_test_kit/client_groups/dinner_static/{prepopulation_attestation_test.rb → dtr_smart_app_prepopulation_attestation_test.rb} +2 -2
  14. data/lib/davinci_dtr_test_kit/client_groups/dinner_static/{prepopulation_override_attestation_test.rb → dtr_smart_app_prepopulation_override_attestation_test.rb} +2 -2
  15. data/lib/davinci_dtr_test_kit/client_groups/dinner_static/{dtr_questionnaire_response_save_test.rb → dtr_smart_app_questionnaire_response_save_test.rb} +2 -2
  16. data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_smart_app_questionnaire_workflow_group.rb +13 -13
  17. data/lib/davinci_dtr_test_kit/client_groups/dinner_static/{rendering_enabled_questions_attestation_test.rb → dtr_smart_app_rendering_enabled_questions_attestation_test.rb} +2 -2
  18. data/lib/davinci_dtr_test_kit/client_groups/shared/dtr_questionnaire_package_request_validation_test.rb +1 -1
  19. data/lib/davinci_dtr_test_kit/client_groups/shared/dtr_questionnaire_response_basic_conformance_test.rb +3 -7
  20. data/lib/davinci_dtr_test_kit/client_groups/shared/dtr_questionnaire_response_pre_population_test.rb +9 -5
  21. data/lib/davinci_dtr_test_kit/cql_test.rb +182 -137
  22. data/lib/davinci_dtr_test_kit/docs/dtr_full_ehr_suite_description_v201.md +127 -0
  23. data/lib/davinci_dtr_test_kit/docs/dtr_light_ehr_suite_description_v201.md +29 -0
  24. data/lib/davinci_dtr_test_kit/dtr_full_ehr_suite.rb +4 -12
  25. data/lib/davinci_dtr_test_kit/dtr_light_ehr_suite.rb +38 -25
  26. data/lib/davinci_dtr_test_kit/dtr_options.rb +7 -0
  27. data/lib/davinci_dtr_test_kit/dtr_questionnaire_response_validation.rb +118 -75
  28. data/lib/davinci_dtr_test_kit/dtr_smart_app_suite.rb +6 -3
  29. data/lib/davinci_dtr_test_kit/fixture_loader.rb +6 -84
  30. data/lib/davinci_dtr_test_kit/fixtures.rb +43 -48
  31. data/lib/davinci_dtr_test_kit/mock_auth_server.rb +101 -18
  32. data/lib/davinci_dtr_test_kit/mock_ehr.rb +32 -24
  33. data/lib/davinci_dtr_test_kit/mock_payer.rb +41 -64
  34. data/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_next_questionnaire_expressions_test.rb +2 -2
  35. data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_request_validation_test.rb +11 -19
  36. data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_response_bundles_validation_test.rb +6 -6
  37. data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_response_search_validation_test.rb +6 -6
  38. data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_response_validation_test.rb +17 -18
  39. data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_next_request_validation_test.rb +6 -7
  40. data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_next_response_validation_test.rb +3 -1
  41. data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_request_validation_test.rb +10 -21
  42. data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_response_validation_test.rb +7 -13
  43. data/lib/davinci_dtr_test_kit/tags.rb +1 -0
  44. data/lib/davinci_dtr_test_kit/urls.rb +13 -10
  45. data/lib/davinci_dtr_test_kit/validation_test.rb +8 -9
  46. data/lib/davinci_dtr_test_kit/version.rb +1 -1
  47. data/lib/davinci_dtr_test_kit.rb +2 -2
  48. metadata +37 -12
  49. data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_full_ehr_questionnaire_workflow_group.rb +0 -19
  50. /data/lib/davinci_dtr_test_kit/fixtures/{pre_populated_questionnaire_response.json → respiratory_assist_device/pre_populated_questionnaire_response.json} +0 -0
  51. /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')
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')
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']
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