davinci_dtr_test_kit 0.13.0 → 0.14.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (128) hide show
  1. checksums.yaml +4 -4
  2. data/lib/davinci_dtr_test_kit/client_groups/dinner_adaptive/dtr_adaptive_questionnaire_completion_group.rb +23 -0
  3. data/lib/davinci_dtr_test_kit/client_groups/dinner_adaptive/dtr_adaptive_questionnaire_followup_questions_group.rb +26 -0
  4. data/lib/davinci_dtr_test_kit/client_groups/dinner_adaptive/dtr_adaptive_questionnaire_next_question_request_test.rb +93 -0
  5. data/lib/davinci_dtr_test_kit/client_groups/dinner_adaptive/dtr_adaptive_questionnaire_next_question_request_validation_test.rb +62 -0
  6. data/lib/davinci_dtr_test_kit/client_groups/dinner_adaptive/dtr_adaptive_questionnaire_next_question_retrieval_group.rb +23 -0
  7. data/lib/davinci_dtr_test_kit/client_groups/dinner_adaptive/dtr_adaptive_questionnaire_response_validation_test.rb +66 -0
  8. data/lib/davinci_dtr_test_kit/client_groups/dinner_adaptive/dtr_full_ehr_adaptive_dinner_questionnaire_workflow_group.rb +76 -0
  9. data/lib/davinci_dtr_test_kit/client_groups/dinner_adaptive/dtr_full_ehr_adaptive_questionnaire_initial_retrieval_group.rb +27 -0
  10. data/lib/davinci_dtr_test_kit/client_groups/dinner_adaptive/dtr_full_ehr_adaptive_questionnaire_request_test.rb +63 -0
  11. data/lib/davinci_dtr_test_kit/client_groups/dinner_adaptive/dtr_smart_app_adaptive_questionnaire_initial_retrieval_group.rb +24 -0
  12. data/lib/davinci_dtr_test_kit/client_groups/dinner_adaptive/dtr_smart_app_adaptive_questionnaire_request_test.rb +148 -0
  13. data/lib/davinci_dtr_test_kit/client_groups/dinner_adaptive/dtr_smart_app_questionnaire_workflow_group.rb +75 -0
  14. data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_full_ehr_questionnaire_workflow_group.rb +22 -38
  15. data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_smart_app_dinner_questionnaire_package_request_test.rb +13 -16
  16. data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_smart_app_questionnaire_workflow_group.rb +9 -31
  17. data/lib/davinci_dtr_test_kit/client_groups/{dinner_static → full_ehr}/dtr_full_ehr_launch_attestation_test.rb +7 -6
  18. data/lib/davinci_dtr_test_kit/client_groups/{dinner_static → full_ehr}/dtr_full_ehr_prepopulation_attestation_test.rb +7 -7
  19. data/lib/davinci_dtr_test_kit/client_groups/{dinner_static → full_ehr}/dtr_full_ehr_prepopulation_override_attestation_test.rb +7 -7
  20. data/lib/davinci_dtr_test_kit/client_groups/{dinner_static/dtr_full_ehr_dinner_questionnaire_package_request_test.rb → full_ehr/dtr_full_ehr_questionnaire_package_request_test.rb} +2 -3
  21. data/lib/davinci_dtr_test_kit/client_groups/{dinner_static/dtr_full_ehr_dinner_static_questionnaire_response_conformance_test.rb → full_ehr/dtr_full_ehr_questionnaire_response_conformance_test.rb} +7 -3
  22. data/lib/davinci_dtr_test_kit/client_groups/{dinner_static/dtr_full_ehr_dinner_static_questionnaire_response_correctness_test.rb → full_ehr/dtr_full_ehr_questionnaire_response_correctness_test.rb} +14 -7
  23. data/lib/davinci_dtr_test_kit/client_groups/{dinner_static → full_ehr}/dtr_full_ehr_rendering_enabled_questions_attestation_test.rb +7 -7
  24. data/lib/davinci_dtr_test_kit/client_groups/full_ehr/dtr_full_ehr_saving_questionnaire_response_group.rb +29 -0
  25. data/lib/davinci_dtr_test_kit/client_groups/{dinner_static → full_ehr}/dtr_full_ehr_store_attestation_test.rb +7 -7
  26. data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_questionnaire_rendering_attestation_test.rb +7 -6
  27. data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_resp_questionnaire_package_request_test.rb +15 -18
  28. data/lib/davinci_dtr_test_kit/client_groups/shared/dtr_questionnaire_response_basic_conformance_test.rb +5 -1
  29. data/lib/davinci_dtr_test_kit/client_groups/shared/dtr_questionnaire_response_pre_population_test.rb +12 -5
  30. data/lib/davinci_dtr_test_kit/client_groups/{dinner_static → smart_app}/dtr_smart_app_prepopulation_attestation_test.rb +7 -7
  31. data/lib/davinci_dtr_test_kit/client_groups/{dinner_static → smart_app}/dtr_smart_app_prepopulation_override_attestation_test.rb +7 -6
  32. data/lib/davinci_dtr_test_kit/client_groups/{dinner_static → smart_app}/dtr_smart_app_questionnaire_response_save_test.rb +17 -7
  33. data/lib/davinci_dtr_test_kit/client_groups/{dinner_static → smart_app}/dtr_smart_app_rendering_enabled_questions_attestation_test.rb +7 -7
  34. data/lib/davinci_dtr_test_kit/client_groups/smart_app/dtr_smart_app_saving_questionnaire_response_group.rb +27 -0
  35. data/lib/davinci_dtr_test_kit/cql_test.rb +37 -140
  36. data/lib/davinci_dtr_test_kit/create_test.rb +25 -0
  37. data/lib/davinci_dtr_test_kit/docs/dtr_full_ehr_suite_description_v201.md +95 -37
  38. data/lib/davinci_dtr_test_kit/docs/dtr_light_ehr_suite_description_v201.md +15 -6
  39. data/lib/davinci_dtr_test_kit/docs/dtr_payer_server_suite_description_v201.md +32 -29
  40. data/lib/davinci_dtr_test_kit/docs/dtr_smart_app_suite_description_v201.md +48 -32
  41. data/lib/davinci_dtr_test_kit/dtr_full_ehr_suite.rb +13 -17
  42. data/lib/davinci_dtr_test_kit/dtr_light_ehr_suite.rb +67 -2
  43. data/lib/davinci_dtr_test_kit/dtr_payer_server_suite.rb +9 -20
  44. data/lib/davinci_dtr_test_kit/dtr_questionnaire_response_validation.rb +18 -10
  45. data/lib/davinci_dtr_test_kit/dtr_smart_app_suite.rb +32 -59
  46. data/lib/davinci_dtr_test_kit/endpoints/cors.rb +20 -0
  47. data/lib/davinci_dtr_test_kit/endpoints/mock_authorization/authorize_endpoint.rb +32 -0
  48. data/lib/davinci_dtr_test_kit/endpoints/mock_authorization/simple_token_endpoint.rb +19 -0
  49. data/lib/davinci_dtr_test_kit/endpoints/mock_authorization/token_endpoint.rb +116 -0
  50. data/lib/davinci_dtr_test_kit/endpoints/mock_authorization.rb +83 -0
  51. data/lib/davinci_dtr_test_kit/endpoints/mock_ehr/fhir_get_endpoint.rb +95 -0
  52. data/lib/davinci_dtr_test_kit/endpoints/mock_ehr/questionnaire_response_endpoint.rb +22 -0
  53. data/lib/davinci_dtr_test_kit/endpoints/mock_ehr.rb +25 -0
  54. data/lib/davinci_dtr_test_kit/endpoints/mock_payer/full_ehr_next_question_endpoint.rb +11 -0
  55. data/lib/davinci_dtr_test_kit/endpoints/mock_payer/full_ehr_questionnaire_package_endpoint.rb +11 -0
  56. data/lib/davinci_dtr_test_kit/endpoints/mock_payer/next_question_endpoint.rb +162 -0
  57. data/lib/davinci_dtr_test_kit/endpoints/mock_payer/next_question_proxy_endpoint.rb +36 -0
  58. data/lib/davinci_dtr_test_kit/endpoints/mock_payer/questionnaire_package_endpoint.rb +62 -0
  59. data/lib/davinci_dtr_test_kit/endpoints/mock_payer/questionnaire_package_proxy_endpoint.rb +38 -0
  60. data/lib/davinci_dtr_test_kit/endpoints/mock_payer.rb +36 -0
  61. data/lib/davinci_dtr_test_kit/fixtures/dinner_adaptive/dinner_order_adaptive_next_question_burrito.json +10 -2
  62. data/lib/davinci_dtr_test_kit/fixtures/dinner_adaptive/dinner_order_adaptive_next_question_hamburger.json +10 -2
  63. data/lib/davinci_dtr_test_kit/fixtures/dinner_adaptive/dinner_order_adaptive_next_question_initial.json +10 -2
  64. data/lib/davinci_dtr_test_kit/fixtures/dinner_adaptive/questionnaire_dinner_order_adaptive.json +4 -3
  65. data/lib/davinci_dtr_test_kit/fixtures.rb +24 -1
  66. data/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_form_libraries_test.rb +2 -2
  67. data/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_form_questionnaire_expressions_test.rb +4 -3
  68. data/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_form_questionnaire_extensions_test.rb +3 -2
  69. data/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_next_questionnaire_expressions_test.rb +6 -6
  70. data/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_next_questionnaire_extensions_test.rb +6 -5
  71. data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_group.rb +2 -2
  72. data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_response_bundles_validation_test.rb +6 -9
  73. data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_response_search_validation_test.rb +15 -12
  74. data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_response_validation_test.rb +33 -23
  75. data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_next_response_complete_test.rb +4 -4
  76. data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_next_response_validation_test.rb +16 -12
  77. data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_libraries_test.rb +2 -2
  78. data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_questionnaire_expressions_test.rb +5 -4
  79. data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_questionnaire_extensions_test.rb +4 -3
  80. data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_response_validation_test.rb +32 -25
  81. data/lib/davinci_dtr_test_kit/profiles/communication_request/communication_request_read.rb +29 -0
  82. data/lib/davinci_dtr_test_kit/profiles/communication_request/communication_request_validation.rb +35 -0
  83. data/lib/davinci_dtr_test_kit/profiles/communication_request_group.rb +39 -0
  84. data/lib/davinci_dtr_test_kit/profiles/coverage/coverage_read.rb +29 -0
  85. data/lib/davinci_dtr_test_kit/profiles/coverage/coverage_validation.rb +35 -0
  86. data/lib/davinci_dtr_test_kit/profiles/coverage_group.rb +38 -0
  87. data/lib/davinci_dtr_test_kit/profiles/device_request/device_request_read.rb +29 -0
  88. data/lib/davinci_dtr_test_kit/profiles/device_request/device_request_validation.rb +35 -0
  89. data/lib/davinci_dtr_test_kit/profiles/device_request_group.rb +39 -0
  90. data/lib/davinci_dtr_test_kit/profiles/encounter/encounter_read.rb +29 -0
  91. data/lib/davinci_dtr_test_kit/profiles/encounter/encounter_validation.rb +35 -0
  92. data/lib/davinci_dtr_test_kit/profiles/encounter_group.rb +39 -0
  93. data/lib/davinci_dtr_test_kit/profiles/medication_request/medication_request_read.rb +29 -0
  94. data/lib/davinci_dtr_test_kit/profiles/medication_request/medication_request_validation.rb +35 -0
  95. data/lib/davinci_dtr_test_kit/profiles/medication_request_group.rb +39 -0
  96. data/lib/davinci_dtr_test_kit/profiles/nutrition_order/nutrition_order_read.rb +29 -0
  97. data/lib/davinci_dtr_test_kit/profiles/nutrition_order/nutrition_order_validation.rb +35 -0
  98. data/lib/davinci_dtr_test_kit/profiles/nutrition_order_group.rb +39 -0
  99. data/lib/davinci_dtr_test_kit/profiles/questionnaire_response/questionnaire_response_context_search.rb +35 -0
  100. data/lib/davinci_dtr_test_kit/profiles/questionnaire_response/questionnaire_response_create.rb +26 -0
  101. data/lib/davinci_dtr_test_kit/profiles/questionnaire_response/questionnaire_response_patient_search.rb +55 -0
  102. data/lib/davinci_dtr_test_kit/profiles/questionnaire_response/questionnaire_response_read.rb +22 -0
  103. data/lib/davinci_dtr_test_kit/profiles/questionnaire_response/questionnaire_response_update.rb +26 -0
  104. data/lib/davinci_dtr_test_kit/profiles/questionnaire_response/questionnaire_response_validation.rb +37 -0
  105. data/lib/davinci_dtr_test_kit/profiles/questionnaire_response_group.rb +66 -0
  106. data/lib/davinci_dtr_test_kit/profiles/service_request/service_request_read.rb +29 -0
  107. data/lib/davinci_dtr_test_kit/profiles/service_request/service_request_validation.rb +35 -0
  108. data/lib/davinci_dtr_test_kit/profiles/service_request_group.rb +39 -0
  109. data/lib/davinci_dtr_test_kit/profiles/task/task_create.rb +26 -0
  110. data/lib/davinci_dtr_test_kit/profiles/task/task_read.rb +29 -0
  111. data/lib/davinci_dtr_test_kit/profiles/task/task_update.rb +26 -0
  112. data/lib/davinci_dtr_test_kit/profiles/task/task_validation.rb +35 -0
  113. data/lib/davinci_dtr_test_kit/profiles/task_group.rb +52 -0
  114. data/lib/davinci_dtr_test_kit/profiles/vision_prescription/vision_prescription_read.rb +29 -0
  115. data/lib/davinci_dtr_test_kit/profiles/vision_prescription/vision_prescription_validation.rb +35 -0
  116. data/lib/davinci_dtr_test_kit/profiles/vision_prescription_group.rb +39 -0
  117. data/lib/davinci_dtr_test_kit/read_test.rb +22 -0
  118. data/lib/davinci_dtr_test_kit/tags.rb +5 -7
  119. data/lib/davinci_dtr_test_kit/update_test.rb +25 -0
  120. data/lib/davinci_dtr_test_kit/validation_test.rb +19 -4
  121. data/lib/davinci_dtr_test_kit/version.rb +1 -1
  122. metadata +110 -21
  123. data/lib/davinci_dtr_test_kit/ext/inferno_core/record_response_route.rb +0 -98
  124. data/lib/davinci_dtr_test_kit/ext/inferno_core/request.rb +0 -19
  125. data/lib/davinci_dtr_test_kit/ext/inferno_core/runnable.rb +0 -35
  126. data/lib/davinci_dtr_test_kit/mock_auth_server.rb +0 -228
  127. data/lib/davinci_dtr_test_kit/mock_ehr.rb +0 -105
  128. data/lib/davinci_dtr_test_kit/mock_payer.rb +0 -100
