gush 3.0.0 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -19,14 +19,43 @@ describe Gush::Client do
19
19
  it "returns Workflow object" do
20
20
  expected_workflow = TestWorkflow.create
21
21
  workflow = client.find_workflow(expected_workflow.id)
22
+ dependencies = workflow.dependencies
22
23
 
23
24
  expect(workflow.id).to eq(expected_workflow.id)
25
+ expect(workflow.persisted).to eq(true)
24
26
  expect(workflow.jobs.map(&:name)).to match_array(expected_workflow.jobs.map(&:name))
27
+ expect(workflow.dependencies).to eq(dependencies)
25
28
  end
26
29
 
27
30
  context "when workflow has parameters" do
28
31
  it "returns Workflow object" do
29
- expected_workflow = ParameterTestWorkflow.create(true)
32
+ expected_workflow = ParameterTestWorkflow.create(true, kwarg: 123)
33
+ workflow = client.find_workflow(expected_workflow.id)
34
+
35
+ expect(workflow.id).to eq(expected_workflow.id)
36
+ expect(workflow.arguments).to eq([true])
37
+ expect(workflow.kwargs).to eq({ kwarg: 123 })
38
+ expect(workflow.jobs.map(&:name)).to match_array(expected_workflow.jobs.map(&:name))
39
+ end
40
+ end
41
+
42
+ context "when workflow has globals" do
43
+ it "returns Workflow object" do
44
+ expected_workflow = TestWorkflow.create(globals: { global1: 'foo' })
45
+ workflow = client.find_workflow(expected_workflow.id)
46
+
47
+ expect(workflow.id).to eq(expected_workflow.id)
48
+ expect(workflow.globals[:global1]).to eq('foo')
49
+ end
50
+ end
51
+
52
+ context "when workflow was persisted without job_klasses" do
53
+ it "returns Workflow object" do
54
+ expected_workflow = TestWorkflow.create
55
+
56
+ json = Gush::JSON.encode(expected_workflow.to_hash.except(:job_klasses))
57
+ redis.set("gush.workflows.#{expected_workflow.id}", json)
58
+
30
59
  workflow = client.find_workflow(expected_workflow.id)
31
60
 
32
61
  expect(workflow.id).to eq(expected_workflow.id)
@@ -82,42 +111,222 @@ describe Gush::Client do
82
111
  end
83
112
  end
84
113
 
114
+ describe "#next_free_job_id" do
115
+ it "returns an id" do
116
+ expect(client.next_free_job_id('123', Prepare.to_s)).to match(/^\h{8}-\h{4}-(\h{4})-\h{4}-\h{12}$/)
117
+ end
118
+
119
+ it "returns an id that doesn't match an existing job id" do
120
+ workflow = TestWorkflow.create
121
+ job = workflow.jobs.first
122
+
123
+ second_try_id = '1234'
124
+ allow(SecureRandom).to receive(:uuid).and_return(job.id, second_try_id)
125
+
126
+ expect(client.next_free_job_id(workflow.id, job.class.to_s)).to eq(second_try_id)
127
+ end
128
+ end
129
+
130
+ describe "#next_free_workflow_id" do
131
+ it "returns an id" do
132
+ expect(client.next_free_workflow_id).to match(/^\h{8}-\h{4}-(\h{4})-\h{4}-\h{12}$/)
133
+ end
134
+
135
+ it "returns an id that doesn't match an existing workflow id" do
136
+ workflow = TestWorkflow.create
137
+
138
+ second_try_id = '1234'
139
+ allow(SecureRandom).to receive(:uuid).and_return(workflow.id, second_try_id)
140
+
141
+ expect(client.next_free_workflow_id).to eq(second_try_id)
142
+ end
143
+ end
144
+
85
145
  describe "#persist_workflow" do
86
146
  it "persists JSON dump of the Workflow and its jobs" do
87
147
  job = double("job", to_json: 'json')
88
148
  workflow = double("workflow", id: 'abcd', jobs: [job, job, job], to_json: '"json"')
89
- expect(client).to receive(:persist_job).exactly(3).times.with(workflow.id, job)
149
+ expect(client).to receive(:persist_job).exactly(3).times.with(workflow.id, job, expires_at: nil)
90
150
  expect(workflow).to receive(:mark_as_persisted)
91
151
  client.persist_workflow(workflow)
