davinci_crd_test_kit 0.9.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 +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
|