@@ -0,0 +1,22 @@
1
+ require_relative '../mock_authorization'
2
+
3
+ module DaVinciDTRTestKit
4
+ module MockEHR
5
+ class QuestionnaireResponseEndpoint < Inferno::DSL::SuiteEndpoint
6
+ def test_run_identifier
7
+ MockAuthorization.extract_client_id_from_bearer_token(request)
8
+ end
9
+
10
+ def make_response
11
+ response.status = 201
12
+ response.format = 'application/fhir+json'
13
+ response['Access-Control-Allow-Origin'] = '*'
14
+ response.body = request.body.string
15
+ end
16
+
17
+ def update_result
18
+ results_repo.update_result(result.id, 'pass')
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DaVinciDTRTestKit
4
+ module MockEHR
5
+ RESOURCE_SERVER_BASE = ENV.fetch('FHIR_REFERENCE_SERVER')
6
+ RESOURCE_SERVER_BEARER_TOKEN = 'SAMPLE_TOKEN'
7
+
8
+ module_function
9
+
10
+ def resource_server_client
11
+ client = FHIR::Client.new(RESOURCE_SERVER_BASE)
12
+ client.set_bearer_token(RESOURCE_SERVER_BEARER_TOKEN)
13
+ client
14
+ end
15
+
16
+ def metadata(_env)
17
+ cs = resource_server_client.capability_statement
18
+ if cs.present?
19
+ [200, { 'Content-Type' => 'application/fhir+json', 'Access-Control-Allow-Origin' => '*' }, [cs.to_json]]
20
+ else
21
+ [500, { 'Access-Control-Allow-Origin' => '*' }, ['Unexpected error occurred while fetching metadata']]
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,11 @@
1
+ require_relative 'next_question_endpoint'
2
+
3
+ module DaVinciDTRTestKit
4
+ module MockPayer
5
+ class FullEHRNextQuestionEndpoint < NextQuestionEndpoint
6
+ def test_run_identifier
7
+ request.headers['authorization']&.delete_prefix('Bearer ')
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ require_relative 'questionnaire_package_endpoint'
2
+
3
+ module DaVinciDTRTestKit
4
+ module MockPayer
5
+ class FullEHRQuestionnairePackageEndpoint < QuestionnairePackageEndpoint
6
+ def test_run_identifier
7
+ request.headers['authorization']&.delete_prefix('Bearer ')
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,162 @@
1
+ require_relative '../mock_authorization'
2
+ require_relative '../mock_payer'
3
+ require_relative '../../fixtures'
4
+
5
+ module DaVinciDTRTestKit
6
+ module MockPayer
7
+ class NextQuestionEndpoint < Inferno::DSL::SuiteEndpoint
8
+ include MockPayer
9
+
10
+ def test_run_identifier
11
+ MockAuthorization.extract_client_id_from_bearer_token(request)
12
+ end
13
+
14
+ def tags
15
+ [test.config.options[:next_tag]]
16
+ end
17
+
18
+ def make_response
19
+ response.status = 200
20
+ response.format = 'application/fhir+json'
21
+ response.headers['Access-Control-Allow-Origin'] = '*'
22
+ response.body = build_questionnaire_next_response.to_json
23
+ end
24
+
25
+ def update_result
26
+ results_repo.update_result(result.id, 'pass') unless test.config.options[:accepts_multiple_requests]
27
+ end
28
+
29
+ private
30
+
31
+ def evaluator
32
+ @evaluator ||= Inferno::DSL::FhirpathEvaluation::Evaluator.new
33
+ end
34
+
35
+ def build_questionnaire_next_response
36
+ input_parameters = parse_fhir_object(request.body.string)
37
+ return input_parameters if input_parameters.is_a?(FHIR::OperationOutcome)
38
+
39
+ questionnaire_response = extract_questionnaire_response(input_parameters)
40
+ return questionnaire_response if questionnaire_response.is_a?(FHIR::OperationOutcome)
41
+
42
+ questionnaire_response_param = FHIR::Parameters::Parameter.new(name: 'return', resource: questionnaire_response)
43
+
44
+ if questionnaire_last_dinner_order_question_present?(questionnaire_response)
45
+ # change the questionnaire response status to completed and return the parameters
46
+ return handle_last_dinner_order(questionnaire_response)
47
+ end
48
+
49
+ next_questionnaire = determine_next_questionnaire(questionnaire_response, test.id)
50
+
51
+ return missing_next_questionnaire_outcome(test.id) unless next_questionnaire
52
+
53
+ update_questionnaire_response(questionnaire_response, next_questionnaire)
54
+
55
+ unless questionnaire_in_questionnaire_response_exist?(questionnaire_response, next_questionnaire)
56
+ issue = "Questionnaire #{questionnaire_response.questionnaire} does not exist"
57
+ outcome_param = build_outcome_param([outcome_issue('warning', 'not-found', issue)])
58
+ questionnaire_response_param.name = 'return'
59
+ return FHIR::Parameters.new(parameter: [questionnaire_response_param, outcome_param])
60
+ end
61
+
62
+ questionnaire_response
63
+ end
64
+
65
+ def extract_questionnaire_response(input_parameters)
66
+ if input_parameters.is_a?(FHIR::Parameters)
67
+ questionnaire_response_param = find_questionnaire_response(input_parameters)
68
+ return questionnaire_response_param if questionnaire_response_param.is_a?(FHIR::OperationOutcome)
69
+
70
+ questionnaire_response = questionnaire_response_param.resource
71
+ unless valid_questionnaire_response?(questionnaire_response)
72
+ return invalid_next_question_param_resource_outcome
73
+ end
74
+
75
+ questionnaire_response
76
+ elsif input_parameters.is_a?(FHIR::QuestionnaireResponse)
77
+ input_parameters
78
+ else
79
+ operation_outcome('error', 'invalid', 'wrong resource type submitted for $next-question request.')
80
+ end
81
+ end
82
+
83
+ def valid_questionnaire_response?(questionnaire_response)
84
+ questionnaire_response.is_a?(FHIR::QuestionnaireResponse)
85
+ end
86
+
87
+ def invalid_next_question_param_resource_outcome
88
+ issue = 'Input `parameter:questionnaire-response.resource` is missing or not a QuestionnaireResponse.'
89
+ operation_outcome('error', 'business-rule', issue)
90
+ end
91
+
92
+ def missing_next_questionnaire_outcome(test_id)
93
+ operation_outcome('error', 'business-rule', "No Questionnaire found for Inferno test #{test_id}")
94
+ end
95
+
96
+ def update_questionnaire_response(questionnaire_response, next_questionnaire = nil)
97
+ if next_questionnaire
98
+ questionnaire_response.contained.reject! { |resource| resource.resourceType == 'Questionnaire' }
99
+ questionnaire_response.contained << next_questionnaire
100
+ else
101
+ questionnaire_response.status = 'completed'
102
+ end
103
+ end
104
+
105
+ def determine_next_questionnaire(questionnaire_response, test_id)
106
+ # Retrieve the selected option from the response and determine the next set of questions
107
+ if questionnaire_dinner_order_selection_present?(questionnaire_response)
108
+ dinner_question_from_selection(questionnaire_response, test_id)
109
+ else
110
+ Fixtures.next_question_for_test(test_id)
111
+ end
112
+ end
113
+
114
+ def handle_last_dinner_order(questionnaire_response)
115
+ update_questionnaire_response(questionnaire_response)
116
+ questionnaire_response
117
+ end
118
+
119
+ # Retrieve the selected option from the response and determine the next set of questions
120
+ def dinner_question_from_selection(questionnaire_response, test_id)
121
+ option = retrieve_dinner_order_selection(questionnaire_response)
122
+ unless option
123
+ issue = 'Cannot determine next question to return: Dinner order selection answer missing '
124
+ return operation_outcome('error', 'business-rule', issue)
125
+ end
126
+
127
+ Fixtures.next_question_for_test(test_id, option)
128
+ end
129
+
130
+ def questionnaire_in_questionnaire_response_exist?(questionnaire_response, next_questionnaire)
131
+ questionnaire_response.questionnaire&.include?(next_questionnaire.id)
132
+ end
133
+
134
+ def questionnaire_dinner_order_selection_present?(questionnaire_response)
135
+ # LinkId = 3.1 for the What would you like for dinner? question
136
+ path = "contained.where($this is Questionnaire).item.where(linkId = '3').item.where(linkId = '3.1').exists()"
137
+ eval_result = evaluate_fhirpath(questionnaire_response, path)
138
+ !!eval_result.first&.dig('element')
139
+ end
140
+
141
+ def questionnaire_last_dinner_order_question_present?(questionnaire_response)
142
+ # LinkId = 3.3 for the Any special requests? question
143
+ path = "contained.where($this is Questionnaire).item.where(linkId = '3').item.where(linkId = '3.3').exists()"
144
+ eval_result = evaluate_fhirpath(questionnaire_response, path)
145
+ !!eval_result.first&.dig('element')
146
+ end
147
+
148
+ def retrieve_dinner_order_selection(questionnaire_response)
149
+ # LinkId = 3.1 for the What would you like for dinner? question
150
+ path = "item.where(linkId = '3').item.where(linkId = '3.1').answer.where(value is Coding).value.code"
151
+ eval_result = evaluate_fhirpath(questionnaire_response, path)
152
+ elem = eval_result.first&.dig('element')
153
+ elem&.parameterize&.underscore
154
+ end
155
+
156
+ # Wrapper around evaluator call. The SuiteEndpoint isn't a runnable, so we just instantiate a dummy runnable
157
+ def evaluate_fhirpath(fhir_resource, fhirpath_expression)
158
+ evaluator.evaluate_fhirpath(fhir_resource, fhirpath_expression, Inferno::TestSuite.new)
159
+ end
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,36 @@
1
+ module DaVinciDTRTestKit
2
+ module MockPayer
3
+ class NextQuestionProxyEndpoint < Inferno::DSL::SuiteEndpoint
4
+ def test_run_identifier
5
+ request.headers['authorization']&.delete_prefix('Bearer ')
6
+ end
7
+
8
+ def tags
9
+ [NEXT_TAG]
10
+ end
11
+
12
+ def make_response
13
+ session_id = result.test_session_id
14
+ session_data = Inferno::Repositories::SessionData.new
15
+ url_input = session_data.load(test_session_id: session_id, name: 'url')
16
+ credentials_input = session_data.load(test_session_id: session_id, name: 'credentials',
17
+ type: 'oauth_credentials')
18
+
19
+ client = FHIR::Client.new(url_input)
20
+ client.default_json
21
+ client.set_bearer_token(credentials_input.access_token) if credentials_input.access_token
22
+ payer_response = client.send(:post, '/Questionnaire/$next-question', JSON.parse(request.body.string),
23
+ { 'Content-Type' => 'application/fhir+json' })
24
+
25
+ response.status = 200
26
+ response.format = 'application/fhir+json'
27
+ response.headers['Access-Control-Allow-Origin'] = '*'
28
+ response.body = payer_response.response[:body].to_s
29
+ end
30
+
31
+ def update_result
32
+ results_repo.update_result(result.id, 'pass') unless test.config.options[:accepts_multiple_requests]
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,62 @@
1
+ require_relative '../mock_authorization'
2
+ require_relative '../mock_payer'
3
+ require_relative '../../fixtures'
4
+
5
+ module DaVinciDTRTestKit
6
+ module MockPayer
7
+ class QuestionnairePackageEndpoint < Inferno::DSL::SuiteEndpoint
8
+ include MockPayer
9
+
10
+ def test_run_identifier
11
+ MockAuthorization.extract_client_id_from_bearer_token(request)
12
+ end
13
+
14
+ def tags
15
+ [QUESTIONNAIRE_PACKAGE_TAG]
16
+ end
17
+
18
+ def make_response
19
+ response.status = 200
20
+ response.format = 'application/fhir+json'
21
+ response.headers['Access-Control-Allow-Origin'] = '*'
22
+ response.body = build_questionnaire_package_response.to_json
23
+ end
24
+
25
+ def update_result
26
+ results_repo.update_result(result.id, 'pass') unless test.config.options[:accepts_multiple_requests]
27
+ end
28
+
29
+ private
30
+
31
+ def build_questionnaire_package_response
32
+ input_parameters = parse_fhir_object(request.body.string)
33
+ return input_parameters if input_parameters.is_a?(FHIR::OperationOutcome)
34
+
35
+ questionnaire_package = Fixtures.questionnaire_package_for_test(test.id)
36
+ unless questionnaire_package
37
+ return operation_outcome('error', 'business-rule', "No Questionnaire found for Inferno test #{test.id}")
38
+ end
39
+
40
+ questionnaire_canonical = find_questionnaire_canonical(questionnaire_package)
41
+
42
+ other_questionnaire_params = input_parameters.parameter.filter do |param|
43
+ param.name == 'questionnaire' && param.valueCanonical != questionnaire_canonical
44
+ end
45
+
46
+ return questionnaire_package unless other_questionnaire_params.any?
47
+
48
+ package_bundle_param = FHIR::Parameters::Parameter.new(name: 'PackageBundle', resource: questionnaire_package)
49
+ issues = other_questionnaire_params.map do |param|
50
+ outcome_issue('warning', 'not-found', "Questionnaire #{param.valueCanonical} does not exist")
51
+ end
52
+ outcome_param = build_outcome_param(issues)
53
+
54
+ FHIR::Parameters.new(parameter: [package_bundle_param, outcome_param])
55
+ end
56
+
57
+ def find_questionnaire_canonical(questionnaire_package)
58
+ questionnaire_package&.entry&.find { |e| e.resource.is_a?(FHIR::Questionnaire) }&.resource&.url
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,38 @@
1
+ module DaVinciDTRTestKit
2
+ module MockPayer
3
+ class QuestionnairePackageProxyEndpoint < Inferno::DSL::SuiteEndpoint
4
+ def test_run_identifier
5
+ request.headers['authorization']&.delete_prefix('Bearer ')
6
+ end
7
+
8
+ def tags
9
+ [QUESTIONNAIRE_TAG]
10
+ end
11
+
12
+ def make_response
13
+ session_id = result.test_session_id
14
+ session_data = Inferno::Repositories::SessionData.new
15
+ endpoint_input = session_data.load(test_session_id: session_id, name: 'custom_endpoint')
16
+ url_input = session_data.load(test_session_id: session_id, name: 'url')
17
+ credentials_input = session_data.load(test_session_id: session_id, name: 'credentials',
18
+ type: 'oauth_credentials')
19
+
20
+ client = FHIR::Client.new(url_input)
21
+ client.default_json
22
+ client.set_bearer_token(credentials_input.access_token) if credentials_input.access_token
23
+ endpoint = endpoint_input.nil? ? '/Questionnaire/$questionnaire-package' : endpoint_input
24
+ payer_response = client.send(:post, endpoint, JSON.parse(request.body.string),
25
+ { 'Content-Type' => 'application/fhir+json' })
26
+
27
+ response.status = 200
28
+ response.format = 'application/fhir+json'
29
+ response.headers['Access-Control-Allow-Origin'] = '*'
30
+ response.body = payer_response.response[:body].to_s
31
+ end
32
+
33
+ def update_result
34
+ results_repo.update_result(result.id, 'pass') unless test.config.options[:accepts_multiple_requests]
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,36 @@
1
+ module DaVinciDTRTestKit
2
+ module MockPayer
3
+ def parse_fhir_object(str)
4
+ FHIR.from_contents(str)
5
+ rescue StandardError
6
+ operation_outcome('error', 'invalid', 'No valid input parameters')
7
+ end
8
+
9
+ def find_questionnaire_response(input_parameters)
10
+ questionnaire_response_param = input_parameters.try(&:parameter)&.find do |param|
11
+ param.name == 'questionnaire-response'
12
+ end
13
+ return questionnaire_response_param if questionnaire_response_param
14
+
15
+ operation_outcome('error', 'business-rule',
16
+ 'Input parameter does not have a `parameter:questionnaire-response` slice.')
17
+ end
18
+
19
+ def build_outcome_param(issues)
20
+ FHIR::Parameters::Parameter.new(
21
+ name: 'Outcome',
22
+ resource: FHIR::OperationOutcome.new(issue: issues)
23
+ )
24
+ end
25
+
26
+ def operation_outcome(severity, code, text = nil)
27
+ FHIR::OperationOutcome.new(issue: outcome_issue(severity, code, text))
28
+ end
29
+
30
+ def outcome_issue(severity, code, text = nil)
31
+ FHIR::OperationOutcome::Issue.new(severity:, code:).tap do |issue|
32
+ issue.details = FHIR::CodeableConcept.new(text:) if text.present?
33
+ end
34
+ end
35
+ end
36
+ end
@@ -4,9 +4,14 @@
4
4
  "meta": {
5
5
  "profile": [
6
6
  "http://hl7.org/fhir/StructureDefinition/cqf-questionnaire",
7
- "http://hl7.org/fhir/us/davinci-dtr/StructureDefinition/dtr-questionnaire-adapt"
7
+ "http://hl7.org/fhir/us/davinci-dtr/StructureDefinition/dtr-questionnaire-adapt",
8
+ "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-adapt"
8
9
  ]
9
10
  },
