Class: DaVinciCRDTestKit::HookRequestEndpoint

Inherits:
Inferno::DSL::SuiteEndpoint
  • Object
show all
Includes:
CardsIdentification, CustomServiceResponse, GatherResponseGenerationData, MockServiceResponse, ScenarioServiceResponse
Defined in:
lib/davinci_crd_test_kit/client/endpoints/hook_request_endpoint.rb

Constant Summary collapse

AVAILABLE_HOOKS =
[
  'appointment-book',
  'encounter-start',
  'encounter-discharge',
  'order-select',
  'order-sign',
  'order-dispatch'
].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

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

Constants included from GatherResponseGenerationData

GatherResponseGenerationData::REFERENCES_TO_READ_BY_RESOURCE_TYPE

Constants included from FhirpathOnCDSRequest

FhirpathOnCDSRequest::SUPPORTED_POST_RESOLVE_FUNCTIONS, FhirpathOnCDSRequest::TODAY_EXPRESSION_PATTERN

Instance Method Summary collapse

Methods included from ScenarioServiceResponse

#add_unknown_action_element, #add_unknown_extension, #build_coverage_information_hook_response, #build_unknown_content_hook_response, #coverage_information_system_actions, #create_missing_coverage_information_message, #random_element_name

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, humanize_response_type, #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 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, #filter_logical_model_extension_issues, #local_reference?, #logical_model_extension_issue?, #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 SuggestionActionsValidation

#action_fields_validation, #action_resource_type_check, #actions_check

Methods included from CustomServiceResponse

#add_first_default, #build_custom_hook_response, #create_instantiated_action_from_target, #custom_response_template, #default_coverage_information_assertion_id, #default_coverage_information_coverage, #default_coverage_information_date, #default_coverage_information_elements, #default_extension_name, #defaults_extension?, #define_extension, #enabled_on_service?, #evaluate_inclusion_for_request, #filter_and_separate_defaults, #filter_response, #finalize_card_list, #get_defaults_extension_value, #get_extension_value, #get_object_inclusion_criteria_extension_value, #get_object_resource_selection_criteria_extension_value, #get_target_resources_for_request, #instantiate_action_using_targets, #instantiate_actions, #instantiate_an_action, #merge_action_resource_into_target, #object_has_inclusion_criteria?, #object_has_resource_selection_criteria?, #object_included_for_this_request?, #object_is_a_default?, #parsed_user_input, #remove_defaults_extension, #remove_extension, #remove_inclusion_criteria, #remove_resource_selection_criteria, #update_card, #update_suggestion

Methods included from GatherResponseGenerationData

#add_location_to_hash, #add_prefetch_resource_to_resource_hash, #analyzed_resources, #data_request_headers, #entry_has_required_details?, #execute_request, #extract_bundle_entries, #fetch_reference, #fhir_server_connection, #find_coverage_for_request, #find_references_to_read, #gather_appointment_book_data, #gather_data_for_request, #gather_encounter_discharge_data, #gather_encounter_start_data, #gather_order_dispatch_data, #gather_order_select_data, #gather_order_sign_data, #get_literal_reference_values, #hook_instance_data_fetch_tag, #normalize_reference, #persist_query_request, #prefetched_coverage, #prefetched_coverage_resource, #prefetched_location_bundle, #prefetched_locations_and_parents_hash, #prefetched_resources, #query_for_coverages, #read_and_analyze, #request_additional_fhir_data, #request_coverage, #request_coverage_payer, #request_parent_locations, #resource_has_required_details?

Methods included from ReplaceTokens

#replace_tokens, #replace_tokens_in_string

Methods included from FhirpathOnCDSRequest

#execute_fhirpath_on_cds_request

Methods included from MockServiceResponse

