more tests for multiple state machines (callbacks, rest)

This commit is contained in:
Thorsten Böttger 2015-05-26 21:21:04 +12:00
parent 69997ffec8
commit d0172e9863
7 changed files with 473 additions and 176 deletions

View File

@ -0,0 +1,66 @@
module Callbacks
class GuardWithinBlockMultiple
include AASM
def initialize(options={})
@fail_event_guard = options[:fail_event_guard]
@fail_transition_guard = options[:fail_transition_guard]
@log = options[:log]
end
aasm(:left) do
state :open, :initial => true,
:before_enter => :before_enter_open,
:enter => :enter_open,
:after_enter => :after_enter_open,
:before_exit => :before_exit_open,
:exit => :exit_open,
:after_exit => :after_exit_open
state :closed,
:before_enter => :before_enter_closed,
:enter => :enter_closed,
:after_enter => :after_enter_closed,
:before_exit => :before_exit_closed,
:exit => :exit_closed,
:after_exit => :after_exit_closed
event :close, :before => :before, :after => :after, :guard => :event_guard do
transitions :to => :closed, :from => [:open], :after => :transitioning do
guard do
transition_guard
end
end
end
event :open, :before => :before, :after => :after do
transitions :to => :open, :from => :closed
end
end
def log(text)
puts text if @log
end
def before_enter_open; log('before_enter_open'); end
def enter_open; log('enter_open'); end
def before_exit_open; log('before_exit_open'); end
def after_enter_open; log('after_enter_open'); end
def exit_open; log('exit_open'); end
def after_exit_open; log('after_exit_open'); end
def before_enter_closed; log('before_enter_closed'); end
def enter_closed; log('enter_closed'); end
def before_exit_closed; log('before_exit_closed'); end
def exit_closed; log('exit_closed'); end
def after_enter_closed; log('after_enter_closed'); end
def after_exit_closed; log('after_exit_closed'); end
def event_guard; log('event_guard'); !@fail_event_guard; end
def transition_guard; log('transition_guard'); !@fail_transition_guard; end
def transitioning; log('transitioning'); end
def before; log('before'); end
def after; log('after'); end
end
end

View File

@ -0,0 +1,65 @@
module Callbacks
class MultipleTransitionsTransitionGuardMultiple
include AASM
def initialize(options={})
@fail_event_guard = options[:fail_event_guard]
@fail_transition_guard = options[:fail_transition_guard]
@log = options[:log]
end
aasm(:left) do
state :open, :initial => true,
:before_enter => :before_enter_open,
:enter => :enter_open,
:after_enter => :after_enter_open,
:before_exit => :before_exit_open,
:exit => :exit_open,
:after_exit => :after_exit_open
state :closed,
:before_enter => :before_enter_closed,
:enter => :enter_closed,
:after_enter => :after_enter_closed,
:before_exit => :before_exit_closed,
:exit => :exit_closed,
:after_exit => :after_exit_closed
state :failed
event :close, :before => :before, :after => :after, :guard => :event_guard do
transitions :to => :closed, :from => [:open], :guard => :transition_guard, :after => :transitioning
transitions :to => :failed, :from => [:open]
end
event :open, :before => :before, :after => :after do
transitions :to => :open, :from => :closed
end
end
def log(text)
puts text if @log
end
def before_enter_open; log('before_enter_open'); end
def enter_open; log('enter_open'); end
def before_exit_open; log('before_exit_open'); end
def after_enter_open; log('after_enter_open'); end
def exit_open; log('exit_open'); end
def after_exit_open; log('after_exit_open'); end
def before_enter_closed; log('before_enter_closed'); end
def enter_closed; log('enter_closed'); end
def before_exit_closed; log('before_exit_closed'); end
def exit_closed; log('exit_closed'); end
def after_enter_closed; log('after_enter_closed'); end
def after_exit_closed; log('after_exit_closed'); end
def event_guard; log('event_guard'); !@fail_event_guard; end
def transition_guard; log('transition_guard'); !@fail_transition_guard; end
def transitioning; log('transitioning'); end
def before; log('before'); end
def after; log('after'); end
end
end

