Module: DaVinciCRDTestKit::CardsValidation

Constant Summary collapse

HOOKS =
[
  'appointment-book', 'encounter-discharge', 'encounter-start',
  'order-dispatch', 'order-select', 'order-sign'
].freeze

Constants included from ProfilesAndResourceTypes

ProfilesAndResourceTypes::ORDER_OR_ENCOUNTER_RESOURCE_CLASSES, ProfilesAndResourceTypes::ORDER_RESOURCE_CLASSES, ProfilesAndResourceTypes::ORDER_RESOURCE_TYPES

Constants included from RequestsLogicalModelValidation

RequestsLogicalModelValidation::CRD_CDS_HOOK_REQUEST_MODEL_URL, RequestsLogicalModelValidation::PERFORMER_ALLOWED_RESOURCE_TYPES, RequestsLogicalModelValidation::USER_ID_ALLOWED_RESOURCE_TYPES

Instance Method Summary collapse

Methods included from SuggestionActionsValidation

#action_fields_validation, #action_resource_type_check, #actions_check

Methods included from HookRequestFieldValidation

#hook_request_context_check, #hook_request_optional_fields_check, #hook_request_prefetch_check, #hook_request_required_fields_check, #json_parse, #no_error_validation

Methods included from ProfilesAndResourceTypes

#structure_definition_map, #structure_definition_map_v201, #structure_definition_map_v221

Methods included from ServerBaseURLs

#client_fhir_base_url, #fhir_url, #instance_url, #search_url

Methods included from BaseURLs

#inferno_base_url, #resume_fail_url, #resume_pass_url

Methods included from RequestsLogicalModelValidation

#validate_request_against_logical_model

Methods included from LogicalModelsOverrideHelper

#allowed_resource_type?, #check_appointment_conformance, #check_order_like_resource_conformance, #check_resource_conformance_to_coverage_profile, #check_resource_conformance_to_order_or_encounter_profile, #check_resource_conformance_to_order_profile, #check_resource_conformance_to_questionnaire_task_profile, #check_resource_type_and_validate, #local_reference?, #manually_check_appointment_validation_errors, #parse_action_resource, #primary_performer_type?, #referenced_resource_present_in_bundle?, #reject_resource_issues, #resolved_participant_patient_slice_issue?, #resolved_participant_primary_performer_slice_issue?

Instance Method Details

#additional_orders_check(card) ⇒ Object



172
173
174
# File 'lib/davinci_crd_test_kit/cross_suite/cards_validation.rb', line 172

def additional_orders_check(card)
  no_links_check(card, 'Additional Orders')
end

#all_requestsObject



288
289
290
291
292
293
# File 'lib/davinci_crd_test_kit/cross_suite/cards_validation.rb', line 288

def all_requests
  @all_requests ||= HOOKS.each_with_object([]) do |hook, reqs|
    load_tagged_requests(hook)
    reqs.concat(requests)
  end
end

#card_indicator_check(card) ⇒ Object



246
247
248
249
250
251
# File 'lib/davinci_crd_test_kit/cross_suite/cards_validation.rb', line 246

def card_indicator_check(card)
  return if !card['indicator'].is_a?(String) || ['info', 'warning', 'critical'].include?(card['indicator'])

  msg = "`indicator` is `#{card['indicator']}`. Allowed values are `info`, `warning`, `critical`: `#{card}`"
  add_message('error', msg)
end

rubocop:disable Metrics/CyclomaticComplexity



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/davinci_crd_test_kit/cross_suite/cards_validation.rb', line 109

def card_links_check(card) # rubocop:disable Metrics/CyclomaticComplexity
  return unless card['links'].is_a?(Array) && card['links'].present?

  card['links'].each do |link|
    link_required_fields.each do |field, type|
      validate_presence_and_type(link, field, type, 'Link')
    end
  end

  if card['links'].all? { |link| link['type'] == 'absolute' }
    external_reference_card_check(card)
  elsif card['links'].all? { |link| link['type'] == 'smart' }
    smart_app_card_check(card)
  else
    add_message(
      'error',
      "All links must either be `absolute` or `smart` in card: #{card}"
    )
  end
end

#card_optional_fieldsObject



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/davinci_crd_test_kit/cross_suite/cards_validation.rb', line 37

def card_optional_fields
  {
    'v221' => {
      'detail' => String,
      'indicator' => String,
      'suggestions' => Array,
      'selectionBehavior' => String,
      'overrideReasons' => Array,
      'links' => Array
    },
    'v201' => {
      'detail' => String,
      'uuid' => String,
      'suggestions' => Array,
      'selectionBehavior' => String,
      'overrideReasons' => Array,
      'links' => Array
    }
  }
end

#card_override_reasons_check(card) ⇒ Object



