DEV: Move chat service objects into core (#26506)

This commit is contained in:
Jan Cernik
2024-04-04 08:57:41 -05:00
committed by GitHub
parent 9af957014e
commit cab178a405
16 changed files with 313 additions and 321 deletions

View File

@@ -1,256 +0,0 @@
# frozen_string_literal: true
RSpec.describe Chat::StepsInspector do
class DummyService
include Service::Base
model :model
policy :policy
contract
transaction do
step :in_transaction_step_1
step :in_transaction_step_2
end
step :final_step
class Contract
attribute :parameter
validates :parameter, presence: true
end
end
subject(:inspector) { described_class.new(result) }
let(:parameter) { "present" }
let(:result) { DummyService.call(parameter: parameter) }
before do
class DummyService
%i[fetch_model policy in_transaction_step_1 in_transaction_step_2 final_step].each do |name|
define_method(name) { true }
end
end
end
describe "#inspect" do
subject(:output) { inspector.inspect.strip }
context "when service runs without error" do
it "outputs all the steps of the service" do
expect(output).to eq <<~OUTPUT.chomp
[1/7] [model] 'model'
[2/7] [policy] 'policy'
[3/7] [contract] 'default'
[4/7] [transaction]
[5/7] [step] 'in_transaction_step_1'
[6/7] [step] 'in_transaction_step_2'
[7/7] [step] 'final_step'
OUTPUT
end
end
context "when the model step is failing" do
before do
class DummyService
def fetch_model
false
end
end
end
it "shows the failing step" do
expect(output).to eq <<~OUTPUT.chomp
[1/7] [model] 'model'
[2/7] [policy] 'policy'
[3/7] [contract] 'default'
[4/7] [transaction]
[5/7] [step] 'in_transaction_step_1'
[6/7] [step] 'in_transaction_step_2'
[7/7] [step] 'final_step'
OUTPUT
end
end
context "when the policy step is failing" do
before do
class DummyService
def policy
false
end
end
end
it "shows the failing step" do
expect(output).to eq <<~OUTPUT.chomp
[1/7] [model] 'model'
[2/7] [policy] 'policy'
[3/7] [contract] 'default'
[4/7] [transaction]
[5/7] [step] 'in_transaction_step_1'
[6/7] [step] 'in_transaction_step_2'
[7/7] [step] 'final_step'
OUTPUT
end
end
context "when the contract step is failing" do
let(:parameter) { nil }
it "shows the failing step" do
expect(output).to eq <<~OUTPUT.chomp
[1/7] [model] 'model'
[2/7] [policy] 'policy'
[3/7] [contract] 'default'
[4/7] [transaction]
[5/7] [step] 'in_transaction_step_1'
[6/7] [step] 'in_transaction_step_2'
[7/7] [step] 'final_step'
OUTPUT
end
end
context "when a common step is failing" do
before do
class DummyService
def in_transaction_step_2
fail!("step error")
end
end
end
it "shows the failing step" do
expect(output).to eq <<~OUTPUT.chomp
[1/7] [model] 'model'
[2/7] [policy] 'policy'
[3/7] [contract] 'default'
[4/7] [transaction]
[5/7] [step] 'in_transaction_step_1'
[6/7] [step] 'in_transaction_step_2'
[7/7] [step] 'final_step'
OUTPUT
end
end
context "when running in specs" do
context "when a successful step is flagged as being an unexpected result" do
before { result["result.policy.policy"]["spec.unexpected_result"] = true }
it "adapts its output accordingly" do
expect(output).to eq <<~OUTPUT.chomp
[1/7] [model] 'model'
[2/7] [policy] 'policy' <= expected to return false but got true instead
[3/7] [contract] 'default'
[4/7] [transaction]
[5/7] [step] 'in_transaction_step_1'
[6/7] [step] 'in_transaction_step_2'
[7/7] [step] 'final_step'
OUTPUT
end
end
context "when a failing step is flagged as being an unexpected result" do
before do
class DummyService
def policy
false
end
end
result["result.policy.policy"]["spec.unexpected_result"] = true
end
it "adapts its output accordingly" do
expect(output).to eq <<~OUTPUT.chomp
[1/7] [model] 'model'
[2/7] [policy] 'policy' <= expected to return true but got false instead
[3/7] [contract] 'default'
[4/7] [transaction]
[5/7] [step] 'in_transaction_step_1'
[6/7] [step] 'in_transaction_step_2'
[7/7] [step] 'final_step'
OUTPUT
end
end
end
end
describe "#error" do
subject(:error) { inspector.error }
context "when there are no errors" do
it "returns nothing" do
expect(error).to be_blank
end
end
context "when the model step is failing" do
context "when the model is missing" do
before do
class DummyService
def fetch_model
false
end
end
end
it "returns an error related to the model" do
expect(error).to match(/Model not found/)
end
end
context "when the model has errors" do
before do
class DummyService
def fetch_model
OpenStruct.new(invalid?: true, errors: ActiveModel::Errors.new(nil))
end
end
end
it "returns an error related to the model" do
expect(error).to match(/ActiveModel::Errors \[\]/)
end
end
end
context "when the contract step is failing" do
let(:parameter) { nil }
it "returns an error related to the contract" do
expect(error).to match(/ActiveModel::Error attribute=parameter, type=blank, options={}/)
end
end
context "when the policy step is failing" do
before do
class DummyService
def policy
false
end
end
end
context "when there is no reason provided" do
it "returns nothing" do
expect(error).to be_blank
end
end
context "when a reason is provided" do
before { result["result.policy.policy"].reason = "failed" }
it "returns the reason" do
expect(error).to eq "failed"
end
end
end
context "when a common step is failing" do
before { result["result.step.final_step"].fail(error: "my error") }
it "returns an error related to the step" do
expect(error).to eq("my error")
end
end
end
end

View File

@@ -130,8 +130,8 @@ end
RSpec.configure do |config|
config.include ChatSystemHelpers, type: :system
config.include ChatSpecHelpers
config.include Chat::WithServiceHelper
config.include Chat::ServiceMatchers
config.include WithServiceHelper
config.include ServiceMatchers
config.expect_with :rspec do |c|
# Or a very large value, if you do want to truncate at some point

View File

@@ -1,153 +0,0 @@
# frozen_string_literal: true
module Chat
module ServiceMatchers
class RunServiceSuccessfully
attr_reader :result
def matches?(result)
@result = result
result.success?
end
def failure_message
inspector = StepsInspector.new(result)
"Expected to run the service sucessfully but failed:\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?
"Expected #{type} '#{name}' (key: '#{step}') was not found in the result object."
elsif !step_failed?
"Expected #{type} '#{name}' (key: '#{step}') to fail but it succeeded."
else
"expected the service to fail but it succeeded."
end
error_message_with_inspection(message)
end
def failure_message_when_negated
set_unexpected_result
message = "Expected #{type} '#{name}' (key: '#{step}') to succeed but it failed."
error_message_with_inspection(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 = 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
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[name].blank?
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
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_a_step(name = "model")
FailStep.new(name)
end
def run_service_successfully
RunServiceSuccessfully.new
end
def inspect_steps(result)
inspector = Chat::StepsInspector.new(result)
puts "Steps:"
puts inspector.inspect
puts "\nFirst error:"
puts inspector.error
end
end
end