@@ -49,7 +49,6 @@ def decision_method
4949 decision_method
5050 end
5151 end
52- expect_any_instance_of ( TestWorkflow ) . to receive ( :__decision_method ) . once . and_return ( true )
5352 end
5453
5554 it 'calls only the yes block' do
@@ -75,7 +74,6 @@ def decision_method
7574 decision_method
7675 end
7776 end
78- expect_any_instance_of ( TestWorkflow ) . to receive ( :__decision_method ) . once . and_return ( false )
7977 end
8078
8179 it 'calls only the no block' do
@@ -117,15 +115,47 @@ def always_true
117115 end
118116 end
119117 end
118+
119+ context 'when the workflow is already finished' do
120+ before do
121+ class TestWorkflow < DecisionTree ::Workflow
122+ def decision_method
123+ end
124+
125+ decision :decision_method do
126+ yes { }
127+ no { }
128+ end
129+ end
130+ end
131+
132+ it 'does not execute the decision' do
133+ allow_any_instance_of ( TestWorkflow ) . to receive ( :finished? ) . and_return ( true )
134+ expect_any_instance_of ( TestWorkflow ) . not_to receive ( :__decision_method )
135+ TestWorkflow . new ( store ) . send ( :decision_method )
136+ end
137+ end
120138 end
121139
122140 describe '.entry' do
123141 before do
124142 class TestWorkflow < DecisionTree ::Workflow
143+ def always_true
144+ true
145+ end
146+
147+ def non_idempotent_action!
148+ end
149+
125150 def test_entry
126151 end
127152
128- entry ( :test_entry ) { }
153+ decision :always_true do
154+ yes { non_idempotent_action! }
155+ no { exit }
156+ end
157+
158+ entry ( :test_entry ) { always_true }
129159 start { }
130160 end
131161 end
@@ -156,6 +186,35 @@ def test_entry
156186 TestWorkflow . new ( store )
157187 end
158188 end
189+
190+ context 'for a store that updates state before yielding to workflow (ie locking)' do
191+ subject { TestWorkflow . new ( store ) }
192+ let ( :store ) { TestStore . new }
193+
194+ before do
195+ class TestStore < DecisionTree ::Store
196+ def start_workflow ( &block )
197+ self . state = '__start_workflow:non_idempotent_action!'
198+ yield
199+ end
200+ end
201+ end
202+
203+ it 'does not call the non-idempotent method again' do
204+ expect_any_instance_of ( TestWorkflow ) . to_not receive ( :non_idempotent_action! )
205+ subject . test_entry
206+ end
207+ end
208+
209+ context 'when the workflow is already finished' do
210+ subject { TestWorkflow . new ( store ) }
211+
212+ it 'does not execute the entry point' do
213+ allow_any_instance_of ( TestWorkflow ) . to receive ( :finished? ) . and_return ( true )
214+ expect_any_instance_of ( TestWorkflow ) . not_to receive ( :__test_entry )
215+ subject . test_entry
216+ end
217+ end
159218 end
160219
161220 describe '.start' do
@@ -188,33 +247,72 @@ def decision_method
188247 end
189248
190249 describe '.initialize' do
250+ subject { TestWorkflow . new ( store ) }
251+
252+ let ( :store ) { TestStore . new }
253+
191254 before do
192255 class TestWorkflow < DecisionTree ::Workflow
193- end
256+ def always_true
257+ true
258+ end
259+
260+ decision :always_true do
261+ yes { non_idempotent_action! }
262+ no { exit }
263+ end
194264
195- allow_any_instance_of ( TestWorkflow ) . to receive ( :finished? ) { finished }
265+ start { always_true }
266+ end
196267 end
197268
198- subject { TestWorkflow . new ( store ) }
269+ context 'for store that simply yields to the workflow' do
270+ before do
271+ class TestStore < DecisionTree ::Store
272+ def start_workflow ( &block )
273+ yield
274+ end
275+ end
199276
200- context 'when workflow not previously completed' do
201- let ( :finished ) { false }
202- specify 'executes the workflow' do
203- expect_any_instance_of ( TestWorkflow ) . to receive ( :execute_workflow )
204- subject
277+ allow_any_instance_of ( TestWorkflow ) . to receive ( :finished? ) { finished }
205278 end
206- end
207279
208- context 'when workflow previously completed' do
209- let ( :finished ) { true }
280+ context 'when workflow previously completed' do
281+ let ( :finished ) { true }
210282
211- specify 'does not execute the workflow' do
212- expect_any_instance_of ( TestWorkflow ) . to_not receive ( :execute_workflow )
213- subject
283+ it 'does not execute the workflow' do
284+ expect_any_instance_of ( TestWorkflow ) . to_not receive ( :execute_workflow )
285+ subject
286+ end
287+
288+ it 'fetches previously executed steps from the store' do
289+ expect ( store ) . to receive ( :fetch_steps )
290+ subject
291+ end
292+ end
293+
294+ context 'when workflow not previously completed' do
295+ let ( :finished ) { false }
296+
297+ it 'executes the workflow' do
298+ expect_any_instance_of ( TestWorkflow ) . to receive ( :execute_workflow )
299+ subject
300+ end
301+ end
302+ end
303+
304+ context 'for a store that updates state before yielding to workflow (ie locking)' do
305+ before do
306+ class TestStore < DecisionTree ::Store
307+ def start_workflow ( &block )
308+ self . state = '__start_workflow:non_idempotent_action!'
309+ yield
310+ end
311+ end
214312 end
215313
216- specify 'fetches previously executed steps from the store ' do
217- expect ( store ) . to receive ( :fetch_steps )
314+ it 'does not call the non-idempotent method again ' do
315+ expect_any_instance_of ( TestWorkflow ) . to_not receive ( :non_idempotent_action! )
218316 subject
219317 end
220318 end
@@ -232,7 +330,7 @@ class TestWorkflow < DecisionTree::Workflow
232330
233331 let ( :workflow ) { TestWorkflow . new ( store ) }
234332
235- specify 'records the finish call' do
333+ it 'records the finish call' do
236334 finish!
237335 expect ( subject ) . to include ( 'finish!' )
238336 end
@@ -249,7 +347,7 @@ class TestWorkflow < DecisionTree::Workflow
249347 end
250348 end
251349
252- specify 'calls finish! on the workflow' do
350+ it 'calls finish! on the workflow' do
253351 expect_any_instance_of ( TestWorkflow ) . to receive ( :finish! )
254352 workflow
255353 end
@@ -271,7 +369,7 @@ def decision_method
271369 end
272370 end
273371
274- specify 'calls finish! on the workflow' do
372+ it 'calls finish! on the workflow' do
275373 expect_any_instance_of ( TestWorkflow ) . to receive ( :finish! )
276374 workflow
277375 end
0 commit comments