Module: DaVinciCRDTestKit::ResponseLogicalModelValidation

Constant Summary collapse

CRD_LOGICAL_MODEL_BASE =
'http://hl7.org/fhir/us/davinci-crd/StructureDefinition'.freeze
CRD_RESPONSE_BASE_LOGICAL_MODEL =
'CRDHooksResponseBase'.freeze
CARD_TYPE_TO_LOGICAL_MODEL =
{
  DaVinciCRDTestKit::CardsIdentification::ADDITIONAL_ORDERS_RESPONSE_TYPE =>
    'CRDHooksResponse-additionalOrders',
  DaVinciCRDTestKit::CardsIdentification::CREATE_OR_UPDATE_COVERAGE_RESPONSE_TYPE =>
    'CRDHooksResponse-adjustCoverage',
  DaVinciCRDTestKit::CardsIdentification::EXTERNAL_REFERENCE_RESPONSE_TYPE =>
    'CRDHooksResponse-externalReference',
  DaVinciCRDTestKit::CardsIdentification::FORM_COMPLETION_RESPONSE_TYPE =>
    'CRDHooksResponse-formCompletion',
  DaVinciCRDTestKit::CardsIdentification::INSTRUCTIONS_RESPONSE_TYPE =>
    'CRDHooksResponse-instructions',
  DaVinciCRDTestKit::CardsIdentification::LAUNCH_SMART_APP_RESPONSE_TYPE =>
    'CRDHooksResponse-launchSMART',
  DaVinciCRDTestKit::CardsIdentification::PROPOSE_ALTERNATIVE_REQUEST_RESPONSE_TYPE =>
    'CRDHooksResponse-alternateRequest'
}.freeze
ACTION_TYPE_TO_LOGICAL_MODEL =
{
  DaVinciCRDTestKit::CardsIdentification::COVERAGE_INFORMATION_RESPONSE_TYPE =>
    'CRDHooksResponse-coverageInformation',
  DaVinciCRDTestKit::CardsIdentification::CREATE_OR_UPDATE_COVERAGE_RESPONSE_TYPE =>
    'CRDHooksResponse-adjustCoverage',
  DaVinciCRDTestKit::CardsIdentification::FORM_COMPLETION_RESPONSE_TYPE =>
    'CRDHooksResponse-formCompletion'
}.freeze

Constants included from CardsIdentification

CardsIdentification::ADDITIONAL_ORDERS_EXPECTED_RESOURCE_TYPES, CardsIdentification::ADDITIONAL_ORDERS_RESPONSE_TYPE, CardsIdentification::COVERAGE_INFORMATION_RESPONSE_TYPE, CardsIdentification::COVERAGE_INFO_CONFIGURATION_CODE, CardsIdentification::COVERAGE_INFO_EXPECTED_RESOURCE_TYPES, CardsIdentification::COVERAGE_INFO_EXT_URL, CardsIdentification::CREATE_OR_UPDATE_COVERAGE_RESPONSE_TYPE, CardsIdentification::EXTERNAL_REFERENCE_RESPONSE_TYPE, CardsIdentification::FORM_COMPLETION_RESPONSE_TYPE, CardsIdentification::INSTRUCTIONS_RESPONSE_TYPE, CardsIdentification::LAUNCH_SMART_APP_RESPONSE_TYPE, CardsIdentification::PROPOSE_ALTERNATIVE_REQUEST_EXPECTED_RESOURCE_TYPES, CardsIdentification::PROPOSE_ALTERNATIVE_REQUEST_RESPONSE_TYPE

Constants included from ProfilesAndResourceTypes

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

Constants included from RequestsLogicalModelValidation

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

Instance Method Summary collapse

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?

Methods included from CardsIdentification

#additional_orders_response_type?, #cache_sorted_cards, #check_action_type, #coverage_info_card_type?, #coverage_info_configuration_disabled?, #coverage_info_content, #coverage_info_response?, #coverage_info_system_action_response?, #coverage_info_system_action_type?, #coverage_information_response_type?, #create_or_update_coverage_action_response_type?, #create_or_update_coverage_card_response_type?, #create_questionnaire_action_response_type?, #disable_coverage_info_configuration!, #extension_url, #external_reference_response_type?, #extract_coverage_information_extensions, #form_completion_action_response_type?, #form_completion_card_response_type?, #form_completion_task_questionnaire?, #hook_instances_from_requests, #identify_action_type, #identify_card_type, #initialize_sorted_cards_hash, #instructions_response_type?, #launch_smart_app_response_type?, #list_card_types_in_requests, #propose_alternative_request_response_type?, #sort_card_types_from_request, #sorted_cards_cached?, #sorted_cards_from_cache, #sorted_cards_from_requests

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 SuggestionActionsValidation