11
+ "text": {
12
+ "status": "generated",
13
+ "div": "<div xmlns=\"http://www.w3.org/1999/xhtml\">This is an adaptive questionnaire used to determine what a patient would like for dinner, including pre-populated patient information.</div>"
14
+ },
10
15
  "extension": [
11
16
  {
12
17
  "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-questionnaireAdaptive",
@@ -17,6 +22,9 @@
17
22
  "valueCanonical": "https://madie.cms.gov/Library/DTRTest"
18
23
  }
19
24
  ],
25
+ "derivedFrom": [
26
+ "urn:inferno:dtr-test-kit:dinner-order-adaptive"
27
+ ],
20
28
  "name": "DinnerOrderAdaptive",
21
29
  "title": "Dinner Order (Adaptive)",
22
30
  "status": "draft",
@@ -167,4 +175,4 @@
167
175
  ]
168
176
  }
169
177
  ]
170
- }
178
+ }
@@ -4,9 +4,14 @@
4
4
  "meta": {
5
5
  "profile": [
6
6
  "http://hl7.org/fhir/StructureDefinition/cqf-questionnaire",
7
- "http://hl7.org/fhir/us/davinci-dtr/StructureDefinition/dtr-questionnaire-adapt"
7
+ "http://hl7.org/fhir/us/davinci-dtr/StructureDefinition/dtr-questionnaire-adapt",
8
+ "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-adapt"
8
9
  ]
9
10
  },
