state_manager 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,112 @@
1
+ require 'helper'
2
+
3
+ class ActiveRecordTest < Test::Unit::TestCase
4
+
5
+ class PostStates < StateManager::Base
6
+ state :unsubmitted do
7
+ event :submit, :transitions_to => 'submitted.awaiting_review'
8
+ end
9
+ state :submitted do
10
+ state :awaiting_review do
11
+ event :review, :transitions_to => 'submitted.reviewing'
12
+ end
13
+ state :reviewing do
14
+ event :accept, :transitions_to => 'active'
15
+ event :clarify, :transitions_to => 'submitted.clarifying'
16
+ end
17
+ state :clarifying do
18
+ event :review, :transitions_to => 'submitted.reviewing'
19
+ end
20
+ end
21
+ state :active
22
+ state :rejected
23
+ end
24
+
25
+ class Post < ActiveRecord::Base
26
+ extend StateManager::Resource
27
+ state_manager
28
+ end
29
+
30
+ def exec(sql)
31
+ ActiveRecord::Base.connection.execute sql
32
+ end
33
+
34
+ def setup
35
+ ActiveRecord::Base.establish_connection(
36
+ :adapter => "sqlite3",
37
+ :database => ":memory:" #"tmp/test"
38
+ )
39
+
40
+ ActiveRecord::Schema.define do
41
+ create_table :posts do |t|
42
+ t.integer :id
43
+ t.string :title
44
+ t.string :body
45
+ t.string :state
46
+ end
47
+ end
48
+
49
+ exec "INSERT INTO posts VALUES(1, NULL, NULL, NULL)"
50
+ exec "INSERT INTO posts VALUES(2, NULL, NULL, 'unsubmitted')"
51
+ exec "INSERT INTO posts VALUES(3, NULL, NULL, 'submitted.reviewing')"
52
+ exec "INSERT INTO posts VALUES(4, NULL, NULL, 'submitted.bad_state')"
53
+
54
+ @resource = nil
55
+ end
56
+
57
+ def teardown
58
+ ActiveRecord::Base.connection.disconnect!
59
+ end
60
+
61
+ def test_adapter_included
62
+ @resource = Post.find(1)
63
+ assert @resource.is_a?(StateManager::Adapters::ActiveRecord::ResourceMethods)
64
+ assert @resource.state_manager.is_a?(StateManager::Adapters::ActiveRecord::ManagerMethods)
65
+ end
66
+
67
+ def test_persist_initial_state
68
+ @resource = Post.find(1)
69
+ assert_state 'unsubmitted'
70
+ assert !@resource.changed?, "state should have been persisted"
71
+ end
72
+
73
+ def test_initial_state_value
74
+ @resource = Post.find(3)
75
+ assert_state 'submitted.reviewing'
76
+ end
77
+
78
+ def test_validate_nil_state
79
+ @resource = Post.find(1)
80
+ assert !@resource.state
81
+ @resource.save
82
+ assert_state 'unsubmitted'
83
+ end
84
+
85
+ def test_validate_invalid_state
86
+ @resource = Post.find(4)
87
+ assert_equal 'submitted.bad_state', @resource.state
88
+ @resource.save
89
+ assert_state 'unsubmitted'
90
+ end
91
+
92
+ def test_scopes
93
+ exec "INSERT INTO posts VALUES(5, NULL, NULL, 'submitted.reviewing')"
94
+ exec "INSERT INTO posts VALUES(6, NULL, NULL, 'submitted.reviewing')"
95
+ exec "INSERT INTO posts VALUES(7, NULL, NULL, 'submitted.reviewing')"
96
+ exec "INSERT INTO posts VALUES(8, NULL, NULL, 'submitted.reviewing')"
97
+ exec "INSERT INTO posts VALUES(9, NULL, NULL, 'submitted.clarifying')"
98
+ exec "INSERT INTO posts VALUES(10, NULL, NULL, 'submitted.clarifying')"
99
+
100
+ exec "INSERT INTO posts VALUES(11, NULL, NULL, 'active')"
101
+ exec "INSERT INTO posts VALUES(12, NULL, NULL, 'active')"
102
+ exec "INSERT INTO posts VALUES(13, NULL, NULL, 'active')"
103
+ exec "INSERT INTO posts VALUES(14, NULL, NULL, 'active')"
104
+
105
+ # +1 from setup
106
+ assert_equal 1, Post.unsubmitted.count
107
+ # +2 from setup
108
+ assert_equal 8, Post.submitted.count
109
+ assert_equal 4, Post.active.count
110
+ assert_equal 0, Post.rejected.count
111
+ end
112
+ end
@@ -0,0 +1,132 @@
1
+ require 'helper'
2
+
3
+ class BasicTest < Test::Unit::TestCase
4
+
5
+ class PostStates < StateManager::Base
6
+ state :unsubmitted do
7
+ event :submit, :transitions_to => 'submitted.awaiting_review'
8
+ end
9
+ state :submitted do
10
+ state :awaiting_review do
11
+ event :review, :transitions_to => 'submitted.reviewing'
12
+ end
13
+ state :reviewing do
14
+ event :accept, :transitions_to => 'active'
15
+ event :clarify, :transitions_to => 'submitted.clarifying'
16
+ end
17
+ state :clarifying do
18
+ event :review, :transitions_to => 'submitted.reviewing'
19
+ end
20
+ end
21
+ state :active
22
+ state :rejected
23
+ end
24
+
25
+ class Post
26
+ attr_accessor :state
27
+ extend StateManager::Resource
28
+ state_manager :state
29
+ end
30
+
31
+ class PostWithInitialState
32
+ attr_accessor :state
33
+ extend StateManager::Resource
34
+ state_manager :state, PostStates do
35
+ initial_state 'submitted.awaiting_review'
36
+ end
37
+ end
38
+
39
+ class PostWithCustomProperty
40
+ attr_accessor :workflow_state
41
+ extend StateManager::Resource
42
+ state_manager :workflow_state, PostStates
43
+ end
44
+
45
+ def setup
46
+ @resource = Post.new
47
+ end
48
+
49
+ def test_state_manager_class_initialized
50
+ assert @resource.state_manager.is_a?(PostStates), "state manager should have been initialized"
51
+ end
52
+
53
+ def test_initial_states
54
+ assert_state 'unsubmitted', "initial state should be set to the default"
55
+
56
+ @resource = PostWithInitialState.new
57
+
58
+ assert_state 'submitted.awaiting_review', "initial state should be set to specified initial state"
59
+
60
+ @resource = Post.new
61
+ @resource.state = 'active'
62
+
63
+ assert_state 'active', "initial state should be read from resource"
64
+
65
+ @resource = PostWithInitialState.new
66
+ @resource.state = ''
67
+
68
+ assert_state 'submitted.awaiting_review', "initial state should be set to specified initial state"
69
+ end
70
+
71
+ def test_context
72
+ @resource.state_manager.context[:user] = 'brogrammer'
73
+ assert_equal 'brogrammer', @resource.state_manager.context[:user]
74
+ end
75
+
76
+ def test_state_changes
77
+ @resource.state_manager.transition_to 'submitted.clarifying'
78
+
79
+ assert_state 'submitted.clarifying', 'state should have transitioned'
80
+ assert_equal @resource.state, 'submitted.clarifying', 'state should have been written'
81
+
82
+ @resource.state_manager.transition_to 'reviewing'
83
+
84
+ assert_state 'submitted.reviewing', 'state should transition with shorthand sibling name'
85
+
86
+ @resource.state_manager.transition_to 'rejected'
87
+
88
+ assert_state 'rejected', 'state should have transitioned'
89
+
90
+ assert_raise StateManager::StateNotFound do
91
+ @resource.state_manager.transition_to 'reviewing'
92
+ end
93
+ end
94
+
95
+ def test_events
96
+ @resource.state_manager.send_event! :submit
97
+
98
+ assert_equal @resource.state_manager.current_state.path, 'submitted.awaiting_review', 'state should have transitioned'
99
+ assert_equal @resource.state, 'submitted.awaiting_review', 'state should have been written'
100
+
101
+ assert_raise StateManager::InvalidEvent do
102
+ @resource.state_manager.send_event! :submit
103
+ end
104
+ assert_equal @resource.state, 'submitted.awaiting_review', 'state should not have changed'
105
+
106
+ @resource.state_manager.send_event! :review
107
+ end
108
+
109
+ def test_alternate_property
110
+ @resource = PostWithCustomProperty.new
111
+ assert_state 'unsubmitted', @resource.workflow_state_manager
112
+
113
+ @resource.workflow_state_manager.send_event! :submit
114
+
115
+ assert_equal @resource.workflow_state_manager.current_state.path, 'submitted.awaiting_review', 'state should have transitioned'
116
+ assert_equal @resource.workflow_state, 'submitted.awaiting_review', 'state should have been written'
117
+
118
+ assert_raise StateManager::InvalidEvent do
119
+ @resource.workflow_state_manager.send_event! :submit
120
+ end
121
+ assert_equal @resource.workflow_state, 'submitted.awaiting_review', 'state should not have changed'
122
+
123
+ @resource.workflow_state_manager.send_event! :review
124
+ end
125
+
126
+ def test_can_only_transition_to_leaf
127
+ assert_raise StateManager::InvalidTransition do
128
+ @resource.state_manager.transition_to('submitted')
129
+ end
130
+ end
131
+
132
+ end
@@ -0,0 +1,132 @@
1
+ require 'helper'
2
+
3
+ class DefinitionTest < Test::Unit::TestCase
4
+
5
+ class CommentStates < StateManager::Base
6
+ attr_accessor :accept_reason, :reject_reason
7
+
8
+ class Submitted < StateManager::State
9
+
10
+ state :awaiting_review do
11
+ event :review, :transitions_to => 'submitted.reviewing'
12
+ end
13
+ state :reviewing do
14
+ event :accept, :transitions_to => 'active'
15
+ event :clarify, :transitions_to => 'submitted.clarifying'
16
+ end
17
+ state :clarifying do
18
+ event :review, :transitions_to => 'submitted.reviewing'
19
+ end
20
+
21
+ class Reviewing
22
+
23
+ def accept(reason)
24
+ state_manager.accept_reason = reason
25
+ end
26
+
27
+ end
28
+ end
29
+
30
+ state :unsubmitted do
31
+ end
32
+ state :submitted do
33
+ end
34
+ state :active
35
+ state :rejected
36
+
37
+ event :reject
38
+
39
+ def accept(reason)
40
+ state_manager.accept_reason = 'bad value'
41
+ end
42
+
43
+ def reject(reason)
44
+ self.reject_reason = reason
45
+ transition_to :rejected
46
+ end
47
+
48
+ class Unsubmitted
49
+
50
+ event :submit, :transitions_to => 'submitted.awaiting_review'
51
+
52
+ end
53
+
54
+ class_attribute :_resource_class
55
+ def self.added_to_resource(klass, property, options)
56
+ self._resource_class = klass
57
+ end
58
+
59
+ end
60
+
61
+ class Comment
62
+ attr_accessor :state
63
+ extend StateManager::Resource
64
+ state_manager
65
+ end
66
+
67
+ class ItemStates < StateManager::Base
68
+ state :default
69
+ end
70
+
71
+ class Item
72
+ attr_accessor :state
73
+ extend StateManager::Resource
74
+ state_manager
75
+ end
76
+
77
+ class BaseStateManager < StateManager::Base
78
+ end
79
+
80
+ class ArticleListingStates < BaseStateManager
81
+ state :default
82
+ end
83
+
84
+ class ArticleListing
85
+ attr_accessor :state
86
+ extend StateManager::Resource
87
+ state_manager
88
+ end
89
+
90
+ def setup
91
+ @resource = Comment.new
92
+ end
93
+
94
+ def test_event_in_separate_class_definition
95
+ @resource.submit!
96
+ assert_state 'submitted.awaiting_review'
97
+ end
98
+
99
+ def test_states_in_separate_class_definition
100
+ @resource.submit!
101
+
102
+ assert_state 'submitted.awaiting_review'
103
+
104
+ @resource.review!
105
+
106
+ assert_state 'submitted.reviewing'
107
+
108
+ @resource.accept!('hipster')
109
+
110
+ assert_equal 'hipster', @resource.state_manager.accept_reason
111
+ assert_state 'active'
112
+
113
+ @resource.reject!('not a hipster')
114
+
115
+ assert_equal 'not a hipster', @resource.state_manager.reject_reason
116
+
117
+ assert_state 'rejected'
118
+ end
119
+
120
+ def test_added_to_resource_callback
121
+ assert_equal Comment, @resource.state_manager.class._resource_class
122
+ end
123
+
124
+ def test_resource_accessor
125
+ assert @resource.state_manager.class.method_defined?(:comment)
126
+ assert @resource.state_manager.class.specification.states.values.first.method_defined?(:comment)
127
+ assert !@resource.state_manager.class.method_defined?(:item), 'accessor for other resource should not be defined'
128
+ assert !@resource.state_manager.class.method_defined?(:article_listing), 'accessor for other resource should not be defined'
129
+ assert ArticleListing.new.state_manager.class.method_defined?(:article_listing)
130
+ end
131
+
132
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,29 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'test/unit'
11
+
12
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
13
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
14
+ require 'pry'
15
+ require 'delayed_job_active_record'
16
+ require 'state_manager'
17
+
18
+ class Test::Unit::TestCase
19
+
20
+ def assert_state(path, state_manager=nil, message=nil)
21
+ if state_manager.is_a? String
22
+ message = state_manager
23
+ state_manager = nil
24
+ end
25
+ state_manager ||= @resource.state_manager
26
+ assert_equal path, state_manager.current_state.path
27
+ end
28
+
29
+ end
@@ -0,0 +1,61 @@
1
+ require 'helper'
2
+
3
+ class HelpersTest < Test::Unit::TestCase
4
+
5
+ class ItemStates < StateManager::Base
6
+ state :default do
7
+ event :do_inner, :transitions_to => 'root.outer1.inner'
8
+ end
9
+ state :root do
10
+ state :outer1 do
11
+ event :next, :transitions_to => 'outer2.inner'
12
+ state :inner do
13
+ event :next, :transitions_to => 'inner2'
14
+ end
15
+ state :inner2
16
+ end
17
+ state :outer2 do
18
+ state :inner do
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ class Item
25
+ attr_accessor :state
26
+ extend StateManager::Resource
27
+ state_manager
28
+ end
29
+
30
+ def setup
31
+ @resource = Item.new
32
+ end
33
+
34
+ def test_helpers
35
+ assert @resource.default?
36
+ assert !@resource.root?
37
+ assert !@resource.root_outer1?
38
+ assert !@resource.root_outer1_inner?
39
+ assert @resource.can_do_inner?
40
+ assert !@resource.can_next?
41
+
42
+ @resource.do_inner!
43
+
44
+ assert @resource.root?
45
+ assert @resource.root_outer1?
46
+ assert @resource.root_outer1_inner?
47
+ assert !@resource.root_outer2_inner?
48
+ assert @resource.can_next?
49
+
50
+ @resource.next!
51
+
52
+ assert @resource.root_outer1_inner2?
53
+ assert @resource.can_next?
54
+
55
+ @resource.next!
56
+
57
+ assert @resource.root_outer2_inner?
58
+ assert !@resource.can_next?
59
+ end
60
+
61
+ end
@@ -0,0 +1,131 @@
1
+ require 'helper'
2
+ require 'timecop'
3
+
4
+ class DelayedJobTest < Test::Unit::TestCase
5
+
6
+ class ProjectStates < StateManager::Base
7
+ initial_state 'unsubmitted.initial'
8
+ state :unsubmitted do
9
+ event :submit, :transitions_to => 'submitted'
10
+ state :initial do
11
+ event :remind, :transitions_to => 'reminded', :delay => 2.hours
12
+ end
13
+ state :reminded
14
+ end
15
+ state :submitted do
16
+ event :accept, :transitions_to => 'accepted'
17
+ event :auto_accept, :transitions_to => 'accepted', :delay => 1.day
18
+ event :reject, :transitions_to => 'rejected'
19
+ end
20
+ state :accepted do
21
+ event :remind, :transitions_to => 'rejected'
22
+ end
23
+ state :rejected
24
+ end
25
+
26
+ def exec(sql)
27
+ ActiveRecord::Base.connection.execute sql
28
+ end
29
+
30
+ def setup
31
+ ActiveRecord::Base.establish_connection(
32
+ :adapter => "sqlite3",
33
+ :database => ":memory:" #"tmp/test"
34
+ )
35
+
36
+ ActiveRecord::Schema.define do
37
+ create_table :projects do |t|
38
+ t.integer :id
39
+ t.string :title
40
+ t.string :state
41
+ end
42
+
43
+ create_table :delayed_jobs, :force => true do |table|
44
+ table.integer :priority, :default => 0 # Allows some jobs to jump to the front of the queue
45
+ table.integer :attempts, :default => 0 # Provides for retries, but still fail eventually.
46
+ table.text :handler # YAML-encoded string of the object that will do work
47
+ table.text :last_error # reason for last failure (See Note below)
48
+ table.datetime :run_at # When to run. Could be Time.zone.now for immediately, or sometime in the future.
49
+ table.datetime :locked_at # Set when a client is working on this object
50
+ table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead)
51
+ table.string :locked_by # Who is working on this object (if locked)
52
+ table.string :queue # The name of the queue this job is in
53
+ table.timestamps
54
+ end
55
+ end
56
+
57
+ exec "INSERT INTO projects VALUES(1, 'Project 1', NULL)"
58
+
59
+ end
60
+
61
+ class Project < ActiveRecord::Base
62
+ extend StateManager::Resource
63
+ state_manager
64
+ end
65
+
66
+ def teardown
67
+ ActiveRecord::Base.connection.disconnect!
68
+ Timecop.return
69
+ end
70
+
71
+
72
+ # Convince delayed job that the duration has passed and perform any jobs that
73
+ # need doing
74
+ def time_warp(duration)
75
+ Timecop.travel(duration.from_now)
76
+ Delayed::Worker.new.work_off
77
+ end
78
+
79
+ def test_delayed_event
80
+ @resource = Project.find(1)
81
+
82
+ assert_state 'unsubmitted.initial'
83
+ assert_equal 1, Delayed::Job.count
84
+
85
+ time_warp(4.hours)
86
+
87
+ assert_equal 0, Delayed::Job.count
88
+ @resource.reload
89
+ assert_state 'unsubmitted.reminded'
90
+
91
+ @resource.submit!
92
+
93
+ assert_state 'submitted'
94
+
95
+ time_warp(1.hour)
96
+
97
+ assert_state 'submitted', 'should not have transitioned yet'
98
+ end
99
+
100
+ def test_expired_event
101
+ @resource = Project.find(1)
102
+
103
+ assert_state 'unsubmitted.initial'
104
+ assert_equal 1, Delayed::Job.count
105
+
106
+ @resource.submit!
107
+
108
+ assert_state 'submitted'
109
+
110
+ time_warp(4.hours)
111
+
112
+ assert_state 'submitted', 'should not have transitioned'
113
+ end
114
+
115
+ def test_event_name_clashes
116
+ @resource = Project.find(1)
117
+
118
+ assert_state 'unsubmitted.initial'
119
+ assert_equal 1, Delayed::Job.count
120
+
121
+ @resource.submit!
122
+ @resource.accept!
123
+
124
+ assert_equal 2, Delayed::Job.count
125
+
126
+ time_warp(1.month)
127
+
128
+ assert_state 'accepted', 'remind event should not have been triggered'
129
+ end
130
+
131
+ end