#action_fields_validation, #action_resource_type_check, #actions_check

Instance Method Details

#add_messages_not_excluded(issues, error_prefix) ⇒ Object



254
255
256
257
258
259
260
# File 'lib/davinci_crd_test_kit/cross_suite/response_logical_model_validation.rb', line 254

def add_messages_not_excluded(issues, error_prefix)
  issues.each do |issue|
    next if issue.filtered || logical_model_extension_issue?(issue)

    add_message(issue.severity, "#{error_prefix}#{issue.message}")
  end
end

#check_action_target(action, request_body, error_prefix, ig_semver) ⇒ Object



395
396
397
398
399
400
# File 'lib/davinci_crd_test_kit/cross_suite/response_logical_model_validation.rb', line 395

def check_action_target(action, request_body, error_prefix, ig_semver)
  local_reference?(action['resourceId'], error_prefix) if action['resourceId'].present?
  return unless action['resource'].present?

  check_resource_conformance_to_order_profile(action['resource'], request_body, error_prefix, ig_semver)
end

#check_multiple_coverage_info_extension_conformance(action) ⇒ Object



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

def check_multiple_coverage_info_extension_conformance(action)
  resource = FHIR.from_contents(action['resource'].to_json)

  grouped_coverage_info = extract_and_group_coverage_info(resource)
  multiple_extensions_conformance_check(grouped_coverage_info, resource)
end

#check_questionnaire_actions(card, error_message, error_prefix) ⇒ Object



320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
# File 'lib/davinci_crd_test_kit/cross_suite/response_logical_model_validation.rb', line 320

def check_questionnaire_actions(card, error_message, error_prefix)
  extracted_indexes =
    error_message.match(/CDSHooksResponse\.cards\[0\]\.suggestions\[(\d+)\]\.actions\[(\d+)\]\.resource/)
  unless extracted_indexes
    raise 'Unexpected validator error message format in check_questionnaire_actions: ' \
          "'#{error_message}'. This indicates an implementation problem in the test kit — please log a ticket."
  end

  suggestion_index = extracted_indexes[1].to_i
  action_index = extracted_indexes[2].to_i

  message_prefix = "#{error_prefix} suggestion #{suggestion_index + 1}, action #{action_index + 1} - "
  resource = FHIR.from_contents(card['suggestions'][suggestion_index]['actions'][action_index]['resource'].to_json)
  resource_is_valid?(resource:, message_prefix:) # no questionnaire profile applied per CRD

  return if resource.id.present?

  add_message('error', "#{message_prefix}Questionnaire must have an id.")
end

#check_required_action_type(action, required_type, error_prefix, description) ⇒ Object



429
430
431
432
433
# File 'lib/davinci_crd_test_kit/cross_suite/response_logical_model_validation.rb', line 429

def check_required_action_type(action, required_type, error_prefix, description)
  return if action['type'] == required_type

  add_message('error', "#{error_prefix}action type must be '#{required_type}' for a #{description}.")
end

#collect_extensions_id(extensions, url, *properties) ⇒ Object



229
230
231
232
233
# File 'lib/davinci_crd_test_kit/cross_suite/response_logical_model_validation.rb', line 229

def collect_extensions_id(extensions, url, *properties)
  extensions.map do |extension|
    find_extension_value(extension, url, *properties)
  end
end

#different_coverage_conformance_error_msg(resource_ref, id_name) ⇒ Object



250
251
252
# File 'lib/davinci_crd_test_kit/cross_suite/response_logical_model_validation.rb', line 250

def different_coverage_conformance_error_msg(resource_ref, id_name)
  "#{resource_ref}: extensions referencing differing coverage SHALL have distinct #{id_name}."
end

#extract_and_group_coverage_info(resource) ⇒ Object



172
173
174
175
176
177
178
179
180
# File 'lib/davinci_crd_test_kit/cross_suite/response_logical_model_validation.rb', line 172

def extract_and_group_coverage_info(resource)
  resource.extension.each_with_object({}) do |extension, grouped_extensions|
    next unless extension.url == COVERAGE_INFO_EXT_URL

    coverage_key = find_extension_value(extension, 'coverage', 'valueReference', 'reference')
    grouped_extensions[coverage_key] ||= []
    grouped_extensions[coverage_key] << extension
  end
