statesman 7.4.0 → 10.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.
@@ -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