92
152
  expect(redis.keys("gush.workflows.abcd").length).to eq(1)
93
153
  end
154
+
155
+ it "sets created_at index" do
156
+ workflow = double("workflow", id: 'abcd', jobs: [], to_json: '"json"')
157
+ expect(workflow).to receive(:mark_as_persisted).twice
158
+
159
+ freeze_time = Time.now.round # travel_to doesn't support fractions of a second
160
+ travel_to(freeze_time) do
161
+ client.persist_workflow(workflow)
162
+ end
163
+
164
+ expect(redis.zrange("gush.idx.workflows.created_at", 0, -1, with_scores: true))
165
+ .to eq([[workflow.id, freeze_time.to_f]])
166
+
167
+ # Persisting the workflow again should not affect its created_at index score
168
+ client.persist_workflow(workflow)
169
+ expect(redis.zrange("gush.idx.workflows.created_at", 0, -1, with_scores: true))
170
+ .to eq([[workflow.id, freeze_time.to_f]])
171
+ end
172
+
173
+ it "sets expires_at index when there is a ttl configured" do
174
+ allow_any_instance_of(Gush::Configuration).to receive(:ttl).and_return(1000)
175
+
176
+ workflow = double("workflow", id: 'abcd', jobs: [], to_json: '"json"')
177
+ expect(workflow).to receive(:mark_as_persisted).twice
178
+
179
+ freeze_time = Time.now.round # travel_to doesn't support fractions of a second
180
+ travel_to(freeze_time) do
181
+ client.persist_workflow(workflow)
182
+ end
183
+
184
+ expires_at = freeze_time + 1000
185
+ expect(redis.zrange("gush.idx.workflows.expires_at", 0, -1, with_scores: true))
186
+ .to eq([[workflow.id, expires_at.to_f]])
187
+
188
+ # Persisting the workflow again should not affect its expires_at index score
189
+ client.persist_workflow(workflow)
190
+ expect(redis.zrange("gush.idx.workflows.expires_at", 0, -1, with_scores: true))
191
+ .to eq([[workflow.id, expires_at.to_f]])
192
+ end
193
+
194
+ it "does not set expires_at index when there is no ttl configured" do
195
+ workflow = double("workflow", id: 'abcd', jobs: [], to_json: '"json"')
196
+ expect(workflow).to receive(:mark_as_persisted)
197
+ client.persist_workflow(workflow)
198
+
199
+ expect(redis.zcard("gush.idx.workflows.expires_at")).to eq(0)
200
+ end
201
+
202
+ it "does not set expires_at index when updating a pre-existing workflow without a ttl" do
203
+ allow_any_instance_of(Gush::Configuration).to receive(:ttl).and_return(1000)
204
+
205
+ workflow = double("workflow", id: 'abcd', jobs: [], to_json: '"json"')
206
+ expect(workflow).to receive(:mark_as_persisted).twice
207
+
208
+ client.persist_workflow(workflow)
209
+
210
+ client.expire_workflow(workflow, -1)
211
+ expect(redis.zcard("gush.idx.workflows.expires_at")).to eq(0)
212
+
213
+ client.persist_workflow(workflow)
214
+ expect(redis.zcard("gush.idx.workflows.expires_at")).to eq(0)
215
+ end
216
+
217
+ it "does not change expires_at index when updating a pre-existing workflow with a non-standard ttl" do
218
+ allow_any_instance_of(Gush::Configuration).to receive(:ttl).and_return(1000)
219
+
220
+ workflow = double("workflow", id: 'abcd', jobs: [], to_json: '"json"')
221
+ expect(workflow).to receive(:mark_as_persisted).twice
222
+
223
+ freeze_time = Time.now.round # travel_to doesn't support fractions of a second
224
+ travel_to(freeze_time) do
225
+ client.persist_workflow(workflow)
226
+
227
+ expires_at = freeze_time.to_i + 1234
228
+ client.expire_workflow(workflow, 1234)
229
+ expect(redis.zscore("gush.idx.workflows.expires_at", workflow.id)).to eq(expires_at)
230
+
231
+ client.persist_workflow(workflow)
232
+ expect(redis.zscore("gush.idx.workflows.expires_at", workflow.id)).to eq(expires_at)
233
+ end
234
+ end
94
235
  end
95
236
 
96
237
  describe "#destroy_workflow" do