#add_basic_cards, #add_coverage_cards, #add_coverage_cards?, #add_coverage_extension, #add_order_hook_cards, #build_mock_hook_response, #connection, #context, #coverage_information_required?, #coverage_information_required_hooks, #create_alternate_request_card, #create_card_response, #create_cards_and_system_actions, #create_companions_prerequisites_card, #create_coverage_extension_system_actions, #create_coverage_resource, #create_form_completion_card, #create_or_update_coverage, #create_system_actions, #create_warning_messages, #current_time, #extract_prefetched_coverage, #find_entry_in_prefetch_bundle, #find_one_prefetched_order_dispatch_order, #find_prefetched_order_dispatch_orders, #find_prefetched_resource, #format_missing_response_types, #get_context_resource, #get_missing_response_types, #get_patient_coverage, #identify_alternate_order_resource, #identify_resources_for_system_actions, #load_json_file, #make_resource_request, #missing_response_type_filter, #patient_coverage, #payer_reference_from_coverage, #prefetch_key_options_for_system_actions, #resource_to_update_field_name, #resource_type_to_update, #selected_response_types, #update_one_card_info, #update_service_request, #update_specific_hook_card_info

Instance Method Details

#apply_hook_configuration(response_body) ⇒ Object

rubocop:disable Metrics/CyclomaticComplexity



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/davinci_crd_test_kit/client/endpoints/hook_request_endpoint.rb', line 149

def apply_hook_configuration(response_body) # rubocop:disable Metrics/CyclomaticComplexity
  return response_body unless response_body.present? && coverage_info_disabled?

  cards = response_body['cards']
  response_body['cards'] = cards.reject { |card| coverage_info_card_type?(card) } if cards.is_a?(Array)

  system_actions = response_body['systemActions']
  if system_actions.is_a?(Array)
    response_body['systemActions'] = system_actions.reject { |action| coverage_info_system_action_type?(action) }
  end

  response_body.delete 'systemActions' if response_body['systemActions'].blank?

  response_body
end

#coverage_info_disabled?Boolean

Returns:

  • (Boolean)


165
166
167
# File 'lib/davinci_crd_test_kit/client/endpoints/hook_request_endpoint.rb', line 165

def coverage_info_disabled?
  request_body.dig('extension', 'davinci-crd.configuration', COVERAGE_INFO_CONFIGURATION_CODE) == false
end

#cross_hook_tagObject



203
204
205
# File 'lib/davinci_crd_test_kit/client/endpoints/hook_request_endpoint.rb', line 203

def cross_hook_tag
  CROSS_HOOK_ANALYSIS_TAG if test.config.options[:include_in_cross_hook_analysis]
end

#error_operation_outcome(code, text) ⇒ Object



214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/davinci_crd_test_kit/client/endpoints/hook_request_endpoint.rb', line 214

def error_operation_outcome(code, text)
  {
    resourceType: 'OperationOutcome',
    issue: [
      {
        severity: 'error',
        code:,
        details: {
          text:
        }
      }
    ]
  }
end

#hook_error_response(error_message, code: 400, outcome_code: 'invalid') ⇒ Object



207
208
209
210
211
212
# File 'lib/davinci_crd_test_kit/client/endpoints/hook_request_endpoint.rb', line 207

def hook_error_response(error_message, code: 400, outcome_code: 'invalid')
  response.status = code
  response.body = error_operation_outcome(outcome_code, error_message).to_json
  response.headers.merge!({ 'Content-Type' => 'application/json', 'Access-Control-Allow-Origin' => '*' })
  response.format = :json
end

#hook_instance_already_used?Boolean

Returns:

  • (Boolean)


169
170
171
# File 'lib/davinci_crd_test_kit/client/endpoints/hook_request_endpoint.rb', line 169

def hook_instance_already_used?
  requests_repo.tagged_requests(test_run.test_session_id, [hook_instance_tag]).present?
end

#hook_instance_tagObject



191
192
193
# File 'lib/davinci_crd_test_kit/client/endpoints/hook_request_endpoint.rb', line 191

def hook_instance_tag
  TagMethods.hook_instance_tag(request_body['hookInstance'])
end

#hook_responseObject



134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/davinci_crd_test_kit/client/endpoints/hook_request_endpoint.rb', line 134

def hook_response
  if unknown_content_group?
    build_unknown_content_hook_response
  elsif self_pay_group? || long_running_group?
    build_coverage_information_hook_response
  elsif response_approach == 'custom'
    build_custom_hook_response
  else
    build_mock_hook_response
  end
rescue StandardError => e
  hook_error_response("Inferno failed to generate a response: #{e.message} at #{e.backtrace.first}", code: 500)
  nil