end

#find_extension_value(extension, url, *properties) ⇒ Object



235
236
237
238
239
240
241
242
243
244
# File 'lib/davinci_crd_test_kit/cross_suite/response_logical_model_validation.rb', line 235

def find_extension_value(extension, url, *properties)
  found_extension = extension.extension.find { |ext| ext.url == url }
  return nil unless found_extension

  properties.reduce(found_extension) do |current, prop|
    return current unless current.respond_to?(prop)

    current.send(prop)
  end
end

#logical_model_entity_label(response_index, entity_index, kind) ⇒ Object



262
263
264
# File 'lib/davinci_crd_test_kit/cross_suite/response_logical_model_validation.rb', line 262

def logical_model_entity_label(response_index, entity_index, kind)
  "Server response #{response_index + 1}, #{kind} #{entity_index + 1}"
end

#logical_model_extension_issue?(issue) ⇒ Boolean


Validator Filtering and Manual Checks Depending on the Card Type

Returns:

  • (Boolean)


270
271
272
# File 'lib/davinci_crd_test_kit/cross_suite/response_logical_model_validation.rb', line 270

def logical_model_extension_issue?(issue)
  issue.message.match(/\.extension: Unrecognized property/).present?
end

#logical_model_url(profile_name) ⇒ Object



38
39
40
# File 'lib/davinci_crd_test_kit/cross_suite/response_logical_model_validation.rb', line 38

def logical_model_url(profile_name)
  "#{CRD_LOGICAL_MODEL_BASE}/#{profile_name}"
end

#manually_check_action_resources_for_order_profile_conformance(card, validation_issues, request_body, error_prefix, ig_semver) ⇒ Object



379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# File 'lib/davinci_crd_test_kit/cross_suite/response_logical_model_validation.rb', line 379

def manually_check_action_resources_for_order_profile_conformance(card, validation_issues, request_body,
                                                                  error_prefix, ig_semver)
  if card['suggestions'].present?
    card['suggestions'].each_with_index do |suggestion, suggestion_index|
      next unless suggestion['actions'].present?

      suggestion['actions'].each_with_index do |action, action_index|
        action_error_prefix = "#{error_prefix}suggestion #{suggestion_index + 1}, action #{action_index + 1} - "
        check_action_target(action, request_body, action_error_prefix, ig_semver)
      end
    end
  end

  reject_resource_issues(validation_issues)
end

#manually_check_action_specific_errors(action, validation_issues, action_type, request_body, error_prefix, ig_semver) ⇒ Object



292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
# File 'lib/davinci_crd_test_kit/cross_suite/response_logical_model_validation.rb', line 292

def manually_check_action_specific_errors(action, validation_issues, action_type, request_body,
                                          error_prefix, ig_semver)
  case action_type
  when DaVinciCRDTestKit::CardsIdentification::COVERAGE_INFORMATION_RESPONSE_TYPE
    manually_check_coverage_information_errors(action, validation_issues, request_body,
                                               error_prefix, ig_semver)
  when DaVinciCRDTestKit::CardsIdentification::CREATE_OR_UPDATE_COVERAGE_RESPONSE_TYPE
    manually_check_update_coverage_action_errors(action, validation_issues,
                                                 error_prefix, ig_semver)
  when DaVinciCRDTestKit::CardsIdentification::FORM_COMPLETION_RESPONSE_TYPE
    manually_check_form_completion_action_errors(action, validation_issues,
                                                 error_prefix, ig_semver)
  else
    validation_issues
  end
end

#manually_check_additional_orders_errors(card, validation_issues, request_body, error_prefix, ig_semver) ⇒ Object



370
371
372
373
374
375
376
377
# File 'lib/davinci_crd_test_kit/cross_suite/response_logical_model_validation.rb', line 370

def manually_check_additional_orders_errors(card, validation_issues, request_body,
                                            error_prefix, ig_semver)
  manually_check_action_resources_for_order_profile_conformance(card,
                                                                validation_issues,
                                                                request_body,
                                                                error_prefix,
                                                                ig_semver)
end

#manually_check_card_specific_errors(card, validation_issues, card_type, request_body, error_prefix, ig_semver) ⇒ Object



274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
# File 'lib/davinci_crd_test_kit/cross_suite/response_logical_model_validation.rb', line 274

