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.
Files changed (83) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +201 -0
  3. data/lib/davinci_crd_test_kit/card_responses/companions_prerequisites.json +58 -0
  4. data/lib/davinci_crd_test_kit/card_responses/create_update_coverage_information.json +20 -0
  5. data/lib/davinci_crd_test_kit/card_responses/external_reference.json +21 -0
  6. data/lib/davinci_crd_test_kit/card_responses/instructions.json +14 -0
  7. data/lib/davinci_crd_test_kit/card_responses/launch_smart_app.json +21 -0
  8. data/lib/davinci_crd_test_kit/card_responses/propose_alternate_request.json +71 -0
  9. data/lib/davinci_crd_test_kit/card_responses/request_form_completion.json +227 -0
  10. data/lib/davinci_crd_test_kit/cards_validation.rb +234 -0
  11. data/lib/davinci_crd_test_kit/client_fhir_api_group.rb +762 -0
  12. data/lib/davinci_crd_test_kit/client_hook_request_validation.rb +15 -0
  13. data/lib/davinci_crd_test_kit/client_hooks_group.rb +706 -0
  14. data/lib/davinci_crd_test_kit/client_tests/appointment_book_receive_request_test.rb +71 -0
  15. data/lib/davinci_crd_test_kit/client_tests/client_display_cards_attest.rb +48 -0
  16. data/lib/davinci_crd_test_kit/client_tests/client_fhir_api_create_test.rb +40 -0
  17. data/lib/davinci_crd_test_kit/client_tests/client_fhir_api_read_test.rb +39 -0
  18. data/lib/davinci_crd_test_kit/client_tests/client_fhir_api_search_test.rb +232 -0
  19. data/lib/davinci_crd_test_kit/client_tests/client_fhir_api_update_test.rb +40 -0
  20. data/lib/davinci_crd_test_kit/client_tests/client_fhir_api_validation_test.rb +60 -0
  21. data/lib/davinci_crd_test_kit/client_tests/decode_auth_token_test.rb +40 -0
  22. data/lib/davinci_crd_test_kit/client_tests/encounter_discharge_receive_request_test.rb +68 -0
  23. data/lib/davinci_crd_test_kit/client_tests/encounter_start_receive_request_test.rb +68 -0
  24. data/lib/davinci_crd_test_kit/client_tests/hook_request_optional_fields_test.rb +41 -0
  25. data/lib/davinci_crd_test_kit/client_tests/hook_request_required_fields_test.rb +40 -0
  26. data/lib/davinci_crd_test_kit/client_tests/hook_request_valid_context_test.rb +63 -0
  27. data/lib/davinci_crd_test_kit/client_tests/hook_request_valid_prefetch_test.rb +151 -0
  28. data/lib/davinci_crd_test_kit/client_tests/order_dispatch_receive_request_test.rb +79 -0
  29. data/lib/davinci_crd_test_kit/client_tests/order_select_receive_request_test.rb +76 -0
  30. data/lib/davinci_crd_test_kit/client_tests/order_sign_receive_request_test.rb +79 -0
  31. data/lib/davinci_crd_test_kit/client_tests/retrieve_jwks_test.rb +65 -0
  32. data/lib/davinci_crd_test_kit/client_tests/token_header_test.rb +34 -0
  33. data/lib/davinci_crd_test_kit/client_tests/token_payload_test.rb +61 -0
  34. data/lib/davinci_crd_test_kit/crd_client_suite.rb +156 -0
  35. data/lib/davinci_crd_test_kit/crd_jwks.json +59 -0
  36. data/lib/davinci_crd_test_kit/crd_options.rb +9 -0
  37. data/lib/davinci_crd_test_kit/crd_server_suite.rb +115 -0
  38. data/lib/davinci_crd_test_kit/ext/inferno_core/runnable.rb +22 -0
  39. data/lib/davinci_crd_test_kit/hook_request_field_validation.rb +410 -0
  40. data/lib/davinci_crd_test_kit/jwks.rb +25 -0
  41. data/lib/davinci_crd_test_kit/jwt_helper.rb +74 -0
  42. data/lib/davinci_crd_test_kit/mock_service_response.rb +421 -0
  43. data/lib/davinci_crd_test_kit/routes/cds-services.json +74 -0
  44. data/lib/davinci_crd_test_kit/routes/cds_services_discovery_handler.rb +18 -0
  45. data/lib/davinci_crd_test_kit/routes/hook_request_endpoint.rb +93 -0
  46. data/lib/davinci_crd_test_kit/routes/jwk_set_endpoint_handler.rb +15 -0
  47. data/lib/davinci_crd_test_kit/server_appointment_book_group.rb +173 -0
  48. data/lib/davinci_crd_test_kit/server_discovery_group.rb +59 -0
  49. data/lib/davinci_crd_test_kit/server_encounter_discharge_group.rb +144 -0
  50. data/lib/davinci_crd_test_kit/server_encounter_start_group.rb +144 -0
  51. data/lib/davinci_crd_test_kit/server_hook_request_validation.rb +15 -0
  52. data/lib/davinci_crd_test_kit/server_hooks_group.rb +69 -0
  53. data/lib/davinci_crd_test_kit/server_order_dispatch_group.rb +173 -0
  54. data/lib/davinci_crd_test_kit/server_order_select_group.rb +169 -0
  55. data/lib/davinci_crd_test_kit/server_order_sign_group.rb +198 -0
  56. data/lib/davinci_crd_test_kit/server_required_card_response_validation_group.rb +23 -0
  57. data/lib/davinci_crd_test_kit/server_tests/additional_orders_validation_test.rb +70 -0
  58. data/lib/davinci_crd_test_kit/server_tests/card_optional_fields_validation_test.rb +47 -0
  59. data/lib/davinci_crd_test_kit/server_tests/coverage_information_system_action_across_hooks_validation_test.rb +32 -0
  60. data/lib/davinci_crd_test_kit/server_tests/coverage_information_system_action_received_test.rb +58 -0
  61. data/lib/davinci_crd_test_kit/server_tests/coverage_information_system_action_validation_test.rb +121 -0
  62. data/lib/davinci_crd_test_kit/server_tests/create_or_update_coverage_info_response_validation_test.rb +72 -0
  63. data/lib/davinci_crd_test_kit/server_tests/discovery_endpoint_test.rb +88 -0
  64. data/lib/davinci_crd_test_kit/server_tests/discovery_services_validation_test.rb +65 -0
  65. data/lib/davinci_crd_test_kit/server_tests/external_reference_card_across_hooks_validation_test.rb +28 -0
  66. data/lib/davinci_crd_test_kit/server_tests/external_reference_card_validation_test.rb +36 -0
  67. data/lib/davinci_crd_test_kit/server_tests/form_completion_response_validation_test.rb +79 -0
  68. data/lib/davinci_crd_test_kit/server_tests/instructions_card_received_across_hooks_test.rb +25 -0
  69. data/lib/davinci_crd_test_kit/server_tests/instructions_card_received_test.rb +28 -0
  70. data/lib/davinci_crd_test_kit/server_tests/launch_smart_app_card_validation_test.rb +38 -0
  71. data/lib/davinci_crd_test_kit/server_tests/propose_alternate_request_card_validation_test.rb +65 -0
  72. data/lib/davinci_crd_test_kit/server_tests/service_call_test.rb +86 -0
  73. data/lib/davinci_crd_test_kit/server_tests/service_request_context_validation_test.rb +30 -0
  74. data/lib/davinci_crd_test_kit/server_tests/service_request_optional_fields_validation_test.rb +41 -0
  75. data/lib/davinci_crd_test_kit/server_tests/service_request_required_fields_validation_test.rb +43 -0
  76. data/lib/davinci_crd_test_kit/server_tests/service_response_validation_test.rb +82 -0
  77. data/lib/davinci_crd_test_kit/suggestion_actions_validation.rb +123 -0
  78. data/lib/davinci_crd_test_kit/tags.rb +8 -0
  79. data/lib/davinci_crd_test_kit/test_helper.rb +23 -0
  80. data/lib/davinci_crd_test_kit/urls.rb +52 -0
  81. data/lib/davinci_crd_test_kit/version.rb +3 -0
  82. data/lib/davinci_crd_test_kit.rb +2 -0
  83. 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