11
+ "text": {
12
+ "status": "generated",
13
+ "div": "<div xmlns=\"http://www.w3.org/1999/xhtml\">This is an adaptive questionnaire used to determine what a patient would like for dinner, including pre-populated patient information.</div>"
14
+ },
10
15
  "extension": [
11
16
  {
12
17
  "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-questionnaireAdaptive",
@@ -17,6 +22,9 @@
17
22
  "valueCanonical": "https://madie.cms.gov/Library/DTRTest"
18
23
  }
19
24
  ],
25
+ "derivedFrom": [
26
+ "urn:inferno:dtr-test-kit:dinner-order-adaptive"
27
+ ],
20
28
  "name": "DinnerOrderAdaptive",
21
29
  "title": "Dinner Order (Adaptive)",
22
30
  "status": "draft",
@@ -172,4 +180,4 @@
172
180
  ]
173
181
  }
174
182
  ]
175
- }
183
+ }
@@ -4,9 +4,14 @@
4
4
  "meta": {
5
5
  "profile": [
6
6
  "http://hl7.org/fhir/StructureDefinition/cqf-questionnaire",
7
- "http://hl7.org/fhir/us/davinci-dtr/StructureDefinition/dtr-questionnaire-adapt"
7
+ "http://hl7.org/fhir/us/davinci-dtr/StructureDefinition/dtr-questionnaire-adapt",
8
+ "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-adapt"
8
9
  ]
9
10
  },
