statesman 7.4.0 → 10.2.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  namespace :statesman do
4
- desc "Set most_recent to false for old transitions and to true for the "\
4
+ desc "Set most_recent to false for old transitions and to true for the " \
5
5
  "latest one. Safe to re-run"
6
6
  task :backfill_most_recent, [:parent_model_name] => :environment do |_, args|
7
7
  parent_model_name = args.parent_model_name
@@ -56,8 +56,8 @@ namespace :statesman do
56
56
  end
57
57
 
58
58
  done_models += batch_size
59
- puts "Updated #{transition_class.name.pluralize} for "\
60
- "#{[done_models, total_models].min}/#{total_models} "\
59
+ puts "Updated #{transition_class.name.pluralize} for " \
60
+ "#{[done_models, total_models].min}/#{total_models} " \
61
61
  "#{parent_model_name.pluralize}"
62
62
  end
63
63
  end
data/spec/spec_helper.rb CHANGED
@@ -48,6 +48,8 @@ RSpec.configure do |config|
48
48
  my_namespace_my_active_record_model_transitions
49
49
  other_active_record_models
50
50
  other_active_record_model_transitions
51
+ sti_active_record_models
52
+ sti_active_record_model_transitions
51
53
  ]
52
54
  tables.each do |table_name|
53
55
  sql = "DROP TABLE IF EXISTS #{table_name};"
@@ -72,6 +74,15 @@ RSpec.configure do |config|
72
74
  OtherActiveRecordModelTransition.reset_column_information
73
75
  end
74
76
 
77
+ def prepare_sti_model_table
78
+ CreateStiActiveRecordModelMigration.migrate(:up)
79
+ end
80
+
81
+ def prepare_sti_transitions_table
82
+ CreateStiActiveRecordModelTransitionMigration.migrate(:up)
83
+ StiActiveRecordModelTransition.reset_column_information
84
+ end
85
+
75
86
  MyNamespace::MyActiveRecordModelTransition.serialize(:metadata, JSON)
76
87
  end
77
88
  end
@@ -50,10 +50,11 @@ describe Statesman::Adapters::ActiveRecordQueries, active_record: true do
50
50
 
51
51
  shared_examples "testing methods" do
52
52
  before do
53
- if config_type == :old
53
+ case config_type
54
+ when :old
54
55
  configure_old(MyActiveRecordModel, MyActiveRecordModelTransition)
55
56
  configure_old(OtherActiveRecordModel, OtherActiveRecordModelTransition)
56
- elsif config_type == :new
57
+ when :new
57
58
  configure_new(MyActiveRecordModel, MyActiveRecordModelTransition)
58
59
  configure_new(OtherActiveRecordModel, OtherActiveRecordModelTransition)
59
60
  else
@@ -116,8 +117,8 @@ describe Statesman::Adapters::ActiveRecordQueries, active_record: true do
116
117
  subject(:not_in_state) { MyActiveRecordModel.not_in_state(:succeeded, :failed) }
117
118
 
118
119
  it do
119
- expect(not_in_state).to match_array([initial_state_model,
120
- returned_to_initial_model])
120
+ expect(not_in_state).to contain_exactly(initial_state_model,
121
+ returned_to_initial_model)
121
122
  end
122
123
  end
123
124
 
@@ -125,8 +126,8 @@ describe Statesman::Adapters::ActiveRecordQueries, active_record: true do
125
126
  subject(:not_in_state) { MyActiveRecordModel.not_in_state(%i[succeeded failed]) }
126
127
 
127
128
  it do
128
- expect(not_in_state).to match_array([initial_state_model,
129
- returned_to_initial_model])
129
+ expect(not_in_state).to contain_exactly(initial_state_model,
130
+ returned_to_initial_model)
130
131
  end
131
132
  end
132
133
  end
@@ -154,6 +155,31 @@ describe Statesman::Adapters::ActiveRecordQueries, active_record: true do
154
155
  end
155
156
  end
156
157
 