end

#hook_tagObject



195
196
197
# File 'lib/davinci_crd_test_kit/client/endpoints/hook_request_endpoint.rb', line 195

def hook_tag
  DaVinciCRDTestKit.const_get(:"#{name.upcase}_TAG") if name.present?
end

#ig_versionObject



25
26
27
# File 'lib/davinci_crd_test_kit/client/endpoints/hook_request_endpoint.rb', line 25

def ig_version
  @ig_version ||= requested_version || request.env['PATH_INFO'].match(/(v\d+)/)&.[](1) || 'v201'
end

#interaction_group_tagObject



199
200
201
# File 'lib/davinci_crd_test_kit/client/endpoints/hook_request_endpoint.rb', line 199

def interaction_group_tag
  test.config.options[:crd_interaction_group].presence
end

#invoked_hookObject

from the url



67
68
69
# File 'lib/davinci_crd_test_kit/client/endpoints/hook_request_endpoint.rb', line 67

def invoked_hook
  @invoked_hook ||= request.env['PATH_INFO'].match(%r{/([^/]+)-(?:service|subset)$})&.[](1)
end

#issObject



47
48
49
50
51
52
53
54
55
# File 'lib/davinci_crd_test_kit/client/endpoints/hook_request_endpoint.rb', line 47

def iss
  @iss ||=
    begin
      payload, = JWT.decode(token, nil, false)
      payload['iss']
    rescue JWT::DecodeError
      nil
    end
end

#long_running_group?Boolean


Scenario Group handling

Returns:

  • (Boolean)


237
238
239
# File 'lib/davinci_crd_test_kit/client/endpoints/hook_request_endpoint.rb', line 237

def long_running_group?
  test.config.options[:crd_interaction_group] == LONG_RUNNING_GROUP_TAG
end

#long_running_pause_timeObject



249
250
251
252
253
# File 'lib/davinci_crd_test_kit/client/endpoints/hook_request_endpoint.rb', line 249

def long_running_pause_time
  JSON.parse(result.input_json)
    .find { |input| input['name'].include?('long_running_pause_time') }
    &.dig('value').to_i
end

#make_responseObject



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/davinci_crd_test_kit/client/endpoints/hook_request_endpoint.rb', line 80

def make_response
  if requested_hook.blank?
    hook_error_response("No hook requested - populate the 'hook' element in the request.",
                        code: 400,
                        outcome_code: 'value')
  elsif invoked_hook != requested_hook
    hook_error_response("#{request.env['PATH_INFO']} serves the #{invoked_hook}, but the client " \
                        "requested the #{requested_hook} hook.",
                        code: 400,
                        outcome_code: 'value')
  elsif wrong_hook_for_test?
    hook_error_response("Hook '#{requested_hook}' is not being tested in the current session. " \
                        "This session is currently testing the '#{tested_hook}' hook.",
                        code: 422,
                        outcome_code: 'value')
  elsif hook_instance_already_used?
    hook_error_response(
      "Invalid Request: Hook instance `#{request_body['hookInstance']}` has already been used in this session.",
      outcome_code: 'value'
    )
  elsif AVAILABLE_HOOKS.include?(requested_hook)
    process_valid_hook
  else
    hook_error_response("Invalid Request: hook `#{requested_hook}` is not supported by this server.",
                        outcome_code: 'value')
  end
rescue StandardError => e
  hook_error_response("Inferno failed to generate a response: #{e.message} at #{e.backtrace.first}",
                      code: 500,
                      outcome_code: 'exception')
end

#nameObject



229
230
231
# File 'lib/davinci_crd_test_kit/client/endpoints/hook_request_endpoint.rb', line 229

def name
  requested_hook&.gsub('-', '_')
end

#process_valid_hookObject



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/davinci_crd_test_kit/client/endpoints/hook_request_endpoint.rb', line 112

def process_valid_hook
  if ig_version == 'v201'
    send(:"gather_#{requested_hook&.gsub('-', '_')}_data")
    request_coverage
  elsif ig_version == 'v221'
    request_additional_fhir_data
  end
  response_body = apply_hook_configuration(hook_response)
  return unless response_body.present?

  response.body = response_body.to_json
  response.headers.merge!({ 'Content-Type' => 'application/json', 'Access-Control-Allow-Origin' => '*' })
  response.status = 200
  response.format = :json