11
+ "text": {
12
+ "status": "generated",
13
+ "div": "<div xmlns=\"http://www.w3.org/1999/xhtml\">This is an adaptive questionnaire used to determine what a patient would like for dinner, including pre-populated patient information.</div>"
14
+ },
10
15
  "extension": [
11
16
  {
12
17
  "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-questionnaireAdaptive",
@@ -17,6 +22,9 @@
17
22
  "valueCanonical": "https://madie.cms.gov/Library/DTRTest"
18
23
  }
19
24
  ],
25
+ "derivedFrom": [
26
+ "urn:inferno:dtr-test-kit:dinner-order-adaptive"
27
+ ],
20
28
  "name": "DinnerOrderAdaptive",
21
29
  "title": "Dinner Order (Adaptive)",
22
30
  "status": "draft",
@@ -137,4 +145,4 @@
137
145
  ]
138
146
  }
139
147
  ]
140
- }
148
+ }
@@ -5,10 +5,12 @@
5
5
  "resource": {
6
6
  "resourceType": "Questionnaire",
7
7
  "id": "DinnerOrderAdaptive",
8
+ "url": "urn:inferno:dtr-test-kit:dinner-order-adaptive",
8
9
  "meta": {
9
10
  "profile": [
10
11
  "http://hl7.org/fhir/StructureDefinition/cqf-questionnaire",
11
- "http://hl7.org/fhir/us/davinci-dtr/StructureDefinition/dtr-questionnaire-adapt-search"
12
+ "http://hl7.org/fhir/us/davinci-dtr/StructureDefinition/dtr-questionnaire-adapt-search",
13
+ "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-adapt"
12
14
  ]
13
15
  },
14
16
  "extension": [
@@ -23,7 +25,6 @@
23
25
  ],
24
26
  "name": "DinnerOrderAdaptive",
25
27
  "title": "Dinner Order (Adaptive)",
26
- "url": "urn:inferno:dtr-test-kit:dinner-order-adaptive",
27
28
  "status": "draft",
28
29
  "subjectType": [
29
30
  "Patient"
@@ -92,4 +93,4 @@
92
93
  }
93
94
  }