def manually_check_card_specific_errors(card, validation_issues, card_type, request_body, error_prefix,
                                        ig_semver)
  case card_type
  when CardsIdentification::FORM_COMPLETION_RESPONSE_TYPE
    manually_check_form_completion_errors(card, validation_issues, error_prefix)
  when CardsIdentification::PROPOSE_ALTERNATIVE_REQUEST_RESPONSE_TYPE
    manually_check_propose_alternative_errors(card, validation_issues, request_body,
                                              error_prefix, ig_semver)
  when CardsIdentification::ADDITIONAL_ORDERS_RESPONSE_TYPE
    manually_check_additional_orders_errors(card, validation_issues, request_body,
                                            error_prefix, ig_semver)
  when CardsIdentification::LAUNCH_SMART_APP_RESPONSE_TYPE
    manually_check_launch_smart_app_errors(validation_issues)
  else
    validation_issues
  end
end

#manually_check_coverage_information_errors(action, validation_issues, request_body, error_prefix, ig_semver) ⇒ Object



402
403
404
405
406
407
408
409
# File 'lib/davinci_crd_test_kit/cross_suite/response_logical_model_validation.rb', line 402

def manually_check_coverage_information_errors(action, validation_issues, request_body,
                                               error_prefix, ig_semver)
  if action['resource'].present?
    check_resource_conformance_to_order_or_encounter_profile(action['resource'], request_body,
                                                             error_prefix, ig_semver)
  end
  reject_resource_issues(validation_issues)
end

#manually_check_form_completion_action_errors(action, validation_issues, error_prefix, ig_semver) ⇒ Object



420
421
422
423
424
425
426
427
# File 'lib/davinci_crd_test_kit/cross_suite/response_logical_model_validation.rb', line 420

def manually_check_form_completion_action_errors(action, validation_issues,
                                                 error_prefix, ig_semver)
  check_required_action_type(action, 'create', error_prefix, 'form completion action response type')
  if action['resource'].present?
    check_resource_conformance_to_questionnaire_task_profile(action['resource'], error_prefix, ig_semver)
  end
  reject_resource_issues(validation_issues)
end

#manually_check_form_completion_errors(card, validation_issues, error_prefix) ⇒ Object



309
310
311
312
313
314
315
316
317
318
# File 'lib/davinci_crd_test_kit/cross_suite/response_logical_model_validation.rb', line 309

def manually_check_form_completion_errors(card, validation_issues, error_prefix)
  validation_issues.reject do |issue|
    if issue.message.match?(/The type 'Questionnaire' is not valid - must be Task/)
      check_questionnaire_actions(card, issue.message, error_prefix)
      true
    else
      false
    end
  end
end

#manually_check_launch_smart_app_errors(validation_issues) ⇒ Object



340
341
342
343
344
# File 'lib/davinci_crd_test_kit/cross_suite/response_logical_model_validation.rb', line 340

def manually_check_launch_smart_app_errors(validation_issues)
  validation_issues.reject do |issue|
    issue.message.match?(/CDSHooksResponse.cards.suggestions: minimum required = 1, but only found 0/)
  end
end

#manually_check_propose_alternative_errors(card, validation_issues, request_body, error_prefix, ig_semver) ⇒ Object



346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
# File 'lib/davinci_crd_test_kit/cross_suite/response_logical_model_validation.rb', line 346

def manually_check_propose_alternative_errors(card, validation_issues, request_body,
                                              error_prefix, ig_semver)
  no_resource_issues = manually_check_action_resources_for_order_profile_conformance(card,
                                                                                     validation_issues,
                                                                                     request_body,
                                                                                     error_prefix,
                                                                                     ig_semver)

  no_resource_issues.each do |issue|
    next unless issue.message.match?(/Constraint failed: crd-respar-1/)

    new_message =
      "#{issue.message}. In some cases [additional create " \
      'actions](https://hl7.org/fhir/us/davinci-crd/2.2.1/en/cards.html#propose-alternate-request-response-type) ' \
      'are permitted. Manually check the suggestion to verify whether this is an actual error.'
    issue.instance_variable_set(:@severity, 'warning')
    issue.instance_variable_set(:@message, new_message)
  end

  no_resource_issues.reject do |issue|
    issue.message.match?(/but is fixed to 'create' in the profile/)
  end
end

#manually_check_update_coverage_action_errors(action, validation_issues, error_prefix, ig_semver) ⇒ Object



411
412
413
414
415
416
417
418
# File 'lib/davinci_crd_test_kit/cross_suite/response_logical_model_validation.rb', line 411