View File

@ -0,0 +1,44 @@
module Callbacks
class PrivateMethodMultiple
include AASM
def initialize(options={})
@fail_event_guard = options[:fail_event_guard]
@fail_transition_guard = options[:fail_transition_guard]
@log = options[:log]
reset_data
end
def reset_data
@data = []
end
def data
@data.join(' ')
end
aasm(:left) do
state :open, :initial => true
state :closed
event :close, :after => :after_event do
transitions :to => :closed, :from => [:open]
end
event :open, :after => :after_event do
transitions :to => :open, :from => :closed
end
end
def log(text)
@data << text
puts text if @log
end
def aasm_write_state(*args); log('aasm_write_state'); true; end
private
def after_event; log('after_event'); end
end
end

View File

@ -0,0 +1,61 @@
module Callbacks
class WithArgsMultiple
include AASM
def initialize(options={})
@log = options[:log]
reset_data
end
def reset_data
@data = []
end
def data
@data.join(' ')
end
aasm(:left) do
state :open, :initial => true,
:before_enter => :before_enter_open,
:after_enter => :after_enter_open,
:before_exit => :before_exit_open,
:after_exit => :after_exit_open
state :closed,
:before_enter => :before_enter_closed,
:after_enter => :after_enter_closed,
:before_exit => :before_exit_closed,
:after_exit => :after_exit_closed
event :close, :before => :before, :after => :after do
transitions :to => :closed, :from => [:open], :after => :transition_proc
end
event :open, :before => :before, :after => :after do
transitions :to => :open, :from => :closed
end
end
def log(text)
@data << text
puts text if @log
end
def aasm_write_state(*args); log('aasm_write_state'); true; end
def before_enter_open(*args); log("before_enter_open(#{args.map(&:inspect).join(',')})"); end
def before_exit_open(*args); log("before_exit_open(#{args.map(&:inspect).join(',')})"); end
def after_enter_open(*args); log("after_enter_open(#{args.map(&:inspect).join(',')})"); end
def after_exit_open(*args); log("after_exit_open(#{args.map(&:inspect).join(',')})"); end
def before_enter_closed(*args); log("before_enter_closed(#{args.map(&:inspect).join(',')})"); end
def before_exit_closed(*args); log("before_enter_closed(#{args.map(&:inspect).join(',')})"); end
def after_enter_closed(*args); log("after_enter_closed(#{args.map(&:inspect).join(',')})"); end
def after_exit_closed(*args); log("after_exit_closed(#{args.map(&:inspect).join(',')})"); end
def before(arg1, *args); log("before(#{arg1.inspect},#{args.map(&:inspect).join(',')})"); end
def transition_proc(arg1, arg2); log("transition_proc(#{arg1.inspect},#{arg2.inspect})"); end
def after(*args); log("after(#{args.map(&:inspect).join(',')})"); end
end
end

View File

@ -0,0 +1,26 @@
module Callbacks
class WithStateArgMultiple
include AASM
aasm(:left) do
state :open, :inital => true
state :closed
state :out_to_lunch
event :close, :before => :before_method, :after => :after_method do
transitions :to => :closed, :from => [:open], :after => :transition_method
transitions :to => :out_to_lunch, :from => [:open], :after => :transition_method2
end
end
def before_method(arg); end
def after_method(arg); end
def transition_method(arg); end
def transition_method2(arg); end
end
end

View File

@ -0,0 +1,35 @@
class FooMultiple
include AASM
aasm(:left) do
state :open, :initial => true, :before_exit => :before_exit
state :closed, :before_enter => :before_enter
state :final
event :close, :success => :success_callback do
transitions :from => [:open], :to => [:closed]
end
event :null do
transitions :from => [:open], :to => [:closed, :final], :guard => :always_false
end
end
def always_false
false
end
def success_callback
end
def before_enter
end
def before_exit
end
end
class FooTwoMultiple < FooMultiple
include AASM
aasm(:left) do
state :foo
end
end

