Class: Inferno::DSL::SuiteEndpoint
- Inherits:
-
Hanami::Action
- Object
- Hanami::Action
- Inferno::DSL::SuiteEndpoint
- Defined in:
- lib/inferno/dsl/suite_endpoint.rb
Overview
A base class for creating endpoints to test client requests. This class is based on Hanami::Action, and may be used similarly to a normal Hanami endpoint.
Constant Summary collapse
- ERROR_RESPONSE_FORMATS =
The built-in options for
error_response_format [:text, :operation_outcome].freeze
Instance Attribute Summary collapse
-
#req ⇒ Object
readonly
Returns the value of attribute req.
-
#res ⇒ Object
readonly
Returns the value of attribute res.
Overrides These methods should be overridden by subclasses to define the behavior of the endpoint collapse
-
#make_response ⇒ Void
Override this method to build the response.
-
#name ⇒ String
Override this method to assign a name to the request.
-
#no_session_response ⇒ Void
Override this method to fully customize the response returned when no waiting test run/session can be found for the incoming request.
-
#persist_request? ⇒ Boolean
Override this method to specify whether this request should be persisted.
-
#tags ⇒ Array<String>
Override this method to define the tags which will be applied to the request.
-
#test_run_identifier ⇒ String
Override this method to determine a test run's identifier based on an incoming request.
-
#test_run_identifier_location_description ⇒ String
Override this method to provide a short narrative description of where the test run identifier is expected to be found in an incoming request.
-
#update_result ⇒ Void
Override this method to update the current waiting result.
Class Method Summary collapse
- .call ⇒ Object
-
.error_response_format(format) ⇒ void
Select one of Inferno's standard response formats to be returned whenever Inferno has to render an error response of its own due to problems finding the target session or an unhandled exception.
- .error_response_format_value ⇒ Object
Instance Method Summary collapse
- #add_persistence_callback ⇒ Object
-
#error_response(message, code:, diagnostics: nil) ⇒ Object
messageis a short, human-readable summary (goes in the OperationOutcome issue'sdetails.text, or stands alone as the whole plain text body). - #find_result ⇒ Object
- #find_test_run_identifier ⇒ Object
- #handle(req, res) ⇒ Object
-
#initialize(config: self.class.config) ⇒ SuiteEndpoint
constructor
A new instance of SuiteEndpoint.
- #log_error(error, url: request.url) ⇒ Object
-
#logger ⇒ Logger
Inferno's logger.
- #no_session_message ⇒ Object
- #operation_outcome_error_response(message, code:, diagnostics: nil) ⇒ Object
-
#persist_request ⇒ Object
The actual persisting happens in Inferno::Utils::Middleware::RequestRecorder, which allows the response to include response headers added by other parts of the rack stack rather than only the response headers explicitly added in the endpoint.
-
#render_error_and_halt ⇒ Object
Yields to build the response, then halts with whatever ended up in
response.status/response.body. -
#request ⇒ Hanami::Action::Request
The incoming request as a
Hanami::Action::Request. - #requests_repo ⇒ Inferno::Repositories::Requests
-
#response ⇒ Hanami::Action::Response
The response as a
Hanami::Action::Response. -
#result ⇒ Inferno::Entities::Result
The result which is waiting for incoming requests for the current test run.
- #results_repo ⇒ Inferno::Repositories::Results
-
#resume ⇒ Object
Inferno::Utils::Middleware::RequestRecorder actually resumes the TestRun.
- #resume_test_run? ⇒ Boolean
-
#test ⇒ Inferno::Entities::Test
The test which is currently waiting for incoming requests.
-
#test_run ⇒ Inferno::Entities::TestRun
The test run which is waiting for incoming requests.
- #test_runs_repo ⇒ Inferno::Repositories::TestRuns
- #tests_repo ⇒ Inferno::Repositories::Tests
- #text_error_response(message, diagnostics: nil) ⇒ Object
Constructor Details
#initialize(config: self.class.config) ⇒ SuiteEndpoint
Returns a new instance of SuiteEndpoint.
236 237 238 |
# File 'lib/inferno/dsl/suite_endpoint.rb', line 236 def initialize(config: self.class.config) # rubocop:disable Lint/MissingSuper @config = config end |
Instance Attribute Details
#req ⇒ Object (readonly)
Returns the value of attribute req.
65 66 67 |
# File 'lib/inferno/dsl/suite_endpoint.rb', line 65 def req @req end |
#res ⇒ Object (readonly)
Returns the value of attribute res.
65 66 67 |
# File 'lib/inferno/dsl/suite_endpoint.rb', line 65 def res @res end |
Class Method Details
.call ⇒ Object
211 212 213 |
# File 'lib/inferno/dsl/suite_endpoint.rb', line 211 def self.call(...) new.call(...) end |
.error_response_format(format) ⇒ void
This method returns an undefined value.
Select one of Inferno's standard response formats to be returned whenever Inferno has to render an error response of its own due to problems finding the target session or an unhandled exception. You can override #no_session_response to customize the response in the no-session case.
:text(default): a500response with a plain text message:operation_outcome: a500response with a FHIROperationOutcomeserialized asapplication/fhir+json
88 89 90 91 92 93 94 95 96 |
# File 'lib/inferno/dsl/suite_endpoint.rb', line 88 def error_response_format(format) unless ERROR_RESPONSE_FORMATS.include?(format) raise ArgumentError, "Unknown error_response_format `#{format.inspect}`. " \ "Must be one of #{ERROR_RESPONSE_FORMATS.join(', ')}." end @error_response_format_value = format end |
.error_response_format_value ⇒ Object
99 100 101 |
# File 'lib/inferno/dsl/suite_endpoint.rb', line 99 def error_response_format_value @error_response_format_value ||= :text end |
Instance Method Details
#add_persistence_callback ⇒ Object
438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 |
# File 'lib/inferno/dsl/suite_endpoint.rb', line 438 def add_persistence_callback # rubocop:disable Metrics/CyclomaticComplexity env = req.env env['rack.after_reply'] ||= [] env['rack.after_reply'] << proc do repo = Inferno::Repositories::Requests.new uri = URI('http://example.com') uri.scheme = env['rack.url_scheme'] uri.host = env['SERVER_NAME'] uri.port = env['SERVER_PORT'] uri.path = env['REQUEST_PATH'] || '' uri.query = env['rack.request.query_string'] if env['rack.request.query_string'].present? url = uri&.to_s verb = env['REQUEST_METHOD'] request_body = env['rack.input'] request_body.rewind if env['rack.input'].respond_to? :rewind request_body = request_body.instance_of?(Puma::NullIO) ? nil : request_body.string request_headers = ::Rack::Request.new(env).headers.to_h.map { |name, value| { name:, value: } } status, response_headers, response_body = env['inferno.response'] response_headers = response_headers.map { |name, value| { name:, value: } } repo.create( verb:, url:, direction: 'incoming', name: env['inferno.name'], status:, request_body:, response_body: response_body.join, result_id: env['inferno.result_id'], test_session_id: env['inferno.test_session_id'], request_headers:, response_headers:, tags: env['inferno.tags'] ) if env['inferno.resume_test_run'] test_run_id = env['inferno.test_run_id'] Inferno::Repositories::TestRuns.new.mark_as_no_longer_waiting(test_run_id) Inferno::Jobs.perform( Jobs::ResumeTestRun, test_run_id, tags: [ 'source:suite_endpoint', "session:#{env['inferno.test_session_id']}", "run:#{env['inferno.run_identifier']}", "test:#{env['inferno.waiting_test_id']}" ] ) end rescue StandardError => e log_error(e, url:) end end |
#error_response(message, code:, diagnostics: nil) ⇒ Object
message is a short, human-readable summary (goes in the
OperationOutcome issue's details.text, or stands alone as the whole
plain text body). diagnostics, if given, is technical detail — e.g.
an exception's full backtrace — that goes in the issue's
diagnostics element, or is appended to the plain text body.
349 350 351 352 353 354 355 356 |
# File 'lib/inferno/dsl/suite_endpoint.rb', line 349 def error_response(, code:, diagnostics: nil) case self.class.error_response_format_value when :operation_outcome operation_outcome_error_response(, code:, diagnostics:) else text_error_response(, diagnostics:) end end |
#find_result ⇒ Object
379 380 381 |
# File 'lib/inferno/dsl/suite_endpoint.rb', line 379 def find_result results_repo.find_waiting_result(test_run_id: test_run.id) end |
#find_test_run_identifier ⇒ Object
303 304 305 306 307 308 309 310 311 312 313 314 315 316 |
# File 'lib/inferno/dsl/suite_endpoint.rb', line 303 def find_test_run_identifier return @test_run_identifier if defined?(@test_run_identifier) # handle memoization in the nil case @test_run_identifier = test_run_identifier rescue StandardError => e log_error(e) render_error_and_halt do error_response( 'An error occurred while determining the test run identifier for this request.', code: 'exception', diagnostics: e. ) end end |
#handle(req, res) ⇒ Object
414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 |
# File 'lib/inferno/dsl/suite_endpoint.rb', line 414 def handle(req, res) @req = req @res = res test_run persist_request if persist_request? update_result resume if resume_test_run? make_response rescue StandardError => e log_error(e) render_error_and_halt do error_response( 'An error occurred while processing this request.', code: 'exception', diagnostics: e. ) end end |
#log_error(error, url: request.url) ⇒ Object
297 298 299 300 |
# File 'lib/inferno/dsl/suite_endpoint.rb', line 297 def log_error(error, url: request.url) session_prefix = @test_run ? " session=#{@test_run.test_session_id}" : '' logger.error("[#{url}]#{session_prefix} #{error.}") end |
#logger ⇒ Logger
Returns Inferno's logger.
292 293 294 |
# File 'lib/inferno/dsl/suite_endpoint.rb', line 292 def logger @logger ||= Application['logger'] end |
#make_response ⇒ Void
Override this method to build the response.
148 149 150 |
# File 'lib/inferno/dsl/suite_endpoint.rb', line 148 def make_response nil end |
#name ⇒ String
Override this method to assign a name to the request
163 164 165 |
# File 'lib/inferno/dsl/suite_endpoint.rb', line 163 def name result&.runnable&.incoming_request_name end |
#no_session_message ⇒ Object
319 320 321 322 323 324 325 326 327 328 329 330 331 |
# File 'lib/inferno/dsl/suite_endpoint.rb', line 319 def = "Unable to find test run for request to '#{request.url}'" location = test_run_identifier_location_description identifier = find_test_run_identifier if identifier.blank? detail = location.present? ? " in #{location}" : '' "#{}: no identifier found#{detail}." else detail = location.present? ? ", found in #{location}," : '' "#{}: identifier '#{identifier}'#{detail} is not associated with a waiting session." end end |
#no_session_response ⇒ Void
Override this method to fully customize the response returned when no
waiting test run/session can be found for the incoming request. Set
response.status and response.body (and response.content_type, if
needed) — Inferno halts the request with those values. By default,
this renders one of Inferno's standard responses based on the format
selected with error_response_format (a plain text 500 response if
none was selected).
204 205 206 |
# File 'lib/inferno/dsl/suite_endpoint.rb', line 204 def no_session_response error_response(, code: 'not-found') end |
#operation_outcome_error_response(message, code:, diagnostics: nil) ⇒ Object
365 366 367 368 369 370 371 372 373 374 375 376 |
# File 'lib/inferno/dsl/suite_endpoint.rb', line 365 def operation_outcome_error_response(, code:, diagnostics: nil) issue = FHIR::OperationOutcome::Issue.new( severity: 'fatal', code:, details: FHIR::CodeableConcept.new(text: ) ) issue.diagnostics = diagnostics if diagnostics response.status = 500 response.content_type = 'application/fhir+json' response.body = FHIR::OperationOutcome.new(issue: [issue]).to_json end |
#persist_request ⇒ Object
The actual persisting happens in Inferno::Utils::Middleware::RequestRecorder, which allows the response to include response headers added by other parts of the rack stack rather than only the response headers explicitly added in the endpoint.
388 389 390 391 392 393 394 395 |
# File 'lib/inferno/dsl/suite_endpoint.rb', line 388 def persist_request req.env['inferno.test_session_id'] = test_run.test_session_id req.env['inferno.result_id'] = result.id req.env['inferno.tags'] = req.env['inferno.name'] = name if name.present? add_persistence_callback end |
#persist_request? ⇒ Boolean
Override this method to specify whether this request should be persisted. Defaults to true.
184 185 186 |
# File 'lib/inferno/dsl/suite_endpoint.rb', line 184 def persist_request? true end |
#render_error_and_halt ⇒ Object
Yields to build the response, then halts with whatever ended up in
response.status/response.body. Centralizing the halt here means
overrides of the response-building hooks (e.g. #no_session_response)
never need to remember to call halt themselves.
338 339 340 341 |
# File 'lib/inferno/dsl/suite_endpoint.rb', line 338 def render_error_and_halt yield halt response.status, response.body.join end |
#request ⇒ Hanami::Action::Request
The incoming request as a Hanami::Action::Request
248 249 250 |
# File 'lib/inferno/dsl/suite_endpoint.rb', line 248 def request req end |
#requests_repo ⇒ Inferno::Repositories::Requests
216 217 218 |
# File 'lib/inferno/dsl/suite_endpoint.rb', line 216 def requests_repo @requests_repo ||= Inferno::Repositories::Requests.new end |
#response ⇒ Hanami::Action::Response
The response as a Hanami::Action::Response. Modify this to build the
response to the incoming request.
262 263 264 |
# File 'lib/inferno/dsl/suite_endpoint.rb', line 262 def response res end |
#result ⇒ Inferno::Entities::Result
The result which is waiting for incoming requests for the current test run
280 281 282 |
# File 'lib/inferno/dsl/suite_endpoint.rb', line 280 def result @result ||= find_result end |
#results_repo ⇒ Inferno::Repositories::Results
221 222 223 |
# File 'lib/inferno/dsl/suite_endpoint.rb', line 221 def results_repo @results_repo ||= Inferno::Repositories::Results.new end |
#resume ⇒ Object
Inferno::Utils::Middleware::RequestRecorder actually resumes the TestRun. If it were resumed here, it would be resuming prior to the Request being persisted.
406 407 408 409 410 411 |
# File 'lib/inferno/dsl/suite_endpoint.rb', line 406 def resume req.env['inferno.resume_test_run'] = true req.env['inferno.test_run_id'] = test_run.id req.env['inferno.run_identifier'] = test_run.test_suite_id || test_run.test_group_id || test_run.test_id req.env['inferno.waiting_test_id'] = test.id end |
#resume_test_run? ⇒ Boolean
398 399 400 |
# File 'lib/inferno/dsl/suite_endpoint.rb', line 398 def resume_test_run? find_result&.result != 'wait' end |
#tags ⇒ Array<String>
Override this method to define the tags which will be applied to the request.
156 157 158 |
# File 'lib/inferno/dsl/suite_endpoint.rb', line 156 def @tags ||= [] end |
#test ⇒ Inferno::Entities::Test
The test which is currently waiting for incoming requests
287 288 289 |
# File 'lib/inferno/dsl/suite_endpoint.rb', line 287 def test @test ||= tests_repo.find(result.test_id) end |
#test_run ⇒ Inferno::Entities::TestRun
The test run which is waiting for incoming requests
269 270 271 272 273 274 |
# File 'lib/inferno/dsl/suite_endpoint.rb', line 269 def test_run @test_run ||= test_runs_repo.find_latest_waiting_by_identifier(find_test_run_identifier).tap do |test_run| render_error_and_halt { no_session_response } if test_run.nil? end end |
#test_run_identifier ⇒ String
Override this method to determine a test run's identifier based on an incoming request.
118 119 120 |
# File 'lib/inferno/dsl/suite_endpoint.rb', line 118 def test_run_identifier nil end |
#test_run_identifier_location_description ⇒ String
Override this method to provide a short narrative description of where the test run identifier is expected to be found in an incoming request. When provided, this description is appended to the #no_session_message to help implementers debug requests that don't match a waiting test run.
134 135 136 |
# File 'lib/inferno/dsl/suite_endpoint.rb', line 134 def test_run_identifier_location_description '' end |
#test_runs_repo ⇒ Inferno::Repositories::TestRuns
226 227 228 |
# File 'lib/inferno/dsl/suite_endpoint.rb', line 226 def test_runs_repo @test_runs_repo ||= Inferno::Repositories::TestRuns.new end |
#tests_repo ⇒ Inferno::Repositories::Tests
231 232 233 |
# File 'lib/inferno/dsl/suite_endpoint.rb', line 231 def tests_repo @tests_repo ||= Inferno::Repositories::Tests.new end |
#text_error_response(message, diagnostics: nil) ⇒ Object
359 360 361 362 |
# File 'lib/inferno/dsl/suite_endpoint.rb', line 359 def text_error_response(, diagnostics: nil) response.status = 500 response.body = diagnostics ? "#{}\n#{diagnostics}" : end |
#update_result ⇒ Void
Override this method to update the current waiting result. To resume the test run, set the result to something other than 'waiting'.
176 177 178 |
# File 'lib/inferno/dsl/suite_endpoint.rb', line 176 def update_result nil end |