97
238
  it "removes all Redis keys related to the workflow" do
239
+ allow_any_instance_of(Gush::Configuration).to receive(:ttl).and_return(1000)
240
+
98
241
  workflow = TestWorkflow.create
99
242
  expect(redis.keys("gush.workflows.#{workflow.id}").length).to eq(1)
100
243
  expect(redis.keys("gush.jobs.#{workflow.id}.*").length).to eq(5)
244
+ expect(redis.zcard("gush.idx.workflows.created_at")).to eq(1)
245
+ expect(redis.zcard("gush.idx.workflows.expires_at")).to eq(1)
246
+ expect(redis.zcard("gush.idx.jobs.expires_at")).to eq(5)
101
247
 
102
248
  client.destroy_workflow(workflow)
103
249
 
104
250
  expect(redis.keys("gush.workflows.#{workflow.id}").length).to eq(0)
105
251
  expect(redis.keys("gush.jobs.#{workflow.id}.*").length).to eq(0)
252
+ expect(redis.zcard("gush.idx.workflows.created_at")).to eq(0)
253
+ expect(redis.zcard("gush.idx.workflows.expires_at")).to eq(0)
254
+ expect(redis.zcard("gush.idx.jobs.expires_at")).to eq(0)
255
+ end
256
+ end
257
+
258
+ describe "#expire_workflows" do
259
+ it "removes auto-expired workflows" do
260
+ allow_any_instance_of(Gush::Configuration).to receive(:ttl).and_return(1000)
261
+
262
+ workflow = TestWorkflow.create
263
+
264
+ # before workflow's expiration time
265
+ client.expire_workflows
266
+
267
+ expect(redis.keys("gush.workflows.*").length).to eq(1)
268
+
269
+ # after workflow's expiration time
270
+ client.expire_workflows(Time.now.to_f + 1001)
271
+
272
+ expect(redis.keys("gush.workflows.#{workflow.id}").length).to eq(0)
273
+ expect(redis.keys("gush.jobs.#{workflow.id}.*").length).to eq(0)
274
+ expect(redis.zcard("gush.idx.workflows.created_at")).to eq(0)
275
+ expect(redis.zcard("gush.idx.workflows.expires_at")).to eq(0)
276
+ expect(redis.zcard("gush.idx.jobs.expires_at")).to eq(0)
277
+ end
278
+
279
+ it "removes manually-expired workflows" do
280
+ workflow = TestWorkflow.create
281
+
282
+ # workflow hasn't been expired
283
+ client.expire_workflows(Time.now.to_f + 100_000)
284
+
285
+ expect(redis.keys("gush.workflows.*").length).to eq(1)
286
+
287
+ client.expire_workflow(workflow, 10)
288
+
289
+ # after workflow's expiration time
290
+ client.expire_workflows(Time.now.to_f + 20)
291
+
292
+ expect(redis.keys("gush.workflows.#{workflow.id}").length).to eq(0)
293
+ expect(redis.keys("gush.jobs.#{workflow.id}.*").length).to eq(0)
294
+ expect(redis.zcard("gush.idx.workflows.created_at")).to eq(0)
295
+ expect(redis.zcard("gush.idx.workflows.expires_at")).to eq(0)
296
+ expect(redis.zcard("gush.idx.jobs.expires_at")).to eq(0)
106
297
  end
107
298
  end
108
299
 
109
300
  describe "#expire_workflow" do
110
301
  let(:ttl) { 2000 }
111
302
 
112
- it "sets TTL for all Redis keys related to the workflow" do
303
+ it "sets an expiration time for the workflow" do
113
304
  workflow = TestWorkflow.create
114
305
 
115
- client.expire_workflow(workflow, ttl)
306
+ freeze_time = Time.now.round # travel_to doesn't support fractions of a second
307
+ expires_at = freeze_time.to_f + ttl
308
+ travel_to(freeze_time) do
309
+ client.expire_workflow(workflow, ttl)
310
+ end
116
311
 
117
- expect(redis.ttl("gush.workflows.#{workflow.id}")).to eq(ttl)
312
+ expect(redis.zscore("gush.idx.workflows.expires_at", workflow.id)).to eq(expires_at)
118
313
 
119
314
  workflow.jobs.each do |job|