end

#request_bodyObject

JSON round-trip normalizes the Hanami params object to a plain string-keyed Hash



30
31
32
# File 'lib/davinci_crd_test_kit/client/endpoints/hook_request_endpoint.rb', line 30

def request_body
  @request_body ||= JSON.parse(request.params.to_json)
end

#requested_hookObject

from the hook body



62
63
64
# File 'lib/davinci_crd_test_kit/client/endpoints/hook_request_endpoint.rb', line 62

def requested_hook
  @requested_hook ||= request_body['hook']
end

#requested_versionObject



34
35
36
37
38
39
40
41
# File 'lib/davinci_crd_test_kit/client/endpoints/hook_request_endpoint.rb', line 34

def requested_version
  requested = request_body.dig('extension', 'davinci-crd.requestedVersion').to_s
  if requested == '2.2'
    'v221'
  elsif requested == '2.0'
    'v201'
  end
end

#response_approachObject



128
129
130
131
132
# File 'lib/davinci_crd_test_kit/client/endpoints/hook_request_endpoint.rb', line 128

def response_approach
  JSON.parse(result.input_json)
    .find { |input| input['name'].ends_with?('_response_approach') }
    &.dig('value')
end

#self_pay_group?Boolean

Returns:

  • (Boolean)


245
246
247
# File 'lib/davinci_crd_test_kit/client/endpoints/hook_request_endpoint.rb', line 245

def self_pay_group?
  test.config.options[:crd_interaction_group] == SELF_PAY_GROUP_TAG
end

#tagsObject



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/davinci_crd_test_kit/client/endpoints/hook_request_endpoint.rb', line 173

def tags
  tags =
    if hook_instance_already_used?
      [DUPLICATED_HOOK_INSTANCE_TAG]
    elsif invoked_hook != requested_hook ||
          wrong_hook_for_test? ||
          !AVAILABLE_HOOKS.include?(requested_hook)
      []
    else
      [hook_instance_tag, hook_tag, interaction_group_tag, cross_hook_tag]
    end

  # the self-pay scenario checks for the absence of requests, so even invalid requests must carry the group tag
  tags << interaction_group_tag if self_pay_group?

  tags.uniq.compact
end

#test_run_identifierObject



43
44
45
# File 'lib/davinci_crd_test_kit/client/endpoints/hook_request_endpoint.rb', line 43

def test_run_identifier
  iss
end

#tested_hookObject

from the waiting test



72
73
74
# File 'lib/davinci_crd_test_kit/client/endpoints/hook_request_endpoint.rb', line 72

def tested_hook
  @tested_hook ||= test.config.options[:hook_name]
end

#tokenObject



57
58
59
# File 'lib/davinci_crd_test_kit/client/endpoints/hook_request_endpoint.rb', line 57

def token
  @token ||= request.headers['authorization']&.delete_prefix('Bearer ')
end

#unknown_content_group?Boolean

Returns:

  • (Boolean)


241
242
243
# File 'lib/davinci_crd_test_kit/client/endpoints/hook_request_endpoint.rb', line 241

def unknown_content_group?
  test.config.options[:crd_interaction_group] == UNKNOWN_CONTENT_GROUP_TAG
end

#update_resultObject

end the wait immediately after the scenario request returns pause for long-running requests here because update_result runs before response generation



257
258
259
260
261
262
# File 'lib/davinci_crd_test_kit/client/endpoints/hook_request_endpoint.rb', line 257

def update_result
  return unless long_running_group? || unknown_content_group? || self_pay_group?

  sleep long_running_pause_time if long_running_group?
  results_repo.update(result.id, result: 'pass', result_message: '')
end

#wrong_hook_for_test?Boolean

Returns:

  • (Boolean)


76
77
78
# File 'lib/davinci_crd_test_kit/client/endpoints/hook_request_endpoint.rb', line 76

def wrong_hook_for_test?
  tested_hook.present? && tested_hook != ANY_HOOK_TAG && requested_hook != tested_hook
end