gush 3.0.0 → 4.0.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.
@@ -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