120
- expect(redis.ttl("gush.jobs.#{workflow.id}.#{job.klass}")).to eq(ttl)
315
+ expect(redis.zscore("gush.idx.jobs.expires_at", "#{workflow.id}.#{job.klass}")).to eq(expires_at)
316
+ end
317
+ end
318
+
319
+ it "clears an expiration time for the workflow when given -1" do
320
+ workflow = TestWorkflow.create
321
+
322
+ client.expire_workflow(workflow, 100)
323
+ expect(redis.zscore("gush.idx.workflows.expires_at", workflow.id)).to be > 0
324
+
325
+ client.expire_workflow(workflow, -1)
326
+ expect(redis.zscore("gush.idx.workflows.expires_at", workflow.id)).to eq(nil)
327
+
328
+ workflow.jobs.each do |job|
329
+ expect(redis.zscore("gush.idx.jobs.expires_at", "#{workflow.id}.#{job.klass}")).to eq(nil)
121
330
  end
122
331
  end
123
332
  end
@@ -130,6 +339,94 @@ describe Gush::Client do
130
339
  client.persist_job('deadbeef', job)
131
340
  expect(redis.keys("gush.jobs.deadbeef.*").length).to eq(1)
132
341
  end
342
+
343
+ it "sets expires_at index when expires_at is provided" do
344
+ job = BobJob.new(name: 'bob', id: 'abcd123')
345
+
346
+ freeze_time = Time.now.round # travel_to doesn't support fractions of a second
347
+ expires_at = freeze_time.to_f + 1000
348
+
349
+ travel_to(freeze_time) do
350
+ client.persist_job('deadbeef', job, expires_at: expires_at)
351
+ end
352
+
353
+ expect(redis.zrange("gush.idx.jobs.expires_at", 0, -1, with_scores: true))
354
+ .to eq([["deadbeef.#{job.klass}", expires_at]])
355
+
356
+ # Persisting the workflow again should not affect its expires_at index score
357
+ client.persist_job('deadbeef', job)
358
+ expect(redis.zrange("gush.idx.jobs.expires_at", 0, -1, with_scores: true))
359
+ .to eq([["deadbeef.#{job.klass}", expires_at]])
360
+ end
361
+
362
+ it "does not set expires_at index when there is no ttl configured" do
363
+ job = BobJob.new(name: 'bob', id: 'abcd123')
364
+ client.persist_job('deadbeef', job)
365
+
366
+ expect(redis.zcard("gush.idx.jobs.expires_at")).to eq(0)
367
+ end
368
+ end
369
+
370
+ describe "#workflow_ids" do
371
+ it "returns a page of registered workflow ids" do
372
+ workflow = TestWorkflow.create
373
+ ids = client.workflow_ids
374
+ expect(ids).to eq([workflow.id])
375
+ end
376
+
377
+ it "sorts workflow ids by created time or reverse created time" do
378
+ ids = 3.times.map { TestWorkflow.create }.map(&:id)
379
+
380
+ expect(client.workflow_ids).to eq(ids)
381
+ expect(client.workflow_ids(order: :asc)).to eq(ids)
382
+ expect(client.workflow_ids(order: :desc)).to eq(ids.reverse)
383
+ end
384
+
385
+ it "supports start and stop params" do
386
+ ids = 3.times.map { TestWorkflow.create }.map(&:id)
387
+
388
+ expect(client.workflow_ids(0, 1)).to eq(ids.slice(0..1))
389
+ expect(client.workflow_ids(1, 1)).to eq(ids.slice(1..1))
390
+ expect(client.workflow_ids(1, 10)).to eq(ids.slice(1..2))
391
+ expect(client.workflow_ids(0, -1)).to eq(ids)
392
+ end
393
+
394
+ it "supports start and stop params using created timestamps" do
395
+ times = [100, 200, 300]
396
+ ids = []
397
+
398
+ times.each do |t|
399
+ travel_to Time.at(t) do
400
+ ids << TestWorkflow.create.id
401
+ end
402
+ end
403
+
404
+ expect(client.workflow_ids(0, 1, by_ts: true)).to be_empty
405
+ expect(client.workflow_ids(50, 150, by_ts: true)).to eq(ids.slice(0..0))
406
+ expect(client.workflow_ids(150, 50, by_ts: true, order: :desc)).to eq(ids.slice(0..0))
407
+ expect(client.workflow_ids("-inf", "inf", by_ts: true)).to eq(ids)
408
+ end
409
+ end
410
+
411
+ describe "#workflows" do
412
+ it "returns a page of registered workflows" do
413
+ workflow = TestWorkflow.create
414
+ expect(client.workflows.map(&:id)).to eq([workflow.id])
415
+ end
416
+ end
417
+
418
+ describe "#workflows_count" do
419
+ it "returns a count of registered workflows" do
420
+ allow_any_instance_of(Gush::Configuration).to receive(:ttl).and_return(1000)
421
+
422
+ expect(client.workflows_count).to eq(0)
423
+
424
+ workflow = TestWorkflow.create
425
+ expect(client.workflows_count).to eq(1)
426
+
427
+ client.expire_workflows(Time.now.to_f + 1001)
428
+ expect(client.workflows_count).to eq(0)
429
+ end
133
430
  end
