davinci_crd_test_kit 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +201 -0
- data/lib/davinci_crd_test_kit/card_responses/companions_prerequisites.json +58 -0
- data/lib/davinci_crd_test_kit/card_responses/create_update_coverage_information.json +20 -0
- data/lib/davinci_crd_test_kit/card_responses/external_reference.json +21 -0
- data/lib/davinci_crd_test_kit/card_responses/instructions.json +14 -0
- data/lib/davinci_crd_test_kit/card_responses/launch_smart_app.json +21 -0
- data/lib/davinci_crd_test_kit/card_responses/propose_alternate_request.json +71 -0
- data/lib/davinci_crd_test_kit/card_responses/request_form_completion.json +227 -0
- data/lib/davinci_crd_test_kit/cards_validation.rb +234 -0
- data/lib/davinci_crd_test_kit/client_fhir_api_group.rb +762 -0
- data/lib/davinci_crd_test_kit/client_hook_request_validation.rb +15 -0
- data/lib/davinci_crd_test_kit/client_hooks_group.rb +706 -0
- data/lib/davinci_crd_test_kit/client_tests/appointment_book_receive_request_test.rb +71 -0
- data/lib/davinci_crd_test_kit/client_tests/client_display_cards_attest.rb +48 -0
- data/lib/davinci_crd_test_kit/client_tests/client_fhir_api_create_test.rb +40 -0
- data/lib/davinci_crd_test_kit/client_tests/client_fhir_api_read_test.rb +39 -0
- data/lib/davinci_crd_test_kit/client_tests/client_fhir_api_search_test.rb +232 -0
- data/lib/davinci_crd_test_kit/client_tests/client_fhir_api_update_test.rb +40 -0
- data/lib/davinci_crd_test_kit/client_tests/client_fhir_api_validation_test.rb +60 -0
- data/lib/davinci_crd_test_kit/client_tests/decode_auth_token_test.rb +40 -0
- data/lib/davinci_crd_test_kit/client_tests/encounter_discharge_receive_request_test.rb +68 -0
- data/lib/davinci_crd_test_kit/client_tests/encounter_start_receive_request_test.rb +68 -0
- data/lib/davinci_crd_test_kit/client_tests/hook_request_optional_fields_test.rb +41 -0
- data/lib/davinci_crd_test_kit/client_tests/hook_request_required_fields_test.rb +40 -0
- data/lib/davinci_crd_test_kit/client_tests/hook_request_valid_context_test.rb +63 -0
- data/lib/davinci_crd_test_kit/client_tests/hook_request_valid_prefetch_test.rb +151 -0
- data/lib/davinci_crd_test_kit/client_tests/order_dispatch_receive_request_test.rb +79 -0
- data/lib/davinci_crd_test_kit/client_tests/order_select_receive_request_test.rb +76 -0
- data/lib/davinci_crd_test_kit/client_tests/order_sign_receive_request_test.rb +79 -0
- data/lib/davinci_crd_test_kit/client_tests/retrieve_jwks_test.rb +65 -0
- data/lib/davinci_crd_test_kit/client_tests/token_header_test.rb +34 -0
- data/lib/davinci_crd_test_kit/client_tests/token_payload_test.rb +61 -0
- data/lib/davinci_crd_test_kit/crd_client_suite.rb +156 -0
- data/lib/davinci_crd_test_kit/crd_jwks.json +59 -0
- data/lib/davinci_crd_test_kit/crd_options.rb +9 -0
- data/lib/davinci_crd_test_kit/crd_server_suite.rb +115 -0
- data/lib/davinci_crd_test_kit/ext/inferno_core/runnable.rb +22 -0
- data/lib/davinci_crd_test_kit/hook_request_field_validation.rb +410 -0
- data/lib/davinci_crd_test_kit/jwks.rb +25 -0
- data/lib/davinci_crd_test_kit/jwt_helper.rb +74 -0
- data/lib/davinci_crd_test_kit/mock_service_response.rb +421 -0
- data/lib/davinci_crd_test_kit/routes/cds-services.json +74 -0
- data/lib/davinci_crd_test_kit/routes/cds_services_discovery_handler.rb +18 -0
- data/lib/davinci_crd_test_kit/routes/hook_request_endpoint.rb +93 -0
- data/lib/davinci_crd_test_kit/routes/jwk_set_endpoint_handler.rb +15 -0
- data/lib/davinci_crd_test_kit/server_appointment_book_group.rb +173 -0
- data/lib/davinci_crd_test_kit/server_discovery_group.rb +59 -0
- data/lib/davinci_crd_test_kit/server_encounter_discharge_group.rb +144 -0
- data/lib/davinci_crd_test_kit/server_encounter_start_group.rb +144 -0
- data/lib/davinci_crd_test_kit/server_hook_request_validation.rb +15 -0
- data/lib/davinci_crd_test_kit/server_hooks_group.rb +69 -0
- data/lib/davinci_crd_test_kit/server_order_dispatch_group.rb +173 -0
- data/lib/davinci_crd_test_kit/server_order_select_group.rb +169 -0
- data/lib/davinci_crd_test_kit/server_order_sign_group.rb +198 -0
- data/lib/davinci_crd_test_kit/server_required_card_response_validation_group.rb +23 -0
- data/lib/davinci_crd_test_kit/server_tests/additional_orders_validation_test.rb +70 -0
- data/lib/davinci_crd_test_kit/server_tests/card_optional_fields_validation_test.rb +47 -0
- data/lib/davinci_crd_test_kit/server_tests/coverage_information_system_action_across_hooks_validation_test.rb +32 -0
- data/lib/davinci_crd_test_kit/server_tests/coverage_information_system_action_received_test.rb +58 -0
- data/lib/davinci_crd_test_kit/server_tests/coverage_information_system_action_validation_test.rb +121 -0
- data/lib/davinci_crd_test_kit/server_tests/create_or_update_coverage_info_response_validation_test.rb +72 -0
- data/lib/davinci_crd_test_kit/server_tests/discovery_endpoint_test.rb +88 -0
- data/lib/davinci_crd_test_kit/server_tests/discovery_services_validation_test.rb +65 -0
- data/lib/davinci_crd_test_kit/server_tests/external_reference_card_across_hooks_validation_test.rb +28 -0
- data/lib/davinci_crd_test_kit/server_tests/external_reference_card_validation_test.rb +36 -0
- data/lib/davinci_crd_test_kit/server_tests/form_completion_response_validation_test.rb +79 -0
- data/lib/davinci_crd_test_kit/server_tests/instructions_card_received_across_hooks_test.rb +25 -0
- data/lib/davinci_crd_test_kit/server_tests/instructions_card_received_test.rb +28 -0
- data/lib/davinci_crd_test_kit/server_tests/launch_smart_app_card_validation_test.rb +38 -0
- data/lib/davinci_crd_test_kit/server_tests/propose_alternate_request_card_validation_test.rb +65 -0
- data/lib/davinci_crd_test_kit/server_tests/service_call_test.rb +86 -0
- data/lib/davinci_crd_test_kit/server_tests/service_request_context_validation_test.rb +30 -0
- data/lib/davinci_crd_test_kit/server_tests/service_request_optional_fields_validation_test.rb +41 -0
- data/lib/davinci_crd_test_kit/server_tests/service_request_required_fields_validation_test.rb +43 -0
- data/lib/davinci_crd_test_kit/server_tests/service_response_validation_test.rb +82 -0
- data/lib/davinci_crd_test_kit/suggestion_actions_validation.rb +123 -0
- data/lib/davinci_crd_test_kit/tags.rb +8 -0
- data/lib/davinci_crd_test_kit/test_helper.rb +23 -0
- data/lib/davinci_crd_test_kit/urls.rb +52 -0
- data/lib/davinci_crd_test_kit/version.rb +3 -0
- data/lib/davinci_crd_test_kit.rb +2 -0
- metadata +170 -0
@@ -0,0 +1,234 @@
|
|
1
|
+
require_relative 'server_hook_request_validation'
|
2
|
+
require_relative 'suggestion_actions_validation'
|
3
|
+
|
4
|
+
module DaVinciCRDTestKit
|
5
|
+
module CardsValidation
|
6
|
+
include DaVinciCRDTestKit::ServerHookRequestValidation
|
7
|
+
include DaVinciCRDTestKit::SuggestionActionsValidation
|
8
|
+
|
9
|
+
HOOKS = [
|
10
|
+
'appointment-book', 'encounter-discharge', 'encounter-start',
|
11
|
+
'order-dispatch', 'order-select', 'order-sign'
|
12
|
+
].freeze
|
13
|
+
|
14
|
+
def card_required_fields
|
15
|
+
{ 'summary' => String, 'indicator' => String, 'source' => Hash }
|
16
|
+
end
|
17
|
+
|
18
|
+
def source_required_fields
|
19
|
+
{ 'label' => String, 'topic' => Hash }
|
20
|
+
end
|
21
|
+
|
22
|
+
def source_topic_required_fields
|
23
|
+
{ 'code' => String, 'system' => String }
|
24
|
+
end
|
25
|
+
|
26
|
+
def card_optional_fields
|
27
|
+
{
|
28
|
+
'uuid' => String,
|
29
|
+
'detail' => String,
|
30
|
+
'suggestions' => Array,
|
31
|
+
'overrideReasons' => Array,
|
32
|
+
'links' => Array
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def override_reasons_required_fields
|
37
|
+
{ 'code' => String, 'system' => String, 'display' => String }
|
38
|
+
end
|
39
|
+
|
40
|
+
def link_required_fields
|
41
|
+
{ 'label' => String, 'type' => String, 'url' => 'URL' }
|
42
|
+
end
|
43
|
+
|
44
|
+
def valid_card_with_optionals?(card)
|
45
|
+
current_error_count = messages.count { |msg| msg[:type] == 'error' }
|
46
|
+
card_optional_fields.each do |field, type|
|
47
|
+
next unless card[field]
|
48
|
+
|
49
|
+
validate_presence_and_type(card, field, type, 'Card')
|
50
|
+
end
|
51
|
+
|
52
|
+
card_selection_behavior_check(card)
|
53
|
+
card_override_reasons_check(card)
|
54
|
+
card_links_check(card)
|
55
|
+
card_suggestions_check(card)
|
56
|
+
|
57
|
+
current_error_count == messages.count { |msg| msg[:type] == 'error' }
|
58
|
+
end
|
59
|
+
|
60
|
+
def card_selection_behavior_check(card)
|
61
|
+
return unless card['suggestions'].present?
|
62
|
+
|
63
|
+
selection_behavior = card['selectionBehavior']
|
64
|
+
unless selection_behavior
|
65
|
+
add_message('error', "`Card.selectionBehavior` must be provided if suggestions are present. In Card `#{card}`")
|
66
|
+
return
|
67
|
+
end
|
68
|
+
|
69
|
+
allowed_values = ['at-most-one', 'any']
|
70
|
+
return if allowed_values.include?(selection_behavior)
|
71
|
+
|
72
|
+
error_msg = "`selectionBehavior` #{selection_behavior} not allowed. " \
|
73
|
+
"Allowed values: #{allowed_values.to_sentence}. In Card `#{card}`"
|
74
|
+
add_message('error', error_msg)
|
75
|
+
end
|
76
|
+
|
77
|
+
def card_override_reasons_check(card)
|
78
|
+
return unless card['overrideReasons'].is_a?(Array)
|
79
|
+
|
80
|
+
card['overrideReasons'].each do |reason|
|
81
|
+
override_reasons_required_fields.each do |field, type|
|
82
|
+
validate_presence_and_type(reason, field, type, 'OverrideReason Coding')
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def card_links_check(card)
|
88
|
+
return unless card['links'].is_a?(Array) && card['links'].present?
|
89
|
+
|
90
|
+
card['links'].each do |link|
|
91
|
+
link_required_fields.each do |field, type|
|
92
|
+
validate_presence_and_type(link, field, type, 'Link')
|
93
|
+
end
|
94
|
+
|
95
|
+
card_link_type_check(card, link)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def card_link_type_check(card, link)
|
100
|
+
return unless link['type']
|
101
|
+
|
102
|
+
unless ['absolute', 'smart'].include?(link['type'])
|
103
|
+
add_message('error',
|
104
|
+
"`Link.type` must be `absolute` or `smart`. Got `#{link['type']}`: `#{link}`. In Card `#{card}`")
|
105
|
+
return
|
106
|
+
end
|
107
|
+
|
108
|
+
return unless link['type'] == 'absolute' && link['appContext'].present?
|
109
|
+
|
110
|
+
msg = '`appContext` field should only be valued if the link type is smart and is not valid for absolute links: ' \
|
111
|
+
"`#{link}`. In Card `#{card}`"
|
112
|
+
add_message('error', msg)
|
113
|
+
end
|
114
|
+
|
115
|
+
def card_suggestions_check(card)
|
116
|
+
return unless card['suggestions'].is_a?(Array) && card['suggestions'].present?
|
117
|
+
|
118
|
+
card['suggestions'].each do |suggestion|
|
119
|
+
process_suggestion(card, suggestion)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def process_suggestion(card, suggestion)
|
124
|
+
validate_presence_and_type(suggestion, 'label', String, 'Suggestion')
|
125
|
+
return unless suggestion['actions']
|
126
|
+
|
127
|
+
validate_and_process_actions(card, suggestion)
|
128
|
+
end
|
129
|
+
|
130
|
+
def validate_and_process_actions(card, suggestion)
|
131
|
+
actions = suggestion['actions']
|
132
|
+
if !actions.is_a?(Array)
|
133
|
+
add_message('error', "Suggestion `actions` field is not of type Array: `#{suggestion}`. In Card `#{card}`")
|
134
|
+
return
|
135
|
+
elsif actions.empty?
|
136
|
+
add_message('error',
|
137
|
+
"Suggestion `actions` field should not be an empty Array: `#{suggestion}`. In Card `#{card}`")
|
138
|
+
return
|
139
|
+
end
|
140
|
+
|
141
|
+
actions.each do |action|
|
142
|
+
action_fields_validation(action)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def card_source_check(card)
|
147
|
+
source = card['source']
|
148
|
+
return unless source.is_a?(Hash)
|
149
|
+
|
150
|
+
source_required_fields.each do |field, type|
|
151
|
+
validate_presence_and_type(source, field, type, 'Source')
|
152
|
+
end
|
153
|
+
|
154
|
+
card_source_topic_check(source['topic'])
|
155
|
+
# TODO: How to validate topic binding to the ValueSet CRD Card Types?
|
156
|
+
end
|
157
|
+
|
158
|
+
def card_source_topic_check(topic)
|
159
|
+
return unless topic.is_a?(Hash)
|
160
|
+
|
161
|
+
source_topic_required_fields.each do |field, type|
|
162
|
+
validate_presence_and_type(topic, field, type, 'Source topic')
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def card_summary_check(card)
|
167
|
+
return if !card['summary'].is_a?(String) || card['summary'].length < 140
|
168
|
+
|
169
|
+
add_message('error', "`summary` is over the 140-character limit: `#{card}`")
|
170
|
+
end
|
171
|
+
|
172
|
+
def card_indicator_check(card)
|
173
|
+
return if !card['indicator'].is_a?(String) || ['info', 'warning', 'critical'].include?(card['indicator'])
|
174
|
+
|
175
|
+
msg = "`indicator` is `#{card['indicator']}`. Allowed values are `info`, `warning`, `critical`: `#{card}`"
|
176
|
+
add_message('error', msg)
|
177
|
+
end
|
178
|
+
|
179
|
+
def cards_check(cards)
|
180
|
+
cards.each do |card|
|
181
|
+
current_error_count = messages.count { |msg| msg[:type] == 'error' }
|
182
|
+
card_required_fields.each do |field, type|
|
183
|
+
validate_presence_and_type(card, field, type, 'Card')
|
184
|
+
end
|
185
|
+
|
186
|
+
card_summary_check(card)
|
187
|
+
card_indicator_check(card)
|
188
|
+
card_source_check(card)
|
189
|
+
|
190
|
+
valid_cards << card if current_error_count == messages.count { |msg| msg[:type] == 'error' }
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def perform_cards_validation(cards, response_index = 0)
|
195
|
+
unless cards
|
196
|
+
add_message('error', "Server response #{response_index + 1} did not have the `cards` field.")
|
197
|
+
return
|
198
|
+
end
|
199
|
+
unless cards.is_a?(Array)
|
200
|
+
add_message('error', "`cards` field of server response #{response_index + 1} is not an array.")
|
201
|
+
return
|
202
|
+
end
|
203
|
+
warning do
|
204
|
+
assert cards.present?, "Server response #{response_index + 1} has no decision support."
|
205
|
+
end
|
206
|
+
cards_check(cards)
|
207
|
+
end
|
208
|
+
|
209
|
+
def all_requests
|
210
|
+
@all_requests ||= HOOKS.each_with_object([]) do |hook, reqs|
|
211
|
+
load_tagged_requests(hook)
|
212
|
+
reqs.concat(requests)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def extract_all_valid_cards_from_hooks_responses
|
217
|
+
all_requests.keep_if { |request| request.status == 200 }
|
218
|
+
all_requests.each_with_index do |request, index|
|
219
|
+
service_response = JSON.parse(request.response_body)
|
220
|
+
perform_cards_validation(service_response['cards'], index)
|
221
|
+
rescue JSON::ParserError
|
222
|
+
add_message('error', "Invalid JSON: server response #{index + 1} is not a valid JSON.")
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def extract_valid_cards_with_links_from_hooks_responses
|
227
|
+
extract_all_valid_cards_from_hooks_responses
|
228
|
+
|
229
|
+
valid_cards.each do |card|
|
230
|
+
valid_cards_with_links << card if valid_card_with_optionals?(card) && (card['links'])
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|