PageRenderTime 45ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/test/unit/machine_collection_test.rb

http://github.com/pluginaweek/state_machine
Ruby | 603 lines | 474 code | 124 blank | 5 comment | 0 complexity | ad54c069745606b9e5043d89cb9fb69e MD5 | raw file
  1. require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
  2. class MachineCollectionByDefaultTest < Test::Unit::TestCase
  3. def setup
  4. @machines = StateMachine::MachineCollection.new
  5. end
  6. def test_should_not_have_any_machines
  7. assert @machines.empty?
  8. end
  9. end
  10. class MachineCollectionStateInitializationTest < Test::Unit::TestCase
  11. def setup
  12. @machines = StateMachine::MachineCollection.new
  13. @klass = Class.new
  14. @machines[:state] = StateMachine::Machine.new(@klass, :state, :initial => :parked)
  15. @machines[:alarm_state] = StateMachine::Machine.new(@klass, :alarm_state, :initial => lambda {|object| :active})
  16. @machines[:alarm_state].state :active, :value => lambda {'active'}
  17. # Prevent the auto-initialization hook from firing
  18. @klass.class_eval do
  19. def initialize
  20. end
  21. end
  22. @object = @klass.new
  23. @object.state = nil
  24. @object.alarm_state = nil
  25. end
  26. def test_should_raise_exception_if_invalid_option_specified
  27. assert_raise(ArgumentError) {@machines.initialize_states(@object, :invalid => true)}
  28. end
  29. def test_should_only_initialize_static_states_prior_to_block
  30. @machines.initialize_states(@object) do
  31. @state_in_block = @object.state
  32. @alarm_state_in_block = @object.alarm_state
  33. end
  34. assert_equal 'parked', @state_in_block
  35. assert_nil @alarm_state_in_block
  36. end
  37. def test_should_only_initialize_dynamic_states_after_block
  38. @machines.initialize_states(@object) do
  39. @alarm_state_in_block = @object.alarm_state
  40. end
  41. assert_nil @alarm_state_in_block
  42. assert_equal 'active', @object.alarm_state
  43. end
  44. def test_should_initialize_all_states_without_block
  45. @machines.initialize_states(@object)
  46. assert_equal 'parked', @object.state
  47. assert_equal 'active', @object.alarm_state
  48. end
  49. def test_should_skip_static_states_if_disabled
  50. @machines.initialize_states(@object, :static => false)
  51. assert_nil @object.state
  52. assert_equal 'active', @object.alarm_state
  53. end
  54. def test_should_not_initialize_existing_static_states_by_default
  55. @object.state = 'idling'
  56. @machines.initialize_states(@object)
  57. assert_equal 'idling', @object.state
  58. end
  59. def test_should_initialize_existing_static_states_if_forced
  60. @object.state = 'idling'
  61. @machines.initialize_states(@object, :static => :force)
  62. assert_equal 'parked', @object.state
  63. end
  64. def test_should_not_initialize_existing_static_states_if_not_forced
  65. @object.state = 'idling'
  66. @machines.initialize_states(@object, :static => true)
  67. assert_equal 'idling', @object.state
  68. end
  69. def test_should_skip_dynamic_states_if_disabled
  70. @machines.initialize_states(@object, :dynamic => false)
  71. assert_equal 'parked', @object.state
  72. assert_nil @object.alarm_state
  73. end
  74. def test_should_not_initialize_existing_dynamic_states_by_default
  75. @object.alarm_state = 'inactive'
  76. @machines.initialize_states(@object)
  77. assert_equal 'inactive', @object.alarm_state
  78. end
  79. def test_should_initialize_existing_dynamic_states_if_forced
  80. @object.alarm_state = 'inactive'
  81. @machines.initialize_states(@object, :dynamic => :force)
  82. assert_equal 'active', @object.alarm_state
  83. end
  84. def test_should_not_initialize_existing_dynamic_states_if_not_forced
  85. @object.alarm_state = 'inactive'
  86. @machines.initialize_states(@object, :dynamic => true)
  87. assert_equal 'inactive', @object.alarm_state
  88. end
  89. end
  90. class MachineCollectionFireTest < Test::Unit::TestCase
  91. def setup
  92. @machines = StateMachine::MachineCollection.new
  93. @klass = Class.new do
  94. attr_reader :saved
  95. def save
  96. @saved = true
  97. end
  98. end
  99. # First machine
  100. @machines[:state] = @state = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save)
  101. @state.event :ignite do
  102. transition :parked => :idling
  103. end
  104. @state.event :park do
  105. transition :idling => :parked
  106. end
  107. # Second machine
  108. @machines[:alarm_state] = @alarm_state = StateMachine::Machine.new(@klass, :alarm_state, :initial => :active, :action => :save, :namespace => 'alarm')
  109. @alarm_state.event :enable do
  110. transition :off => :active
  111. end
  112. @alarm_state.event :disable do
  113. transition :active => :off
  114. end
  115. @object = @klass.new
  116. end
  117. def test_should_raise_exception_if_invalid_event_specified
  118. exception = assert_raise(StateMachine::InvalidEvent) { @machines.fire_events(@object, :invalid) }
  119. assert_equal :invalid, exception.event
  120. exception = assert_raise(StateMachine::InvalidEvent) { @machines.fire_events(@object, :ignite, :invalid) }
  121. assert_equal :invalid, exception.event
  122. end
  123. def test_should_fail_if_any_event_cannot_transition
  124. assert !@machines.fire_events(@object, :park, :disable_alarm)
  125. assert_equal 'parked', @object.state
  126. assert_equal 'active', @object.alarm_state
  127. assert !@object.saved
  128. assert !@machines.fire_events(@object, :ignite, :enable_alarm)
  129. assert_equal 'parked', @object.state
  130. assert_equal 'active', @object.alarm_state
  131. assert !@object.saved
  132. end
  133. def test_should_run_failure_callbacks_if_any_event_cannot_transition
  134. @state_failure_run = @alarm_state_failure_run = false
  135. @machines[:state].after_failure {@state_failure_run = true}
  136. @machines[:alarm_state].after_failure {@alarm_state_failure_run = true}
  137. assert !@machines.fire_events(@object, :park, :disable_alarm)
  138. assert @state_failure_run
  139. assert !@alarm_state_failure_run
  140. end
  141. def test_should_be_successful_if_all_events_transition
  142. assert @machines.fire_events(@object, :ignite, :disable_alarm)
  143. assert_equal 'idling', @object.state
  144. assert_equal 'off', @object.alarm_state
  145. assert @object.saved
  146. end
  147. def test_should_not_save_if_skipping_action
  148. assert @machines.fire_events(@object, :ignite, :disable_alarm, false)
  149. assert_equal 'idling', @object.state
  150. assert_equal 'off', @object.alarm_state
  151. assert !@object.saved
  152. end
  153. end
  154. class MachineCollectionFireWithTransactionsTest < Test::Unit::TestCase
  155. def setup
  156. @machines = StateMachine::MachineCollection.new
  157. @klass = Class.new do
  158. attr_accessor :allow_save
  159. def save
  160. @allow_save
  161. end
  162. end
  163. StateMachine::Integrations.const_set('Custom', Module.new do
  164. include StateMachine::Integrations::Base
  165. attr_reader :rolled_back
  166. def transaction(object)
  167. @rolled_back = yield
  168. end
  169. end)
  170. # First machine
  171. @machines[:state] = @state = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save, :integration => :custom)
  172. @state.event :ignite do
  173. transition :parked => :idling
  174. end
  175. # Second machine
  176. @machines[:alarm_state] = @alarm_state = StateMachine::Machine.new(@klass, :alarm_state, :initial => :active, :action => :save, :namespace => 'alarm', :integration => :custom)
  177. @alarm_state.event :disable do
  178. transition :active => :off
  179. end
  180. @object = @klass.new
  181. end
  182. def test_should_not_rollback_if_successful
  183. @object.allow_save = true
  184. assert @machines.fire_events(@object, :ignite, :disable_alarm)
  185. assert_equal true, @state.rolled_back
  186. assert_nil @alarm_state.rolled_back
  187. assert_equal 'idling', @object.state
  188. assert_equal 'off', @object.alarm_state
  189. end
  190. def test_should_rollback_if_not_successful
  191. @object.allow_save = false
  192. assert !@machines.fire_events(@object, :ignite, :disable_alarm)
  193. assert_equal false, @state.rolled_back
  194. assert_nil @alarm_state.rolled_back
  195. assert_equal 'parked', @object.state
  196. assert_equal 'active', @object.alarm_state
  197. end
  198. def test_should_run_failure_callbacks_if_not_successful
  199. @object.allow_save = false
  200. @state_failure_run = @alarm_state_failure_run = false
  201. @machines[:state].after_failure {@state_failure_run = true}
  202. @machines[:alarm_state].after_failure {@alarm_state_failure_run = true}
  203. assert !@machines.fire_events(@object, :ignite, :disable_alarm)
  204. assert @state_failure_run
  205. assert @alarm_state_failure_run
  206. end
  207. def teardown
  208. StateMachine::Integrations.send(:remove_const, 'Custom')
  209. end
  210. end
  211. class MachineCollectionFireWithValidationsTest < Test::Unit::TestCase
  212. def setup
  213. StateMachine::Integrations.const_set('Custom', Module.new do
  214. include StateMachine::Integrations::Base
  215. def invalidate(object, attribute, message, values = [])
  216. (object.errors ||= []) << generate_message(message, values)
  217. end
  218. def reset(object)
  219. object.errors = []
  220. end
  221. end)
  222. @klass = Class.new do
  223. attr_accessor :errors
  224. def initialize
  225. @errors = []
  226. super
  227. end
  228. end
  229. @machines = StateMachine::MachineCollection.new
  230. @machines[:state] = @state = StateMachine::Machine.new(@klass, :state, :initial => :parked, :integration => :custom)
  231. @state.event :ignite do
  232. transition :parked => :idling
  233. end
  234. @machines[:alarm_state] = @alarm_state = StateMachine::Machine.new(@klass, :alarm_state, :initial => :active, :namespace => 'alarm', :integration => :custom)
  235. @alarm_state.event :disable do
  236. transition :active => :off
  237. end
  238. @object = @klass.new
  239. end
  240. def test_should_not_invalidate_if_transitions_exist
  241. assert @machines.fire_events(@object, :ignite, :disable_alarm)
  242. assert_equal [], @object.errors
  243. end
  244. def test_should_invalidate_if_no_transitions_exist
  245. @object.state = 'idling'
  246. @object.alarm_state = 'off'
  247. assert !@machines.fire_events(@object, :ignite, :disable_alarm)
  248. assert_equal ['cannot transition via "ignite"', 'cannot transition via "disable"'], @object.errors
  249. end
  250. def test_should_run_failure_callbacks_if_no_transitions_exist
  251. @object.state = 'idling'
  252. @object.alarm_state = 'off'
  253. @state_failure_run = @alarm_state_failure_run = false
  254. @machines[:state].after_failure {@state_failure_run = true}
  255. @machines[:alarm_state].after_failure {@alarm_state_failure_run = true}
  256. assert !@machines.fire_events(@object, :ignite, :disable_alarm)
  257. assert @state_failure_run
  258. assert @alarm_state_failure_run
  259. end
  260. def teardown
  261. StateMachine::Integrations.send(:remove_const, 'Custom')
  262. end
  263. end
  264. class MachineCollectionTransitionsWithoutEventsTest < Test::Unit::TestCase
  265. def setup
  266. @klass = Class.new
  267. @machines = StateMachine::MachineCollection.new
  268. @machines[:state] = @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save)
  269. @machine.event :ignite do
  270. transition :parked => :idling
  271. end
  272. @object = @klass.new
  273. @object.state_event = nil
  274. @transitions = @machines.transitions(@object, :save)
  275. end
  276. def test_should_be_empty
  277. assert @transitions.empty?
  278. end
  279. def test_should_perform
  280. assert_equal true, @transitions.perform
  281. end
  282. end
  283. class MachineCollectionTransitionsWithBlankEventsTest < Test::Unit::TestCase
  284. def setup
  285. @klass = Class.new
  286. @machines = StateMachine::MachineCollection.new
  287. @machines[:state] = @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save)
  288. @machine.event :ignite do
  289. transition :parked => :idling
  290. end
  291. @object = @klass.new
  292. @object.state_event = ''
  293. @transitions = @machines.transitions(@object, :save)
  294. end
  295. def test_should_be_empty
  296. assert @transitions.empty?
  297. end
  298. def test_should_perform
  299. assert_equal true, @transitions.perform
  300. end
  301. end
  302. class MachineCollectionTransitionsWithInvalidEventsTest < Test::Unit::TestCase
  303. def setup
  304. @klass = Class.new
  305. @machines = StateMachine::MachineCollection.new
  306. @machines[:state] = @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save)
  307. @machine.event :ignite do
  308. transition :parked => :idling
  309. end
  310. @object = @klass.new
  311. @object.state_event = 'invalid'
  312. @transitions = @machines.transitions(@object, :save)
  313. end
  314. def test_should_be_empty
  315. assert @transitions.empty?
  316. end
  317. def test_should_not_perform
  318. assert_equal false, @transitions.perform
  319. end
  320. end
  321. class MachineCollectionTransitionsWithoutTransitionTest < Test::Unit::TestCase
  322. def setup
  323. @klass = Class.new
  324. @machines = StateMachine::MachineCollection.new
  325. @machines[:state] = @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save)
  326. @machine.event :ignite do
  327. transition :parked => :idling
  328. end
  329. @object = @klass.new
  330. @object.state = 'idling'
  331. @object.state_event = 'ignite'
  332. @transitions = @machines.transitions(@object, :save)
  333. end
  334. def test_should_be_empty
  335. assert @transitions.empty?
  336. end
  337. def test_should_not_perform
  338. assert_equal false, @transitions.perform
  339. end
  340. end
  341. class MachineCollectionTransitionsWithTransitionTest < Test::Unit::TestCase
  342. def setup
  343. @klass = Class.new
  344. @machines = StateMachine::MachineCollection.new
  345. @machines[:state] = @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save)
  346. @machine.event :ignite do
  347. transition :parked => :idling
  348. end
  349. @object = @klass.new
  350. @object.state_event = 'ignite'
  351. @transitions = @machines.transitions(@object, :save)
  352. end
  353. def test_should_not_be_empty
  354. assert_equal 1, @transitions.length
  355. end
  356. def test_should_perform
  357. assert_equal true, @transitions.perform
  358. end
  359. end
  360. class MachineCollectionTransitionsWithSameActionsTest < Test::Unit::TestCase
  361. def setup
  362. @klass = Class.new
  363. @machines = StateMachine::MachineCollection.new
  364. @machines[:state] = @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save)
  365. @machine.event :ignite do
  366. transition :parked => :idling
  367. end
  368. @machines[:status] = @machine = StateMachine::Machine.new(@klass, :status, :initial => :first_gear, :action => :save)
  369. @machine.event :shift_up do
  370. transition :first_gear => :second_gear
  371. end
  372. @object = @klass.new
  373. @object.state_event = 'ignite'
  374. @object.status_event = 'shift_up'
  375. @transitions = @machines.transitions(@object, :save)
  376. end
  377. def test_should_not_be_empty
  378. assert_equal 2, @transitions.length
  379. end
  380. def test_should_perform
  381. assert_equal true, @transitions.perform
  382. end
  383. end
  384. class MachineCollectionTransitionsWithDifferentActionsTest < Test::Unit::TestCase
  385. def setup
  386. @klass = Class.new
  387. @machines = StateMachine::MachineCollection.new
  388. @machines[:state] = @state = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save)
  389. @state.event :ignite do
  390. transition :parked => :idling
  391. end
  392. @machines[:status] = @status = StateMachine::Machine.new(@klass, :status, :initial => :first_gear, :action => :persist)
  393. @status.event :shift_up do
  394. transition :first_gear => :second_gear
  395. end
  396. @object = @klass.new
  397. @object.state_event = 'ignite'
  398. @object.status_event = 'shift_up'
  399. @transitions = @machines.transitions(@object, :save)
  400. end
  401. def test_should_only_select_matching_actions
  402. assert_equal 1, @transitions.length
  403. end
  404. end
  405. class MachineCollectionTransitionsWithExisitingTransitionsTest < Test::Unit::TestCase
  406. def setup
  407. @klass = Class.new
  408. @machines = StateMachine::MachineCollection.new
  409. @machines[:state] = @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save)
  410. @machine.event :ignite do
  411. transition :parked => :idling
  412. end
  413. @object = @klass.new
  414. @object.send(:state_event_transition=, StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling))
  415. @transitions = @machines.transitions(@object, :save)
  416. end
  417. def test_should_not_be_empty
  418. assert_equal 1, @transitions.length
  419. end
  420. def test_should_perform
  421. assert_equal true, @transitions.perform
  422. end
  423. end
  424. class MachineCollectionTransitionsWithCustomOptionsTest < Test::Unit::TestCase
  425. def setup
  426. @klass = Class.new
  427. @machines = StateMachine::MachineCollection.new
  428. @machines[:state] = @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save)
  429. @machine.event :ignite do
  430. transition :parked => :idling
  431. end
  432. @object = @klass.new
  433. @transitions = @machines.transitions(@object, :save, :after => false)
  434. end
  435. def test_should_use_custom_options
  436. assert @transitions.skip_after
  437. end
  438. end
  439. class MachineCollectionFireAttributesWithValidationsTest < Test::Unit::TestCase
  440. def setup
  441. @klass = Class.new do
  442. attr_accessor :errors
  443. def initialize
  444. @errors = []
  445. super
  446. end
  447. end
  448. @machines = StateMachine::MachineCollection.new
  449. @machines[:state] = @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save)
  450. @machine.event :ignite do
  451. transition :parked => :idling
  452. end
  453. class << @machine
  454. def invalidate(object, attribute, message, values = [])
  455. (object.errors ||= []) << generate_message(message, values)
  456. end
  457. def reset(object)
  458. object.errors = []
  459. end
  460. end
  461. @object = @klass.new
  462. end
  463. def test_should_invalidate_if_event_is_invalid
  464. @object.state_event = 'invalid'
  465. @machines.transitions(@object, :save)
  466. assert !@object.errors.empty?
  467. end
  468. def test_should_invalidate_if_no_transition_exists
  469. @object.state = 'idling'
  470. @object.state_event = 'ignite'
  471. @machines.transitions(@object, :save)
  472. assert !@object.errors.empty?
  473. end
  474. def test_should_not_invalidate_if_transition_exists
  475. @object.state_event = 'ignite'
  476. @machines.transitions(@object, :save)
  477. assert @object.errors.empty?
  478. end
  479. end