statesman 1.1.0 → 1.2.0
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.
- checksums.yaml +4 -4
- data/.rubocop.yml +7 -1
- data/.travis.yml +19 -4
- data/CHANGELOG.md +30 -0
- data/README.md +11 -35
- data/lib/generators/statesman/add_constraints_to_most_recent_generator.rb +28 -0
- data/lib/generators/statesman/add_most_recent_generator.rb +25 -0
- data/lib/generators/statesman/generator_helpers.rb +6 -2
- data/lib/generators/statesman/templates/add_constraints_to_most_recent_migration.rb.erb +13 -0
- data/lib/generators/statesman/templates/add_most_recent_migration.rb.erb +9 -0
- data/lib/generators/statesman/templates/create_migration.rb.erb +4 -3
- data/lib/generators/statesman/templates/update_migration.rb.erb +5 -4
- data/lib/statesman.rb +1 -0
- data/lib/statesman/adapters/active_record.rb +23 -8
- data/lib/statesman/adapters/active_record_queries.rb +62 -15
- data/lib/statesman/railtie.rb +9 -0
- data/lib/statesman/version.rb +1 -1
- data/lib/tasks/statesman.rake +49 -0
- data/spec/fixtures/add_constraints_to_most_recent_for_bacon_transitions.rb +13 -0
- data/spec/fixtures/add_most_recent_to_bacon_transitions.rb +9 -0
- data/spec/generators/statesman/active_record_transition_generator_spec.rb +0 -2
- data/spec/generators/statesman/add_constraints_to_most_recent_generator_spec.rb +38 -0
- data/spec/generators/statesman/add_most_recent_generator_spec.rb +35 -0
- data/spec/generators/statesman/migration_generator_spec.rb +10 -1
- data/spec/generators/statesman/mongoid_transition_generator_spec.rb +0 -2
- data/spec/spec_helper.rb +22 -7
- data/spec/statesman/adapters/active_record_queries_spec.rb +110 -28
- data/spec/statesman/adapters/active_record_spec.rb +61 -31
- data/spec/statesman/adapters/mongoid_spec.rb +8 -17
- data/spec/statesman/adapters/shared_examples.rb +10 -17
- data/spec/statesman/callback_spec.rb +2 -6
- data/spec/statesman/config_spec.rb +2 -5
- data/spec/statesman/guard_spec.rb +3 -9
- data/spec/statesman/machine_spec.rb +91 -129
- data/spec/support/active_record.rb +35 -4
- data/spec/support/generators_shared_examples.rb +1 -4
- data/statesman.gemspec +5 -3
- metadata +52 -10
@@ -2,18 +2,14 @@ require "spec_helper"
|
|
2
2
|
require "statesman/adapters/shared_examples"
|
3
3
|
require "statesman/exceptions"
|
4
4
|
|
5
|
-
describe Statesman::Adapters::ActiveRecord do
|
5
|
+
describe Statesman::Adapters::ActiveRecord, active_record: true do
|
6
6
|
before do
|
7
7
|
prepare_model_table
|
8
8
|
prepare_transitions_table
|
9
9
|
end
|
10
10
|
|
11
11
|
before { MyActiveRecordModelTransition.serialize(:metadata, JSON) }
|
12
|
-
let(:observer)
|
13
|
-
result = double(Statesman::Machine)
|
14
|
-
allow(result).to receive(:execute)
|
15
|
-
result
|
16
|
-
end
|
12
|
+
let(:observer) { double(Statesman::Machine, execute: nil) }
|
17
13
|
let(:model) { MyActiveRecordModel.create(current_state: :pending) }
|
18
14
|
it_behaves_like "an adapter", described_class, MyActiveRecordModelTransition
|
19
15
|
|
@@ -24,11 +20,12 @@ describe Statesman::Adapters::ActiveRecord do
|
|
24
20
|
allow(metadata_column).to receive_messages(sql_type: '')
|
25
21
|
allow(MyActiveRecordModelTransition).to receive_messages(columns_hash:
|
26
22
|
{ 'metadata' => metadata_column })
|
27
|
-
if ::ActiveRecord.gem_version
|
23
|
+
if ::ActiveRecord.respond_to?(:gem_version) &&
|
24
|
+
::ActiveRecord.gem_version >= Gem::Version.new('4.2.0.a')
|
28
25
|
allow(metadata_column).to receive_messages(cast_type: '')
|
29
26
|
else
|
30
|
-
allow(MyActiveRecordModelTransition)
|
31
|
-
|
27
|
+
allow(MyActiveRecordModelTransition).
|
28
|
+
to receive_messages(serialized_attributes: {})
|
32
29
|
end
|
33
30
|
end
|
34
31
|
|
@@ -46,16 +43,17 @@ describe Statesman::Adapters::ActiveRecord do
|
|
46
43
|
allow(metadata_column).to receive_messages(sql_type: 'json')
|
47
44
|
allow(MyActiveRecordModelTransition).to receive_messages(columns_hash:
|
48
45
|
{ 'metadata' => metadata_column })
|
49
|
-
if ::ActiveRecord.gem_version
|
46
|
+
if ::ActiveRecord.respond_to?(:gem_version) &&
|
47
|
+
::ActiveRecord.gem_version >= Gem::Version.new('4.2.0.a')
|
50
48
|
serialized_type = ::ActiveRecord::Type::Serialized.new(
|
51
49
|
'', ::ActiveRecord::Coders::JSON
|
52
50
|
)
|
53
|
-
expect(metadata_column)
|
54
|
-
|
55
|
-
|
51
|
+
expect(metadata_column).
|
52
|
+
to receive(:cast_type).
|
53
|
+
and_return(serialized_type)
|
56
54
|
else
|
57
|
-
expect(MyActiveRecordModelTransition)
|
58
|
-
|
55
|
+
expect(MyActiveRecordModelTransition).
|
56
|
+
to receive_messages(serialized_attributes: { 'metadata' => '' })
|
59
57
|
end
|
60
58
|
end
|
61
59
|
|
@@ -83,15 +81,15 @@ describe Statesman::Adapters::ActiveRecord do
|
|
83
81
|
adapter2.create(:x, :y)
|
84
82
|
adapter.last
|
85
83
|
adapter2.create(:y, :z)
|
86
|
-
expect { adapter.create(:y, :z) }
|
87
|
-
|
84
|
+
expect { adapter.create(:y, :z) }.
|
85
|
+
to raise_exception(Statesman::TransitionConflictError)
|
88
86
|
end
|
89
87
|
end
|
90
88
|
|
91
89
|
context "when other exceptions occur" do
|
92
90
|
before do
|
93
|
-
allow_any_instance_of(MyActiveRecordModelTransition)
|
94
|
-
|
91
|
+
allow_any_instance_of(MyActiveRecordModelTransition).
|
92
|
+
to receive(:save!).and_raise(error)
|
95
93
|
end
|
96
94
|
|
97
95
|
context "ActiveRecord::RecordNotUnique unrelated to this transition" do
|
@@ -104,6 +102,44 @@ describe Statesman::Adapters::ActiveRecord do
|
|
104
102
|
it { is_expected.to raise_exception(StandardError) }
|
105
103
|
end
|
106
104
|
end
|
105
|
+
|
106
|
+
context "when the transition_class has a most_recent column" do
|
107
|
+
subject { create }
|
108
|
+
|
109
|
+
context "with no previous transition" do
|
110
|
+
its(:most_recent) { is_expected.to eq(true) }
|
111
|
+
end
|
112
|
+
|
113
|
+
context "with a previous transition" do
|
114
|
+
let!(:previous_transition) { adapter.create(from, to) }
|
115
|
+
its(:most_recent) { is_expected.to eq(true) }
|
116
|
+
|
117
|
+
it "updates the previous transition's most_recent flag" do
|
118
|
+
expect { create }.
|
119
|
+
to change { previous_transition.reload.most_recent }.
|
120
|
+
from(true).to(false)
|
121
|
+
end
|
122
|
+
|
123
|
+
context "and the parent model is updated in a callback" do
|
124
|
+
before do
|
125
|
+
allow(observer).to receive(:execute) do |phase|
|
126
|
+
if phase == :before
|
127
|
+
model.update_attributes!(current_state: :ready)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
it "doesn't save the transition too early" do
|
133
|
+
expect { create }.to_not raise_exception
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
context "when the transition_class doesn't have a most_recent column" do
|
140
|
+
before { drop_most_recent_column }
|
141
|
+
it { is_expected.to_not raise_exception }
|
142
|
+
end
|
107
143
|
end
|
108
144
|
|
109
145
|
describe "#last" do
|
@@ -111,18 +147,14 @@ describe Statesman::Adapters::ActiveRecord do
|
|
111
147
|
described_class.new(MyActiveRecordModelTransition, model, observer)
|
112
148
|
end
|
113
149
|
|
114
|
-
before
|
115
|
-
adapter.create(:x, :y)
|
116
|
-
end
|
150
|
+
before { adapter.create(:x, :y) }
|
117
151
|
|
118
152
|
context "with a previously looked up transition" do
|
119
|
-
before
|
120
|
-
adapter.last
|
121
|
-
end
|
153
|
+
before { adapter.last }
|
122
154
|
|
123
155
|
it "caches the transition" do
|
124
|
-
expect_any_instance_of(MyActiveRecordModel)
|
125
|
-
|
156
|
+
expect_any_instance_of(MyActiveRecordModel).
|
157
|
+
to receive(:my_active_record_model_transitions).never
|
126
158
|
adapter.last
|
127
159
|
end
|
128
160
|
|
@@ -135,10 +167,8 @@ describe Statesman::Adapters::ActiveRecord do
|
|
135
167
|
end
|
136
168
|
|
137
169
|
context "with a pre-fetched transition history" do
|
138
|
-
before
|
139
|
-
|
140
|
-
model.my_active_record_model_transitions.load_target
|
141
|
-
end
|
170
|
+
before { adapter.create(:x, :y) }
|
171
|
+
before { model.my_active_record_model_transitions.load_target }
|
142
172
|
|
143
173
|
it "doesn't query the database" do
|
144
174
|
expect(MyActiveRecordModelTransition).not_to receive(:connection)
|
@@ -5,23 +5,16 @@ require "support/mongoid"
|
|
5
5
|
require "mongoid"
|
6
6
|
|
7
7
|
describe Statesman::Adapters::Mongoid, mongo: true do
|
8
|
-
|
9
|
-
|
10
|
-
Mongoid.purge!
|
11
|
-
end
|
12
|
-
let(:observer) do
|
13
|
-
result = double(Statesman::Machine)
|
14
|
-
allow(result).to receive(:execute)
|
15
|
-
result
|
16
|
-
end
|
8
|
+
after { Mongoid.purge! }
|
9
|
+
let(:observer) { double(Statesman::Machine, execute: nil) }
|
17
10
|
let(:model) { MyMongoidModel.create(current_state: :pending) }
|
18
11
|
it_behaves_like "an adapter", described_class, MyMongoidModelTransition
|
19
12
|
|
20
13
|
describe "#initialize" do
|
21
14
|
context "with unserialized metadata" do
|
22
15
|
before do
|
23
|
-
allow_any_instance_of(described_class)
|
24
|
-
|
16
|
+
allow_any_instance_of(described_class).
|
17
|
+
to receive_messages(transition_class_hash_fields: [])
|
25
18
|
end
|
26
19
|
|
27
20
|
it "raises an exception if metadata is not serialized" do
|
@@ -39,14 +32,12 @@ describe Statesman::Adapters::Mongoid, mongo: true do
|
|
39
32
|
end
|
40
33
|
|
41
34
|
context "with a previously looked up transition" do
|
42
|
-
before
|
43
|
-
|
44
|
-
adapter.last
|
45
|
-
end
|
35
|
+
before { adapter.create(:x, :y) }
|
36
|
+
before { adapter.last }
|
46
37
|
|
47
38
|
it "caches the transition" do
|
48
|
-
expect_any_instance_of(MyMongoidModel)
|
49
|
-
|
39
|
+
expect_any_instance_of(MyMongoidModel).
|
40
|
+
to receive(:my_mongoid_model_transitions).never
|
50
41
|
adapter.last
|
51
42
|
end
|
52
43
|
|
@@ -12,11 +12,7 @@ require "spec_helper"
|
|
12
12
|
# last: Returns the latest transition history item
|
13
13
|
#
|
14
14
|
shared_examples_for "an adapter" do |adapter_class, transition_class|
|
15
|
-
let(:observer)
|
16
|
-
result = double(Statesman::Machine)
|
17
|
-
allow(result).to receive(:execute)
|
18
|
-
result
|
19
|
-
end
|
15
|
+
let(:observer) { double(Statesman::Machine, execute: nil) }
|
20
16
|
let(:adapter) { adapter_class.new(transition_class, model, observer) }
|
21
17
|
|
22
18
|
describe "#initialize" do
|
@@ -55,8 +51,8 @@ shared_examples_for "an adapter" do |adapter_class, transition_class|
|
|
55
51
|
|
56
52
|
context "with before callbacks" do
|
57
53
|
it "is called before the state transition" do
|
58
|
-
expect(observer).to receive(:execute)
|
59
|
-
|
54
|
+
expect(observer).to receive(:execute).
|
55
|
+
with(:before, anything, anything, anything) {
|
60
56
|
expect(adapter.history.length).to eq(0)
|
61
57
|
}.once
|
62
58
|
adapter.create(from, to)
|
@@ -66,9 +62,8 @@ shared_examples_for "an adapter" do |adapter_class, transition_class|
|
|
66
62
|
|
67
63
|
context "with after callbacks" do
|
68
64
|
it "is called after the state transition" do
|
69
|
-
expect(observer).to receive(:execute)
|
70
|
-
|
71
|
-
|_phase, _from_state, _to_state, transition|
|
65
|
+
expect(observer).to receive(:execute).
|
66
|
+
with(:after, anything, anything, anything) { |_, _, _, transition|
|
72
67
|
expect(adapter.last).to eq(transition)
|
73
68
|
}.once
|
74
69
|
adapter.create(from, to)
|
@@ -77,9 +72,8 @@ shared_examples_for "an adapter" do |adapter_class, transition_class|
|
|
77
72
|
it "exposes the new transition for subsequent transitions" do
|
78
73
|
adapter.create(from, to)
|
79
74
|
|
80
|
-
expect(observer).to receive(:execute)
|
81
|
-
|
82
|
-
|_phase, _from_state, _to_state, transition|
|
75
|
+
expect(observer).to receive(:execute).
|
76
|
+
with(:after, anything, anything, anything) { |_, _, _, transition|
|
83
77
|
expect(adapter.last).to eq(transition)
|
84
78
|
}.once
|
85
79
|
adapter.create(to, there)
|
@@ -104,16 +98,15 @@ shared_examples_for "an adapter" do |adapter_class, transition_class|
|
|
104
98
|
context "sorting" do
|
105
99
|
let!(:transition2) { adapter.create(:x, :y) }
|
106
100
|
subject { adapter.history }
|
101
|
+
|
107
102
|
it { is_expected.to eq(adapter.history.sort_by(&:sort_key)) }
|
108
103
|
end
|
109
104
|
end
|
110
105
|
end
|
111
106
|
|
112
107
|
describe "#last" do
|
113
|
-
before
|
114
|
-
|
115
|
-
adapter.create(:y, :z)
|
116
|
-
end
|
108
|
+
before { adapter.create(:x, :y) }
|
109
|
+
before { adapter.create(:y, :z) }
|
117
110
|
subject { adapter.last }
|
118
111
|
|
119
112
|
it { is_expected.to be_a(transition_class) }
|
@@ -61,9 +61,7 @@ describe Statesman::Callback do
|
|
61
61
|
end
|
62
62
|
|
63
63
|
context "with any to and any from value on the callback" do
|
64
|
-
let(:callback)
|
65
|
-
Statesman::Callback.new(callback: cb_lambda)
|
66
|
-
end
|
64
|
+
let(:callback) { Statesman::Callback.new(callback: cb_lambda) }
|
67
65
|
let(:from) { :x }
|
68
66
|
let(:to) { :y }
|
69
67
|
|
@@ -93,9 +91,7 @@ describe Statesman::Callback do
|
|
93
91
|
end
|
94
92
|
|
95
93
|
context "with any to value on the callback" do
|
96
|
-
let(:callback)
|
97
|
-
Statesman::Callback.new(from: :x, callback: cb_lambda)
|
98
|
-
end
|
94
|
+
let(:callback) { Statesman::Callback.new(from: :x, callback: cb_lambda) }
|
99
95
|
let(:to) { :y }
|
100
96
|
|
101
97
|
context "and an allowed to value" do
|
@@ -2,16 +2,13 @@ require "spec_helper"
|
|
2
2
|
|
3
3
|
describe Statesman::Config do
|
4
4
|
let(:instance) { Statesman::Config.new }
|
5
|
-
|
6
|
-
after do
|
7
|
-
# Don't leak global config changes into other specs
|
8
|
-
Statesman.configure { storage_adapter(Statesman::Adapters::Memory) }
|
9
|
-
end
|
5
|
+
after { Statesman.configure { storage_adapter(Statesman::Adapters::Memory) } }
|
10
6
|
|
11
7
|
describe "#storage_adapter" do
|
12
8
|
let(:adapter) { Class.new }
|
13
9
|
before { instance.storage_adapter(adapter) }
|
14
10
|
subject { instance.adapter_class }
|
11
|
+
|
15
12
|
it { is_expected.to be(adapter) }
|
16
13
|
|
17
14
|
it "is DSL configurable" do
|
@@ -7,22 +7,16 @@ describe Statesman::Guard do
|
|
7
7
|
specify { expect(guard).to be_a(Statesman::Callback) }
|
8
8
|
|
9
9
|
describe "#call" do
|
10
|
-
subject { guard.call }
|
10
|
+
subject(:call) { guard.call }
|
11
11
|
|
12
12
|
context "success" do
|
13
13
|
let(:callback) { -> { true } }
|
14
|
-
|
15
|
-
it "does not raise an error" do
|
16
|
-
expect { guard.call }.to_not raise_error
|
17
|
-
end
|
14
|
+
specify { expect { call }.to_not raise_error }
|
18
15
|
end
|
19
16
|
|
20
17
|
context "error" do
|
21
18
|
let(:callback) { -> { false } }
|
22
|
-
|
23
|
-
it "raises an error" do
|
24
|
-
expect { guard.call }.to raise_error(Statesman::GuardFailedError)
|
25
|
-
end
|
19
|
+
specify { expect { call }.to raise_error(Statesman::GuardFailedError) }
|
26
20
|
end
|
27
21
|
end
|
28
22
|
end
|
@@ -15,9 +15,8 @@ describe Statesman::Machine do
|
|
15
15
|
|
16
16
|
context "when an initial state is already defined" do
|
17
17
|
it "raises an error" do
|
18
|
-
expect
|
19
|
-
|
20
|
-
end.to raise_error(Statesman::InvalidStateError)
|
18
|
+
expect { machine.state(:y, initial: true) }.
|
19
|
+
to raise_error(Statesman::InvalidStateError)
|
21
20
|
end
|
22
21
|
end
|
23
22
|
end
|
@@ -51,15 +50,15 @@ describe Statesman::Machine do
|
|
51
50
|
|
52
51
|
context "when an irrelevant exception occurs" do
|
53
52
|
it "runs the transition once" do
|
54
|
-
expect(instance)
|
55
|
-
|
56
|
-
|
53
|
+
expect(instance).
|
54
|
+
to receive(:transition_to).once.
|
55
|
+
and_raise(StandardError)
|
57
56
|
transition_state rescue nil # rubocop:disable RescueModifier
|
58
57
|
end
|
59
58
|
|
60
59
|
it "re-raises the exception" do
|
61
|
-
allow(instance).to receive(:transition_to).once
|
62
|
-
|
60
|
+
allow(instance).to receive(:transition_to).once.
|
61
|
+
and_raise(StandardError)
|
63
62
|
expect { transition_state }.to raise_error(StandardError)
|
64
63
|
end
|
65
64
|
end
|
@@ -67,31 +66,31 @@ describe Statesman::Machine do
|
|
67
66
|
context "when a TransitionConflictError occurs" do
|
68
67
|
context "and is resolved on the second attempt" do
|
69
68
|
it "runs the transition twice" do
|
70
|
-
expect(instance)
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
expect(instance)
|
75
|
-
|
69
|
+
expect(instance).
|
70
|
+
to receive(:transition_to).once.
|
71
|
+
and_raise(Statesman::TransitionConflictError).
|
72
|
+
ordered
|
73
|
+
expect(instance).
|
74
|
+
to receive(:transition_to).once.ordered.and_call_original
|
76
75
|
transition_state
|
77
76
|
end
|
78
77
|
end
|
79
78
|
|
80
79
|
context "and keeps occurring" do
|
81
80
|
it "runs the transition `retry_attempts + 1` times" do
|
82
|
-
expect(instance)
|
83
|
-
|
84
|
-
|
85
|
-
|
81
|
+
expect(instance).
|
82
|
+
to receive(:transition_to).
|
83
|
+
exactly(retry_attempts + 1).times.
|
84
|
+
and_raise(Statesman::TransitionConflictError)
|
86
85
|
transition_state rescue nil # rubocop:disable RescueModifier
|
87
86
|
end
|
88
87
|
|
89
88
|
it "re-raises the conflict" do
|
90
|
-
allow(instance)
|
91
|
-
|
92
|
-
|
93
|
-
expect { transition_state }
|
94
|
-
|
89
|
+
allow(instance).
|
90
|
+
to receive(:transition_to).
|
91
|
+
and_raise(Statesman::TransitionConflictError)
|
92
|
+
expect { transition_state }.
|
93
|
+
to raise_error(Statesman::TransitionConflictError)
|
95
94
|
end
|
96
95
|
end
|
97
96
|
end
|
@@ -108,49 +107,43 @@ describe Statesman::Machine do
|
|
108
107
|
|
109
108
|
context "given neither a 'from' nor a 'to' state" do
|
110
109
|
it "raises an error" do
|
111
|
-
expect
|
112
|
-
|
113
|
-
end.to raise_error(Statesman::InvalidStateError)
|
110
|
+
expect { machine.transition }.
|
111
|
+
to raise_error(Statesman::InvalidStateError)
|
114
112
|
end
|
115
113
|
end
|
116
114
|
|
117
115
|
context "given no 'from' state and a valid 'to' state" do
|
118
116
|
it "raises an error" do
|
119
|
-
expect
|
120
|
-
|
121
|
-
end.to raise_error(Statesman::InvalidStateError)
|
117
|
+
expect { machine.transition from: nil, to: :x }.
|
118
|
+
to raise_error(Statesman::InvalidStateError)
|
122
119
|
end
|
123
120
|
end
|
124
121
|
|
125
122
|
context "given a valid 'from' state and a no 'to' state" do
|
126
123
|
it "raises an error" do
|
127
|
-
expect
|
128
|
-
|
129
|
-
end.to raise_error(Statesman::InvalidStateError)
|
124
|
+
expect { machine.transition from: :x, to: nil }.
|
125
|
+
to raise_error(Statesman::InvalidStateError)
|
130
126
|
end
|
131
127
|
end
|
132
128
|
|
133
129
|
context "given a valid 'from' state and an empty 'to' state array" do
|
134
130
|
it "raises an error" do
|
135
|
-
expect
|
136
|
-
|
137
|
-
end.to raise_error(Statesman::InvalidStateError)
|
131
|
+
expect { machine.transition from: :x, to: [] }.
|
132
|
+
to raise_error(Statesman::InvalidStateError)
|
138
133
|
end
|
139
134
|
end
|
140
135
|
|
141
136
|
context "given an invalid 'from' state" do
|
142
137
|
it "raises an error" do
|
143
|
-
expect
|
144
|
-
|
145
|
-
end.to raise_error(Statesman::InvalidStateError)
|
138
|
+
expect { machine.transition(from: :a, to: :x) }.
|
139
|
+
to raise_error(Statesman::InvalidStateError)
|
146
140
|
end
|
147
141
|
end
|
148
142
|
|
149
143
|
context "given an invalid 'to' state" do
|
150
144
|
it "raises an error" do
|
151
|
-
expect
|
152
|
-
|
153
|
-
end.to raise_error(Statesman::InvalidStateError)
|
145
|
+
expect { machine.transition(from: :x, to: :a) }.
|
146
|
+
to raise_error(Statesman::InvalidStateError)
|
154
147
|
end
|
155
148
|
end
|
156
149
|
|
@@ -176,25 +169,22 @@ describe Statesman::Machine do
|
|
176
169
|
|
177
170
|
context "with a terminal 'from' state" do
|
178
171
|
it "raises an exception" do
|
179
|
-
expect
|
180
|
-
|
181
|
-
end.to raise_error(Statesman::InvalidTransitionError)
|
172
|
+
expect { machine.validate_callback_condition(from: :z, to: :y) }.
|
173
|
+
to raise_error(Statesman::InvalidTransitionError)
|
182
174
|
end
|
183
175
|
end
|
184
176
|
|
185
177
|
context "with an initial 'to' state" do
|
186
178
|
it "raises an exception" do
|
187
|
-
expect
|
188
|
-
|
189
|
-
end.to raise_error(Statesman::InvalidTransitionError)
|
179
|
+
expect { machine.validate_callback_condition(from: :y, to: :x) }.
|
180
|
+
to raise_error(Statesman::InvalidTransitionError)
|
190
181
|
end
|
191
182
|
end
|
192
183
|
|
193
184
|
context "with an invalid transition" do
|
194
185
|
it "raises an exception" do
|
195
|
-
expect
|
196
|
-
|
197
|
-
end.to raise_error(Statesman::InvalidTransitionError)
|
186
|
+
expect { machine.validate_callback_condition(from: :x, to: :z) }.
|
187
|
+
to raise_error(Statesman::InvalidTransitionError)
|
198
188
|
end
|
199
189
|
end
|
200
190
|
|
@@ -206,9 +196,8 @@ describe Statesman::Machine do
|
|
206
196
|
|
207
197
|
context "with a valid transition" do
|
208
198
|
it "does not raise an exception" do
|
209
|
-
expect
|
210
|
-
|
211
|
-
end.to_not raise_error
|
199
|
+
expect { machine.validate_callback_condition(from: :x, to: :y) }.
|
200
|
+
to_not raise_error
|
212
201
|
end
|
213
202
|
end
|
214
203
|
end
|
@@ -227,9 +216,7 @@ describe Statesman::Machine do
|
|
227
216
|
let(:set_callback) { machine.send(assignment_method, options) {} }
|
228
217
|
|
229
218
|
shared_examples "fails" do |error_type|
|
230
|
-
|
231
|
-
expect { set_callback }.to raise_error(error_type)
|
232
|
-
end
|
219
|
+
specify { expect { set_callback }.to raise_error(error_type) }
|
233
220
|
|
234
221
|
it "does not add a callback" do
|
235
222
|
expect do
|
@@ -243,13 +230,11 @@ describe Statesman::Machine do
|
|
243
230
|
end
|
244
231
|
|
245
232
|
shared_examples "adds callback" do
|
246
|
-
|
247
|
-
expect { set_callback }.to_not raise_error
|
248
|
-
end
|
233
|
+
specify { expect { set_callback }.to_not raise_error }
|
249
234
|
|
250
235
|
it "stores callbacks" do
|
251
|
-
expect { set_callback }.
|
252
|
-
machine.callbacks[callback_store], :count).by(1)
|
236
|
+
expect { set_callback }.
|
237
|
+
to change(machine.callbacks[callback_store], :count).by(1)
|
253
238
|
end
|
254
239
|
|
255
240
|
it "stores callback instances" do
|
@@ -330,22 +315,22 @@ describe Statesman::Machine do
|
|
330
315
|
|
331
316
|
context "transition class" do
|
332
317
|
it "sets a default" do
|
333
|
-
expect(Statesman.storage_adapter).to receive(:new).once
|
334
|
-
|
318
|
+
expect(Statesman.storage_adapter).to receive(:new).once.
|
319
|
+
with(Statesman::Adapters::MemoryTransition, my_model, anything)
|
335
320
|
machine.new(my_model)
|
336
321
|
end
|
337
322
|
|
338
323
|
it "sets the passed class" do
|
339
324
|
my_transition_class = Class.new
|
340
|
-
expect(Statesman.storage_adapter).to receive(:new).once
|
341
|
-
|
325
|
+
expect(Statesman.storage_adapter).to receive(:new).once.
|
326
|
+
with(my_transition_class, my_model, anything)
|
342
327
|
machine.new(my_model, transition_class: my_transition_class)
|
343
328
|
end
|
344
329
|
|
345
330
|
it "falls back to Memory without transaction_class" do
|
346
331
|
allow(Statesman).to receive(:storage_adapter).and_return(Class.new)
|
347
|
-
expect(Statesman::Adapters::Memory).to receive(:new).once
|
348
|
-
|
332
|
+
expect(Statesman::Adapters::Memory).to receive(:new).once.
|
333
|
+
with(Statesman::Adapters::MemoryTransition, my_model, anything)
|
349
334
|
machine.new(my_model)
|
350
335
|
end
|
351
336
|
end
|
@@ -380,10 +365,8 @@ describe Statesman::Machine do
|
|
380
365
|
end
|
381
366
|
|
382
367
|
context "with multiple transitions" do
|
383
|
-
before
|
384
|
-
|
385
|
-
instance.transition_to!(:z)
|
386
|
-
end
|
368
|
+
before { instance.transition_to!(:y) }
|
369
|
+
before { instance.transition_to!(:z) }
|
387
370
|
|
388
371
|
it { is_expected.to eq("z") }
|
389
372
|
end
|
@@ -408,18 +391,12 @@ describe Statesman::Machine do
|
|
408
391
|
end
|
409
392
|
|
410
393
|
context "with one possible state" do
|
411
|
-
before
|
412
|
-
instance.transition_to!(:y)
|
413
|
-
end
|
414
|
-
|
394
|
+
before { instance.transition_to!(:y) }
|
415
395
|
it { is_expected.to eq(['z']) }
|
416
396
|
end
|
417
397
|
|
418
398
|
context "with no possible transitions" do
|
419
|
-
before
|
420
|
-
instance.transition_to!(:z)
|
421
|
-
end
|
422
|
-
|
399
|
+
before { instance.transition_to!(:z) }
|
423
400
|
it { is_expected.to eq([]) }
|
424
401
|
end
|
425
402
|
end
|
@@ -429,8 +406,8 @@ describe Statesman::Machine do
|
|
429
406
|
let(:last_action) { "Whatever" }
|
430
407
|
|
431
408
|
it "delegates to the storage adapter" do
|
432
|
-
expect_any_instance_of(Statesman.storage_adapter).to receive(:last).once
|
433
|
-
|
409
|
+
expect_any_instance_of(Statesman.storage_adapter).to receive(:last).once.
|
410
|
+
and_return(last_action)
|
434
411
|
expect(instance.last_transition).to be(last_action)
|
435
412
|
end
|
436
413
|
end
|
@@ -499,9 +476,8 @@ describe Statesman::Machine do
|
|
499
476
|
|
500
477
|
context "when the state cannot be transitioned to" do
|
501
478
|
it "raises an error" do
|
502
|
-
expect
|
503
|
-
|
504
|
-
end.to raise_error(Statesman::TransitionFailedError)
|
479
|
+
expect { instance.transition_to!(:z) }.
|
480
|
+
to raise_error(Statesman::TransitionFailedError)
|
505
481
|
end
|
506
482
|
end
|
507
483
|
|
@@ -512,12 +488,11 @@ describe Statesman::Machine do
|
|
512
488
|
end
|
513
489
|
|
514
490
|
it "creates a new transition object" do
|
515
|
-
expect
|
516
|
-
instance.
|
517
|
-
end.to change(instance.history, :count).by(1)
|
491
|
+
expect { instance.transition_to!(:y) }.
|
492
|
+
to change(instance.history, :count).by(1)
|
518
493
|
|
519
|
-
expect(instance.history.first)
|
520
|
-
|
494
|
+
expect(instance.history.first).
|
495
|
+
to be_a(Statesman::Adapters::MemoryTransition)
|
521
496
|
expect(instance.history.first.to_state).to eq("y")
|
522
497
|
end
|
523
498
|
|
@@ -532,9 +507,7 @@ describe Statesman::Machine do
|
|
532
507
|
expect(instance.history.first.metadata).to eq({})
|
533
508
|
end
|
534
509
|
|
535
|
-
|
536
|
-
expect(instance.transition_to!(:y)).to be_truthy
|
537
|
-
end
|
510
|
+
specify { expect(instance.transition_to!(:y)).to be_truthy }
|
538
511
|
|
539
512
|
context "with a guard" do
|
540
513
|
let(:result) { true }
|
@@ -545,8 +518,8 @@ describe Statesman::Machine do
|
|
545
518
|
let(:instance) { machine.new(my_model) }
|
546
519
|
|
547
520
|
it "passes the object to the guard" do
|
548
|
-
expect(guard_cb).to receive(:call).once
|
549
|
-
|
521
|
+
expect(guard_cb).to receive(:call).once.
|
522
|
+
with(my_model, instance.last_transition, {}).and_return(true)
|
550
523
|
instance.transition_to!(:y)
|
551
524
|
end
|
552
525
|
end
|
@@ -562,9 +535,8 @@ describe Statesman::Machine do
|
|
562
535
|
let(:result) { false }
|
563
536
|
|
564
537
|
it "raises an exception" do
|
565
|
-
expect
|
566
|
-
|
567
|
-
end.to raise_error(Statesman::GuardFailedError)
|
538
|
+
expect { instance.transition_to!(:y) }.
|
539
|
+
to raise_error(Statesman::GuardFailedError)
|
568
540
|
end
|
569
541
|
end
|
570
542
|
end
|
@@ -578,32 +550,29 @@ describe Statesman::Machine do
|
|
578
550
|
|
579
551
|
context "when it is succesful" do
|
580
552
|
before do
|
581
|
-
expect(instance).to receive(:transition_to!).once
|
582
|
-
|
553
|
+
expect(instance).to receive(:transition_to!).once.
|
554
|
+
with(:some_state, metadata).and_return(:some_state)
|
583
555
|
end
|
584
556
|
it { is_expected.to be(:some_state) }
|
585
557
|
end
|
586
558
|
|
587
559
|
context "when it is unsuccesful" do
|
588
560
|
before do
|
589
|
-
allow(instance).to receive(:transition_to!)
|
590
|
-
|
561
|
+
allow(instance).to receive(:transition_to!).
|
562
|
+
and_raise(Statesman::GuardFailedError)
|
591
563
|
end
|
592
564
|
it { is_expected.to be_falsey }
|
593
565
|
end
|
594
566
|
|
595
567
|
context "when a non statesman exception is raised" do
|
596
568
|
before do
|
597
|
-
allow(instance).to receive(:transition_to!)
|
598
|
-
|
569
|
+
allow(instance).to receive(:transition_to!).
|
570
|
+
and_raise(RuntimeError, 'user defined exception')
|
599
571
|
end
|
600
572
|
|
601
573
|
it "should not rescue the exception" do
|
602
|
-
|
603
|
-
|
604
|
-
end
|
605
|
-
|
606
|
-
expectation.to raise_error(RuntimeError, 'user defined exception')
|
574
|
+
expect { instance.transition_to(:some_state, metadata) }.
|
575
|
+
to raise_error(RuntimeError, 'user defined exception')
|
607
576
|
end
|
608
577
|
end
|
609
578
|
end
|
@@ -650,13 +619,11 @@ describe Statesman::Machine do
|
|
650
619
|
end
|
651
620
|
|
652
621
|
describe "#before_callbacks_for" do
|
653
|
-
it_behaves_like "a callback filter", :before_transition,
|
654
|
-
:before
|
622
|
+
it_behaves_like "a callback filter", :before_transition, :before
|
655
623
|
end
|
656
624
|
|
657
625
|
describe "#after_callbacks_for" do
|
658
|
-
it_behaves_like "a callback filter", :after_transition,
|
659
|
-
:after
|
626
|
+
it_behaves_like "a callback filter", :after_transition, :after
|
660
627
|
end
|
661
628
|
|
662
629
|
describe "#event" do
|
@@ -680,9 +647,8 @@ describe Statesman::Machine do
|
|
680
647
|
|
681
648
|
context "when the state cannot be transitioned to" do
|
682
649
|
it "raises an error" do
|
683
|
-
expect
|
684
|
-
|
685
|
-
end.to raise_error(Statesman::TransitionFailedError)
|
650
|
+
expect { instance.trigger!(:event_2) }.
|
651
|
+
to raise_error(Statesman::TransitionFailedError)
|
686
652
|
end
|
687
653
|
end
|
688
654
|
|
@@ -693,12 +659,11 @@ describe Statesman::Machine do
|
|
693
659
|
end
|
694
660
|
|
695
661
|
it "creates a new transition object" do
|
696
|
-
expect
|
697
|
-
instance.
|
698
|
-
end.to change(instance.history, :count).by(1)
|
662
|
+
expect { instance.trigger!(:event_1) }.
|
663
|
+
to change(instance.history, :count).by(1)
|
699
664
|
|
700
|
-
expect(instance.history.first)
|
701
|
-
|
665
|
+
expect(instance.history.first).
|
666
|
+
to be_a(Statesman::Adapters::MemoryTransition)
|
702
667
|
expect(instance.history.first.to_state).to eq("y")
|
703
668
|
end
|
704
669
|
|
@@ -728,16 +693,16 @@ describe Statesman::Machine do
|
|
728
693
|
let(:instance) { machine.new(my_model) }
|
729
694
|
|
730
695
|
it "passes the object to the guard" do
|
731
|
-
expect(guard_cb).to receive(:call).once
|
732
|
-
|
696
|
+
expect(guard_cb).to receive(:call).once.
|
697
|
+
with(my_model, instance.last_transition, {}).and_return(true)
|
733
698
|
instance.trigger!(:event_1)
|
734
699
|
end
|
735
700
|
end
|
736
701
|
|
737
702
|
context "which passes" do
|
738
703
|
it "changes state" do
|
739
|
-
instance.trigger!(:event_1)
|
740
|
-
|
704
|
+
expect { instance.trigger!(:event_1) }.
|
705
|
+
to change { instance.current_state }.to("y")
|
741
706
|
end
|
742
707
|
end
|
743
708
|
|
@@ -745,14 +710,12 @@ describe Statesman::Machine do
|
|
745
710
|
let(:result) { false }
|
746
711
|
|
747
712
|
it "raises an exception" do
|
748
|
-
expect
|
749
|
-
|
750
|
-
end.to raise_error(Statesman::GuardFailedError)
|
713
|
+
expect { instance.trigger!(:event_1) }.
|
714
|
+
to raise_error(Statesman::GuardFailedError)
|
751
715
|
end
|
752
716
|
end
|
753
717
|
end
|
754
718
|
end
|
755
|
-
|
756
719
|
end
|
757
720
|
|
758
721
|
describe "#available_events" do
|
@@ -784,5 +747,4 @@ describe Statesman::Machine do
|
|
784
747
|
expect(instance.available_events).to eq([:event_2, :event_3])
|
785
748
|
end
|
786
749
|
end
|
787
|
-
|
788
750
|
end
|