davinci_dtr_test_kit 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/davinci_dtr_test_kit/client_groups/dinner_adaptive/dtr_adaptive_questionnaire_completion_group.rb +23 -0
- data/lib/davinci_dtr_test_kit/client_groups/dinner_adaptive/dtr_adaptive_questionnaire_followup_questions_group.rb +26 -0
- data/lib/davinci_dtr_test_kit/client_groups/dinner_adaptive/dtr_adaptive_questionnaire_next_question_request_test.rb +93 -0
- data/lib/davinci_dtr_test_kit/client_groups/dinner_adaptive/dtr_adaptive_questionnaire_next_question_request_validation_test.rb +62 -0
- data/lib/davinci_dtr_test_kit/client_groups/dinner_adaptive/dtr_adaptive_questionnaire_next_question_retrieval_group.rb +23 -0
- data/lib/davinci_dtr_test_kit/client_groups/dinner_adaptive/dtr_adaptive_questionnaire_response_validation_test.rb +66 -0
- data/lib/davinci_dtr_test_kit/client_groups/dinner_adaptive/dtr_full_ehr_adaptive_dinner_questionnaire_workflow_group.rb +76 -0
- data/lib/davinci_dtr_test_kit/client_groups/dinner_adaptive/dtr_full_ehr_adaptive_questionnaire_initial_retrieval_group.rb +27 -0
- data/lib/davinci_dtr_test_kit/client_groups/dinner_adaptive/dtr_full_ehr_adaptive_questionnaire_request_test.rb +63 -0
- data/lib/davinci_dtr_test_kit/client_groups/dinner_adaptive/dtr_smart_app_adaptive_questionnaire_initial_retrieval_group.rb +24 -0
- data/lib/davinci_dtr_test_kit/client_groups/dinner_adaptive/dtr_smart_app_adaptive_questionnaire_request_test.rb +148 -0
- data/lib/davinci_dtr_test_kit/client_groups/dinner_adaptive/dtr_smart_app_questionnaire_workflow_group.rb +75 -0
- data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_full_ehr_questionnaire_workflow_group.rb +22 -38
- data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_smart_app_dinner_questionnaire_package_request_test.rb +13 -16
- data/lib/davinci_dtr_test_kit/client_groups/dinner_static/dtr_smart_app_questionnaire_workflow_group.rb +9 -31
- data/lib/davinci_dtr_test_kit/client_groups/{dinner_static → full_ehr}/dtr_full_ehr_launch_attestation_test.rb +7 -6
- data/lib/davinci_dtr_test_kit/client_groups/{dinner_static → full_ehr}/dtr_full_ehr_prepopulation_attestation_test.rb +7 -7
- data/lib/davinci_dtr_test_kit/client_groups/{dinner_static → full_ehr}/dtr_full_ehr_prepopulation_override_attestation_test.rb +7 -7
- 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
- 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
- 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
- data/lib/davinci_dtr_test_kit/client_groups/{dinner_static → full_ehr}/dtr_full_ehr_rendering_enabled_questions_attestation_test.rb +7 -7
- data/lib/davinci_dtr_test_kit/client_groups/full_ehr/dtr_full_ehr_saving_questionnaire_response_group.rb +29 -0
- data/lib/davinci_dtr_test_kit/client_groups/{dinner_static → full_ehr}/dtr_full_ehr_store_attestation_test.rb +7 -7
- data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_questionnaire_rendering_attestation_test.rb +7 -6
- data/lib/davinci_dtr_test_kit/client_groups/resp_assist_device/dtr_resp_questionnaire_package_request_test.rb +15 -18
- data/lib/davinci_dtr_test_kit/client_groups/shared/dtr_questionnaire_response_basic_conformance_test.rb +5 -1
- data/lib/davinci_dtr_test_kit/client_groups/shared/dtr_questionnaire_response_pre_population_test.rb +12 -5
- data/lib/davinci_dtr_test_kit/client_groups/{dinner_static → smart_app}/dtr_smart_app_prepopulation_attestation_test.rb +7 -7
- data/lib/davinci_dtr_test_kit/client_groups/{dinner_static → smart_app}/dtr_smart_app_prepopulation_override_attestation_test.rb +7 -6
- data/lib/davinci_dtr_test_kit/client_groups/{dinner_static → smart_app}/dtr_smart_app_questionnaire_response_save_test.rb +17 -7
- data/lib/davinci_dtr_test_kit/client_groups/{dinner_static → smart_app}/dtr_smart_app_rendering_enabled_questions_attestation_test.rb +7 -7
- data/lib/davinci_dtr_test_kit/client_groups/smart_app/dtr_smart_app_saving_questionnaire_response_group.rb +27 -0
- data/lib/davinci_dtr_test_kit/cql_test.rb +37 -140
- data/lib/davinci_dtr_test_kit/create_test.rb +25 -0
- data/lib/davinci_dtr_test_kit/docs/dtr_full_ehr_suite_description_v201.md +95 -37
- data/lib/davinci_dtr_test_kit/docs/dtr_light_ehr_suite_description_v201.md +11 -6
- data/lib/davinci_dtr_test_kit/docs/dtr_payer_server_suite_description_v201.md +32 -29
- data/lib/davinci_dtr_test_kit/docs/dtr_smart_app_suite_description_v201.md +48 -32
- data/lib/davinci_dtr_test_kit/dtr_full_ehr_suite.rb +13 -17
- data/lib/davinci_dtr_test_kit/dtr_light_ehr_suite.rb +67 -2
- data/lib/davinci_dtr_test_kit/dtr_payer_server_suite.rb +9 -20
- data/lib/davinci_dtr_test_kit/dtr_questionnaire_response_validation.rb +18 -10
- data/lib/davinci_dtr_test_kit/dtr_smart_app_suite.rb +32 -59
- data/lib/davinci_dtr_test_kit/endpoints/cors.rb +20 -0
- data/lib/davinci_dtr_test_kit/endpoints/mock_authorization/authorize_endpoint.rb +32 -0
- data/lib/davinci_dtr_test_kit/endpoints/mock_authorization/simple_token_endpoint.rb +19 -0
- data/lib/davinci_dtr_test_kit/endpoints/mock_authorization/token_endpoint.rb +116 -0
- data/lib/davinci_dtr_test_kit/endpoints/mock_authorization.rb +83 -0
- data/lib/davinci_dtr_test_kit/endpoints/mock_ehr/fhir_get_endpoint.rb +95 -0
- data/lib/davinci_dtr_test_kit/endpoints/mock_ehr/questionnaire_response_endpoint.rb +22 -0
- data/lib/davinci_dtr_test_kit/endpoints/mock_ehr.rb +25 -0
- data/lib/davinci_dtr_test_kit/endpoints/mock_payer/full_ehr_next_question_endpoint.rb +11 -0
- data/lib/davinci_dtr_test_kit/endpoints/mock_payer/full_ehr_questionnaire_package_endpoint.rb +11 -0
- data/lib/davinci_dtr_test_kit/endpoints/mock_payer/next_question_endpoint.rb +162 -0
- data/lib/davinci_dtr_test_kit/endpoints/mock_payer/next_question_proxy_endpoint.rb +36 -0
- data/lib/davinci_dtr_test_kit/endpoints/mock_payer/questionnaire_package_endpoint.rb +62 -0
- data/lib/davinci_dtr_test_kit/endpoints/mock_payer/questionnaire_package_proxy_endpoint.rb +38 -0
- data/lib/davinci_dtr_test_kit/endpoints/mock_payer.rb +36 -0
- data/lib/davinci_dtr_test_kit/fixtures/dinner_adaptive/dinner_order_adaptive_next_question_burrito.json +10 -2
- data/lib/davinci_dtr_test_kit/fixtures/dinner_adaptive/dinner_order_adaptive_next_question_hamburger.json +10 -2
- data/lib/davinci_dtr_test_kit/fixtures/dinner_adaptive/dinner_order_adaptive_next_question_initial.json +10 -2
- data/lib/davinci_dtr_test_kit/fixtures/dinner_adaptive/questionnaire_dinner_order_adaptive.json +4 -3
- data/lib/davinci_dtr_test_kit/fixtures.rb +24 -1
- data/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_form_libraries_test.rb +2 -2
- data/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_form_questionnaire_expressions_test.rb +4 -3
- data/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_form_questionnaire_extensions_test.rb +3 -2
- data/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_next_questionnaire_expressions_test.rb +6 -6
- data/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_next_questionnaire_extensions_test.rb +6 -5
- data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_group.rb +2 -2
- data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_response_bundles_validation_test.rb +6 -9
- data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_response_search_validation_test.rb +15 -12
- data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_response_validation_test.rb +33 -23
- data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_next_response_complete_test.rb +4 -4
- data/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_next_response_validation_test.rb +16 -12
- data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_libraries_test.rb +2 -2
- data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_questionnaire_expressions_test.rb +5 -4
- data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_questionnaire_extensions_test.rb +4 -3
- data/lib/davinci_dtr_test_kit/payer_server_groups/static_form_response_validation_test.rb +32 -25
- data/lib/davinci_dtr_test_kit/profiles/communication_request/communication_request_read.rb +29 -0
- data/lib/davinci_dtr_test_kit/profiles/communication_request/communication_request_validation.rb +35 -0
- data/lib/davinci_dtr_test_kit/profiles/communication_request_group.rb +39 -0
- data/lib/davinci_dtr_test_kit/profiles/coverage/coverage_read.rb +29 -0
- data/lib/davinci_dtr_test_kit/profiles/coverage/coverage_validation.rb +35 -0
- data/lib/davinci_dtr_test_kit/profiles/coverage_group.rb +38 -0
- data/lib/davinci_dtr_test_kit/profiles/device_request/device_request_read.rb +29 -0
- data/lib/davinci_dtr_test_kit/profiles/device_request/device_request_validation.rb +35 -0
- data/lib/davinci_dtr_test_kit/profiles/device_request_group.rb +39 -0
- data/lib/davinci_dtr_test_kit/profiles/encounter/encounter_read.rb +29 -0
- data/lib/davinci_dtr_test_kit/profiles/encounter/encounter_validation.rb +35 -0
- data/lib/davinci_dtr_test_kit/profiles/encounter_group.rb +39 -0
- data/lib/davinci_dtr_test_kit/profiles/medication_request/medication_request_read.rb +29 -0
- data/lib/davinci_dtr_test_kit/profiles/medication_request/medication_request_validation.rb +35 -0
- data/lib/davinci_dtr_test_kit/profiles/medication_request_group.rb +39 -0
- data/lib/davinci_dtr_test_kit/profiles/nutrition_order/nutrition_order_read.rb +29 -0
- data/lib/davinci_dtr_test_kit/profiles/nutrition_order/nutrition_order_validation.rb +35 -0
- data/lib/davinci_dtr_test_kit/profiles/nutrition_order_group.rb +39 -0
- data/lib/davinci_dtr_test_kit/profiles/questionnaire_response/questionnaire_response_context_search.rb +35 -0
- data/lib/davinci_dtr_test_kit/profiles/questionnaire_response/questionnaire_response_create.rb +26 -0
- data/lib/davinci_dtr_test_kit/profiles/questionnaire_response/questionnaire_response_patient_search.rb +55 -0
- data/lib/davinci_dtr_test_kit/profiles/questionnaire_response/questionnaire_response_read.rb +22 -0
- data/lib/davinci_dtr_test_kit/profiles/questionnaire_response/questionnaire_response_update.rb +26 -0
- data/lib/davinci_dtr_test_kit/profiles/questionnaire_response/questionnaire_response_validation.rb +37 -0
- data/lib/davinci_dtr_test_kit/profiles/questionnaire_response_group.rb +66 -0
- data/lib/davinci_dtr_test_kit/profiles/service_request/service_request_read.rb +29 -0
- data/lib/davinci_dtr_test_kit/profiles/service_request/service_request_validation.rb +35 -0
- data/lib/davinci_dtr_test_kit/profiles/service_request_group.rb +39 -0
- data/lib/davinci_dtr_test_kit/profiles/task/task_create.rb +26 -0
- data/lib/davinci_dtr_test_kit/profiles/task/task_read.rb +29 -0
- data/lib/davinci_dtr_test_kit/profiles/task/task_update.rb +26 -0
- data/lib/davinci_dtr_test_kit/profiles/task/task_validation.rb +35 -0
- data/lib/davinci_dtr_test_kit/profiles/task_group.rb +52 -0
- data/lib/davinci_dtr_test_kit/profiles/vision_prescription/vision_prescription_read.rb +29 -0
- data/lib/davinci_dtr_test_kit/profiles/vision_prescription/vision_prescription_validation.rb +35 -0
- data/lib/davinci_dtr_test_kit/profiles/vision_prescription_group.rb +39 -0
- data/lib/davinci_dtr_test_kit/read_test.rb +22 -0
- data/lib/davinci_dtr_test_kit/tags.rb +5 -7
- data/lib/davinci_dtr_test_kit/update_test.rb +25 -0
- data/lib/davinci_dtr_test_kit/validation_test.rb +19 -4
- data/lib/davinci_dtr_test_kit/version.rb +1 -1
- metadata +109 -20
- data/lib/davinci_dtr_test_kit/ext/inferno_core/record_response_route.rb +0 -98
- data/lib/davinci_dtr_test_kit/ext/inferno_core/request.rb +0 -19
- data/lib/davinci_dtr_test_kit/ext/inferno_core/runnable.rb +0 -35
- data/lib/davinci_dtr_test_kit/mock_auth_server.rb +0 -228
- data/lib/davinci_dtr_test_kit/mock_ehr.rb +0 -105
- data/lib/davinci_dtr_test_kit/mock_payer.rb +0 -100
|
@@ -1,6 +1,17 @@
|
|
|
1
|
+
require 'us_core_test_kit'
|
|
1
2
|
require 'tls_test_kit'
|
|
2
3
|
require_relative 'version'
|
|
3
4
|
require_relative 'dtr_options'
|
|
5
|
+
require_relative 'profiles/questionnaire_response_group'
|
|
6
|
+
require_relative 'profiles/coverage_group'
|
|
7
|
+
require_relative 'profiles/communication_request_group'
|
|
8
|
+
require_relative 'profiles/device_request_group'
|
|
9
|
+
require_relative 'profiles/encounter_group'
|
|
10
|
+
require_relative 'profiles/medication_request_group'
|
|
11
|
+
require_relative 'profiles/nutrition_order_group'
|
|
12
|
+
require_relative 'profiles/service_request_group'
|
|
13
|
+
require_relative 'profiles/task_group'
|
|
14
|
+
require_relative 'profiles/vision_prescription_group'
|
|
4
15
|
require 'smart_app_launch/smart_stu1_suite'
|
|
5
16
|
require 'smart_app_launch/smart_stu2_suite'
|
|
6
17
|
|
|
@@ -32,8 +43,17 @@ module DaVinciDTRTestKit
|
|
|
32
43
|
]
|
|
33
44
|
|
|
34
45
|
input :url,
|
|
35
|
-
title: 'FHIR
|
|
36
|
-
description: 'URL of the DTR
|
|
46
|
+
title: 'FHIR Server Base Url',
|
|
47
|
+
description: 'URL of the target DTR Light EHR'
|
|
48
|
+
|
|
49
|
+
# Hl7 Validator Wrapper:
|
|
50
|
+
fhir_resource_validator do
|
|
51
|
+
igs('hl7.fhir.us.davinci-dtr#2.0.1', 'hl7.fhir.us.davinci-pas#2.0.1', 'hl7.fhir.us.davinci-crd#2.0.1')
|
|
52
|
+
|
|
53
|
+
exclude_message do |message|
|
|
54
|
+
message.message.match?(/\A\S+: \S+: URL value '.*' does not resolve/)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
37
57
|
|
|
38
58
|
group do
|
|
39
59
|
title 'Authorization'
|
|
@@ -71,5 +91,50 @@ module DaVinciDTRTestKit
|
|
|
71
91
|
required_suite_options: DTROptions::SMART_2_REQUIREMENT,
|
|
72
92
|
run_as_group: true
|
|
73
93
|
end
|
|
94
|
+
|
|
95
|
+
group do
|
|
96
|
+
title 'FHIR API'
|
|
97
|
+
description %(This test group tests systems for their conformance to
|
|
98
|
+
the US Core v3.1.1 Capability Statement as defined by the DaVinci Documentation
|
|
99
|
+
Templates and Rules (DTR) v2.0.1 Implementation Guide Light DTR EHR
|
|
100
|
+
Capability Statement.
|
|
101
|
+
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
group from: :'us_core_v311-us_core_v311_fhir_api',
|
|
105
|
+
run_as_group: true
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
group do
|
|
109
|
+
title 'DTR Light EHR Profiles'
|
|
110
|
+
description %(This test group tests system for their conformance to
|
|
111
|
+
the RESTful capabilities by specified Resources/Profiles as defined by
|
|
112
|
+
the DaVinci Documentation Templates and Rules (DTR) v2.0,1 Implementation
|
|
113
|
+
Guide Light DTR EHR Capability Statement.
|
|
114
|
+
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
input :credentials,
|
|
118
|
+
title: 'OAuth Credentials',
|
|
119
|
+
type: :oauth_credentials,
|
|
120
|
+
optional: true
|
|
121
|
+
|
|
122
|
+
# All FHIR requests in this suite will use this FHIR client
|
|
123
|
+
fhir_client do
|
|
124
|
+
url :url
|
|
125
|
+
oauth_credentials :credentials
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
group from: :questionnaire_response_group
|
|
129
|
+
group from: :coverage_group
|
|
130
|
+
group from: :communication_request_group
|
|
131
|
+
group from: :device_request_group
|
|
132
|
+
group from: :encounter_group
|
|
133
|
+
group from: :medication_request_group
|
|
134
|
+
group from: :nutrition_order_group
|
|
135
|
+
group from: :service_request_group
|
|
136
|
+
group from: :task_group
|
|
137
|
+
group from: :vision_prescription_group
|
|
138
|
+
end
|
|
74
139
|
end
|
|
75
140
|
end
|
|
@@ -1,17 +1,15 @@
|
|
|
1
|
-
require_relative 'ext/inferno_core/runnable'
|
|
2
|
-
require_relative 'ext/inferno_core/record_response_route'
|
|
3
|
-
require_relative 'ext/inferno_core/request'
|
|
4
1
|
require_relative 'payer_server_groups/payer_server_static_group'
|
|
5
2
|
require_relative 'payer_server_groups/payer_server_adaptive_group'
|
|
6
3
|
require_relative 'tags'
|
|
7
|
-
require_relative '
|
|
8
|
-
require_relative '
|
|
4
|
+
require_relative 'endpoints/cors'
|
|
5
|
+
require_relative 'endpoints/mock_authorization/simple_token_endpoint'
|
|
6
|
+
require_relative 'endpoints/mock_payer/questionnaire_package_proxy_endpoint'
|
|
7
|
+
require_relative 'endpoints/mock_payer/next_question_proxy_endpoint'
|
|
9
8
|
require_relative 'version'
|
|
10
9
|
|
|
11
10
|
module DaVinciDTRTestKit
|
|
12
11
|
class DTRPayerServerSuite < Inferno::TestSuite
|
|
13
|
-
extend
|
|
14
|
-
extend MockPayer
|
|
12
|
+
extend CORS
|
|
15
13
|
|
|
16
14
|
id :dtr_payer_server
|
|
17
15
|
title 'Da Vinci DTR Payer Server Test Suite'
|
|
@@ -106,22 +104,13 @@ module DaVinciDTRTestKit
|
|
|
106
104
|
|
|
107
105
|
allow_cors QUESTIONNAIRE_PACKAGE_PATH, NEXT_PATH
|
|
108
106
|
|
|
109
|
-
|
|
110
|
-
DTRPayerServerSuite.extract_client_id_from_form_params(request)
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
record_response_route :post, QUESTIONNAIRE_PACKAGE_PATH, QUESTIONNAIRE_TAG,
|
|
114
|
-
method(:payer_questionnaire_response), resumes: method(:test_resumes?) do |request|
|
|
115
|
-
DTRPayerServerSuite.extract_bearer_token(request)
|
|
116
|
-
end
|
|
107
|
+
suite_endpoint :post, PAYER_TOKEN_PATH, MockAuthorization::SimpleTokenEndpoint
|
|
117
108
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
DTRPayerServerSuite.extract_bearer_token(request)
|
|
121
|
-
end
|
|
109
|
+
suite_endpoint :post, QUESTIONNAIRE_PACKAGE_PATH, MockPayer::QuestionnairePackageProxyEndpoint
|
|
110
|
+
suite_endpoint :post, NEXT_PATH, MockPayer::NextQuestionProxyEndpoint
|
|
122
111
|
|
|
123
112
|
resume_test_route :get, RESUME_PASS_PATH do |request|
|
|
124
|
-
|
|
113
|
+
request.query_parameters['token']
|
|
125
114
|
end
|
|
126
115
|
|
|
127
116
|
group from: :payer_server_static_package
|
|
@@ -20,10 +20,10 @@ module DaVinciDTRTestKit
|
|
|
20
20
|
assert_resource_type(:questionnaire_response, resource: questionnaire_response)
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
def verify_basic_conformance(questionnaire_response_json)
|
|
23
|
+
def verify_basic_conformance(questionnaire_response_json, profile_url = nil)
|
|
24
|
+
profile_url ||= 'http://hl7.org/fhir/us/davinci-dtr/StructureDefinition/dtr-questionnaireresponse|2.0.1'
|
|
24
25
|
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')
|
|
26
|
+
assert_valid_resource(resource: FHIR.from_contents(questionnaire_response_json), profile_url:)
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
# This only checks answers in the questionnaire response, meaning it does not catch missing answers
|
|
@@ -55,17 +55,25 @@ module DaVinciDTRTestKit
|
|
|
55
55
|
end
|
|
56
56
|
end
|
|
57
57
|
|
|
58
|
-
#
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
add_message('error', "No answer for item #{
|
|
58
|
+
# Ensures that all required questions have been answered.
|
|
59
|
+
# If required_link_ids not provided, all questions are treated as optional.
|
|
60
|
+
def check_answer_presence(response_items, required_link_ids = [])
|
|
61
|
+
required_link_ids.each do |link_id|
|
|
62
|
+
item = find_item_by_link_id(response_items, link_id)
|
|
63
|
+
unless item&.answer&.any? { |answer| answer.value.present? }
|
|
64
|
+
add_message('error', "No answer for item #{link_id}")
|
|
65
65
|
end
|
|
66
66
|
end
|
|
67
67
|
end
|
|
68
68
|
|
|
69
|
+
def extract_required_link_ids(questionnaire_items)
|
|
70
|
+
questionnaire_items.each_with_object([]) do |item, required_link_ids|
|
|
71
|
+
required_link_ids << item.linkId if item.required
|
|
72
|
+
|
|
73
|
+
required_link_ids.concat(extract_required_link_ids(item.item)) if item.item.present?
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
69
77
|
# Requirements:
|
|
70
78
|
# - Prior to exposing the draft QuestionnaireResponse to the user for completion and/or review, the DTR client
|
|
71
79
|
# SHALL execute all CQL necessary to resolve the initialExpression, candidateExpression and
|
|
@@ -1,19 +1,21 @@
|
|
|
1
|
-
require_relative 'ext/inferno_core/runnable'
|
|
2
|
-
require_relative 'ext/inferno_core/record_response_route'
|
|
3
|
-
require_relative 'ext/inferno_core/request'
|
|
4
1
|
require_relative 'auth_groups/oauth2_authentication_group'
|
|
5
2
|
require_relative 'client_groups/resp_assist_device/dtr_smart_app_questionnaire_workflow_group'
|
|
6
3
|
require_relative 'client_groups/dinner_static/dtr_smart_app_questionnaire_workflow_group'
|
|
7
4
|
require_relative 'client_groups/dinner_adaptive/dtr_smart_app_questionnaire_workflow_group'
|
|
8
|
-
require_relative '
|
|
9
|
-
require_relative '
|
|
5
|
+
require_relative 'endpoints/cors'
|
|
6
|
+
require_relative 'endpoints/mock_authorization'
|
|
7
|
+
require_relative 'endpoints/mock_authorization/authorize_endpoint'
|
|
8
|
+
require_relative 'endpoints/mock_authorization/token_endpoint'
|
|
9
|
+
require_relative 'endpoints/mock_payer/questionnaire_package_endpoint'
|
|
10
|
+
require_relative 'endpoints/mock_payer/next_question_endpoint'
|
|
11
|
+
require_relative 'endpoints/mock_ehr'
|
|
12
|
+
require_relative 'endpoints/mock_ehr/questionnaire_response_endpoint'
|
|
13
|
+
require_relative 'endpoints/mock_ehr/fhir_get_endpoint'
|
|
10
14
|
require_relative 'version'
|
|
11
15
|
|
|
12
16
|
module DaVinciDTRTestKit
|
|
13
17
|
class DTRSmartAppSuite < Inferno::TestSuite
|
|
14
|
-
extend
|
|
15
|
-
extend MockEHR
|
|
16
|
-
extend MockPayer
|
|
18
|
+
extend CORS
|
|
17
19
|
|
|
18
20
|
id :dtr_smart_app
|
|
19
21
|
title 'Da Vinci DTR SMART App Test Suite'
|
|
@@ -50,61 +52,32 @@ module DaVinciDTRTestKit
|
|
|
50
52
|
end
|
|
51
53
|
|
|
52
54
|
allow_cors QUESTIONNAIRE_PACKAGE_PATH, QUESTIONNAIRE_RESPONSE_PATH, FHIR_RESOURCE_PATH, FHIR_SEARCH_PATH,
|
|
53
|
-
EHR_AUTHORIZE_PATH, EHR_TOKEN_PATH, JKWS_PATH, OPENID_CONFIG_PATH
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
route(:get,
|
|
58
|
-
route(:get,
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
record_response_route :post, EHR_TOKEN_PATH, 'dtr_smart_app_ehr_token', method(:ehr_token_response),
|
|
73
|
-
resumes: ->(_) { false } do |request|
|
|
74
|
-
DTRSmartAppSuite.extract_client_id_from_token_request(request)
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
record_response_route :post, PAYER_TOKEN_PATH, 'dtr_smart_app_payer_token',
|
|
78
|
-
method(:payer_token_response) do |request|
|
|
79
|
-
DTRSmartAppSuite.extract_client_id_from_client_assertion(request)
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
record_response_route :post, QUESTIONNAIRE_PACKAGE_PATH, QUESTIONNAIRE_PACKAGE_TAG,
|
|
83
|
-
method(:questionnaire_package_response), resumes: ->(_) { false } do |request|
|
|
84
|
-
DTRSmartAppSuite.extract_client_id_from_bearer_token(request)
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
record_response_route :post, QUESTIONNAIRE_RESPONSE_PATH, 'dtr_smart_app_questionnaire_response',
|
|
88
|
-
method(:questionnaire_response_response) do |request|
|
|
89
|
-
DTRSmartAppSuite.extract_client_id_from_bearer_token(request)
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
record_response_route :get, FHIR_RESOURCE_PATH, SMART_APP_EHR_REQUEST_TAG, method(:get_fhir_resource),
|
|
93
|
-
resumes: ->(_) { false } do |request|
|
|
94
|
-
DTRSmartAppSuite.extract_client_id_from_bearer_token(request)
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
record_response_route :get, FHIR_SEARCH_PATH, SMART_APP_EHR_REQUEST_TAG, method(:get_fhir_resource),
|
|
98
|
-
resumes: ->(_) { false } do |request|
|
|
99
|
-
DTRSmartAppSuite.extract_client_id_from_bearer_token(request)
|
|
100
|
-
end
|
|
55
|
+
EHR_AUTHORIZE_PATH, EHR_TOKEN_PATH, JKWS_PATH, OPENID_CONFIG_PATH, NEXT_PATH
|
|
56
|
+
|
|
57
|
+
# Authorization server
|
|
58
|
+
route(:get, SMART_CONFIG_PATH, MockAuthorization.method(:ehr_smart_config))
|
|
59
|
+
route(:get, OPENID_CONFIG_PATH, MockAuthorization.method(:ehr_openid_config))
|
|
60
|
+
route(:get, JKWS_PATH, MockAuthorization.method(:jwks))
|
|
61
|
+
suite_endpoint :get, EHR_AUTHORIZE_PATH, MockAuthorization::AuthorizeEndpoint
|
|
62
|
+
suite_endpoint :post, EHR_AUTHORIZE_PATH, MockAuthorization::AuthorizeEndpoint
|
|
63
|
+
suite_endpoint :post, EHR_TOKEN_PATH, MockAuthorization::TokenEndpoint
|
|
64
|
+
|
|
65
|
+
# Payer
|
|
66
|
+
suite_endpoint :post, QUESTIONNAIRE_PACKAGE_PATH, MockPayer::QuestionnairePackageEndpoint
|
|
67
|
+
suite_endpoint :post, NEXT_PATH, MockPayer::NextQuestionEndpoint
|
|
68
|
+
|
|
69
|
+
# EHR
|
|
70
|
+
route(:get, '/fhir/metadata', MockEHR.method(:metadata))
|
|
71
|
+
suite_endpoint :post, QUESTIONNAIRE_RESPONSE_PATH, MockEHR::QuestionnaireResponseEndpoint
|
|
72
|
+
suite_endpoint :get, FHIR_RESOURCE_PATH, MockEHR::FHIRGetEndpoint
|
|
73
|
+
suite_endpoint :get, FHIR_SEARCH_PATH, MockEHR::FHIRGetEndpoint
|
|
101
74
|
|
|
102
75
|
resume_test_route :get, RESUME_PASS_PATH do |request|
|
|
103
|
-
|
|
76
|
+
request.query_parameters['client_id'] || request.query_parameters['token']
|
|
104
77
|
end
|
|
105
78
|
|
|
106
79
|
resume_test_route :get, RESUME_FAIL_PATH, result: 'fail' do |request|
|
|
107
|
-
|
|
80
|
+
request.query_parameters['client_id'] || request.query_parameters['token']
|
|
108
81
|
end
|
|
109
82
|
|
|
110
83
|
# TODO: Update based on SMART Launch changes. Do we even want to have this group now?
|
|
@@ -118,7 +91,7 @@ module DaVinciDTRTestKit
|
|
|
118
91
|
)
|
|
119
92
|
|
|
120
93
|
group from: :dtr_smart_app_static_dinner_questionnaire_workflow
|
|
121
|
-
|
|
94
|
+
group from: :dtr_smart_app_adaptive_dinner_questionnaire_workflow
|
|
122
95
|
end
|
|
123
96
|
group do
|
|
124
97
|
id :dtr_smart_app_questionnaire_functionality
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module DaVinciDTRTestKit
|
|
2
|
+
module CORS
|
|
3
|
+
PRE_FLIGHT_HANDLER = proc do
|
|
4
|
+
[
|
|
5
|
+
200,
|
|
6
|
+
{
|
|
7
|
+
'Access-Control-Allow-Origin' => '*',
|
|
8
|
+
'Access-Control-Allow-Headers' => 'Content-Type, Authorization'
|
|
9
|
+
},
|
|
10
|
+
['']
|
|
11
|
+
]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def allow_cors(*paths)
|
|
15
|
+
paths.each do |path|
|
|
16
|
+
route(:options, path, PRE_FLIGHT_HANDLER)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module DaVinciDTRTestKit
|
|
2
|
+
module MockAuthorization
|
|
3
|
+
class AuthorizeEndpoint < Inferno::DSL::SuiteEndpoint
|
|
4
|
+
def test_run_identifier
|
|
5
|
+
request.params[:client_id]
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def tags
|
|
9
|
+
[EHR_AUTHORIZE_TAG]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def make_response
|
|
13
|
+
if request.params[:redirect_uri].present?
|
|
14
|
+
redirect_uri = "#{request.params[:redirect_uri]}?" \
|
|
15
|
+
"code=#{SecureRandom.hex}&" \
|
|
16
|
+
"state=#{request.params[:state]}"
|
|
17
|
+
response.status = 302
|
|
18
|
+
response.headers['Location'] = redirect_uri
|
|
19
|
+
else
|
|
20
|
+
response.status = 400
|
|
21
|
+
response.format = 'application/fhir+json'
|
|
22
|
+
response.body = FHIR::OperationOutcome.new(
|
|
23
|
+
issue: FHIR::OperationOutcome::Issue.new(severity: 'fatal', code: 'required',
|
|
24
|
+
details: FHIR::CodeableConcept.new(
|
|
25
|
+
text: 'No redirect_uri provided'
|
|
26
|
+
))
|
|
27
|
+
).to_json
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module DaVinciDTRTestKit
|
|
2
|
+
module MockAuthorization
|
|
3
|
+
class SimpleTokenEndpoint < Inferno::DSL::SuiteEndpoint
|
|
4
|
+
def test_run_identifier
|
|
5
|
+
request.params[:client_id]
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
# Placeholder for a more complete mock token endpoint
|
|
9
|
+
def make_response
|
|
10
|
+
response.body = { access_token: SecureRandom.hex, token_type: 'bearer', expires_in: 3600 }.to_json
|
|
11
|
+
response.status = 200
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def update_result
|
|
15
|
+
results_repo.update_result(result.id, 'pass')
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../urls'
|
|
4
|
+
require_relative '../mock_authorization'
|
|
5
|
+
|
|
6
|
+
module DaVinciDTRTestKit
|
|
7
|
+
module MockAuthorization
|
|
8
|
+
AUTHORIZED_PRACTITIONER_ID = 'pra1234' # Must exist on the FHIR_REFERENCE_SERVER (env var)
|
|
9
|
+
|
|
10
|
+
class TokenEndpoint < Inferno::DSL::SuiteEndpoint
|
|
11
|
+
def test_run_identifier
|
|
12
|
+
extract_client_id
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def make_response
|
|
16
|
+
client_id = extract_client_id
|
|
17
|
+
access_token = JWT.encode({ inferno_client_id: client_id }, nil, 'none')
|
|
18
|
+
granted_scopes = SUPPORTED_SCOPES & requested_scopes
|
|
19
|
+
|
|
20
|
+
response_hash = { access_token:, scope: granted_scopes.join(' '), token_type: 'bearer', expires_in: 3600 }
|
|
21
|
+
|
|
22
|
+
if granted_scopes.include?('openid')
|
|
23
|
+
response_hash.merge!(id_token: create_id_token(client_id, fhir_user: granted_scopes.include?('fhirUser')))
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
fhir_context_input = find_test_input("#{input_group_prefix}_smart_fhir_context")
|
|
27
|
+
begin
|
|
28
|
+
fhir_context = JSON.parse(fhir_context_input)
|
|
29
|
+
rescue StandardError
|
|
30
|
+
fhir_context = nil
|
|
31
|
+
end
|
|
32
|
+
response_hash.merge!(fhirContext: fhir_context) if fhir_context
|
|
33
|
+
|
|
34
|
+
smart_patient_input = find_test_input("#{input_group_prefix}_smart_patient_id")
|
|
35
|
+
response_hash.merge!(patient: smart_patient_input) if smart_patient_input
|
|
36
|
+
|
|
37
|
+
response.body = response_hash.to_json
|
|
38
|
+
response.headers['Cache-Control'] = 'no-store'
|
|
39
|
+
response.headers['Pragma'] = 'no-cache'
|
|
40
|
+
response.headers['Access-Control-Allow-Origin'] = '*'
|
|
41
|
+
response.status = 200
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def extract_client_id
|
|
47
|
+
# Public client || confidential client asymmetric || confidential client symmetric
|
|
48
|
+
request.params[:client_id] || extract_client_id_from_client_assertion || extract_client_id_from_basic_auth
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def extract_client_id_from_client_assertion
|
|
52
|
+
encoded_jwt = request.params[:client_assertion]
|
|
53
|
+
return unless encoded_jwt.present?
|
|
54
|
+
|
|
55
|
+
jwt_payload =
|
|
56
|
+
begin
|
|
57
|
+
JWT.decode(encoded_jwt, nil, false)&.first # skip signature verification
|
|
58
|
+
rescue StandardError
|
|
59
|
+
nil
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
jwt_payload['iss'] || jwt_payload['sub'] if jwt_payload.present?
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def input_group_prefix
|
|
66
|
+
if test.id.include?('static')
|
|
67
|
+
'static'
|
|
68
|
+
elsif test.id.include?('adaptive')
|
|
69
|
+
'adaptive'
|
|
70
|
+
else
|
|
71
|
+
'resp'
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def find_test_input(input_name)
|
|
76
|
+
JSON.parse(result.input_json)&.find { |input| input['name'] == input_name }&.dig('value')
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def extract_client_id_from_basic_auth
|
|
80
|
+
encoded_credentials = request.headers['authorization']&.delete_prefix('Basic ')
|
|
81
|
+
return unless encoded_credentials.present?
|
|
82
|
+
|
|
83
|
+
decoded_credentials = Base64.decode64(encoded_credentials)
|
|
84
|
+
decoded_credentials&.split(':')&.first
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def requested_scopes
|
|
88
|
+
auth_request = requests_repo.tagged_requests(result.test_session_id, [EHR_AUTHORIZE_TAG]).last
|
|
89
|
+
return [] unless auth_request
|
|
90
|
+
|
|
91
|
+
auth_params = if auth_request.verb.downcase == 'get'
|
|
92
|
+
auth_request.query_parameters
|
|
93
|
+
else
|
|
94
|
+
URI.decode_www_form(auth_request.request_body)&.to_h
|
|
95
|
+
end
|
|
96
|
+
scope_str = auth_params&.dig('scope')
|
|
97
|
+
scope_str ? URI.decode_www_form_component(scope_str).split : []
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def create_id_token(client_id, fhir_user: false)
|
|
101
|
+
# No point in mocking an identity provider, just always use known Practitioner as the authorized user
|
|
102
|
+
suite_fhir_base_url = request.url.split(EHR_TOKEN_PATH).first + FHIR_BASE_PATH
|
|
103
|
+
id_token_hash = {
|
|
104
|
+
iss: suite_fhir_base_url,
|
|
105
|
+
sub: AUTHORIZED_PRACTITIONER_ID,
|
|
106
|
+
aud: client_id,
|
|
107
|
+
exp: Time.now.to_i + (24 * 60 * 60), # 24 hrs
|
|
108
|
+
iat: Time.now.to_i
|
|
109
|
+
}
|
|
110
|
+
id_token_hash.merge!(fhirUser: "#{suite_fhir_base_url}/Practitioner/#{AUTHORIZED_PRACTITIONER_ID}") if fhir_user
|
|
111
|
+
|
|
112
|
+
JWT.encode(id_token_hash, RSA_PRIVATE_KEY, 'RS256')
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
module DaVinciDTRTestKit
|
|
2
|
+
module MockAuthorization
|
|
3
|
+
RSA_PRIVATE_KEY = OpenSSL::PKey::RSA.generate(2048)
|
|
4
|
+
RSA_PUBLIC_KEY = RSA_PRIVATE_KEY.public_key
|
|
5
|
+
SUPPORTED_SCOPES = ['launch', 'patient/*.rs', 'user/*.rs', 'offline_access', 'openid', 'fhirUser'].freeze
|
|
6
|
+
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def extract_client_id_from_bearer_token(request)
|
|
10
|
+
token = request.headers['authorization']&.delete_prefix('Bearer ')
|
|
11
|
+
jwt =
|
|
12
|
+
begin
|
|
13
|
+
JWT.decode(token, nil, false)
|
|
14
|
+
rescue StandardError
|
|
15
|
+
nil
|
|
16
|
+
end
|
|
17
|
+
jwt&.first&.dig('inferno_client_id')
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def jwks(_env)
|
|
21
|
+
response_body = {
|
|
22
|
+
keys: [
|
|
23
|
+
{
|
|
24
|
+
kty: 'RSA',
|
|
25
|
+
alg: 'RS256',
|
|
26
|
+
n: Base64.urlsafe_encode64(RSA_PUBLIC_KEY.n.to_s(2), padding: false),
|
|
27
|
+
e: Base64.urlsafe_encode64(RSA_PUBLIC_KEY.e.to_s(2), padding: false),
|
|
28
|
+
use: 'sig'
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
}.to_json
|
|
32
|
+
|
|
33
|
+
[200, { 'Content-Type' => 'application/json', 'Access-Control-Allow-Origin' => '*' }, [response_body]]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def ehr_smart_config(env)
|
|
37
|
+
base_url = env_base_url(env, SMART_CONFIG_PATH)
|
|
38
|
+
response_body =
|
|
39
|
+
{
|
|
40
|
+
authorization_endpoint: base_url + EHR_AUTHORIZE_PATH,
|
|
41
|
+
token_endpoint: base_url + EHR_TOKEN_PATH,
|
|
42
|
+
token_endpoint_auth_methods_supported: ['private_key_jwt'],
|
|
43
|
+
token_endpoint_auth_signing_alg_values_supported: ['RS256'],
|
|
44
|
+
grant_types_supported: ['authorization_code'],
|
|
45
|
+
scopes_supported: SUPPORTED_SCOPES,
|
|
46
|
+
response_types_supported: ['code'],
|
|
47
|
+
code_challenge_methods_supported: ['S256'],
|
|
48
|
+
capabilities: [
|
|
49
|
+
'launch-ehr',
|
|
50
|
+
'permission-patient',
|
|
51
|
+
'permission-user',
|
|
52
|
+
'client-public',
|
|
53
|
+
'client-confidential-symmetric',
|
|
54
|
+
'client-confidential-asymmetric'
|
|
55
|
+
]
|
|
56
|
+
}.to_json
|
|
57
|
+
|
|
58
|
+
[200, { 'Content-Type' => 'application/json', 'Access-Control-Allow-Origin' => '*' }, [response_body]]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def ehr_openid_config(env)
|
|
62
|
+
base_url = env_base_url(env, OPENID_CONFIG_PATH)
|
|
63
|
+
response_body = {
|
|
64
|
+
issuer: base_url + FHIR_BASE_PATH,
|
|
65
|
+
authorization_endpoint: base_url + EHR_AUTHORIZE_PATH,
|
|
66
|
+
token_endpoint: base_url + EHR_TOKEN_PATH,
|
|
67
|
+
jwks_uri: base_url + JKWS_PATH,
|
|
68
|
+
response_types_supported: ['id_token'],
|
|
69
|
+
subject_types_supported: ['public'],
|
|
70
|
+
id_token_signing_alg_values_supported: ['RS256']
|
|
71
|
+
}.to_json
|
|
72
|
+
[200, { 'Content-Type' => 'application/json', 'Access-Control-Allow-Origin' => '*' }, [response_body]]
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def env_base_url(env, endpoint_path)
|
|
76
|
+
protocol = env['rack.url_scheme']
|
|
77
|
+
host = env['HTTP_HOST']
|
|
78
|
+
path = env['REQUEST_PATH'] || env['PATH_INFO']
|
|
79
|
+
path.gsub!(%r{#{endpoint_path}(/)?}, '')
|
|
80
|
+
"#{protocol}://#{host + path}"
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
require_relative '../mock_authorization'
|
|
2
|
+
require_relative '../mock_ehr'
|
|
3
|
+
|
|
4
|
+
module DaVinciDTRTestKit
|
|
5
|
+
module MockEHR
|
|
6
|
+
class FHIRGetEndpoint < Inferno::DSL::SuiteEndpoint
|
|
7
|
+
include MockEHR
|
|
8
|
+
|
|
9
|
+
def test_run_identifier
|
|
10
|
+
MockAuthorization.extract_client_id_from_bearer_token(request)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def make_response
|
|
14
|
+
fhir_class, id = fhir_class_and_id_from_url(request.url)
|
|
15
|
+
response.format = 'application/fhir+json'
|
|
16
|
+
response.headers['Access-Control-Allow-Origin'] = '*'
|
|
17
|
+
|
|
18
|
+
if fhir_class.nil?
|
|
19
|
+
response.status = 400
|
|
20
|
+
response.body = FHIR::OperationOutcome.new(
|
|
21
|
+
issue: FHIR::OperationOutcome::Issue.new(severity: 'warning', code: 'not-supported',
|
|
22
|
+
details: FHIR::CodeableConcept.new(
|
|
23
|
+
text: 'No recognized resource type in URL'
|
|
24
|
+
))
|
|
25
|
+
).to_json
|
|
26
|
+
return
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Respond with user-inputted resource if there is one that matches the request
|
|
30
|
+
ehr_bundle = ehr_input_bundle(test, result)
|
|
31
|
+
if id.present? && ehr_bundle.present?
|
|
32
|
+
matching_resource = find_resource_in_bundle(ehr_bundle, fhir_class, id)
|
|
33
|
+
if matching_resource.present?
|
|
34
|
+
response.status = 200
|
|
35
|
+
response.body = matching_resource.to_json
|
|
36
|
+
return
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Proxy resource request to the reference server
|
|
41
|
+
proxy_response = if id.present?
|
|
42
|
+
resource_server_client.read(fhir_class, id)
|
|
43
|
+
else
|
|
44
|
+
resource_server_client.search(fhir_class,
|
|
45
|
+
search: { parameters: request.env['rack.request.query_hash'] })
|
|
46
|
+
end
|
|
47
|
+
response.status = proxy_response.code
|
|
48
|
+
response.body = proxy_response.body
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def ehr_input_bundle(test, test_result)
|
|
52
|
+
input_name = "#{input_group_prefix(test)}_ehr_bundle"
|
|
53
|
+
ehr_bundle_input = JSON.parse(test_result.input_json).find { |input| input['name'] == input_name }
|
|
54
|
+
ehr_bundle_input_value = ehr_bundle_input_value = ehr_bundle_input['value'] if ehr_bundle_input.present?
|
|
55
|
+
ehr_bundle = FHIR.from_contents(ehr_bundle_input_value) if ehr_bundle_input_value.present?
|
|
56
|
+
ehr_bundle if ehr_bundle.is_a?(FHIR::Bundle)
|
|
57
|
+
rescue StandardError
|
|
58
|
+
nil
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def input_group_prefix(test)
|
|
62
|
+
if test.id.include?('static')
|
|
63
|
+
'static'
|
|
64
|
+
elsif test.id.include?('adaptive')
|
|
65
|
+
'adaptive'
|
|
66
|
+
else
|
|
67
|
+
'resp'
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def find_resource_in_bundle(bundle, fhir_class, id)
|
|
72
|
+
bundle.entry&.find do |entry|
|
|
73
|
+
entry.resource.is_a?(fhir_class) && entry.resource&.id == id
|
|
74
|
+
end&.resource
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Pull resource type class and ID from url
|
|
78
|
+
# e.g. http://example.org/fhir/Patient/123 -> [FHIR::Patient, '123']
|
|
79
|
+
# @private
|
|
80
|
+
def fhir_class_and_id_from_url(url)
|
|
81
|
+
path = url.split('?').first.split('/fhir/').second
|
|
82
|
+
path.sub!(%r{/$}, '')
|
|
83
|
+
resource_type, id = path.split('/')
|
|
84
|
+
|
|
85
|
+
begin
|
|
86
|
+
fhir_class = FHIR.const_get(resource_type)
|
|
87
|
+
rescue NameError
|
|
88
|
+
fhir_class = nil
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
[fhir_class, id]
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|