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