def manually_check_update_coverage_action_errors(action, validation_issues,
                                                 error_prefix, ig_semver)
  check_required_action_type(action, 'update', error_prefix, 'coverage update action response type')
  if action['resource'].present?
    check_resource_conformance_to_coverage_profile(action['resource'], error_prefix, ig_semver)
  end
  reject_resource_issues(validation_issues)
end

#multiple_extensions_conformance_check(grouped_coverage_info, resource) ⇒ Object

For the same coverage, ensure coverage-assertion-ids and satisfied-pa-ids are the same. For different coverages, ensure coverage-assertion-ids and satisfied-pa-ids are distinct.



184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/davinci_crd_test_kit/cross_suite/response_logical_model_validation.rb', line 184

def multiple_extensions_conformance_check(grouped_coverage_info, resource)
  resource_ref = "#{resource.resourceType}/#{resource.id}"
  assertion_ids_across_coverages = Set.new
  pa_ids_across_coverages = Set.new

  grouped_coverage_info.each do |coverage, extensions|
    coverage_assertion_ids = collect_extensions_id(extensions, 'coverage-assertion-id', 'valueString').uniq
    satisfied_pa_ids = collect_extensions_id(extensions, 'satisfied-pa-id', 'valueString').uniq.compact
    if coverage_assertion_ids.length != 1
      add_message(
        'error',
        same_coverage_conformance_error_msg(resource_ref, coverage, 'coverage-assertion-ids')
      )
    end

    if satisfied_pa_ids.length > 1
      add_message(
        'error',
        same_coverage_conformance_error_msg(resource_ref, coverage, 'satisfied-pa-ids')
      )
    end

    assertion_id = coverage_assertion_ids.first
    if assertion_ids_across_coverages.include?(assertion_id)
      add_message(
        'error',
        different_coverage_conformance_error_msg(resource_ref, 'coverage-assertion-ids')
      )
    end

    assertion_ids_across_coverages.add(assertion_id)
    pa_id = satisfied_pa_ids.first
    next unless pa_id

    if pa_ids_across_coverages.include?(pa_id)
      add_message(
        'error',
        different_coverage_conformance_error_msg(resource_ref, 'satisfied-pa-ids')
      )
    end

    pa_ids_across_coverages.add(pa_id)
  end
end

#perform_response_logical_model_validation(cards, system_actions, request_body, response_index, ig_semver) ⇒ Object



42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/davinci_crd_test_kit/cross_suite/response_logical_model_validation.rb', line 42

def perform_response_logical_model_validation(cards, system_actions, request_body, response_index, ig_semver)
  if cards.is_a?(Array)
    cards.each_with_index do |card, card_index|
      validate_card_against_logical_model(card, response_index, request_body, card_index, ig_semver)
    end
  end

  return unless system_actions.is_a?(Array)

  system_actions.each_with_index do |action, action_index|
    validate_system_action_against_logical_model(action, response_index, request_body, action_index, ig_semver)
  end
end

#same_coverage_conformance_error_msg(resource_ref, coverage, id_name) ⇒ Object



246
247
248
# File 'lib/davinci_crd_test_kit/cross_suite/response_logical_model_validation.rb', line 246

def same_coverage_conformance_error_msg(resource_ref, coverage, id_name)
  "#{resource_ref}: extension has multiple repetitions of coverage `#{coverage}` with different #{id_name}."
end

#validate_card_against_logical_model(card, response_index, request_body, card_index, ig_semver) ⇒ Object



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/davinci_crd_test_kit/cross_suite/response_logical_model_validation.rb', line 56

def validate_card_against_logical_model(card, response_index, request_body, card_index, ig_semver)
  label = logical_model_entity_label(response_index, card_index, 'card')
  unless card.is_a?(Hash)
    add_message('error', "#{label} is not a JSON object; skipping logical model validation.")
    return
  end

  card_type = identify_card_type(card)
  profile_name = CARD_TYPE_TO_LOGICAL_MODEL[card_type]
  unless profile_name
    add_message('warning',
                "#{label} could not be categorized as a known CRD response type; " \
                'validating against the base CRD response logical model.')
    profile_name = CRD_RESPONSE_BASE_LOGICAL_MODEL
  end

  validation_issues = []
  conforms_to_logical_model?({ 'cards' => [card] }, logical_model_url(profile_name),
                             add_messages_to_runnable: false, validator_response_details: validation_issues)

  validate_summary_length(card, label)
  validate_suggestion_uuid_presence(card, label) unless ['v201', '2.0.1'].include? ig_semver
  validate_no_smart_suggestions(card, label) if card_type == CardsIdentification::LAUNCH_SMART_APP_RESPONSE_TYPE

  error_prefix = "#{label} (#{card_type || 'uncategorized'}): "
  filtered_issues = manually_check_card_specific_errors(card, validation_issues, card_type,
                                                        request_body, error_prefix, ig_semver)
  add_messages_not_excluded(filtered_issues, error_prefix)