99
100
101
102
103
104
105
106
107
# File 'lib/davinci_crd_test_kit/cross_suite/cards_validation.rb', line 99

def card_override_reasons_check(card)
  return unless card['overrideReasons'].is_a?(Array)

  card['overrideReasons'].each do |reason|
    override_reasons_required_fields.each do |field, type|
      validate_presence_and_type(reason, field, type, 'OverrideReason Coding')
    end
  end
end

#card_required_fieldsObject



14
15
16
17
18
19
20
21
22
23
24
25
26
27
# File 'lib/davinci_crd_test_kit/cross_suite/cards_validation.rb', line 14

def card_required_fields
  {
    'v221' => {
      'uuid' => String,
      'summary' => String,
      'source' => Hash
    },
    'v201' => {
      'indicator' => String,
      'summary' => String,
      'source' => Hash
    }
  }
end

#card_selection_behavior_check(card) ⇒ Object



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/davinci_crd_test_kit/cross_suite/cards_validation.rb', line 82

def card_selection_behavior_check(card)
  return unless card['suggestions'].present?

  selection_behavior = card['selectionBehavior']
  unless selection_behavior
    add_message('error', "`Card.selectionBehavior` must be provided if suggestions are present. In Card `#{card}`")
    return
  end

  allowed_values = ['at-most-one', 'any']
  return if allowed_values.include?(selection_behavior)

  error_msg = "`selectionBehavior` #{selection_behavior} not allowed. " \
              "Allowed values: #{allowed_values.to_sentence}. In Card `#{card}`"
  add_message('error', error_msg)
end

#card_source_check(card) ⇒ Object



220
221
222
223
224
225
226
227
228
229
230
# File 'lib/davinci_crd_test_kit/cross_suite/cards_validation.rb', line 220

def card_source_check(card)
  source = card['source']
  return unless source.is_a?(Hash)

  source_required_fields.each do |field, type|
    validate_presence_and_type(source, field, type, 'Source')
  end

  card_source_topic_check(source['topic'])
  # TODO: How to validate topic binding to the ValueSet CRD Card Types?
end

#card_source_topic_check(topic) ⇒ Object



232
233
234
235
236
237
238
# File 'lib/davinci_crd_test_kit/cross_suite/cards_validation.rb', line 232

def card_source_topic_check(topic)
  return unless topic.is_a?(Hash)

  source_topic_required_fields.each do |field, type|
    validate_presence_and_type(topic, field, type, 'Source topic')
  end
end

#card_suggestions_check(card) ⇒ Object



188
189
190
191
192
193
194
# File 'lib/davinci_crd_test_kit/cross_suite/cards_validation.rb', line 188

def card_suggestions_check(card)
  return unless card['suggestions'].is_a?(Array) && card['suggestions'].present?

  card['suggestions'].each do |suggestion|
    process_suggestion(card, suggestion)
  end
end

#card_summary_check(card) ⇒ Object



240
241
242
243
244
# File 'lib/davinci_crd_test_kit/cross_suite/cards_validation.rb', line 240

def card_summary_check(card)
  return if !card['summary'].is_a?(String) || card['summary'].length < 140

  add_message('error', "`summary` is over the 140-character limit: `#{card['summary']}`")
end

#cards_check(cards, version = 'v221') ⇒ Object



253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/davinci_crd_test_kit/cross_suite/cards_validation.rb', line 253

def cards_check(cards, version = 'v221')
  cards.each do |card|
    current_error_count = messages.count { |msg| msg[:type] == 'error' }
    card_required_fields[version].each do |field, type|
      validate_presence_and_type(card, field, type, 'Card')
    end

    card_summary_check(card)
    card_indicator_check(card)
    card_source_check(card)

    valid_cards << card if current_error_count == messages.count { |msg| msg[:type] == 'error' }
  end
end

#create_or_update_coverage_check(card) ⇒ Object



176
177
178
# File 'lib/davinci_crd_test_kit/cross_suite/cards_validation.rb', line 176

def create_or_update_coverage_check(card)
  no_links_check(card, 'Update Coverage Records')
end

#external_reference_card_check(card) ⇒ Object



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/davinci_crd_test_kit/cross_suite/cards_validation.rb', line 130

def external_reference_card_check(card)
  if card['suggestions'].present?
    add_message(
      'error',
      "Cards with `absolute` links must not contain suggestions. In card `#{card}`"
    )
  end

  card['links'].each do |link|
    next if link['appContext'].blank?

    add_message(
      'error',
      '`appContext` field must not be present if the link type is absolute: ' \
      "`#{link}`. In Card `#{card}`"
    )
  end
end

#form_completion_check(card) ⇒ Object



180
181
182
# File 'lib/davinci_crd_test_kit/cross_suite/cards_validation.rb', line 180