158
+ context "with a custom primary key for the model" do
159
+ before do
160
+ # Switch to using OtherActiveRecordModelTransition, so the existing
161
+ # relation with MyActiveRecordModelTransition doesn't interfere with
162
+ # this spec.
163
+ # Configure the relationship to use a different primary key,
164
+ MyActiveRecordModel.send(:has_many,
165
+ :custom_name,
166
+ class_name: "OtherActiveRecordModelTransition",
167
+ primary_key: :external_id)
168
+
169
+ MyActiveRecordModel.class_eval do
170
+ def self.transition_class
171
+ OtherActiveRecordModelTransition
172
+ end
173
+ end
174
+ end
175
+
176
+ describe ".in_state" do
177
+ subject(:query) { MyActiveRecordModel.in_state(:succeeded) }
178
+
179
+ specify { expect { query }.to_not raise_error }
180
+ end
181
+ end
182
+
157
183
  context "after_commit transactional integrity" do
158
184
  before do
159
185
  MyStateMachine.class_eval do
@@ -176,7 +202,6 @@ describe Statesman::Adapters::ActiveRecordQueries, active_record: true do
176
202
  MyActiveRecordModel.create
177
203
  end
178
204
 
179
- # rubocop:disable RSpec/ExampleLength
180
205
  it do
181
206
  expect do
182
207
  ActiveRecord::Base.transaction do
@@ -185,7 +210,6 @@ describe Statesman::Adapters::ActiveRecordQueries, active_record: true do
185
210
  end
186
211
  end.to_not change(MyStateMachine, :after_commit_callback_executed)
187
212
  end
188
- # rubocop:enable RSpec/ExampleLength
189
213
  end
190
214
  end
191
215
 
@@ -230,7 +254,7 @@ describe Statesman::Adapters::ActiveRecordQueries, active_record: true do
230
254
  end
231
255
 
232
256
  it "does not raise an error" do
233
- expect { check_missing_methods! }.to_not raise_exception(NotImplementedError)
257
+ expect { check_missing_methods! }.to_not raise_exception
234
258
  end
235
259
  end
236
260
 
@@ -12,6 +12,9 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
12
12
 
13
13
  MyActiveRecordModelTransition.serialize(:metadata, JSON)
14
14
 
15
+ prepare_sti_model_table
16
+ prepare_sti_transitions_table
17
+
15
18
  Statesman.configure do
16
19
  # Rubocop requires described_class to be used, but this block
17
20
  # is instance_eval'd and described_class won't be defined
@@ -35,8 +38,8 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
35
38
  allow(metadata_column).to receive_messages(sql_type: "")
36
39
  allow(MyActiveRecordModelTransition).to receive_messages(columns_hash:
37
40
  { "metadata" => metadata_column })
38
- if ::ActiveRecord.respond_to?(:gem_version) &&
39
- ::ActiveRecord.gem_version >= Gem::Version.new("4.2.0.a")
41
+ if ActiveRecord.respond_to?(:gem_version) &&
42
+ ActiveRecord.gem_version >= Gem::Version.new("4.2.0.a")
40
43
  expect(MyActiveRecordModelTransition).
41
44
  to receive(:type_for_attribute).with("metadata").
42
45
  and_return(ActiveRecord::Type::Value.new)
@@ -60,10 +63,10 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
60
63
  allow(metadata_column).to receive_messages(sql_type: "json")
61
64
  allow(MyActiveRecordModelTransition).to receive_messages(columns_hash:
62
65
  { "metadata" => metadata_column })