94
95
  ]
95
- }
96
+ }
@@ -21,6 +21,23 @@ module DaVinciDTRTestKit
21
21
  ],
22
22
  questionnaire_package: File.join('dinner_static', 'questionnaire_dinner_order_static.json'),
23
23
  questionnaire_response: File.join('dinner_static', 'questionnaire_response_dinner_order_static.json')
24
+ },
25
+ {
26
+ group_ids: [
27
+ 'dtr_full_ehr_adaptive_questionnaire_retrieval',
28
+ 'dtr_smart_app_adaptive_questionnaire_retrieval'
29
+ ],
30
+ questionnaire_package: File.join('dinner_adaptive', 'questionnaire_dinner_order_adaptive.json'),
31
+ next_question: File.join('dinner_adaptive', 'dinner_order_adaptive_next_question_initial.json')
32
+ },
33
+ {
34
+ group_ids: [
35
+ 'dtr_adaptive_questionnaire_followup_questions'
36
+ ],
37
+ next_question: {
38
+ bean_burrito: File.join('dinner_adaptive', 'dinner_order_adaptive_next_question_burrito.json'),
39
+ hamburger: File.join('dinner_adaptive', 'dinner_order_adaptive_next_question_hamburger.json')
40
+ }
24
41
  }
25
42
  ].freeze