end

#validate_no_smart_suggestions(card, label) ⇒ Object



114
115
116
117
118
119
120
121
122
123
124
# File 'lib/davinci_crd_test_kit/cross_suite/response_logical_model_validation.rb', line 114

def validate_no_smart_suggestions(card, label)
  suggestion_count = card['suggestions']&.length || 0

  return if suggestion_count.zero?

  add_message(
    'error',
    "#{label} CDSHooksResponse.cards.suggestions: max allowed = 0, but found #{suggestion_count} " \
    '(from http://hl7.org/fhir/us/davinci-crd/StructureDefinition/CRDHooksResponse-launchSMART|2.2.1)'
  )
end

#validate_suggestion_uuid_presence(card, label) ⇒ Object



100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/davinci_crd_test_kit/cross_suite/response_logical_model_validation.rb', line 100

def validate_suggestion_uuid_presence(card, label)
  return unless card['suggestions'].presence.is_a? Array
  return unless card['suggestions'].all?(Hash)

  card['suggestions'].each_with_index do |suggestion, index|
    next if suggestion['uuid'].present?

    add_message(
      'error',
      "#{label} suggestion #{index + 1} does not contain a `uuid`"
    )
  end
end

#validate_summary_length(card, label) ⇒ Object



86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/davinci_crd_test_kit/cross_suite/response_logical_model_validation.rb', line 86

def validate_summary_length(card, label)
  return unless card['summary'].is_a? String

  summary_length = card['summary'].length

  return if summary_length < 140

  add_message(
    'error',
    "#{label} summary length of #{summary_length} characters is longer than " \
    'the maximum allowed of 139 characters.'
  )
end

#validate_system_action_against_logical_model(action, response_index, request_body, action_index, ig_semver) ⇒ Object



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/davinci_crd_test_kit/cross_suite/response_logical_model_validation.rb', line 126

def validate_system_action_against_logical_model(action, response_index, request_body, action_index, ig_semver)
  label = logical_model_entity_label(response_index, action_index, 'systemAction')
  unless action.is_a?(Hash)
    add_message('error', "#{label} is not a JSON object; skipping logical model validation.")
    return
  end

  action_type = identify_action_type(action)
  profile_name = ACTION_TYPE_TO_LOGICAL_MODEL[action_type]
  unless profile_name
    add_message('warning',
                "#{label} could not be categorized as a known CRD response type; " \
                'validating against the base CRD response logical model.')
    profile_name = CRD_RESPONSE_BASE_LOGICAL_MODEL
  end
  if [DaVinciCRDTestKit::CardsIdentification::CREATE_OR_UPDATE_COVERAGE_RESPONSE_TYPE,
      DaVinciCRDTestKit::CardsIdentification::FORM_COMPLETION_RESPONSE_TYPE]
      .include?(action_type)
    profile_name = CRD_RESPONSE_BASE_LOGICAL_MODEL
  end

  validation_issues = []
  conforms_to_logical_model?({ 'systemActions' => [action] }, logical_model_url(profile_name),
                             add_messages_to_runnable: false, validator_response_details: validation_issues)

  validation_issues
    .reject! do |issue|
      issue.message.match?(%r{The extension definition http://hl7\.org/fhir/us/davinci-crd/StructureDefinition/CDSHookServiceResponseExtensionIfNoneExist|2\.2\.1 defines the contexts of use as.*CDSHooksResponse.systemActions.extension\z}) # rubocop:disable Layout/LineLength
    end

  if action_type == CardsIdentification::COVERAGE_INFORMATION_RESPONSE_TYPE
    check_multiple_coverage_info_extension_conformance(action)
  end
  error_prefix = "#{label} (#{action_type || 'uncategorized'}): "
  filtered_issues = manually_check_action_specific_errors(action, validation_issues, action_type,
                                                          request_body, error_prefix, ig_semver)
  add_messages_not_excluded(filtered_issues, error_prefix)
end