PageRenderTime 79ms CodeModel.GetById 0ms RepoModel.GetById 0ms app.codeStats 0ms

/test/unit/integrations/data_mapper_test.rb

http://github.com/pluginaweek/state_machine
Ruby | 2194 lines | 1724 code | 461 blank | 9 comment | 35 complexity | 16ff50082d59631a11ac47d419b1f1a8 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1. require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')
  2. require 'dm-core'
  3. require 'dm-core/version' unless defined?(DataMapper::VERSION)
  4. require 'dm-observer'
  5. if Gem::Version.new(DataMapper::VERSION) >= Gem::Version.new('0.10.3')
  6. require 'dm-migrations'
  7. end
  8. # Establish database connection
  9. DataMapper.setup(:default, 'sqlite3::memory:')
  10. DataObjects::Sqlite3.logger = DataObjects::Logger.new("#{File.dirname(__FILE__)}/../../data_mapper.log", :debug)
  11. module DataMapperTest
  12. class BaseTestCase < Test::Unit::TestCase
  13. def default_test
  14. end
  15. def teardown
  16. super
  17. @resources.uniq.each {|resource| DataMapperTest.send(:remove_const, resource)} if instance_variable_defined?('@resources')
  18. end
  19. protected
  20. # Creates a new DataMapper resource (and the associated table)
  21. def new_resource(create_table = :foo, &block)
  22. base_table_name = create_table || :foo
  23. name = base_table_name.to_s.capitalize
  24. table_name = "#{base_table_name}_#{rand(1000000)}"
  25. resource = Class.new
  26. DataMapperTest.send(:remove_const, name) if DataMapperTest.const_defined?(name)
  27. DataMapperTest.const_set(name, resource)
  28. (@resources ||= []) << name
  29. resource.class_eval do
  30. include DataMapper::Resource
  31. storage_names[:default] = table_name.to_s
  32. property :id, resource.class_eval('Serial')
  33. property :state, String
  34. end
  35. resource.class_eval(&block) if block_given?
  36. resource.auto_migrate! if create_table
  37. resource
  38. end
  39. # Creates a new DataMapper observer
  40. def new_observer(resource, &block)
  41. observer = Class.new do
  42. include DataMapper::Observer
  43. end
  44. observer.observe(resource)
  45. observer.class_eval(&block) if block_given?
  46. observer
  47. end
  48. end
  49. class IntegrationTest < BaseTestCase
  50. def test_should_have_an_integration_name
  51. assert_equal :data_mapper, StateMachine::Integrations::DataMapper.integration_name
  52. end
  53. def test_should_be_available
  54. assert StateMachine::Integrations::DataMapper.available?
  55. end
  56. def test_should_match_if_class_includes_data_mapper
  57. assert StateMachine::Integrations::DataMapper.matches?(new_resource)
  58. end
  59. def test_should_not_match_if_class_does_not_include_data_mapper
  60. assert !StateMachine::Integrations::DataMapper.matches?(Class.new)
  61. end
  62. def test_should_have_defaults
  63. assert_equal({:action => :save, :use_transactions => false}, StateMachine::Integrations::DataMapper.defaults)
  64. end
  65. def test_should_not_have_a_locale_path
  66. assert_nil StateMachine::Integrations::DataMapper.locale_path
  67. end
  68. end
  69. class MachineWithoutDatabaseTest < BaseTestCase
  70. def setup
  71. @resource = new_resource(false) do
  72. # Simulate the database not being available entirely
  73. def self.repository
  74. raise DataObjects::SyntaxError
  75. end
  76. end
  77. end
  78. def test_should_allow_machine_creation
  79. assert_nothing_raised { StateMachine::Machine.new(@resource) }
  80. end
  81. end
  82. class MachineUnmigratedTest < BaseTestCase
  83. def setup
  84. @resource = new_resource(false)
  85. end
  86. def test_should_allow_machine_creation
  87. assert_nothing_raised { StateMachine::Machine.new(@resource) }
  88. end
  89. end
  90. class MachineWithoutPropertyTest < BaseTestCase
  91. def setup
  92. @resource = new_resource
  93. StateMachine::Machine.new(@resource, :status)
  94. end
  95. def test_should_define_field_with_string_type
  96. property = @resource.properties.detect {|p| p.name == :status}
  97. assert_not_nil property
  98. if Gem::Version.new(DataMapper::VERSION) >= Gem::Version.new('1.0.0')
  99. assert_instance_of DataMapper::Property::String, property
  100. else
  101. assert_equal String, property.type
  102. end
  103. end
  104. end
  105. class MachineWithPropertyTest < BaseTestCase
  106. def setup
  107. @resource = new_resource do
  108. property :status, Integer
  109. end
  110. StateMachine::Machine.new(@resource, :status)
  111. end
  112. def test_should_not_redefine_field
  113. property = @resource.properties.detect {|p| p.name == :status}
  114. assert_not_nil property
  115. if Gem::Version.new(DataMapper::VERSION) >= Gem::Version.new('1.0.0')
  116. assert_instance_of DataMapper::Property::Integer, property
  117. else
  118. assert_equal Integer, property.type
  119. end
  120. end
  121. end
  122. class MachineByDefaultTest < BaseTestCase
  123. def setup
  124. @resource = new_resource
  125. @machine = StateMachine::Machine.new(@resource)
  126. end
  127. def test_should_use_save_as_action
  128. assert_equal :save, @machine.action
  129. end
  130. def test_should_not_use_transactions
  131. assert_equal false, @machine.use_transactions
  132. end
  133. def test_should_not_have_any_before_callbacks
  134. assert_equal 0, @machine.callbacks[:before].size
  135. end
  136. def test_should_not_have_any_after_callbacks
  137. assert_equal 0, @machine.callbacks[:after].size
  138. end
  139. end
  140. class MachineWithStatesTest < BaseTestCase
  141. def setup
  142. @resource = new_resource
  143. @machine = StateMachine::Machine.new(@resource)
  144. @machine.state :first_gear
  145. end
  146. def test_should_humanize_name
  147. assert_equal 'first gear', @machine.state(:first_gear).human_name
  148. end
  149. end
  150. class MachineWithStaticInitialStateTest < BaseTestCase
  151. def setup
  152. @resource = new_resource(:vehicle) do
  153. attr_accessor :value
  154. end
  155. @machine = StateMachine::Machine.new(@resource, :initial => :parked)
  156. end
  157. def test_should_set_initial_state_on_created_object
  158. record = @resource.new
  159. assert_equal 'parked', record.state
  160. end
  161. def test_should_set_initial_state_with_nil_attributes
  162. @resource.class_eval do
  163. def attributes=(attributes)
  164. super(attributes || {})
  165. end
  166. end
  167. record = @resource.new(nil)
  168. assert_equal 'parked', record.state
  169. end
  170. def test_should_still_set_attributes
  171. record = @resource.new(:value => 1)
  172. assert_equal 1, record.value
  173. end
  174. def test_should_not_allow_initialize_blocks
  175. block_args = nil
  176. @resource.new do |*args|
  177. block_args = args
  178. end
  179. assert_nil block_args
  180. end
  181. def test_should_set_initial_state_before_setting_attributes
  182. @resource.class_eval do
  183. attr_accessor :state_during_setter
  184. remove_method :value=
  185. define_method(:value=) do |value|
  186. self.state_during_setter = state
  187. end
  188. end
  189. record = @resource.new(:value => 1)
  190. assert_equal 'parked', record.state_during_setter
  191. end
  192. def test_should_not_set_initial_state_after_already_initialized
  193. record = @resource.new(:value => 1)
  194. assert_equal 'parked', record.state
  195. record.state = 'idling'
  196. record.attributes = {}
  197. assert_equal 'idling', record.state
  198. end
  199. def test_should_persist_initial_state
  200. record = @resource.new
  201. record.save
  202. record.reload
  203. assert_equal 'parked', record.state
  204. end
  205. def test_should_persist_initial_state_on_dup
  206. record = @resource.create.dup
  207. record.save
  208. record.reload
  209. assert_equal 'parked', record.state
  210. end
  211. def test_should_use_stored_values_when_loading_from_database
  212. @machine.state :idling
  213. record = @resource.get(@resource.create(:state => 'idling').id)
  214. assert_equal 'idling', record.state
  215. end
  216. def test_should_use_stored_values_when_loading_from_database_with_nil_state
  217. @machine.state nil
  218. record = @resource.get(@resource.create(:state => nil).id)
  219. assert_nil record.state
  220. end
  221. def test_should_use_stored_values_when_loading_for_many_association
  222. @machine.state :idling
  223. @resource.property :owner_id, Integer
  224. @resource.auto_migrate!
  225. owner_resource = new_resource(:owner) do
  226. has n, :vehicles
  227. end
  228. owner = owner_resource.create
  229. record = @resource.new(:state => 'idling')
  230. record.owner_id = owner.id
  231. record.save
  232. assert_equal 'idling', owner.vehicles[0].state
  233. end
  234. def test_should_use_stored_values_when_loading_for_one_association
  235. @machine.state :idling
  236. @resource.property :owner_id, Integer
  237. @resource.auto_migrate!
  238. owner_resource = new_resource(:owner) do
  239. has 1, :vehicle
  240. end
  241. owner = owner_resource.create
  242. record = @resource.new(:state => 'idling')
  243. record.owner_id = owner.id
  244. record.save
  245. assert_equal 'idling', owner.vehicle.state
  246. end
  247. def test_should_use_stored_values_when_loading_for_belongs_to_association
  248. @machine.state :idling
  249. driver_resource = new_resource(:driver) do
  250. belongs_to :vehicle
  251. end
  252. record = @resource.create(:state => 'idling')
  253. driver = driver_resource.create(:vehicle_id => record.id)
  254. assert_equal 'idling', driver.vehicle.state
  255. end
  256. end
  257. class MachineWithDynamicInitialStateTest < BaseTestCase
  258. def setup
  259. @resource = new_resource do
  260. attr_accessor :value
  261. end
  262. @machine = StateMachine::Machine.new(@resource, :initial => lambda {|object| :parked})
  263. @machine.state :parked
  264. end
  265. def test_should_set_initial_state_on_created_object
  266. record = @resource.new
  267. assert_equal 'parked', record.state
  268. end
  269. def test_should_still_set_attributes
  270. record = @resource.new(:value => 1)
  271. assert_equal 1, record.value
  272. end
  273. def test_should_not_allow_initialize_blocks
  274. block_args = nil
  275. @resource.new do |*args|
  276. block_args = args
  277. end
  278. assert_nil block_args
  279. end
  280. def test_should_set_initial_state_after_setting_attributes
  281. @resource.class_eval do
  282. attr_accessor :state_during_setter
  283. remove_method :value=
  284. define_method(:value=) do |value|
  285. self.state_during_setter = state || 'nil'
  286. end
  287. end
  288. record = @resource.new(:value => 1)
  289. assert_equal 'nil', record.state_during_setter
  290. end
  291. def test_should_not_set_initial_state_after_already_initialized
  292. record = @resource.new(:value => 1)
  293. assert_equal 'parked', record.state
  294. record.state = 'idling'
  295. record.attributes = {}
  296. assert_equal 'idling', record.state
  297. end
  298. def test_should_persist_initial_state
  299. record = @resource.new
  300. record.save
  301. record.reload
  302. assert_equal 'parked', record.state
  303. end
  304. def test_should_persist_initial_state_on_dup
  305. record = @resource.create.dup
  306. record.save
  307. record.reload
  308. assert_equal 'parked', record.state
  309. end
  310. def test_should_use_stored_values_when_loading_from_database
  311. @machine.state :idling
  312. record = @resource.get(@resource.create(:state => 'idling').id)
  313. assert_equal 'idling', record.state
  314. end
  315. def test_should_use_stored_values_when_loading_from_database_with_nil_state
  316. @machine.state nil
  317. record = @resource.get(@resource.create(:state => nil).id)
  318. assert_nil record.state
  319. end
  320. end
  321. class MachineWithEventsTest < BaseTestCase
  322. def setup
  323. @resource = new_resource
  324. @machine = StateMachine::Machine.new(@resource)
  325. @machine.event :shift_up
  326. end
  327. def test_should_humanize_name
  328. assert_equal 'shift up', @machine.event(:shift_up).human_name
  329. end
  330. end
  331. class MachineWithSameColumnDefaultTest < BaseTestCase
  332. def setup
  333. @original_stderr, $stderr = $stderr, StringIO.new
  334. @resource = new_resource do
  335. property :status, String, :default => 'parked'
  336. end
  337. @machine = StateMachine::Machine.new(@resource, :status, :initial => :parked)
  338. @record = @resource.new
  339. end
  340. def test_should_use_machine_default
  341. assert_equal 'parked', @record.status
  342. end
  343. def test_should_not_generate_a_warning
  344. assert_no_match(/have defined a different default/, $stderr.string)
  345. end
  346. def teardown
  347. $stderr = @original_stderr
  348. super
  349. end
  350. end
  351. class MachineWithDifferentColumnDefaultTest < BaseTestCase
  352. def setup
  353. @original_stderr, $stderr = $stderr, StringIO.new
  354. @resource = new_resource do
  355. property :status, String, :default => 'idling'
  356. end
  357. @machine = StateMachine::Machine.new(@resource, :status, :initial => :parked)
  358. @record = @resource.new
  359. end
  360. def test_should_use_machine_default
  361. assert_equal 'parked', @record.status
  362. end
  363. def test_should_generate_a_warning
  364. assert_match(/Both DataMapperTest::Foo and its :status machine have defined a different default for "status". Use only one or the other for defining defaults to avoid unexpected behaviors\./, $stderr.string)
  365. end
  366. def teardown
  367. $stderr = @original_stderr
  368. super
  369. end
  370. end
  371. class MachineWithDifferentIntegerColumnDefaultTest < BaseTestCase
  372. def setup
  373. @original_stderr, $stderr = $stderr, StringIO.new
  374. @resource = new_resource do
  375. property :status, Integer, :default => 0
  376. end
  377. @machine = StateMachine::Machine.new(@resource, :status, :initial => :parked)
  378. @machine.state :parked, :value => 1
  379. @record = @resource.new
  380. end
  381. def test_should_use_machine_default
  382. assert_equal 1, @record.status
  383. end
  384. def test_should_generate_a_warning
  385. assert_match(/Both DataMapperTest::Foo and its :status machine have defined a different default for "status". Use only one or the other for defining defaults to avoid unexpected behaviors\./, $stderr.string)
  386. end
  387. def teardown
  388. $stderr = @original_stderr
  389. super
  390. end
  391. end
  392. class MachineWithConflictingPredicateTest < BaseTestCase
  393. def setup
  394. @resource = new_resource do
  395. def state?(*args)
  396. true
  397. end
  398. end
  399. @machine = StateMachine::Machine.new(@resource)
  400. @record = @resource.new
  401. end
  402. def test_should_not_define_attribute_predicate
  403. assert @record.state?
  404. end
  405. end
  406. class MachineWithConflictingStateNameTest < BaseTestCase
  407. def setup
  408. require 'stringio'
  409. @original_stderr, $stderr = $stderr, StringIO.new
  410. @resource = new_resource
  411. end
  412. def test_should_output_warning_with_same_machine_name
  413. @machine = StateMachine::Machine.new(@resource)
  414. @machine.state :state
  415. assert_match(/^Instance method "state\?" is already defined in DataMapperTest::Foo :state instance helpers, use generic helper instead.*\n$/, $stderr.string)
  416. end
  417. def test_should_not_output_warning_with_same_machine_attribute
  418. @machine = StateMachine::Machine.new(@resource, :public_state, :attribute => :state)
  419. @machine.state :state
  420. assert_no_match(/^Instance method "state\?" is already defined.*\n$/, $stderr.string)
  421. end
  422. def teardown
  423. $stderr = @original_stderr
  424. super
  425. end
  426. end
  427. class MachineWithColumnStateAttributeTest < BaseTestCase
  428. def setup
  429. @resource = new_resource
  430. @machine = StateMachine::Machine.new(@resource, :initial => :parked)
  431. @machine.other_states(:idling)
  432. @record = @resource.new
  433. end
  434. def test_should_not_override_the_column_reader
  435. @record.attribute_set(:state, 'parked')
  436. assert_equal 'parked', @record.state
  437. end
  438. def test_should_not_override_the_column_writer
  439. @record.state = 'parked'
  440. assert_equal 'parked', @record.attribute_get(:state)
  441. end
  442. def test_should_have_an_attribute_predicate
  443. assert @record.respond_to?(:state?)
  444. end
  445. def test_should_raise_exception_for_predicate_without_parameters
  446. assert_raise(ArgumentError) { @record.state? }
  447. end
  448. def test_should_return_false_for_predicate_if_does_not_match_current_value
  449. assert !@record.state?(:idling)
  450. end
  451. def test_should_return_true_for_predicate_if_matches_current_value
  452. assert @record.state?(:parked)
  453. end
  454. def test_should_raise_exception_for_predicate_if_invalid_state_specified
  455. assert_raise(IndexError) { @record.state?(:invalid) }
  456. end
  457. end
  458. class MachineWithNonColumnStateAttributeUndefinedTest < BaseTestCase
  459. def setup
  460. @resource = new_resource do
  461. def initialize
  462. # Skip attribute initialization
  463. @initialized_state_machines = true
  464. super
  465. end
  466. end
  467. @machine = StateMachine::Machine.new(@resource, :status, :initial => 'parked')
  468. @record = @resource.new
  469. end
  470. def test_should_define_a_new_property_for_the_attribute
  471. assert_not_nil @resource.properties[:status]
  472. end
  473. def test_should_define_a_reader_attribute_for_the_attribute
  474. assert @record.respond_to?(:status)
  475. end
  476. def test_should_define_a_writer_attribute_for_the_attribute
  477. assert @record.respond_to?(:status=)
  478. end
  479. def test_should_define_an_attribute_predicate
  480. assert @record.respond_to?(:status?)
  481. end
  482. end
  483. class MachineWithNonColumnStateAttributeDefinedTest < BaseTestCase
  484. def setup
  485. @resource = new_resource do
  486. attr_accessor :status
  487. end
  488. @machine = StateMachine::Machine.new(@resource, :status, :initial => :parked)
  489. @machine.other_states(:idling)
  490. @record = @resource.new
  491. end
  492. def test_should_return_false_for_predicate_if_does_not_match_current_value
  493. assert !@record.status?(:idling)
  494. end
  495. def test_should_return_true_for_predicate_if_matches_current_value
  496. assert @record.status?(:parked)
  497. end
  498. def test_should_raise_exception_for_predicate_if_invalid_state_specified
  499. assert_raise(IndexError) { @record.status?(:invalid) }
  500. end
  501. def test_should_set_initial_state_on_created_object
  502. assert_equal 'parked', @record.status
  503. end
  504. end
  505. class MachineWithInitializedStateTest < BaseTestCase
  506. def setup
  507. @resource = new_resource
  508. @machine = StateMachine::Machine.new(@resource, :initial => :parked)
  509. @machine.state :idling
  510. end
  511. def test_should_allow_nil_initial_state_when_static
  512. @machine.state nil
  513. record = @resource.new(:state => nil)
  514. assert_nil record.state
  515. end
  516. def test_should_allow_nil_initial_state_when_dynamic
  517. @machine.state nil
  518. @machine.initial_state = lambda {:parked}
  519. record = @resource.new(:state => nil)
  520. assert_nil record.state
  521. end
  522. def test_should_allow_different_initial_state_when_static
  523. record = @resource.new(:state => 'idling')
  524. assert_equal 'idling', record.state
  525. end
  526. def test_should_allow_different_initial_state_when_dynamic
  527. @machine.initial_state = lambda {:parked}
  528. record = @resource.new(:state => 'idling')
  529. assert_equal 'idling', record.state
  530. end
  531. if Gem::Version.new(DataMapper::VERSION) >= Gem::Version.new('0.9.8')
  532. def test_should_raise_exception_if_protected
  533. resource = new_resource do
  534. protected :state=
  535. end
  536. machine = StateMachine::Machine.new(resource, :initial => :parked)
  537. machine.state :idling
  538. assert_raise(ArgumentError) { resource.new(:state => 'idling') }
  539. end
  540. end
  541. end
  542. class MachineMultipleTest < BaseTestCase
  543. def setup
  544. @resource = new_resource do
  545. property :status, String
  546. end
  547. @state_machine = StateMachine::Machine.new(@resource, :initial => :parked)
  548. @status_machine = StateMachine::Machine.new(@resource, :status, :initial => :idling)
  549. end
  550. def test_should_should_initialize_each_state
  551. record = @resource.new
  552. assert_equal 'parked', record.state
  553. assert_equal 'idling', record.status
  554. end
  555. end
  556. class MachineWithLoopbackTest < BaseTestCase
  557. def setup
  558. @resource = new_resource do
  559. property :updated_at, DateTime
  560. # Simulate dm-timestamps
  561. before :update do
  562. return unless dirty?
  563. self.updated_at = DateTime.now
  564. end
  565. end
  566. @machine = StateMachine::Machine.new(@resource, :initial => :parked)
  567. @machine.event :park
  568. @record = @resource.create(:updated_at => Time.now - 1)
  569. @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
  570. @timestamp = @record.updated_at
  571. @transition.perform
  572. end
  573. def test_should_not_update_record
  574. assert_equal @timestamp, @record.updated_at
  575. end
  576. end
  577. class MachineWithDirtyAttributesTest < BaseTestCase
  578. def setup
  579. @resource = new_resource
  580. @machine = StateMachine::Machine.new(@resource, :initial => :parked)
  581. @machine.event :ignite
  582. @machine.state :idling
  583. @record = @resource.create
  584. @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
  585. @transition.perform(false)
  586. end
  587. def test_should_include_state_in_changed_attributes
  588. assert_equal({@resource.properties[:state] => 'idling'}, @record.dirty_attributes)
  589. end
  590. def test_should_track_attribute_change
  591. if Gem::Version.new(DataMapper::VERSION) >= Gem::Version.new('0.10.0')
  592. assert_equal({@resource.properties[:state] => 'parked'}, @record.original_attributes)
  593. else
  594. assert_equal({:state => 'parked'}, @record.original_values)
  595. end
  596. end
  597. def test_should_not_reset_changes_on_multiple_transitions
  598. transition = StateMachine::Transition.new(@record, @machine, :ignite, :idling, :idling)
  599. transition.perform(false)
  600. if Gem::Version.new(DataMapper::VERSION) >= Gem::Version.new('0.10.0')
  601. assert_equal({@resource.properties[:state] => 'parked'}, @record.original_attributes)
  602. else
  603. assert_equal({:state => 'parked'}, @record.original_values)
  604. end
  605. end
  606. def test_should_not_have_changes_when_loaded_from_database
  607. record = @resource.get(@record.id)
  608. assert record.dirty_attributes.empty?
  609. end
  610. end
  611. class MachineWithDirtyAttributesDuringLoopbackTest < BaseTestCase
  612. def setup
  613. @resource = new_resource
  614. @machine = StateMachine::Machine.new(@resource, :initial => :parked)
  615. @machine.event :park
  616. @record = @resource.create
  617. @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
  618. @transition.perform(false)
  619. end
  620. def test_should_not_include_state_in_changed_attributes
  621. assert_equal({}, @record.dirty_attributes)
  622. end
  623. end
  624. class MachineWithDirtyAttributesAndCustomAttributeTest < BaseTestCase
  625. def setup
  626. @resource = new_resource do
  627. property :status, String
  628. end
  629. @machine = StateMachine::Machine.new(@resource, :status, :initial => :parked)
  630. @machine.event :ignite
  631. @machine.state :idling
  632. @record = @resource.create
  633. @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
  634. @transition.perform(false)
  635. end
  636. def test_should_include_state_in_changed_attributes
  637. assert_equal({@resource.properties[:status] => 'idling'}, @record.dirty_attributes)
  638. end
  639. def test_should_track_attribute_change
  640. if Gem::Version.new(DataMapper::VERSION) >= Gem::Version.new('0.10.0')
  641. assert_equal({@resource.properties[:status] => 'parked'}, @record.original_attributes)
  642. else
  643. assert_equal({:status => 'parked'}, @record.original_values)
  644. end
  645. end
  646. def test_should_not_reset_changes_on_multiple_transitions
  647. transition = StateMachine::Transition.new(@record, @machine, :ignite, :idling, :idling)
  648. transition.perform(false)
  649. if Gem::Version.new(DataMapper::VERSION) >= Gem::Version.new('0.10.0')
  650. assert_equal({@resource.properties[:status] => 'parked'}, @record.original_attributes)
  651. else
  652. assert_equal({:status => 'parked'}, @record.original_values)
  653. end
  654. end
  655. end
  656. class MachineWithDirtyAttributeAndCustomAttributesDuringLoopbackTest < BaseTestCase
  657. def setup
  658. @resource = new_resource do
  659. property :status, String
  660. end
  661. @machine = StateMachine::Machine.new(@resource, :status, :initial => :parked)
  662. @machine.event :park
  663. @record = @resource.create
  664. @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
  665. @transition.perform(false)
  666. end
  667. def test_should_not_include_state_in_changed_attributes
  668. assert_equal({}, @record.dirty_attributes)
  669. end
  670. end
  671. class MachineWithDirtyAttributeAndStateEventsTest < BaseTestCase
  672. def setup
  673. @resource = new_resource
  674. @machine = StateMachine::Machine.new(@resource, :initial => :parked)
  675. @machine.event :ignite
  676. @record = @resource.create
  677. @record.state_event = 'ignite'
  678. end
  679. def test_should_not_include_state_in_changed_attributes
  680. assert_equal({}, @record.dirty_attributes)
  681. end
  682. def test_should_not_track_attribute_change
  683. if Gem::Version.new(DataMapper::VERSION) >= Gem::Version.new('0.10.0')
  684. assert_equal({}, @record.original_attributes)
  685. else
  686. assert_equal({}, @record.original_values)
  687. end
  688. end
  689. end
  690. class MachineWithoutTransactionsTest < BaseTestCase
  691. def setup
  692. @resource = new_resource
  693. @machine = StateMachine::Machine.new(@resource, :use_transactions => false)
  694. end
  695. def test_should_not_rollback_transaction_if_false
  696. @machine.within_transaction(@resource.new) do
  697. @resource.create
  698. false
  699. end
  700. assert_equal 1, @resource.all.size
  701. end
  702. def test_should_not_rollback_transaction_if_true
  703. @machine.within_transaction(@resource.new) do
  704. @resource.create
  705. true
  706. end
  707. assert_equal 1, @resource.all.size
  708. end
  709. end
  710. begin
  711. if Gem::Version.new(DataMapper::VERSION) >= Gem::Version.new('0.10.3')
  712. require 'dm-transactions'
  713. end
  714. class MachineWithTransactionsTest < BaseTestCase
  715. def setup
  716. @resource = new_resource
  717. @machine = StateMachine::Machine.new(@resource, :use_transactions => true)
  718. end
  719. def test_should_rollback_transaction_if_false
  720. @machine.within_transaction(@resource.new) do
  721. @resource.create
  722. false
  723. end
  724. assert_equal 0, @resource.all.size
  725. end
  726. def test_should_not_rollback_transaction_if_true
  727. @machine.within_transaction(@resource.new) do
  728. @resource.create
  729. true
  730. end
  731. assert_equal 1, @resource.all.size
  732. end
  733. end
  734. rescue LoadError
  735. $stderr.puts "Skipping DataMapper Transaction tests."
  736. end
  737. class MachineWithCallbacksTest < BaseTestCase
  738. def setup
  739. @resource = new_resource
  740. @machine = StateMachine::Machine.new(@resource)
  741. @machine.state :parked, :idling
  742. @machine.event :ignite
  743. @record = @resource.new(:state => 'parked')
  744. @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
  745. end
  746. def test_should_run_before_callbacks
  747. called = false
  748. @machine.before_transition {called = true}
  749. @transition.perform
  750. assert called
  751. end
  752. def test_should_pass_transition_to_before_callbacks_with_one_argument
  753. transition = nil
  754. @machine.before_transition {|arg| transition = arg}
  755. @transition.perform
  756. assert_equal @transition, transition
  757. end
  758. def test_should_pass_transition_to_before_callbacks_with_multiple_arguments
  759. callback_args = nil
  760. @machine.before_transition {|*args| callback_args = args}
  761. @transition.perform
  762. assert_equal [@transition], callback_args
  763. end
  764. def test_should_run_before_callbacks_within_the_context_of_the_record
  765. context = nil
  766. @machine.before_transition {context = self}
  767. @transition.perform
  768. assert_equal @record, context
  769. end
  770. def test_should_run_after_callbacks
  771. called = false
  772. @machine.after_transition {called = true}
  773. @transition.perform
  774. assert called
  775. end
  776. def test_should_pass_transition_to_after_callbacks_with_multiple_arguments
  777. callback_args = nil
  778. @machine.after_transition {|*args| callback_args = args}
  779. @transition.perform
  780. assert_equal [@transition], callback_args
  781. end
  782. def test_should_run_after_callbacks_with_the_context_of_the_record
  783. context = nil
  784. @machine.after_transition {context = self}
  785. @transition.perform
  786. assert_equal @record, context
  787. end
  788. def test_should_run_around_callbacks
  789. before_called = false
  790. after_called = false
  791. ensure_called = 0
  792. @machine.around_transition do |block|
  793. before_called = true
  794. begin
  795. block.call
  796. ensure
  797. ensure_called += 1
  798. end
  799. after_called = true
  800. end
  801. @transition.perform
  802. assert before_called
  803. assert after_called
  804. assert_equal ensure_called, 1
  805. end
  806. def test_should_run_around_callbacks_with_the_context_of_the_record
  807. context = nil
  808. @machine.around_transition {|block| context = self; block.call}
  809. @transition.perform
  810. assert_equal @record, context
  811. end
  812. def test_should_allow_symbolic_callbacks
  813. callback_args = nil
  814. klass = class << @record; self; end
  815. klass.send(:define_method, :after_ignite) do |*args|
  816. callback_args = args
  817. end
  818. @machine.before_transition(:after_ignite)
  819. @transition.perform
  820. assert_equal [@transition], callback_args
  821. end
  822. def test_should_allow_string_callbacks
  823. class << @record
  824. attr_reader :callback_result
  825. end
  826. @machine.before_transition('@callback_result = [1, 2, 3]')
  827. @transition.perform
  828. assert_equal [1, 2, 3], @record.callback_result
  829. end
  830. def test_should_run_in_expected_order
  831. # Avoid Ruby 2.0.0 stack too deep issues
  832. @resource.class_eval do
  833. def valid?(*)
  834. super
  835. end
  836. end
  837. expected = [
  838. :before_transition, :before_validation, :after_validation,
  839. :before_save, :before_create, :after_create, :after_save,
  840. :after_transition
  841. ]
  842. callbacks = []
  843. @resource.before(:valid?) { callbacks << :before_validation }
  844. @resource.after(:valid?) { callbacks << :after_validation }
  845. @resource.before(:save) { callbacks << :before_save }
  846. @resource.before(:create) { callbacks << :before_create }
  847. @resource.after(:create) { callbacks << :after_create }
  848. @resource.after(:save) { callbacks << :after_save }
  849. @machine.before_transition { callbacks << :before_transition }
  850. @machine.after_transition { callbacks << :after_transition }
  851. @transition.perform
  852. assert_equal expected, callbacks
  853. end
  854. end
  855. class MachineWithFailedBeforeCallbacksTest < BaseTestCase
  856. def setup
  857. callbacks = []
  858. @resource = new_resource
  859. @machine = StateMachine::Machine.new(@resource)
  860. @machine.state :parked, :idling
  861. @machine.event :ignite
  862. @machine.before_transition {callbacks << :before_1; throw :halt}
  863. @machine.before_transition {callbacks << :before_2}
  864. @machine.after_transition {callbacks << :after}
  865. @machine.around_transition {|block| callbacks << :around_before; block.call; callbacks << :around_after}
  866. @record = @resource.new(:state => 'parked')
  867. @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
  868. @result = @transition.perform
  869. @callbacks = callbacks
  870. end
  871. def test_should_not_be_successful
  872. assert !@result
  873. end
  874. def test_should_not_change_current_state
  875. assert_equal 'parked', @record.state
  876. end
  877. def test_should_not_run_action
  878. assert @record.respond_to?(:new?) ? @record.new? : @record.new_record?
  879. end
  880. def test_should_not_run_further_callbacks
  881. assert_equal [:before_1], @callbacks
  882. end
  883. end
  884. if Gem::Version.new(DataMapper::VERSION) >= Gem::Version.new('1.0.0')
  885. class MachineNestedActionTest < BaseTestCase
  886. def setup
  887. @callbacks = []
  888. @resource = new_resource
  889. @machine = StateMachine::Machine.new(@resource)
  890. @machine.event :ignite do
  891. transition :parked => :idling
  892. end
  893. @record = @resource.new(:state => 'parked')
  894. end
  895. def test_should_allow_transition_prior_to_creation_if_skipping_action
  896. record = @record
  897. @resource.before(:create) { record.ignite }
  898. result = @record.save
  899. assert_equal true, result
  900. assert_equal "idling", @record.state
  901. @record.reload
  902. assert_equal "idling", @record.state
  903. end
  904. def test_should_not_allow_transition_after_creation
  905. record = @record
  906. @resource.after(:create) { record.ignite(false) }
  907. result = @record.save
  908. assert_equal false, result
  909. end
  910. end
  911. end
  912. class MachineWithFailedActionTest < BaseTestCase
  913. def setup
  914. @resource = new_resource do
  915. before(:create) { throw :halt }
  916. end
  917. @machine = StateMachine::Machine.new(@resource)
  918. @machine.state :parked, :idling
  919. @machine.event :ignite
  920. callbacks = []
  921. @machine.before_transition {callbacks << :before}
  922. @machine.after_transition {callbacks << :after}
  923. @machine.after_failure {callbacks << :after_failure}
  924. @machine.around_transition {|block| callbacks << :around_before; block.call; callbacks << :around_after}
  925. @record = @resource.new(:state => 'parked')
  926. @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
  927. @result = @transition.perform
  928. @callbacks = callbacks
  929. end
  930. def test_should_not_be_successful
  931. assert !@result
  932. end
  933. def test_should_not_change_current_state
  934. assert_equal 'parked', @record.state
  935. end
  936. def test_should_not_save_record
  937. assert @record.respond_to?(:new?) ? @record.new? : @record.new_record?
  938. end
  939. def test_should_run_before_callbacks_and_after_callbacks_with_failures
  940. assert_equal [:before, :around_before, :after_failure], @callbacks
  941. end
  942. end
  943. class MachineWithFailedAfterCallbacksTest < BaseTestCase
  944. def setup
  945. callbacks = []
  946. @resource = new_resource
  947. @machine = StateMachine::Machine.new(@resource)
  948. @machine.state :parked, :idling
  949. @machine.event :ignite
  950. @machine.after_transition {callbacks << :after_1; throw :halt}
  951. @machine.after_transition {callbacks << :after_2}
  952. @machine.around_transition {|block| callbacks << :around_before; block.call; callbacks << :around_after}
  953. @record = @resource.new(:state => 'parked')
  954. @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
  955. @result = @transition.perform
  956. @callbacks = callbacks
  957. end
  958. def test_should_be_successful
  959. assert @result
  960. end
  961. def test_should_change_current_state
  962. assert_equal 'idling', @record.state
  963. end
  964. def test_should_save_record
  965. assert !(@record.respond_to?(:new?) ? @record.new? : @record.new_record?)
  966. end
  967. def test_should_not_run_further_after_callbacks
  968. assert_equal [:around_before, :around_after, :after_1], @callbacks
  969. end
  970. end
  971. begin
  972. require 'dm-validations'
  973. class MachineWithValidationsTest < BaseTestCase
  974. def setup
  975. @resource = new_resource
  976. @machine = StateMachine::Machine.new(@resource)
  977. @machine.state :parked
  978. @record = @resource.new
  979. end
  980. def test_should_invalidate_using_errors
  981. @record.state = 'parked'
  982. @machine.invalidate(@record, :state, :invalid_transition, [[:event, 'park']])
  983. assert_equal ['cannot transition via "park"'], @record.errors.on(:state)
  984. end
  985. def test_should_auto_prefix_custom_attributes_on_invalidation
  986. @machine.invalidate(@record, :event, :invalid)
  987. assert_equal ['is invalid'], @record.errors.on(:state_event)
  988. end
  989. def test_should_clear_errors_on_reset
  990. @record.state = 'parked'
  991. @record.errors.add(:state, 'is invalid')
  992. @machine.reset(@record)
  993. assert_nil @record.errors.on(:id)
  994. end
  995. def test_should_be_valid_if_state_is_known
  996. @record.state = 'parked'
  997. assert @record.valid?
  998. end
  999. def test_should_not_be_valid_if_state_is_unknown
  1000. @record.state = 'invalid'
  1001. assert !@record.valid?
  1002. assert_equal ['is invalid'], @record.errors.on(:state)
  1003. end
  1004. end
  1005. class MachineWithValidationsAndCustomAttributeTest < BaseTestCase
  1006. def setup
  1007. @resource = new_resource
  1008. @machine = StateMachine::Machine.new(@resource, :status, :attribute => :state)
  1009. @machine.state :parked
  1010. @record = @resource.new
  1011. end
  1012. def test_should_add_validation_errors_to_custom_attribute
  1013. @record.state = 'invalid'
  1014. assert !@record.valid?
  1015. assert_equal ['is invalid'], @record.errors.on(:state)
  1016. @record.state = 'parked'
  1017. assert @record.valid?
  1018. end
  1019. end
  1020. class MachineErrorsTest < BaseTestCase
  1021. def setup
  1022. @resource = new_resource
  1023. @machine = StateMachine::Machine.new(@resource)
  1024. @record = @resource.new
  1025. end
  1026. def test_should_be_able_to_describe_current_errors
  1027. @record.errors.add(:id, 'cannot be blank')
  1028. @record.errors.add(:state, 'is invalid')
  1029. assert_equal ['id cannot be blank', 'state is invalid'], @machine.errors_for(@record).split(', ').sort
  1030. end
  1031. def test_should_describe_as_halted_with_no_errors
  1032. assert_equal 'Transition halted', @machine.errors_for(@record)
  1033. end
  1034. end
  1035. class MachineWithStateDrivenValidationsTest < BaseTestCase
  1036. def setup
  1037. @resource = resource = new_resource do
  1038. attr_accessor :seatbelt
  1039. end
  1040. @machine = StateMachine::Machine.new(@resource)
  1041. @machine.state :first_gear, :second_gear do
  1042. if resource.respond_to?(:validates_presence_of)
  1043. validates_presence_of :seatbelt
  1044. else
  1045. validates_present :seatbelt
  1046. end
  1047. end
  1048. @machine.other_states :parked
  1049. end
  1050. def test_should_be_valid_if_validation_fails_outside_state_scope
  1051. record = @resource.new(:state => 'parked', :seatbelt => nil)
  1052. assert record.valid?
  1053. end
  1054. def test_should_be_invalid_if_validation_fails_within_state_scope
  1055. record = @resource.new(:state => 'first_gear', :seatbelt => nil)
  1056. assert !record.valid?
  1057. end
  1058. def test_should_be_valid_if_validation_succeeds_within_state_scope
  1059. record = @resource.new(:state => 'second_gear', :seatbelt => true)
  1060. assert record.valid?
  1061. end
  1062. end
  1063. # See README caveats
  1064. if Gem::Version.new(DataMapper::VERSION) > Gem::Version.new('0.9.6')
  1065. class MachineWithEventAttributesOnValidationTest < BaseTestCase
  1066. def setup
  1067. @resource = new_resource
  1068. @machine = StateMachine::Machine.new(@resource)
  1069. @machine.event :ignite do
  1070. transition :parked => :idling
  1071. end
  1072. @record = @resource.new
  1073. @record.state = 'parked'
  1074. @record.state_event = 'ignite'
  1075. end
  1076. def test_should_fail_if_event_is_invalid
  1077. @record.state_event = 'invalid'
  1078. assert !@record.valid?
  1079. assert_equal ['is invalid'], @record.errors.full_messages
  1080. end
  1081. def test_should_fail_if_event_has_no_transition
  1082. @record.state = 'idling'
  1083. assert !@record.valid?
  1084. assert_equal ['cannot transition when idling'], @record.errors.full_messages
  1085. end
  1086. def test_should_be_successful_if_event_has_transition
  1087. assert @record.valid?
  1088. end
  1089. def test_should_run_before_callbacks
  1090. ran_callback = false
  1091. @machine.before_transition { ran_callback = true }
  1092. @record.valid?
  1093. assert ran_callback
  1094. end
  1095. def test_should_run_around_callbacks_before_yield
  1096. ran_callback = false
  1097. @machine.around_transition {|block| ran_callback = true; block.call }
  1098. begin
  1099. @record.valid?
  1100. rescue ArgumentError
  1101. raise if StateMachine::Transition.pause_supported?
  1102. end
  1103. assert ran_callback
  1104. end
  1105. def test_should_persist_new_state
  1106. @record.valid?
  1107. assert_equal 'idling', @record.state
  1108. end
  1109. def test_should_not_run_after_callbacks
  1110. ran_callback = false
  1111. @machine.after_transition { ran_callback = true }
  1112. @record.valid?
  1113. assert !ran_callback
  1114. end
  1115. def test_should_not_run_after_callbacks_with_failures_disabled_if_validation_fails
  1116. @resource.class_eval do
  1117. attr_accessor :seatbelt
  1118. if respond_to?(:validates_presence_of)
  1119. validates_presence_of :seatbelt
  1120. else
  1121. validates_present :seatbelt
  1122. end
  1123. end
  1124. ran_callback = false
  1125. @machine.after_transition { ran_callback = true }
  1126. @record.valid?
  1127. assert !ran_callback
  1128. end
  1129. def test_should_run_failure_callbacks_if_validation_fails
  1130. @resource.class_eval do
  1131. attr_accessor :seatbelt
  1132. if respond_to?(:validates_presence_of)
  1133. validates_presence_of :seatbelt
  1134. else
  1135. validates_present :seatbelt
  1136. end
  1137. end
  1138. ran_callback = false
  1139. @machine.after_failure { ran_callback = true }
  1140. @record.valid?
  1141. assert ran_callback
  1142. end
  1143. def test_should_not_run_around_callbacks_after_yield
  1144. ran_callback = [false]
  1145. @machine.around_transition {|block| block.call; ran_callback[0] = true }
  1146. begin
  1147. @record.valid?
  1148. rescue ArgumentError
  1149. raise if StateMachine::Transition.pause_supported?
  1150. end
  1151. assert !ran_callback[0]
  1152. end
  1153. def test_should_not_run_around_callbacks_after_yield_with_failures_disabled_if_validation_fails
  1154. @resource.class_eval do
  1155. attr_accessor :seatbelt
  1156. if respond_to?(:validates_presence_of)
  1157. validates_presence_of :seatbelt
  1158. else
  1159. validates_present :seatbelt
  1160. end
  1161. end
  1162. ran_callback = [false]
  1163. @machine.around_transition {|block| block.call; ran_callback[0] = true }
  1164. begin
  1165. @record.valid?
  1166. rescue ArgumentError
  1167. raise if StateMachine::Transition.pause_supported?
  1168. end
  1169. assert !ran_callback[0]
  1170. end
  1171. def test_should_not_run_before_transitions_within_transaction
  1172. @machine.before_transition { self.class.create; throw :halt }
  1173. assert !@record.valid?
  1174. assert_equal 1, @resource.all.size
  1175. end
  1176. end
  1177. class MachineWithEventAttributesOnSaveTest < BaseTestCase
  1178. def setup
  1179. @resource = new_resource
  1180. @machine = StateMachine::Machine.new(@resource)
  1181. @machine.event :ignite do
  1182. transition :parked => :idling
  1183. end
  1184. @record = @resource.new
  1185. @record.state = 'parked'
  1186. @record.state_event = 'ignite'
  1187. end
  1188. def test_should_fail_if_event_is_invalid
  1189. @record.state_event = 'invalid'
  1190. assert !@record.save
  1191. end
  1192. def test_should_fail_if_event_has_no_transition
  1193. @record.state = 'idling'
  1194. assert !@record.save
  1195. end
  1196. def test_should_be_successful_if_event_has_transition
  1197. assert_equal true, @record.save
  1198. end
  1199. def test_should_run_before_callbacks
  1200. ran_callback = false
  1201. @machine.before_transition { ran_callback = true }
  1202. @record.save
  1203. assert ran_callback
  1204. end
  1205. def test_should_run_before_callbacks_once
  1206. before_count = 0
  1207. @machine.before_transition { before_count += 1 }
  1208. @record.save
  1209. assert_equal 1, before_count
  1210. end
  1211. def test_should_run_around_callbacks_before_yield
  1212. ran_callback = false
  1213. @machine.around_transition {|block| ran_callback = true; block.call }
  1214. @record.save
  1215. assert ran_callback
  1216. end
  1217. def test_should_run_around_callbacks_before_yield_once
  1218. around_before_count = 0
  1219. @machine.around_transition {|block| around_before_count += 1; block.call }
  1220. @record.save
  1221. assert_equal 1, around_before_count
  1222. end
  1223. def test_should_persist_new_state
  1224. @record.save
  1225. assert_equal 'idling', @record.state
  1226. end
  1227. def test_should_run_after_callbacks
  1228. ran_callback = false
  1229. @machine.after_transition { ran_callback = true }
  1230. @record.save
  1231. assert ran_callback
  1232. end
  1233. def test_should_not_run_after_callbacks_with_failures_disabled_if_fails
  1234. @resource.before(:create) { throw :halt }
  1235. ran_callback = false
  1236. @machine.after_transition { ran_callback = true }
  1237. @record.save
  1238. assert !ran_callback
  1239. end
  1240. def test_should_run_failure_callbacks_if_fails
  1241. @resource.before(:create) { throw :halt }
  1242. ran_callback = false
  1243. @machine.after_failure { ran_callback = true }
  1244. @record.save
  1245. assert ran_callback
  1246. end
  1247. def test_should_not_run_around_callbacks_with_failures_disabled_if_fails
  1248. @resource.before(:create) { throw :halt }
  1249. ran_callback = [false]
  1250. @machine.around_transition {|block| block.call; ran_callback[0] = true }
  1251. @record.save
  1252. assert !ran_callback[0]
  1253. end
  1254. def test_should_run_around_callbacks_after_yield
  1255. ran_callback = [false]
  1256. @machine.around_transition {|block| block.call; ran_callback[0] = true }
  1257. @record.save
  1258. assert ran_callback[0]
  1259. end
  1260. def test_should_not_run_before_transitions_within_transaction
  1261. @machine.before_transition { self.class.create; throw :halt }
  1262. assert_equal false, @record.save
  1263. assert_equal 1, @resource.all.size
  1264. end
  1265. def test_should_not_run_after_transitions_within_transaction
  1266. @machine.before_transition { self.class.create; throw :halt }
  1267. assert_equal false, @record.save
  1268. assert_equal 1, @resource.all.size
  1269. end
  1270. def test_should_not_run_around_transition_within_transaction
  1271. @machine.around_transition { self.class.create; throw :halt }
  1272. assert_equal false, @record.save
  1273. assert_equal 1, @resource.all.size
  1274. end
  1275. def test_should_allow_additional_transitions_to_new_state_in_after_transitions
  1276. @machine.event :park do
  1277. transition :idling => :parked
  1278. end
  1279. @machine.after_transition(:on => :ignite) { park }
  1280. @record.save
  1281. assert_equal 'parked', @record.state
  1282. @record.reload
  1283. assert_equal 'parked', @record.state
  1284. end
  1285. def test_should_allow_additional_transitions_to_previous_state_in_after_transitions
  1286. @machine.event :shift_up do
  1287. transition :idling => :first_gear
  1288. end
  1289. @machine.after_transition(:on => :ignite) { shift_up }
  1290. @record.save
  1291. assert_equal 'first_gear', @record.state
  1292. @record.reload
  1293. assert_equal 'first_gear', @record.state
  1294. end
  1295. end
  1296. end
  1297. if Gem::Version.new(DataMapper::VERSION) >= Gem::Version.new('0.10.0')
  1298. class MachineWithEventAttributesOnSaveBangTest < BaseTestCase
  1299. def setup
  1300. @resource = new_resource
  1301. @machine = StateMachine::Machine.new(@resource)
  1302. @machine.event :ignite do
  1303. transition :parked => :idling
  1304. end
  1305. @record = @resource.new
  1306. @record.state = 'parked'
  1307. @record.state_event = 'ignite'
  1308. end
  1309. def test_should_fail_if_event_is_invalid
  1310. @r

Large files files are truncated, but you can click here to view the full file