26
43
 
@@ -31,6 +48,11 @@ module DaVinciDTRTestKit
31
48
  get_fixture(full_test_id, :questionnaire_package)
32
49
  end
33
50
 
51
+ # full_test_id needs to be the long inferno-generated ID that includes hyphenated ancestor IDs
52
+ def next_question_for_test(full_test_id, option = nil)
53
+ get_fixture(full_test_id, :next_question, option)
54
+ end
55
+
34
56
  # full_test_id needs to be the long inferno-generated ID that includes hyphenated ancestor IDs
35
57
  def questionnaire_response_for_test(full_test_id)
36
58
  get_fixture(full_test_id, :questionnaire_response)
@@ -43,10 +65,11 @@ module DaVinciDTRTestKit
43
65
 
44
66
  private
45
67
 
46
- def get_fixture(full_test_id, fixture_type)
68
+ def get_fixture(full_test_id, fixture_type, option = nil)
47
69
  fixture_path = extract_group_ids(full_test_id).filter_map do |group_id|
48
70
  FIXTURE_CONFIG.find { |fc| fc[:group_ids].include?(group_id) }&.dig(fixture_type)
49
71
  end&.first
72
+ fixture_path = fixture_path[option.to_sym] if option && fixture_path.is_a?(Hash)
50
73
 