def form_completion_check(card)
  no_links_check(card, 'Form Completion')
end


62
63
64
# File 'lib/davinci_crd_test_kit/cross_suite/cards_validation.rb', line 62

def link_required_fields
  { 'label' => String, 'type' => String, 'url' => 'URL' }
end


163
164
165
166
167
168
169
170
# File 'lib/davinci_crd_test_kit/cross_suite/cards_validation.rb', line 163

def no_links_check(card, card_type)
  return unless card['links'].present?

  add_message(
    'error',
    "#{card_type} response must not contain links. In card: `#{card}`"
  )
end

#override_reasons_required_fieldsObject



58
59
60
# File 'lib/davinci_crd_test_kit/cross_suite/cards_validation.rb', line 58

def override_reasons_required_fields
  { 'code' => String, 'system' => String, 'display' => String }
end

#perform_cards_validation(cards, response_has_system_actions, version, response_index) ⇒ Object



272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/davinci_crd_test_kit/cross_suite/cards_validation.rb', line 272

def perform_cards_validation(cards, response_has_system_actions, version, response_index)
  unless cards
    add_message('error', "#{response_label(response_index + 1)} did not have the `cards` field.")
    return
  end
  unless cards.is_a?(Array)
    add_message('error', "`cards` field of #{response_label(response_index + 1).downcase} is not an array.")
    return
  end
  warning do
    assert cards.present? || response_has_system_actions,
           "#{response_label(response_index + 1)} has no decision support."
  end
  cards_check(cards, version)
end

#process_suggestion(card, suggestion) ⇒ Object



196
197
198
199
200
201
202
# File 'lib/davinci_crd_test_kit/cross_suite/cards_validation.rb', line 196

def process_suggestion(card, suggestion)
  validate_presence_and_type(suggestion, 'label', String, 'Suggestion')
  validate_presence_and_type(suggestion, 'uuid', String, 'Suggestion')
  return unless suggestion['actions']

  validate_and_process_actions(card, suggestion)
end

#propose_alternate_request_check(card) ⇒ Object



184
185
186
# File 'lib/davinci_crd_test_kit/cross_suite/cards_validation.rb', line 184

def propose_alternate_request_check(card)
  no_links_check(card, 'Propose Alternate Request')
end

#response_label(index = nil) ⇒ Object



268
269
270
# File 'lib/davinci_crd_test_kit/cross_suite/cards_validation.rb', line 268

def response_label(index = nil)
  "Server response #{index}"
end

#smart_app_card_check(card) ⇒ Object



149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/davinci_crd_test_kit/cross_suite/cards_validation.rb', line 149

def smart_app_card_check(card)
  if card['suggestions'].blank?
    add_message(
      'error',
      "Cards with `smart` links must contain at least one suggestion. In card `#{card}`"
    )
  elsif card['suggestions'].any? { |suggestion| suggestion['actions'].present? }
    add_message(
      'error',
      "Cards with `smart` links must not contain any suggestion actions. In card `#{card}`"
    )
  end
end

#source_required_fieldsObject



29
30
31
# File 'lib/davinci_crd_test_kit/cross_suite/cards_validation.rb', line 29

def source_required_fields
  { 'label' => String, 'topic' => Hash }
end

#source_topic_required_fieldsObject



33
34
35
# File 'lib/davinci_crd_test_kit/cross_suite/cards_validation.rb', line 33

def source_topic_required_fields
  { 'code' => String, 'system' => String }
end

#valid_card_with_optionals?(card, version) ⇒ Boolean

Returns:

  • (Boolean)


66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/davinci_crd_test_kit/cross_suite/cards_validation.rb', line 66

def valid_card_with_optionals?(card, version)
  current_error_count = messages.count { |msg| msg[:type] == 'error' }
  card_optional_fields[version].each do |field, type|
    next unless card[field]

    validate_presence_and_type(card, field, type, 'Card', required: false)
  end

  card_selection_behavior_check(card)
  card_override_reasons_check(card)
  card_links_check(card)
  card_suggestions_check(card)

  current_error_count == messages.count { |msg| msg[:type] == 'error' }
end

#validate_and_process_actions(card, suggestion) ⇒ Object



204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/davinci_crd_test_kit/cross_suite/cards_validation.rb', line 204

def validate_and_process_actions(card, suggestion)
  actions = suggestion['actions']
  if !actions.is_a?(Array)
    add_message('error', "Suggestion `actions` field is not of type Array: `#{suggestion}`. In Card `#{card}`")
    return
  elsif actions.empty?
    add_message('error',
                "Suggestion `actions` field should not be an empty Array: `#{suggestion}`. In Card `#{card}`")
    return
  end

  actions.each do |action|
    action_fields_validation(action)
  end
end