134
431
 
135
432
  describe "#all_workflows" do
@@ -49,6 +49,45 @@ describe Gush::Job do
49
49
  end
50
50
  end
51
51
 
52
+ describe "#enqueue_worker!" do
53
+ it "enqueues the job using Gush::Worker" do
54
+ job = described_class.new(name: "a-job", workflow_id: 123)
55
+
56
+ expect {
57
+ job.enqueue_worker!
58
+ }.to change{ActiveJob::Base.queue_adapter.enqueued_jobs.size}.from(0).to(1)
59
+ end
60
+
61
+ it "handles ActiveJob.set options" do
62
+ freeze_time = Time.utc(2023, 01, 21, 14, 36, 0)
63
+
64
+ travel_to freeze_time do
65
+ job = described_class.new(name: "a-job", workflow_id: 123)
66
+ job.enqueue_worker!(wait_until: freeze_time + 5.minutes)
67
+ expect(Gush::Worker).to have_a_job_enqueued_at(123, job_with_id(job.class.name), 5.minutes)
68
+ end
69
+ end
70
+ end
71
+
72
+ describe "#worker_options" do
73
+ it "returns a blank options hash by default" do
74
+ job = described_class.new
75
+ expect(job.worker_options).to eq({})
76
+ end
77
+
78
+ it "returns a hash with the queue setting" do
79
+ job = described_class.new
80
+ job.queue = 'my-queue'
81
+ expect(job.worker_options).to eq({ queue: 'my-queue' })
82
+ end
83
+
84
+ it "returns a hash with the wait setting" do
85
+ job = described_class.new
86
+ job.wait = 123
87
+ expect(job.worker_options).to eq({ wait: 123 })
88
+ end
89
+ end
90
+
52
91
  describe "#start!" do
53
92
  it "resets flags and marks as running" do
54
93
  job = described_class.new(name: "a-job")
@@ -70,7 +109,13 @@ describe Gush::Job do
70
109
  describe "#as_json" do
71
110
  context "finished and enqueued set to true" do
72
111
  it "returns correct hash" do
73
- job = described_class.new(workflow_id: 123, id: "702bced5-bb72-4bba-8f6f-15a3afa358bd", finished_at: 123, enqueued_at: 120)
112
+ job = described_class.new(
113
+ workflow_id: 123,
114
+ id: '702bced5-bb72-4bba-8f6f-15a3afa358bd',
115
+ finished_at: 123,
116
+ enqueued_at: 120,
117
+ wait: 300
118
+ )
74
119
  expected = {
75
120
  id: '702bced5-bb72-4bba-8f6f-15a3afa358bd',
76
121
  klass: "Gush::Job",
@@ -83,7 +128,8 @@ describe Gush::Job do
83
128
  params: {},
84
129
  queue: nil,
85
130
  output_payload: nil,
86
- workflow_id: 123
131
+ workflow_id: 123,
132
+ wait: 300
87
133
  }
88
134
  expect(job.as_json).to eq(expected)
89
135
  end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+ require 'gush/migrate/1_create_gush_workflows_created'