View File

@ -52,17 +52,17 @@ describe 'callbacks for the new DSL' do
}.to raise_error(AASM::InvalidTransition) }.to raise_error(AASM::InvalidTransition)
end end
# it "it handles private callback methods as well" do it "handles private callback methods as well" do
# show_debug_log = false show_debug_log = false
# callback = Callbacks::PrivateMethod.new(:log => show_debug_log) callback = Callbacks::PrivateMethodMultiple.new(:log => show_debug_log)
# callback.aasm.current_state callback.aasm(:left).current_state
# # puts "------- close!" # puts "------- close!"
# expect { expect {
# callback.close! callback.close!
# }.to_not raise_error }.to_not raise_error
# end end
context "if the transition guard fails" do context "if the transition guard fails" do
it "does not run any state callback if guard is defined inline" do it "does not run any state callback if guard is defined inline" do
@ -90,206 +90,206 @@ describe 'callbacks for the new DSL' do
}.to raise_error(AASM::InvalidTransition) }.to raise_error(AASM::InvalidTransition)
end end
# it "does not run transition_guard twice for multiple permitted transitions" do it "does not run transition_guard twice for multiple permitted transitions" do
# show_debug_log = false show_debug_log = false
# callback = Callbacks::MultipleTransitionsTransitionGuard.new(:log => show_debug_log, :fail_transition_guard => true) callback = Callbacks::MultipleTransitionsTransitionGuardMultiple.new(:log => show_debug_log, :fail_transition_guard => true)
# callback.aasm.current_state callback.aasm(:left).current_state
# unless show_debug_log unless show_debug_log
# expect(callback).to receive(:before).once.ordered expect(callback).to receive(:before).once.ordered
# expect(callback).to receive(:event_guard).once.ordered.and_return(true) expect(callback).to receive(:event_guard).once.ordered.and_return(true)
# expect(callback).to receive(:transition_guard).once.ordered.and_return(false) expect(callback).to receive(:transition_guard).once.ordered.and_return(false)
# expect(callback).to receive(:event_guard).once.ordered.and_return(true) expect(callback).to receive(:event_guard).once.ordered.and_return(true)
# expect(callback).to receive(:before_exit_open).once.ordered expect(callback).to receive(:before_exit_open).once.ordered
# expect(callback).to receive(:exit_open).once.ordered expect(callback).to receive(:exit_open).once.ordered
# expect(callback).to receive(:aasm_write_state).once.ordered.and_return(true) # this is when the state changes expect(callback).to receive(:aasm_write_state).once.ordered.and_return(true) # this is when the state changes
# expect(callback).to receive(:after_exit_open).once.ordered expect(callback).to receive(:after_exit_open).once.ordered
# expect(callback).to receive(:after).once.ordered expect(callback).to receive(:after).once.ordered
# expect(callback).to_not receive(:transitioning) expect(callback).to_not receive(:transitioning)
# expect(callback).to_not receive(:before_enter_closed) expect(callback).to_not receive(:before_enter_closed)
# expect(callback).to_not receive(:enter_closed) expect(callback).to_not receive(:enter_closed)
# expect(callback).to_not receive(:after_enter_closed) expect(callback).to_not receive(:after_enter_closed)
# end end
# callback.close! callback.close!
# expect(callback.aasm.current_state).to eql :failed expect(callback.aasm(:left).current_state).to eql :failed
# end end
# it "does not run any state callback if guard is defined with block" do it "does not run any state callback if guard is defined with block" do
# callback = Callbacks::GuardWithinBlock.new #(:log => true, :fail_transition_guard => true) callback = Callbacks::GuardWithinBlockMultiple.new #(:log => true, :fail_transition_guard => true)
# callback.aasm.current_state callback.aasm(:left).current_state
# expect(callback).to receive(:before).once.ordered expect(callback).to receive(:before).once.ordered
# expect(callback).to receive(:event_guard).once.ordered.and_return(true) expect(callback).to receive(:event_guard).once.ordered.and_return(true)
# expect(callback).to receive(:transition_guard).once.ordered.and_return(false) expect(callback).to receive(:transition_guard).once.ordered.and_return(false)
# expect(callback).to_not receive(:before_exit_open) expect(callback).to_not receive(:before_exit_open)
# expect(callback).to_not receive(:exit_open) expect(callback).to_not receive(:exit_open)
# expect(callback).to_not receive(:transitioning) expect(callback).to_not receive(:transitioning)
# expect(callback).to_not receive(:before_enter_closed) expect(callback).to_not receive(:before_enter_closed)
# expect(callback).to_not receive(:enter_closed) expect(callback).to_not receive(:enter_closed)
# expect(callback).to_not receive(:aasm_write_state) expect(callback).to_not receive(:aasm_write_state)
# expect(callback).to_not receive(:after_exit_open) expect(callback).to_not receive(:after_exit_open)
# expect(callback).to_not receive(:after_enter_closed) expect(callback).to_not receive(:after_enter_closed)
# expect(callback).to_not receive(:after) expect(callback).to_not receive(:after)
# expect { expect {
# callback.close! callback.close!
# }.to raise_error(AASM::InvalidTransition) }.to raise_error(AASM::InvalidTransition)
# end end
end end
# it "should properly pass arguments" do it "should properly pass arguments" do
# cb = Callbacks::WithArgs.new(:log => false) cb = Callbacks::WithArgsMultiple.new(:log => false)
# cb.aasm.current_state cb.aasm(:left).current_state
# cb.reset_data cb.reset_data
# cb.close!(:arg1, :arg2) cb.close!(:arg1, :arg2)
# expect(cb.data).to eql 'before(:arg1,:arg2) before_exit_open(:arg1,:arg2) transition_proc(:arg1,:arg2) before_enter_closed(:arg1,:arg2) aasm_write_state after_exit_open(:arg1,:arg2) after_enter_closed(:arg1,:arg2) after(:arg1,:arg2)' expect(cb.data).to eql 'before(:arg1,:arg2) before_exit_open(:arg1,:arg2) transition_proc(:arg1,:arg2) before_enter_closed(:arg1,:arg2) aasm_write_state after_exit_open(:arg1,:arg2) after_enter_closed(:arg1,:arg2) after(:arg1,:arg2)'
# end end
# it "should call the callbacks given the to-state as argument" do it "should call the callbacks given the to-state as argument" do
# cb = Callbacks::WithStateArg.new cb = Callbacks::WithStateArgMultiple.new
# expect(cb).to receive(:before_method).with(:arg1).once.ordered expect(cb).to receive(:before_method).with(:arg1).once.ordered
# expect(cb).to receive(:transition_method).never expect(cb).to receive(:transition_method).never
# expect(cb).to receive(:transition_method2).with(:arg1).once.ordered expect(cb).to receive(:transition_method2).with(:arg1).once.ordered
# expect(cb).to receive(:after_method).with(:arg1).once.ordered expect(cb).to receive(:after_method).with(:arg1).once.ordered
# cb.close!(:out_to_lunch, :arg1) cb.close!(:out_to_lunch, :arg1)
# cb = Callbacks::WithStateArg.new cb = Callbacks::WithStateArgMultiple.new
# some_object = double('some object') some_object = double('some object')
# expect(cb).to receive(:before_method).with(some_object).once.ordered expect(cb).to receive(:before_method).with(some_object).once.ordered
# expect(cb).to receive(:transition_method2).with(some_object).once.ordered expect(cb).to receive(:transition_method2).with(some_object).once.ordered
# expect(cb).to receive(:after_method).with(some_object).once.ordered expect(cb).to receive(:after_method).with(some_object).once.ordered
# cb.close!(:out_to_lunch, some_object) cb.close!(:out_to_lunch, some_object)
# end end
# it "should call the proper methods just with arguments" do it "should call the proper methods just with arguments" do
# cb = Callbacks::WithStateArg.new cb = Callbacks::WithStateArgMultiple.new
# expect(cb).to receive(:before_method).with(:arg1).once.ordered expect(cb).to receive(:before_method).with(:arg1).once.ordered
# expect(cb).to receive(:transition_method).with(:arg1).once.ordered expect(cb).to receive(:transition_method).with(:arg1).once.ordered
# expect(cb).to receive(:transition_method).never expect(cb).to receive(:transition_method).never
# expect(cb).to receive(:after_method).with(:arg1).once.ordered expect(cb).to receive(:after_method).with(:arg1).once.ordered
# cb.close!(:arg1) cb.close!(:arg1)
# cb = Callbacks::WithStateArg.new cb = Callbacks::WithStateArgMultiple.new
# some_object = double('some object') some_object = double('some object')
# expect(cb).to receive(:before_method).with(some_object).once.ordered expect(cb).to receive(:before_method).with(some_object).once.ordered
# expect(cb).to receive(:transition_method).with(some_object).once.ordered expect(cb).to receive(:transition_method).with(some_object).once.ordered
# expect(cb).to receive(:transition_method).never expect(cb).to receive(:transition_method).never
# expect(cb).to receive(:after_method).with(some_object).once.ordered expect(cb).to receive(:after_method).with(some_object).once.ordered
# cb.close!(some_object) cb.close!(some_object)
# end end
end end # callbacks for the new DSL
# describe 'event callbacks' do describe 'event callbacks' do
# describe "with an error callback defined" do describe "with an error callback defined" do
# before do before do
# class Foo class FooMultiple
# # this hack is needed to allow testing of parameters, since RSpec # this hack is needed to allow testing of parameters, since RSpec
# # destroys a method's arity when mocked # destroys a method's arity when mocked
# attr_accessor :data attr_accessor :data
# aasm do aasm(:left) do
# event :safe_close, :success => :success_callback, :error => :error_callback do event :safe_close, :success => :success_callback, :error => :error_callback do
# transitions :to => :closed, :from => [:open] transitions :to => :closed, :from => [:open]
# end end
# end end
# end end
# @foo = Foo.new @foo = FooMultiple.new
# end end
# context "error_callback defined" do context "error_callback defined" do
# it "should run error_callback if an exception is raised" do it "should run error_callback if an exception is raised" do
# def @foo.error_callback(e) def @foo.error_callback(e)
# @data = [e] @data = [e]
# end end
# allow(@foo).to receive(:before_enter).and_raise(e = StandardError.new) allow(@foo).to receive(:before_enter).and_raise(e = StandardError.new)
# @foo.safe_close! @foo.safe_close!
# expect(@foo.data).to eql [e] expect(@foo.data).to eql [e]
# end end
# it "should run error_callback without parameters if callback does not support any" do it "should run error_callback without parameters if callback does not support any" do
# def @foo.error_callback(e) def @foo.error_callback(e)
# @data = [] @data = []
# end end
# allow(@foo).to receive(:before_enter).and_raise(e = StandardError.new) allow(@foo).to receive(:before_enter).and_raise(e = StandardError.new)
# @foo.safe_close!('arg1', 'arg2') @foo.safe_close!('arg1', 'arg2')
# expect(@foo.data).to eql [] expect(@foo.data).to eql []
# end end
# it "should run error_callback with parameters if callback supports them" do it "should run error_callback with parameters if callback supports them" do
# def @foo.error_callback(e, arg1, arg2) def @foo.error_callback(e, arg1, arg2)
# @data = [arg1, arg2] @data = [arg1, arg2]
# end end
# allow(@foo).to receive(:before_enter).and_raise(e = StandardError.new) allow(@foo).to receive(:before_enter).and_raise(e = StandardError.new)
# @foo.safe_close!('arg1', 'arg2') @foo.safe_close!('arg1', 'arg2')
# expect(@foo.data).to eql ['arg1', 'arg2'] expect(@foo.data).to eql ['arg1', 'arg2']
# end end
# end end
# it "should raise NoMethodError if exception is raised and error_callback is declared but not defined" do it "should raise NoMethodError if exception is raised and error_callback is declared but not defined" do
# allow(@foo).to receive(:before_enter).and_raise(StandardError) allow(@foo).to receive(:before_enter).and_raise(StandardError)
# expect{@foo.safe_close!}.to raise_error(NoMethodError) expect{@foo.safe_close!}.to raise_error(NoMethodError)
# end end
# it "should propagate an error if no error callback is declared" do it "should propagate an error if no error callback is declared" do
# allow(@foo).to receive(:before_enter).and_raise("Cannot enter safe") allow(@foo).to receive(:before_enter).and_raise("Cannot enter safe")
# expect{@foo.close!}.to raise_error(StandardError, "Cannot enter safe") expect{@foo.close!}.to raise_error(StandardError, "Cannot enter safe")
# end end
# end end
# describe "with aasm_event_fired defined" do describe "with aasm_event_fired defined" do
# before do before do
# @foo = Foo.new @foo = FooMultiple.new
# def @foo.aasm_event_fired(event, from, to); end def @foo.aasm_event_fired(event, from, to); end
# end end
# it 'should call it for successful bang fire' do it 'should call it for successful bang fire' do
# expect(@foo).to receive(:aasm_event_fired).with(:close, :open, :closed) expect(@foo).to receive(:aasm_event_fired).with(:close, :open, :closed)
# @foo.close! @foo.close!
# end end
# it 'should call it for successful non-bang fire' do it 'should call it for successful non-bang fire' do
# expect(@foo).to receive(:aasm_event_fired) expect(@foo).to receive(:aasm_event_fired)
# @foo.close @foo.close
# end end
# it 'should not call it for failing bang fire' do it 'should not call it for failing bang fire' do
# allow(@foo.aasm).to receive(:set_current_state_with_persistence).and_return(false) allow(@foo.aasm(:left)).to receive(:set_current_state_with_persistence).and_return(false)
# expect(@foo).not_to receive(:aasm_event_fired) expect(@foo).not_to receive(:aasm_event_fired)
# @foo.close! @foo.close!
# end end
# end end
# describe "with aasm_event_failed defined" do describe "with aasm_event_failed defined" do
# before do before do
# @foo = Foo.new @foo = FooMultiple.new
# def @foo.aasm_event_failed(event, from); end def @foo.aasm_event_failed(event, from); end
# end end
# it 'should call it when transition failed for bang fire' do it 'should call it when transition failed for bang fire' do
# expect(@foo).to receive(:aasm_event_failed).with(:null, :open) expect(@foo).to receive(:aasm_event_failed).with(:null, :open)
# expect {@foo.null!}.to raise_error(AASM::InvalidTransition) expect {@foo.null!}.to raise_error(AASM::InvalidTransition)
# end end
# it 'should call it when transition failed for non-bang fire' do it 'should call it when transition failed for non-bang fire' do
# expect(@foo).to receive(:aasm_event_failed).with(:null, :open) expect(@foo).to receive(:aasm_event_failed).with(:null, :open)
# expect {@foo.null}.to raise_error(AASM::InvalidTransition) expect {@foo.null}.to raise_error(AASM::InvalidTransition)
# end end
# it 'should not call it if persist fails for bang fire' do it 'should not call it if persist fails for bang fire' do
# allow(@foo.aasm).to receive(:set_current_state_with_persistence).and_return(false) allow(@foo.aasm(:left)).to receive(:set_current_state_with_persistence).and_return(false)
# expect(@foo).to receive(:aasm_event_failed) expect(@foo).to receive(:aasm_event_failed)
# @foo.close! @foo.close!
# end end
# end end
# end end # event callbacks