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