3
+
4
+ describe Gush::IndexWorkflowsByCreatedAtAndExpiresAt do
5
+
6
+ describe "#up" do
7
+ it "adds existing workflows to created_at index, but not expires_at index" do
8
+ TestWorkflow.create
9
+ redis.del("gush.idx.workflows.created_at")
10
+
11
+ allow_any_instance_of(Gush::Configuration).to receive(:ttl).and_return(1000)
12
+
13
+ subject.migrate
14
+
15
+ expect(redis.zcard("gush.idx.workflows.created_at")).to eq(1)
16
+ expect(redis.zcard("gush.idx.workflows.expires_at")).to eq(0)
17
+ expect(redis.zcard("gush.idx.jobs.expires_at")).to eq(0)
18
+ end
19
+
20
+ it "adds expiring workflows to expires_at index" do
21
+ workflow = TestWorkflow.create
22
+ redis.del("gush.idx.workflows.created_at")
23
+
24
+ freeze_time = Time.now.round # travel_to doesn't support fractions of a second
25
+ travel_to(freeze_time) do
26
+ redis.expire("gush.workflows.#{workflow.id}", 1234)
27
+ expires_at = freeze_time.to_f + 1234
28
+
29
+ allow_any_instance_of(Gush::Configuration).to receive(:ttl).and_return(1000)
30
+
31
+ subject.migrate
32
+
33
+ expect(redis.ttl("gush.workflows.#{workflow.id}")).to eq(-1)
34
+ expect(redis.ttl("gush.jobs.#{workflow.id}.#{workflow.jobs.first.class.name}")).to eq(-1)
35
+
36
+ expect(redis.zrange("gush.idx.workflows.expires_at", 0, -1, with_scores: true))
37
+ .to eq([[workflow.id, expires_at]])
38
+ expect(redis.zcard("gush.idx.jobs.expires_at")).to eq(5)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe Gush::Migration do
4
+
5
+ describe "#migrate" do
6
+ it "applies a migration once" do
7
+ class TestMigration < Gush::Migration
8
+ def self.version
9
+ 123
10
+ end
11
+ end
12
+
13
+ migration = TestMigration.new
14
+ expect(migration).to receive(:up).once
15
+
16
+ expect(migration.migrated?).to be(false)
17
+ migration.migrate
18
+
19
+ expect(migration.migrated?).to be(true)
20
+ migration.migrate
21
+ end
22
+ end
23
+ end
@@ -15,16 +15,68 @@ describe Gush::Workflow do
15
15
  expect_any_instance_of(klass).to receive(:configure).with("arg1", "arg2")
16
16
  klass.new("arg1", "arg2")
17
17
  end
18
- end
19
18
 
20
- describe "#status" do
21
- context "when failed" do
22
- it "returns :failed" do
23
- flow = TestWorkflow.create
24
- flow.find_job("Prepare").fail!
25
- flow.persist!
26
- expect(flow.reload.status).to eq(:failed)
19
+ it "passes constructor keyword arguments to the method" do
20
+ klass = Class.new(Gush::Workflow) do
21
+ def configure(*args, **kwargs)
22
+ run FetchFirstJob
23
+ run PersistFirstJob, after: FetchFirstJob
24
+ end
25
+ end
26
+
27
+ expect_any_instance_of(klass).to receive(:configure).with("arg1", "arg2", arg3: 123)
28
+ klass.new("arg1", "arg2", arg3: 123)
29
+ end
30
+
31
+ it "accepts globals" do
32
+ flow = TestWorkflow.new(globals: { global1: 'foo' })
33
+ expect(flow.globals[:global1]).to eq('foo')
34
+ end
35
+
36
+ it "accepts internal_state" do
37
+ flow = TestWorkflow.new
38
+
39
+ internal_state = {
40
+ id: flow.id,
41
+ jobs: flow.jobs,
42
+ dependencies: flow.dependencies,
43
+ persisted: true,
44
+ stopped: true
45
+ }
46
+
47
+ flow_copy = TestWorkflow.new(internal_state: internal_state)
48
+
49
+ expect(flow_copy.id).to eq(flow.id)
50
+ expect(flow_copy.jobs).to eq(flow.jobs)
51
+ expect(flow_copy.dependencies).to eq(flow.dependencies)
52
+ expect(flow_copy.persisted).to eq(true)
53
+ expect(flow_copy.stopped).to eq(true)
54
+ end
55
+
56
+ it "does not call #configure if needs_setup is false" do
57
+ INTERNAL_SETUP_SPY = double('configure spy')
58
+ klass = Class.new(Gush::Workflow) do
59
+ def configure(*args)
60
+ INTERNAL_SETUP_SPY.some_method
61
+ end
27
62
  end
