mirror of
https://github.com/discourse/discourse.git
synced 2024-12-25 17:53:45 +08:00
719457e430
This patch adds a new step to services named `try`. It’s useful to rescue exceptions that some steps could raise. That way, if an exception is caught, the service will stop its execution and can be inspected like with any other steps. Just wrap the steps that can raise with a `try` block: ```ruby try do step :step_that_can_raise step :another_step_that_can_raise end ``` By default, `try` will catch any exception inheriting from `StandardError`, but we can specify what exceptions to catch: ```ruby try(ArgumentError, RuntimeError) do step :will_raise end ``` An outcome matcher has been added: `on_exceptions`. By default it will be executed for any exception caught by the `try` step. Here also, we can specify what exceptions to catch: ```ruby on_exceptions(ArgumentError, RuntimeError) do |exception| … end ``` Finally, an RSpec matcher has been added: ```ruby it { is_expected.to fail_with_exception } # or it { is_expected.to fail_with_exception(ArgumentError) } ```
223 lines
4.5 KiB
Ruby
223 lines
4.5 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module ServiceMatchers
|
|
class RunServiceSuccessfully
|
|
attr_reader :result
|
|
|
|
def matches?(result)
|
|
@result = result
|
|
result.success?
|
|
end
|
|
|
|
def failure_message
|
|
message = "Expected the service to succeed but it failed."
|
|
error_message_with_inspection(message)
|
|
end
|
|
|
|
def failure_message_when_negated
|
|
message = "Expected the service to fail but it succeeded."
|
|
error_message_with_inspection(message)
|
|
end
|
|
|
|
def description
|
|
"run the service successfully"
|
|
end
|
|
|
|
private
|
|
|
|
def error_message_with_inspection(message)
|
|
inspector = Service::StepsInspector.new(result)
|
|
"#{message}\n\n#{inspector.inspect}\n\n#{inspector.error}"
|
|
end
|
|
end
|
|
|
|
class FailStep
|
|
attr_reader :name, :result
|
|
|
|
def initialize(name)
|
|
@name = name
|
|
end
|
|
|
|
def matches?(result)
|
|
@result = result
|
|
step_exists? && step_failed? && service_failed?
|
|
end
|
|
|
|
def failure_message
|
|
set_unexpected_result
|
|
message =
|
|
if !step_exists?
|
|
step_not_existing_message
|
|
elsif !step_failed?
|
|
step_failed_message
|
|
else
|
|
"expected the service to fail but it succeeded."
|
|
end
|
|
error_message_with_inspection(message)
|
|
end
|
|
|
|
def failure_message_when_negated
|
|
set_unexpected_result
|
|
error_message_with_inspection(negated_message)
|
|
end
|
|
|
|
def description
|
|
"fail a #{type} named '#{name}'"
|
|
end
|
|
|
|
private
|
|
|
|
def step_exists?
|
|
result[step].present?
|
|
end
|
|
|
|
def step_failed?
|
|
result[step].failure?
|
|
end
|
|
|
|
def service_failed?
|
|
result.failure?
|
|
end
|
|
|
|
def type
|
|
self.class.name.split("::").last.sub("Fail", "").downcase
|
|
end
|
|
|
|
def step
|
|
"result.#{type}.#{name}"
|
|
end
|
|
|
|
def error_message_with_inspection(message)
|
|
inspector = Service::StepsInspector.new(result)
|
|
"#{message}\n\n#{inspector.inspect}\n\n#{inspector.error}"
|
|
end
|
|
|
|
def set_unexpected_result
|
|
return unless result[step]
|
|
result[step]["spec.unexpected_result"] = true
|
|
end
|
|
|
|
def step_not_existing_message
|
|
"Expected #{type} '#{name}' (key: '#{step}') was not found in the result object."
|
|
end
|
|
|
|
def step_failed_message
|
|
"Expected #{type} '#{name}' (key: '#{step}') to fail but it succeeded."
|
|
end
|
|
|
|
def negated_message
|
|
"Expected #{type} '#{name}' (key: '#{step}') to succeed but it failed."
|
|
end
|
|
end
|
|
|
|
class FailContract < FailStep
|
|
end
|
|
|
|
class FailPolicy < FailStep
|
|
end
|
|
|
|
class FailToFindModel < FailStep
|
|
def type
|
|
"model"
|
|
end
|
|
|
|
def description
|
|
"fail to find a model named '#{name}'"
|
|
end
|
|
|
|
def step_failed?
|
|
super && result[step].not_found
|
|
end
|
|
end
|
|
|
|
class FailWithInvalidModel < FailStep
|
|
def type
|
|
"model"
|
|
end
|
|
|
|
def description
|
|
"fail to have a valid model named '#{name}'"
|
|
end
|
|
|
|
def step_failed?
|
|
super && result[step].invalid
|
|
end
|
|
end
|
|
|
|
class FailWithException < FailStep
|
|
attr_reader :exception
|
|
|
|
def initialize(exception)
|
|
@exception = exception
|
|
@name = "default"
|
|
end
|
|
|
|
def type
|
|
"try"
|
|
end
|
|
|
|
def description
|
|
"fail with an exception (#{exception})"
|
|
end
|
|
|
|
def step_failed?
|
|
super && result[step].exception.is_a?(exception)
|
|
end
|
|
|
|
def step_not_existing_message
|
|
"Expected try block (key: '#{step}') was not found in the result object."
|
|
end
|
|
|
|
def step_failed_message
|
|
message =
|
|
"Expected try block (key: '#{step}') to fail with an exception of type '#{exception}'"
|
|
message +=
|
|
if result[step].exception.blank?
|
|
" but it succeeded."
|
|
else
|
|
" but it failed with an exception of type '#{result[step].exception.class}'"
|
|
end
|
|
end
|
|
|
|
def negated_message
|
|
"Expected try block (key: '#{step}') to succeed but it failed."
|
|
end
|
|
end
|
|
|
|
def fail_a_policy(name)
|
|
FailPolicy.new(name)
|
|
end
|
|
|
|
def fail_a_contract(name = "default")
|
|
FailContract.new(name)
|
|
end
|
|
|
|
def fail_to_find_a_model(name = "model")
|
|
FailToFindModel.new(name)
|
|
end
|
|
|
|
def fail_with_an_invalid_model(name = "model")
|
|
FailWithInvalidModel.new(name)
|
|
end
|
|
|
|
def fail_with_exception(exception = StandardError)
|
|
FailWithException.new(exception)
|
|
end
|
|
|
|
def fail_a_step(name = "model")
|
|
FailStep.new(name)
|
|
end
|
|
|
|
def run_successfully
|
|
RunServiceSuccessfully.new
|
|
end
|
|
|
|
def inspect_steps(result)
|
|
inspector = Service::StepsInspector.new(result)
|
|
puts "Steps:"
|
|
puts inspector.inspect
|
|
puts "\nFirst error:"
|
|
puts inspector.error
|
|
end
|
|
end
|