state_manager 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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