63
+
64
+ expect(INTERNAL_SETUP_SPY).not_to receive(:some_method)
65
+
66
+ flow = TestWorkflow.new(internal_state: { needs_setup: false })
67
+ end
68
+ end
69
+
70
+ describe "#find" do
71
+ it "fiends a workflow by id" do
72
+ expect(Gush::Workflow.find(subject.id).id).to eq(subject.id)
73
+ end
74
+ end
75
+
76
+ describe "#page" do
77
+ it "returns a page of registered workflows" do
78
+ flow = TestWorkflow.create
79
+ expect(Gush::Workflow.page.map(&:id)).to eq([flow.id])
28
80
  end
29
81
  end
30
82
 
@@ -81,6 +133,41 @@ describe Gush::Workflow do
81
133
  end
82
134
  end
83
135
 
136
+ describe "#status" do
137
+ context "when failed" do
138
+ it "returns :failed" do
139
+ flow = TestWorkflow.create
140
+ flow.find_job("Prepare").fail!
141
+ flow.persist!
142
+ expect(flow.reload.status).to eq(:failed)
143
+ end
144
+ end
145
+
146
+ it "returns failed" do
147
+ subject.find_job('Prepare').fail!
148
+ expect(subject.status).to eq(:failed)
149
+ end
150
+
151
+ it "returns running" do
152
+ subject.find_job('Prepare').start!
153
+ expect(subject.status).to eq(:running)
154
+ end
155
+
156
+ it "returns finished" do
157
+ subject.jobs.each {|n| n.finish! }
158
+ expect(subject.status).to eq(:finished)
159
+ end
160
+
161
+ it "returns stopped" do
162
+ subject.stopped = true
163
+ expect(subject.status).to eq(:stopped)
164
+ end
165
+
166
+ it "returns pending" do
167
+ expect(subject.status).to eq(:pending)
168
+ end
169
+ end
170
+
84
171
  describe "#to_json" do
85
172
  it "returns correct hash" do
86
173
  klass = Class.new(Gush::Workflow) do
@@ -90,18 +177,25 @@ describe Gush::Workflow do
90
177
  end
91
178
  end
92
179
 
93
- result = JSON.parse(klass.create("arg1", "arg2").to_json)
180
+ result = JSON.parse(klass.create("arg1", "arg2", arg3: 123).to_json)
94
181
  expected = {
95
182
  "id" => an_instance_of(String),
96
183
  "name" => klass.to_s,
97
184
  "klass" => klass.to_s,
98
- "status" => "running",
185
+ "job_klasses" => ["FetchFirstJob", "PersistFirstJob"],
186
+ "status" => "pending",
99
187
  "total" => 2,
100
188
  "finished" => 0,
101
189
  "started_at" => nil,
102
190
  "finished_at" => nil,
103
191
  "stopped" => false,
104
- "arguments" => ["arg1", "arg2"]
192
+ "dependencies" => [{
193
+ "from" => "FetchFirstJob",
194
+ "to" => job_with_id("PersistFirstJob")
195
+ }],
196
+ "arguments" => ["arg1", "arg2"],
197
+ "kwargs" => {"arg3" => 123},
198
+ "globals" => {}
105
199
  }
106
200
  expect(result).to match(expected)
107
201
  end
@@ -118,14 +212,21 @@ describe Gush::Workflow do
118
212
  flow = Gush::Workflow.new
119
213
  flow.run(Gush::Job, params: { something: 1 })
120
214
  flow.save
121
- expect(flow.jobs.first.params).to eq ({ something: 1 })
215
+ expect(flow.jobs.first.params).to eq({ something: 1 })
216
+ end
217
+
218
+ it "merges globals with params and passes them to the job, with job param taking precedence" do
219
+ flow = Gush::Workflow.new(globals: { something: 2, global1: 123 })
220
+ flow.run(Gush::Job, params: { something: 1 })
221
+ flow.save
222
+ expect(flow.jobs.first.params).to eq({ something: 1, global1: 123 })
122
223
  end
123
224
 
124
225
  it "allows passing wait param to the job" do
125
226
  flow = Gush::Workflow.new
126
227
  flow.run(Gush::Job, wait: 5.seconds)
127
228
  flow.save
128
- expect(flow.jobs.first.wait).to eq (5.seconds)
229
+ expect(flow.jobs.first.wait).to eq(5.seconds)
129
230
  end
130
231
 
131
232
  context "when graph is empty" do