63
- if ::ActiveRecord.respond_to?(:gem_version) &&
64
- ::ActiveRecord.gem_version >= Gem::Version.new("4.2.0.a")
65
- serialized_type = ::ActiveRecord::Type::Serialized.new(
66
- "", ::ActiveRecord::Coders::JSON
66
+ if ActiveRecord.respond_to?(:gem_version) &&
67
+ ActiveRecord.gem_version >= Gem::Version.new("4.2.0.a")
68
+ serialized_type = ActiveRecord::Type::Serialized.new(
69
+ "", ActiveRecord::Coders::JSON
67
70
  )
68
71
  expect(MyActiveRecordModelTransition).
69
72
  to receive(:type_for_attribute).with("metadata").
@@ -88,10 +91,10 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
88
91
  allow(metadata_column).to receive_messages(sql_type: "jsonb")
89
92
  allow(MyActiveRecordModelTransition).to receive_messages(columns_hash:
90
93
  { "metadata" => metadata_column })
91
- if ::ActiveRecord.respond_to?(:gem_version) &&
92
- ::ActiveRecord.gem_version >= Gem::Version.new("4.2.0.a")
93
- serialized_type = ::ActiveRecord::Type::Serialized.new(
94
- "", ::ActiveRecord::Coders::JSON
94
+ if ActiveRecord.respond_to?(:gem_version) &&
95
+ ActiveRecord.gem_version >= Gem::Version.new("4.2.0.a")
96
+ serialized_type = ActiveRecord::Type::Serialized.new(
97
+ "", ActiveRecord::Coders::JSON
95
98
  )
96
99
  expect(MyActiveRecordModelTransition).
97
100
  to receive(:type_for_attribute).with("metadata").
@@ -112,7 +115,7 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
112
115
  end
113
116
 
114
117
  describe "#create" do
115
- subject { -> { create } }
118
+ subject(:transition) { create }
116
119
 
117
120
  let!(:adapter) do
118
121
  described_class.new(MyActiveRecordModelTransition, model, observer)
@@ -130,6 +133,31 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
130
133
  expect { adapter.create(:y, :z) }.
131
134
  to raise_exception(Statesman::TransitionConflictError)
132
135
  end
136
+
137
+ it "does not pollute the state when the transition fails" do
138
+ # this increments the sort_key in the database
139
+ adapter.create(:x, :y)
140
+
141
+ # we then pre-load the transitions for efficiency
142
+ preloaded_model = MyActiveRecordModel.
143
+ includes(:my_active_record_model_transitions).
144
+ find(model.id)
145
+
146
+ adapter2 = described_class.
147
+ new(MyActiveRecordModelTransition, preloaded_model, observer)
148
+
149
+ # Now we generate a race
150
+ adapter.create(:y, :z)
151
+ expect { adapter2.create(:y, :a) }.
152
+ to raise_error(Statesman::TransitionConflictError)
153
+
154
+ # The preloaded adapter should discard the preloaded info
155
+ expect(adapter2.last).to have_attributes(to_state: "z")
156
+ expect(adapter2.history).to contain_exactly(
157
+ have_attributes(to_state: "y"),
158
+ have_attributes(to_state: "z"),
159
+ )
160
+ end
133
161
  end
134
162
 
135
163
  context "when other exceptions occur" do
@@ -140,27 +168,25 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
140
168
 
141
169
  context "ActiveRecord::RecordNotUnique unrelated to this transition" do
142
170
  let(:error) do
143
- if ::ActiveRecord.respond_to?(:gem_version) &&
144
- ::ActiveRecord.gem_version >= Gem::Version.new("4.0.0")
171
+ if ActiveRecord.respond_to?(:gem_version) &&
172
+ ActiveRecord.gem_version >= Gem::Version.new("4.0.0")
145
173
  ActiveRecord::RecordNotUnique.new("unrelated")
146
174
  else
147
175
  ActiveRecord::RecordNotUnique.new("unrelated", nil)
148
176
  end
149
177
  end
150
178
 
151
- it { is_expected.to raise_exception(ActiveRecord::RecordNotUnique) }
179
+ it { expect { transition }.to raise_exception(ActiveRecord::RecordNotUnique) }
152
180
  end
153
181
 
154
182
  context "other errors" do
155
183
  let(:error) { StandardError }
156
184
 
157
- it { is_expected.to raise_exception(StandardError) }
185
+ it { expect { transition }.to raise_exception(StandardError) }
158
186
  end
159
187
  end
160
188
 
161
189
  describe "updating the most_recent column" do
162
- subject { create }
163
-
164
190
  context "with no previous transition" do
165
191
  its(:most_recent) { is_expected.to eq(true) }
166
192
  end
@@ -277,6 +303,57 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
277
303
  from(true).to be_falsey
278
304
  end
279
305
  end
306
+
307
+ context "when transition uses STI" do
308
+ let(:sti_model) { StiActiveRecordModel.create }
309
+
310
+ let(:adapter_a) do
311
+ described_class.new(
312
+ StiAActiveRecordModelTransition,
313
+ sti_model,
314
+ observer,
315
+ { association_name: :sti_a_active_record_model_transitions },
316
+ )
317
+ end
318
+ let(:adapter_b) do
319
+ described_class.new(
320
+ StiBActiveRecordModelTransition,
321
+ sti_model,
322
+ observer,
323
+ { association_name: :sti_b_active_record_model_transitions },
324
+ )
325
+ end
326
+ let(:create) { adapter_a.create(from, to) }
327
+
328
+ context "with a previous unrelated transition" do
329
+ let!(:transition_b) { adapter_b.create(from, to) }
330
+
331
+ its(:most_recent) { is_expected.to eq(true) }
332
+
333
+ it "doesn't update the previous transition's most_recent flag" do
334
+ expect { create }.
335
+ to_not(change { transition_b.reload.most_recent })
336
+ end
337
+ end
338
+
339
+ context "with previous related and unrelated transitions" do
340
+ let!(:transition_a) { adapter_a.create(from, to) }
341
+ let!(:transition_b) { adapter_b.create(from, to) }
342
+
343
+ its(:most_recent) { is_expected.to eq(true) }
344
+
345
+ it "updates the previous transition's most_recent flag" do
346
+ expect { create }.
347
+ to change { transition_a.reload.most_recent }.
348
+ from(true).to be_falsey
349
+ end
350
+
351
+ it "doesn't update the previous unrelated transition's most_recent flag" do
352
+ expect { create }.
353
+ to_not(change { transition_b.reload.most_recent })
354
+ end
355
+ end
356
+ end
280
357
  end
281
358
  end
282
359
 
@@ -285,9 +362,9 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
285
362
  described_class.new(MyActiveRecordModelTransition, model, observer)
286
363
  end
287
364
 
288
- before { adapter.create(:x, :y) }
289
-
290
365
  context "with a previously looked up transition" do
366
+ before { adapter.create(:x, :y) }
367
+
291
368
  before { adapter.last }
292
369
 
293
370
  it "caches the transition" do
@@ -353,6 +430,35 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
353
430
  expect(adapter.last.to_state).to eq("y")
354
431
  end
355
432
  end
433
+
434
+ context "without previous transitions" do
435
+ it "does query the database only once" do
436
+ expect(model.my_active_record_model_transitions).
437
+ to receive(:order).once.and_call_original
438
+
439
+ expect(adapter.last).to eq(nil)
440
+ expect(adapter.last).to eq(nil)
441
+ end
442
+ end
443
+ end
444
+
445
+ describe "#reset" do
446
+ it "works with empty cache" do
447
+ expect { model.state_machine.reset }.to_not raise_error
448
+ end
449
+ end
450
+
451
+ it "resets last with #reload" do
452
+ model.save!
453
+ ActiveRecord::Base.transaction do
454
+ model.state_machine.transition_to!(:succeeded)
455
+ # force to cache value in last_transition instance variable
456
+ expect(model.state_machine.current_state).to eq("succeeded")
457
+ raise ActiveRecord::Rollback
458
+ end
459
+ expect(model.state_machine.current_state).to eq("succeeded")
460
+ model.reload
461
+ expect(model.state_machine.current_state).to eq("initial")
356
462
  end
357
463
 
358
464
  context "with a namespaced model" do
@@ -30,14 +30,14 @@ shared_examples_for "an adapter" do |adapter_class, transition_class, options =
30
30
  end
31
31
 
32
32
  describe "#create" do
33
- subject { -> { create } }
33
+ subject(:transition) { create }
34
34
 
35
35
  let(:from) { :x }
36
36
  let(:to) { :y }
37
37
  let(:there) { :z }
38
38
  let(:create) { adapter.create(from, to) }
39
39
 
40
- it { is_expected.to change(adapter.history, :count).by(1) }
40
+ it { expect { transition }.to change(adapter.history, :count).by(1) }
41
41
 
42
42
  context "the new transition" do
43
43
  subject(:instance) { create }
@@ -128,6 +128,7 @@ shared_examples_for "an adapter" do |adapter_class, transition_class, options =
128
128
 
129
129
  it { is_expected.to be_a(transition_class) }
130
130
  specify { expect(adapter.last.to_state.to_sym).to eq(:z) }
131
+
131
132
  specify do
132
133
  expect(adapter.last(force_reload: true).to_state.to_sym).to eq(:z)
133
134
  end
@@ -0,0 +1,208 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ describe Statesman::Adapters::TypeSafeActiveRecordQueries, active_record: true do
6
+ def configure(klass, transition_class)
7
+ klass.send(:extend, described_class)
8
+ klass.configure_state_machine(
9
+ transition_class: transition_class,
10
+ initial_state: :initial,
11
+ )
12
+ end
13
+
14
+ before do
15
+ prepare_model_table
16
+ prepare_transitions_table
17
+ prepare_other_model_table
18
+ prepare_other_transitions_table
19
+
20
+ Statesman.configure do
21
+ storage_adapter(Statesman::Adapters::ActiveRecord)
22
+ end
23
+ end
24
+
25
+ after { Statesman.configure { storage_adapter(Statesman::Adapters::Memory) } }
26
+
27
+ let!(:model) do
28
+ model = MyActiveRecordModel.create
29
+ model.state_machine.transition_to(:succeeded)
30
+ model
31
+ end
32
+
33
+ let!(:other_model) do
34
+ model = MyActiveRecordModel.create
35
+ model.state_machine.transition_to(:failed)
36
+ model
37
+ end
38
+
39
+ let!(:initial_state_model) { MyActiveRecordModel.create }
40
+
41
+ let!(:returned_to_initial_model) do
42
+ model = MyActiveRecordModel.create
43
+ model.state_machine.transition_to(:failed)
44
+ model.state_machine.transition_to(:initial)
45
+ model
46
+ end
47
+
48
+ shared_examples "testing methods" do
49
+ before do
50
+ configure(MyActiveRecordModel, MyActiveRecordModelTransition)
51
+ configure(OtherActiveRecordModel, OtherActiveRecordModelTransition)
52
+
53
+ MyActiveRecordModel.send(:has_one, :other_active_record_model)
54
+ OtherActiveRecordModel.send(:belongs_to, :my_active_record_model)
55
+ end
56
+
57
+ describe ".in_state" do
58
+ context "given a single state" do
59
+ subject { MyActiveRecordModel.in_state(:succeeded) }
60
+
61
+ it { is_expected.to include model }
62
+ it { is_expected.to_not include other_model }
63
+ end
64
+
65
+ context "given multiple states" do
66
+ subject { MyActiveRecordModel.in_state(:succeeded, :failed) }
67
+
68
+ it { is_expected.to include model }
69
+ it { is_expected.to include other_model }
70
+ end
71
+
72
+ context "given the initial state" do
73
+ subject { MyActiveRecordModel.in_state(:initial) }
74
+
75
+ it { is_expected.to include initial_state_model }
76
+ it { is_expected.to include returned_to_initial_model }
77
+ end
78
+
79
+ context "given an array of states" do
80
+ subject { MyActiveRecordModel.in_state(%i[succeeded failed]) }
81
+
82
+ it { is_expected.to include model }
83
+ it { is_expected.to include other_model }
84
+ end
85
+
86
+ context "merging two queries" do
87
+ subject do
88
+ MyActiveRecordModel.in_state(:succeeded).
89
+ joins(:other_active_record_model).
90
+ merge(OtherActiveRecordModel.in_state(:initial))
91
+ end
92
+
93
+ it { is_expected.to be_empty }
94
+ end
95
+ end
96
+
97
+ describe ".not_in_state" do
98
+ context "given a single state" do
99
+ subject { MyActiveRecordModel.not_in_state(:failed) }
100
+
101
+ it { is_expected.to include model }
102
+ it { is_expected.to_not include other_model }
103
+ end
104
+
105
+ context "given multiple states" do
106
+ subject(:not_in_state) { MyActiveRecordModel.not_in_state(:succeeded, :failed) }
107
+
108
+ it do
109
+ expect(not_in_state).to contain_exactly(initial_state_model,
110
+ returned_to_initial_model)
111
+ end
112
+ end
113
+
114
+ context "given an array of states" do
115
+ subject(:not_in_state) { MyActiveRecordModel.not_in_state(%i[succeeded failed]) }
116
+
117
+ it do
118
+ expect(not_in_state).to contain_exactly(initial_state_model,
119
+ returned_to_initial_model)
120
+ end
121
+ end
122
+ end
123
+
124
+ context "with a custom name for the transition association" do
125
+ before do
126
+ # Switch to using OtherActiveRecordModelTransition, so the existing
127
+ # relation with MyActiveRecordModelTransition doesn't interfere with
128
+ # this spec.
129
+ MyActiveRecordModel.send(:has_many,
130
+ :custom_name,
131
+ class_name: "OtherActiveRecordModelTransition")
132
+
133
+ MyActiveRecordModel.class_eval do
134
+ def self.transition_class
135
+ OtherActiveRecordModelTransition
136
+ end
137
+ end
138
+ end
139
+
140
+ describe ".in_state" do
141
+ subject(:query) { MyActiveRecordModel.in_state(:succeeded) }
142
+
143
+ specify { expect { query }.to_not raise_error }
144
+ end
145
+ end
146
+
147
+ context "with a custom primary key for the model" do
148
+ before do
149
+ # Switch to using OtherActiveRecordModelTransition, so the existing
150
+ # relation with MyActiveRecordModelTransition doesn't interfere with
151
+ # this spec.
152
+ # Configure the relationship to use a different primary key,
153
+ MyActiveRecordModel.send(:has_many,
154
+ :custom_name,
155
+ class_name: "OtherActiveRecordModelTransition",
156
+ primary_key: :external_id)
157
+
158
+ MyActiveRecordModel.class_eval do
159
+ def self.transition_class
160
+ OtherActiveRecordModelTransition
161
+ end
162
+ end
163
+ end
164
+
165
+ describe ".in_state" do
166
+ subject(:query) { MyActiveRecordModel.in_state(:succeeded) }
167
+
168
+ specify { expect { query }.to_not raise_error }
169
+ end
170
+ end
171
+
172
+ context "after_commit transactional integrity" do
173
+ before do
174
+ MyStateMachine.class_eval do
175
+ cattr_accessor(:after_commit_callback_executed) { false }
176
+
177
+ after_transition(from: :initial, to: :succeeded, after_commit: true) do
178
+ # This leaks state in a testable way if transactional integrity is broken.
179
+ MyStateMachine.after_commit_callback_executed = true
180
+ end
181
+ end
182
+ end
183
+
184
+ after do
185
+ MyStateMachine.class_eval do
186
+ callbacks[:after_commit] = []
187
+ end
188
+ end
189
+
190
+ let!(:model) do
191
+ MyActiveRecordModel.create
192
+ end
193
+
194
+ it do
195
+ expect do
196
+ ActiveRecord::Base.transaction do
197
+ model.state_machine.transition_to!(:succeeded)
198
+ raise ActiveRecord::Rollback
199
+ end
200
+ end.to_not change(MyStateMachine, :after_commit_callback_executed)
201
+ end
202
+ end
203
+ end
204
+
205
+ context "using configuration method" do
206
+ include_examples "testing methods"
207
+ end
208
+ end
@@ -7,6 +7,7 @@ describe Statesman do
7
7
  subject(:error) { Statesman::InvalidStateError.new }
8
8
 
9
9
  its(:message) { is_expected.to eq("Statesman::InvalidStateError") }
10
+
10
11
  its "string matches its message" do
11
12
  expect(error.to_s).to eq(error.message)
12
13
  end
@@ -16,6 +17,7 @@ describe Statesman do
16
17
  subject(:error) { Statesman::InvalidTransitionError.new }
17
18
 
18
19
  its(:message) { is_expected.to eq("Statesman::InvalidTransitionError") }
20
+
19
21
  its "string matches its message" do
20
22
  expect(error.to_s).to eq(error.message)
21
23
  end
@@ -25,6 +27,7 @@ describe Statesman do
25
27
  subject(:error) { Statesman::InvalidTransitionError.new }
26
28
 
27
29
  its(:message) { is_expected.to eq("Statesman::InvalidTransitionError") }
30
+
28
31
  its "string matches its message" do
29
32
  expect(error.to_s).to eq(error.message)
30
33
  end
@@ -34,6 +37,7 @@ describe Statesman do
34
37
  subject(:error) { Statesman::TransitionConflictError.new }
35
38
 
36
39
  its(:message) { is_expected.to eq("Statesman::TransitionConflictError") }
40
+
37
41
  its "string matches its message" do
38
42
  expect(error.to_s).to eq(error.message)
39
43
  end
@@ -43,6 +47,7 @@ describe Statesman do
43
47
  subject(:error) { Statesman::MissingTransitionAssociation.new }
44
48
 
45
49
  its(:message) { is_expected.to eq("Statesman::MissingTransitionAssociation") }
50
+
46
51
  its "string matches its message" do
47
52
  expect(error.to_s).to eq(error.message)
48
53
  end
@@ -52,17 +57,25 @@ describe Statesman do
52
57
  subject(:error) { Statesman::TransitionFailedError.new("from", "to") }
53
58
 
54
59
  its(:message) { is_expected.to eq("Cannot transition from 'from' to 'to'") }
60
+
55
61
  its "string matches its message" do
56
62
  expect(error.to_s).to eq(error.message)
57
63
  end
58
64
  end
59
65
 
60
66
  describe "GuardFailedError" do
61
- subject(:error) { Statesman::GuardFailedError.new("from", "to") }
67
+ subject(:error) { Statesman::GuardFailedError.new("from", "to", callback) }
68
+
69
+ let(:callback) { -> { "hello" } }
62
70
 
63
71
  its(:message) do
64
72
  is_expected.to eq("Guard on transition from: 'from' to 'to' returned false")
65
73
  end
74
+
75
+ its(:backtrace) do
76
+ is_expected.to eq([callback.source_location.join(":")])
77
+ end
78
+
66
79
  its "string matches its message" do
67
80
  expect(error.to_s).to eq(error.message)
68
81
  end
@@ -72,6 +85,7 @@ describe Statesman do
72
85
  subject(:error) { Statesman::UnserializedMetadataError.new("foo") }
73
86
 
74
87
  its(:message) { is_expected.to match(/foo#metadata is not serialized/) }
88
+
75
89
  its "string matches its message" do
76
90
  expect(error.to_s).to eq(error.message)
77
91
  end
@@ -81,6 +95,7 @@ describe Statesman do
81
95
  subject(:error) { Statesman::IncompatibleSerializationError.new("foo") }
82
96
 
83
97
  its(:message) { is_expected.to match(/foo#metadata column type cannot be json/) }
98
+
84
99
  its "string matches its message" do
85
100
  expect(error.to_s).to eq(error.message)
86
101
  end