51
74
  FixtureLoader.instance[fixture_path]
52
75
  end
@@ -12,8 +12,8 @@ module DaVinciDTRTestKit
12
12
 
13
13
  run do
14
14
  skip_if retrieval_method == 'Static', 'Performing only static flow tests - only one flow is required.'
15
- skip_if scratch[:adaptive_responses].nil?, 'No questionnaire bundle returned.'
16
- check_libraries(scratch[:adaptive_responses])
15
+ skip_if scratch[:adaptive_questionnaire_bundles].nil?, 'No questionnaire bundle returned.'
16
+ check_libraries(scratch[:adaptive_questionnaire_bundles])
17
17
  end
18
18
  end
19
19
  end
@@ -12,9 +12,10 @@ module DaVinciDTRTestKit
12
12
 
13
13
  run do
14
14
  skip_if retrieval_method == 'Static', 'Performing only static flow tests - only one flow is required.'
15
- skip_if scratch[:adaptive_responses].nil?, 'No questionnaire bundle returned.'
16
- questionnaire_items_test(scratch[:adaptive_responses], final_cql_test: false)
17
- scratch[:adaptive_responses] = nil
15
+ skip_if scratch[:adaptive_questionnaire_bundles].nil?, 'No questionnaire bundle returned.'
16
+ questionnaires = extract_questionnaires_from_bundles(scratch[:adaptive_questionnaire_bundles])
17
+ verify_questionnaire_items(questionnaires)
18
+ scratch[:adaptive_questionnaire_bundles] = nil
18
19
  end
19
20
  end
20
21
  end
@@ -12,8 +12,9 @@ module DaVinciDTRTestKit
12
12
 
13
13
  run do
14
14
  skip_if retrieval_method == 'Static', 'Performing only static flow tests - only one flow is required.'
15
- skip_if scratch[:adaptive_responses].nil?, 'No questionnaire bundle returned.'
16
- questionnaire_extensions_test(scratch[:adaptive_responses])
15
+ skip_if scratch[:adaptive_questionnaire_bundles].nil?, 'No questionnaire bundle returned.'
16
+ questionnaires = extract_questionnaires_from_bundles(scratch[:adaptive_questionnaire_bundles])
17
+ verify_questionnaire_extensions(questionnaires)
17
18
  end
18
19
  end
19
20
  end
@@ -1,10 +1,10 @@
1
1
  require_relative '../cql_test'
2
2
  module DaVinciDTRTestKit
3
- class PayerStaticFormExpressionsTest < Inferno::Test
3
+ class PayerAdaptiveNextQuestionExpressionsTest < Inferno::Test
4
4
  include DaVinciDTRTestKit::CQLTest
5
5
 
6
- id :dtr_v201_payer_adaptive_next_form_expressions_test
7
- title 'Questionnaire(s) contains items with expressions necessary for pre-population'
6
+ id :dtr_v201_payer_adaptive_next_question_expressions_test
7
+ title 'Adaptive Next Question questionnaire(s) contain items with expressions necessary for pre-population'
8
8
  description %(
9
9
  Inferno checks that the payer server response to $next-question operation has appropriate expressions and that
10
10
  expressions are written in cql.
@@ -12,9 +12,9 @@ module DaVinciDTRTestKit
12
12
 
13
13
  run do
14
14
  skip_if retrieval_method == 'Static', 'Performing only static flow tests - only one flow is required.'
15
- skip_if scratch[:next_responses].nil?, 'No questionnaires returned.'
16
- questionnaire_items_test(scratch[:next_responses], final_cql_test: true)
17
- scratch[:next_responses] = nil
15
+ skip_if scratch[:next_question_questionnaire_responses].nil?, 'No questionnaires returned.'
16
+ questionnaires = extract_contained_questionnaires(scratch[:next_question_questionnaire_responses])
17
+ verify_questionnaire_items(questionnaires, final_cql_test: true)
18
18
  end
19
19
  end
20
20
  end