sidekiq-status 0.5.0 → 3.0.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.
Files changed (39) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ci.yaml +53 -0
  3. data/.gitignore +4 -1
  4. data/.gitlab-ci.yml +17 -0
  5. data/Appraisals +11 -0
  6. data/CHANGELOG.md +41 -0
  7. data/README.md +148 -41
  8. data/Rakefile +2 -0
  9. data/gemfiles/sidekiq_6.1.gemfile +7 -0
  10. data/gemfiles/sidekiq_6.x.gemfile +7 -0
  11. data/gemfiles/sidekiq_7.x.gemfile +7 -0
  12. data/lib/sidekiq-status/client_middleware.rb +54 -1
  13. data/lib/sidekiq-status/redis_adapter.rb +18 -0
  14. data/lib/sidekiq-status/redis_client_adapter.rb +14 -0
  15. data/lib/sidekiq-status/server_middleware.rb +76 -17
  16. data/lib/sidekiq-status/sidekiq_extensions.rb +7 -0
  17. data/lib/sidekiq-status/storage.rb +26 -13
  18. data/lib/sidekiq-status/testing/inline.rb +10 -0
  19. data/lib/sidekiq-status/version.rb +1 -1
  20. data/lib/sidekiq-status/web.rb +158 -10
  21. data/lib/sidekiq-status/worker.rb +12 -5
  22. data/lib/sidekiq-status.rb +43 -10
  23. data/sidekiq-status.gemspec +9 -4
  24. data/spec/environment.rb +1 -0
  25. data/spec/lib/sidekiq-status/client_middleware_spec.rb +30 -13
  26. data/spec/lib/sidekiq-status/server_middleware_spec.rb +102 -30
  27. data/spec/lib/sidekiq-status/web_spec.rb +84 -0
  28. data/spec/lib/sidekiq-status/worker_spec.rb +21 -2
  29. data/spec/lib/sidekiq-status_spec.rb +158 -77
  30. data/spec/spec_helper.rb +104 -24
  31. data/spec/support/test_jobs.rb +84 -7
  32. data/web/sidekiq-status-single-web.png +0 -0
  33. data/web/sidekiq-status-web.png +0 -0
  34. data/web/views/status.erb +118 -0
  35. data/web/views/status_not_found.erb +6 -0
  36. data/web/views/statuses.erb +135 -17
  37. metadata +102 -16
  38. data/.travis.yml +0 -2
  39. data/CHANGELOG +0 -2
@@ -5,62 +5,134 @@ describe Sidekiq::Status::ServerMiddleware do
5
5
  let!(:redis) { Sidekiq.redis { |conn| conn } }
6
6
  let!(:job_id) { SecureRandom.hex(12) }
7
7
 
8
- # Clean Redis before each test
9
- # Seems like flushall has no effect on recently published messages,
10
- # so we should wait till they expire
11
- before { redis.flushall; sleep 0.1 }
12
-
13
- describe "#call" do
8
+ describe "without :expiration parameter" do
14
9
  it "sets working/complete status" do
15
- thread = confirmations_thread 4, "status_updates", "job_messages_#{job_id}"
16
- SecureRandom.should_receive(:hex).once.and_return(job_id)
10
+ allow(SecureRandom).to receive(:hex).once.and_return(job_id)
17
11
  start_server do
18
- ConfirmationJob.perform_async(:arg1 => 'val1').should == job_id
19
- thread.value.should == [job_id, job_id,
20
- "while in #perform, status = working",
21
- job_id]
12
+ thread = branched_redis_thread 4, "status_updates", "job_messages_#{job_id}" do
13
+ expect(ConfirmationJob.perform_async 'arg1' => 'val1').to eq(job_id)
14
+ end
15
+ expect(thread.value).to eq([
16
+ job_id,
17
+ job_id,
18
+ "while in #perform, status = working",
19
+ job_id
20
+ ])
22
21
  end
23
- redis.hget(job_id, :status).should == 'complete'
24
- Sidekiq::Status::complete?(job_id).should be_true
22
+ expect(redis.hget("sidekiq:status:#{job_id}", :status)).to eq('complete')
23
+ expect(Sidekiq::Status::complete?(job_id)).to be_truthy
25
24
  end
26
25
 
27
26
  it "sets failed status" do
28
- SecureRandom.should_receive(:hex).once.and_return(job_id)
27
+ allow(SecureRandom).to receive(:hex).once.and_return(job_id)
28
+ start_server do
29
+ expect(capture_status_updates(3) {
30
+ expect(FailingJob.perform_async).to eq(job_id)
31
+ }).to eq([job_id]*3)
32
+ end
33
+ expect(redis.hget("sidekiq:status:#{job_id}", :status)).to eq('failed')
34
+ expect(Sidekiq::Status::failed?(job_id)).to be_truthy
35
+ end
36
+
37
+ it "sets failed status when Exception raised" do
38
+ allow(SecureRandom).to receive(:hex).once.and_return(job_id)
29
39
  start_server do
30
- capture_status_updates(3) {
31
- FailingJob.perform_async.should == job_id
32
- }.should == [job_id]*3
40
+ expect(capture_status_updates(3) {
41
+ expect(FailingHardJob.perform_async).to eq(job_id)
42
+ }).to eq([job_id]*3)
33
43
  end
34
- redis.hget(job_id, :status).should == 'failed'
35
- Sidekiq::Status::failed?(job_id).should be_true
44
+ expect(redis.hget("sidekiq:status:#{job_id}", :status)).to eq('failed')
45
+ expect(Sidekiq::Status::failed?(job_id)).to be_truthy
46
+ end
47
+
48
+ context "when Sidekiq::Status::Worker is not included in the job" do
49
+ it "should not set a failed status" do
50
+ allow(SecureRandom).to receive(:hex).once.and_return(job_id)
51
+ start_server do
52
+ expect(FailingNoStatusJob.perform_async).to eq(job_id)
53
+ end
54
+ expect(redis.hget("sidekiq:status:#{job_id}", :status)).to be_nil
55
+ end
56
+
57
+ it "should not set any status when Exception raised" do
58
+ allow(SecureRandom).to receive(:hex).once.and_return(job_id)
59
+ start_server do
60
+ expect(FailingHardNoStatusJob.perform_async).to eq(job_id)
61
+ end
62
+ expect(redis.hget("sidekiq:status:#{job_id}", :status)).to be_nil
63
+ end
64
+
65
+ it "should not set any status on system exit signal" do
66
+ allow(SecureRandom).to receive(:hex).once.and_return(job_id)
67
+ start_server do
68
+ expect(ExitedNoStatusJob.perform_async).to eq(job_id)
69
+ end
70
+ expect(redis.hget("sidekiq:status:#{job_id}", :status)).to be_nil
71
+ end
72
+
73
+ it "should not set any status on interrupt signal" do
74
+ allow(SecureRandom).to receive(:hex).once.and_return(job_id)
75
+ start_server do
76
+ expect(InterruptedNoStatusJob.perform_async).to eq(job_id)
77
+ end
78
+ expect(redis.hget("sidekiq:status:#{job_id}", :status)).to be_nil
79
+ end
80
+ end
81
+
82
+ context "sets interrupted status" do
83
+ it "on system exit signal" do
84
+ allow(SecureRandom).to receive(:hex).once.and_return(job_id)
85
+ start_server do
86
+ expect(capture_status_updates(3) {
87
+ expect(ExitedJob.perform_async).to eq(job_id)
88
+ }).to eq([job_id]*3)
89
+ end
90
+ expect(redis.hget("sidekiq:status:#{job_id}", :status)).to eq('interrupted')
91
+ expect(Sidekiq::Status::interrupted?(job_id)).to be_truthy
92
+ end
93
+
94
+ it "on interrupt signal" do
95
+ allow(SecureRandom).to receive(:hex).once.and_return(job_id)
96
+ start_server do
97
+ expect(capture_status_updates(3) {
98
+ expect(InterruptedJob.perform_async).to eq(job_id)
99
+ }).to eq([job_id]*3)
100
+ end
101
+ expect(redis.hget("sidekiq:status:#{job_id}", :status)).to eq('interrupted')
102
+ expect(Sidekiq::Status::interrupted?(job_id)).to be_truthy
103
+ end
104
+
36
105
  end
37
106
 
38
107
  it "sets status hash ttl" do
39
- SecureRandom.should_receive(:hex).once.and_return(job_id)
40
- StubJob.perform_async(:arg1 => 'val1').should == job_id
41
- (1..Sidekiq::Status::DEFAULT_EXPIRY).should cover redis.ttl(job_id)
108
+ allow(SecureRandom).to receive(:hex).once.and_return(job_id)
109
+ start_server do
110
+ expect(StubJob.perform_async 'arg1' => 'val1').to eq(job_id)
111
+ end
112
+ expect(1..Sidekiq::Status::DEFAULT_EXPIRY).to cover redis.ttl("sidekiq:status:#{job_id}")
42
113
  end
43
114
  end
44
115
 
45
- describe ":expiration parameter" do
116
+ describe "with :expiration parameter" do
46
117
  let(:huge_expiration) { Sidekiq::Status::DEFAULT_EXPIRY * 100 }
47
118
  before do
48
- SecureRandom.should_receive(:hex).once.and_return(job_id)
119
+ allow(SecureRandom).to receive(:hex).once.and_return(job_id)
49
120
  end
121
+
50
122
  it "overwrites default expiry value" do
51
123
  start_server(:expiration => huge_expiration) do
52
- StubJob.perform_async(:arg1 => 'val1')
124
+ StubJob.perform_async 'arg1' => 'val1'
53
125
  end
54
- ((Sidekiq::Status::DEFAULT_EXPIRY+1)..huge_expiration).should cover redis.ttl(job_id)
126
+ expect((Sidekiq::Status::DEFAULT_EXPIRY-1)..huge_expiration).to cover redis.ttl("sidekiq:status:#{job_id}")
55
127
  end
56
128
 
57
129
  it "can be overwritten by worker expiration method" do
58
130
  overwritten_expiration = huge_expiration * 100
59
- StubJob.any_instance.stub(expiration: overwritten_expiration)
131
+ allow_any_instance_of(StubJob).to receive(:expiration).and_return(overwritten_expiration)
60
132
  start_server(:expiration => huge_expiration) do
61
- StubJob.perform_async(:arg1 => 'val1')
133
+ StubJob.perform_async 'arg1' => 'val1'
62
134
  end
63
- ((huge_expiration+1)..overwritten_expiration).should cover redis.ttl(job_id)
135
+ expect((huge_expiration+1)..overwritten_expiration).to cover redis.ttl("sidekiq:status:#{job_id}")
64
136
  end
65
137
  end
66
138
  end
@@ -0,0 +1,84 @@
1
+ require 'spec_helper'
2
+ require 'sidekiq-status/web'
3
+ require 'rack/test'
4
+
5
+ describe 'sidekiq status web' do
6
+ include Rack::Test::Methods
7
+
8
+ let!(:redis) { Sidekiq.redis { |conn| conn } }
9
+ let!(:job_id) { SecureRandom.hex(12) }
10
+
11
+ def app
12
+ Sidekiq::Web
13
+ end
14
+
15
+ before do
16
+ env 'rack.session', csrf: Base64.urlsafe_encode64('token')
17
+ client_middleware
18
+ allow(SecureRandom).to receive(:hex).and_return(job_id)
19
+ end
20
+
21
+ around { |example| start_server(&example) }
22
+
23
+ it 'shows the list of jobs in progress' do
24
+ capture_status_updates(2) do
25
+ expect(LongJob.perform_async(0.5)).to eq(job_id)
26
+ end
27
+
28
+ get '/statuses'
29
+ expect(last_response).to be_ok
30
+ expect(last_response.body).to match(/#{job_id}/)
31
+ expect(last_response.body).to match(/LongJob/)
32
+ expect(last_response.body).to match(/working/)
33
+ end
34
+
35
+ it 'allows filtering the list of jobs by status' do
36
+ capture_status_updates(2) do
37
+ LongJob.perform_async(0.5)
38
+ end
39
+
40
+ get '/statuses?status=working'
41
+ expect(last_response).to be_ok
42
+ expect(last_response.body).to match(/#{job_id}/)
43
+ expect(last_response.body).to match(/LongJob/)
44
+ expect(last_response.body).to match(/working/)
45
+ end
46
+
47
+ it 'allows filtering the list of jobs by completed status' do
48
+ capture_status_updates(2) do
49
+ LongJob.perform_async(0.5)
50
+ end
51
+ get '/statuses?status=completed'
52
+ expect(last_response).to be_ok
53
+ expect(last_response.body).to_not match(/LongJob/)
54
+ end
55
+
56
+ it 'shows a single job in progress' do
57
+ capture_status_updates(2) do
58
+ LongJob.perform_async(1, 'another argument')
59
+ end
60
+
61
+ get "/statuses/#{job_id}"
62
+ expect(last_response).to be_ok
63
+ expect(last_response.body).to match(/#{job_id}/)
64
+ expect(last_response.body).to match(/1,"another argument"/)
65
+ expect(last_response.body).to match(/working/)
66
+ end
67
+
68
+ it 'shows custom data for a single job' do
69
+ capture_status_updates(3) do
70
+ CustomDataJob.perform_async
71
+ end
72
+
73
+ get "/statuses/#{job_id}"
74
+ expect(last_response).to be_ok
75
+ expect(last_response.body).to match(/mister_cat/)
76
+ expect(last_response.body).to match(/meow/)
77
+ end
78
+
79
+ it 'show an error when the requested job ID is not found' do
80
+ get '/statuses/12345'
81
+ expect(last_response).to be_not_found
82
+ expect(last_response.body).to match(/That job can't be found/)
83
+ end
84
+ end
@@ -6,17 +6,36 @@ describe Sidekiq::Status::Worker do
6
6
 
7
7
  describe ".perform_async" do
8
8
  it "generates and returns job id" do
9
- SecureRandom.should_receive(:hex).once.and_return(job_id)
10
- StubJob.perform_async().should == job_id
9
+ allow(SecureRandom).to receive(:hex).once.and_return(job_id)
10
+ expect(StubJob.perform_async()).to eq(job_id)
11
11
  end
12
12
  end
13
13
 
14
14
  describe ".expiration" do
15
15
  subject { StubJob.new }
16
+
16
17
  it "allows to set/get expiration" do
17
18
  expect(subject.expiration).to be_nil
18
19
  subject.expiration = :val
19
20
  expect(subject.expiration).to eq(:val)
20
21
  end
21
22
  end
23
+
24
+ describe ".at" do
25
+ subject { StubJob.new }
26
+
27
+ it "records when the worker has started" do
28
+ expect { subject.at(0) }.to(change { subject.retrieve('working_at') })
29
+ end
30
+
31
+ context "when setting the total for the worker" do
32
+ it "records when the worker has started" do
33
+ expect { subject.total(100) }.to(change { subject.retrieve('working_at') })
34
+ end
35
+ end
36
+
37
+ it "records when the worker last worked" do
38
+ expect { subject.at(0) }.to(change { subject.retrieve('update_time') })
39
+ end
40
+ end
22
41
  end
@@ -8,113 +8,132 @@ describe Sidekiq::Status do
8
8
  let!(:unused_id) { SecureRandom.hex(12) }
9
9
  let!(:plain_sidekiq_job_id) { SecureRandom.hex(12) }
10
10
  let!(:retried_job_id) { SecureRandom.hex(12) }
11
-
12
- # Clean Redis before each test
13
- # Seems like flushall has no effect on recently published messages,
14
- # so we should wait till they expire
15
- before { redis.flushall; sleep 0.1 }
11
+ let!(:retry_and_fail_job_id) { SecureRandom.hex(12) }
16
12
 
17
13
  describe ".status, .working?, .complete?" do
18
14
  it "gets job status by id as symbol" do
19
- SecureRandom.should_receive(:hex).once.and_return(job_id)
15
+ allow(SecureRandom).to receive(:hex).once.and_return(job_id)
20
16
 
21
17
  start_server do
22
- capture_status_updates(2) {
23
- LongJob.perform_async(1).should == job_id
24
- }.should == [job_id]*2
25
- Sidekiq::Status.status(job_id).should == :working
26
- Sidekiq::Status.working?(job_id).should be_true
27
- Sidekiq::Status::queued?(job_id).should be_false
28
- Sidekiq::Status::failed?(job_id ).should be_false
29
- Sidekiq::Status::complete?(job_id).should be_false
30
- Sidekiq::Status::stopped?(job_id).should be_false
31
- end
32
- Sidekiq::Status.status(job_id).should == :complete
33
- Sidekiq::Status.complete?(job_id).should be_true
18
+ expect(capture_status_updates(2) {
19
+ expect(LongJob.perform_async(0.5)).to eq(job_id)
20
+ }).to eq([job_id]*2)
21
+ expect(Sidekiq::Status.status(job_id)).to eq(:working)
22
+ expect(Sidekiq::Status.working?(job_id)).to be_truthy
23
+ expect(Sidekiq::Status::queued?(job_id)).to be_falsey
24
+ expect(Sidekiq::Status::retrying?(job_id)).to be_falsey
25
+ expect(Sidekiq::Status::failed?(job_id)).to be_falsey
26
+ expect(Sidekiq::Status::complete?(job_id)).to be_falsey
27
+ expect(Sidekiq::Status::stopped?(job_id)).to be_falsey
28
+ expect(Sidekiq::Status::interrupted?(job_id)).to be_falsey
29
+ end
30
+ expect(Sidekiq::Status.status(job_id)).to eq(:complete)
31
+ expect(Sidekiq::Status.complete?(job_id)).to be_truthy
34
32
  end
35
33
  end
36
34
 
37
35
  describe ".get" do
38
36
  it "gets a single value from data hash as string" do
39
- SecureRandom.should_receive(:hex).once.and_return(job_id)
37
+ allow(SecureRandom).to receive(:hex).once.and_return(job_id)
40
38
 
41
39
  start_server do
42
- capture_status_updates(3) {
43
- DataJob.perform_async.should == job_id
44
- }.should == [job_id]*3
45
- Sidekiq::Status.get(job_id, :status).should == 'working'
40
+ expect(capture_status_updates(3) {
41
+ expect(DataJob.perform_async).to eq(job_id)
42
+ }).to eq([job_id]*3)
43
+ expect(Sidekiq::Status.get(job_id, :status)).to eq('working')
46
44
  end
47
- Sidekiq::Status.get(job_id, :data).should == 'meow'
45
+ expect(Sidekiq::Status.get(job_id, :data)).to eq('meow')
48
46
  end
49
47
  end
50
48
 
51
49
  describe ".at, .total, .pct_complete, .message" do
52
50
  it "should return job progress with correct type to it" do
53
- SecureRandom.should_receive(:hex).once.and_return(job_id)
51
+ allow(SecureRandom).to receive(:hex).once.and_return(job_id)
54
52
 
55
53
  start_server do
56
- capture_status_updates(3) {
57
- ProgressJob.perform_async.should == job_id
58
- }.should == [job_id]*3
54
+ expect(capture_status_updates(4) {
55
+ expect(ProgressJob.perform_async).to eq(job_id)
56
+ }).to eq([job_id]*4)
59
57
  end
60
- Sidekiq::Status.at(job_id).should == 100
61
- Sidekiq::Status.total(job_id).should == 500
62
- Sidekiq::Status.pct_complete(job_id).should == 20
63
- Sidekiq::Status.message(job_id).should == 'howdy, partner?'
58
+ expect(Sidekiq::Status.at(job_id)).to be(100)
59
+ expect(Sidekiq::Status.total(job_id)).to be(500)
60
+ # It returns a float therefor we need eq()
61
+ expect(Sidekiq::Status.pct_complete(job_id)).to eq(20)
62
+ expect(Sidekiq::Status.message(job_id)).to eq('howdy, partner?')
64
63
  end
65
64
  end
66
65
 
67
66
  describe ".get_all" do
68
67
  it "gets the job hash by id" do
69
- SecureRandom.should_receive(:hex).once.and_return(job_id)
68
+ allow(SecureRandom).to receive(:hex).once.and_return(job_id)
69
+
70
+ start_server do
71
+ expect(capture_status_updates(2) {
72
+ expect(LongJob.perform_async(0.5)).to eq(job_id)
73
+ }).to eq([job_id]*2)
74
+ expect(hash = Sidekiq::Status.get_all(job_id)).to include 'status' => 'working'
75
+ expect(hash).to include 'update_time'
76
+ end
77
+ expect(hash = Sidekiq::Status.get_all(job_id)).to include 'status' => 'complete'
78
+ expect(hash).to include 'update_time'
79
+ end
80
+ end
70
81
 
82
+ describe '.delete' do
83
+ it 'deletes the status hash for given job id' do
84
+ allow(SecureRandom).to receive(:hex).once.and_return(job_id)
71
85
  start_server do
72
- capture_status_updates(2) {
73
- LongJob.perform_async(1).should == job_id
74
- }.should == [job_id]*2
75
- (hash = Sidekiq::Status.get_all(job_id)).should include 'status' => 'working'
76
- hash.should include 'update_time'
86
+ expect(capture_status_updates(2) {
87
+ expect(LongJob.perform_async(0.5)).to eq(job_id)
88
+ }).to eq([job_id]*2)
77
89
  end
78
- (hash = Sidekiq::Status.get_all(job_id)).should include 'status' => 'complete'
79
- hash.should include 'update_time'
90
+ expect(Sidekiq::Status.delete(job_id)).to eq(1)
91
+ end
92
+
93
+ it 'should not raise error while deleting status hash if invalid job id' do
94
+ allow(SecureRandom).to receive(:hex).once.and_return(job_id)
95
+ expect(Sidekiq::Status.delete(job_id)).to eq(0)
80
96
  end
81
97
  end
82
98
 
83
99
  describe ".cancel" do
84
100
  it "cancels a job by id" do
85
- SecureRandom.should_receive(:hex).twice.and_return(job_id, job_id_1)
101
+ allow(SecureRandom).to receive(:hex).twice.and_return(job_id, job_id_1)
86
102
  start_server do
87
103
  job = LongJob.perform_in(3600)
88
- job.should == job_id
104
+ expect(job).to eq(job_id)
89
105
  second_job = LongJob.perform_in(3600)
90
- second_job.should == job_id_1
106
+ expect(second_job).to eq(job_id_1)
91
107
 
92
- initial_schedule = redis.zrange "schedule", 0, -1, {withscores: true}
93
- initial_schedule.size.should be 2
94
- initial_schedule.select {|scheduled_job| JSON.parse(scheduled_job[0])["jid"] == job_id }.size.should be 1
108
+ initial_schedule = redis.zrange "schedule", 0, -1, withscores: true
109
+ expect(initial_schedule.size).to be(2)
110
+ expect(initial_schedule.select {|scheduled_job| JSON.parse(scheduled_job[0])["jid"] == job_id }.size).to be(1)
95
111
 
96
- Sidekiq::Status.unschedule(job_id).should be_true
97
- Sidekiq::Status.cancel(unused_id).should be_false # Unused, therefore unfound => false
112
+ expect(Sidekiq::Status.unschedule(job_id)).to be_truthy
113
+ # Unused, therefore unfound => false
114
+ expect(Sidekiq::Status.cancel(unused_id)).to be_falsey
98
115
 
99
- remaining_schedule = redis.zrange "schedule", 0, -1, {withscores: true}
100
- remaining_schedule.size.should == (initial_schedule.size - 1)
101
- remaining_schedule.select {|scheduled_job| JSON.parse(scheduled_job[0])["jid"] == job_id }.size.should be 0
116
+ remaining_schedule = redis.zrange "schedule", 0, -1, withscores: true
117
+ expect(remaining_schedule.size).to be(initial_schedule.size - 1)
118
+ expect(remaining_schedule.select {|scheduled_job| JSON.parse(scheduled_job[0])["jid"] == job_id }.size).to be(0)
102
119
  end
103
120
  end
104
121
 
105
122
  it "does not cancel a job with correct id but wrong time" do
106
- SecureRandom.should_receive(:hex).once.and_return(job_id)
123
+ allow(SecureRandom).to receive(:hex).once.and_return(job_id)
107
124
  start_server do
108
125
  scheduled_time = Time.now.to_i + 3600
109
126
  returned_job_id = LongJob.perform_at(scheduled_time)
110
- returned_job_id.should == job_id
127
+ expect(returned_job_id).to eq(job_id)
111
128
 
112
- initial_schedule = redis.zrange "schedule", 0, -1, {withscores: true}
113
- initial_schedule.size.should == 1
114
- Sidekiq::Status.cancel(returned_job_id, (scheduled_time + 1)).should be_false # wrong time, therefore unfound => false
115
- (redis.zrange "schedule", 0, -1, {withscores: true}).size.should be 1
116
- Sidekiq::Status.cancel(returned_job_id, (scheduled_time)).should be_true # same id, same time, deletes
117
- (redis.zrange "schedule", 0, -1, {withscores: true}).size.should be_zero
129
+ initial_schedule = redis.zrange "schedule", 0, -1, withscores: true
130
+ expect(initial_schedule.size).to be(1)
131
+ # wrong time, therefore unfound => false
132
+ expect(Sidekiq::Status.cancel(returned_job_id, (scheduled_time + 1))).to be_falsey
133
+ expect((redis.zrange "schedule", 0, -1, withscores: true).size).to be(1)
134
+ # same id, same time, deletes
135
+ expect(Sidekiq::Status.cancel(returned_job_id, (scheduled_time))).to be_truthy
136
+ expect(redis.zrange "schedule", 0, -1, withscores: true).to be_empty
118
137
  end
119
138
  end
120
139
  end
@@ -129,14 +148,63 @@ describe Sidekiq::Status do
129
148
  expect_2_jobs_ttl_covers 1..Sidekiq::Status::DEFAULT_EXPIRY
130
149
  end
131
150
 
151
+ it "does jobs without a known class" do
152
+ seed_secure_random_with_job_ids
153
+ start_server(:expiration => expiration_param) do
154
+ expect {
155
+ Sidekiq::Client.new(Sidekiq.redis_pool).
156
+ push("class" => "NotAKnownClass", "args" => [])
157
+ }.to_not raise_error
158
+ end
159
+ end
160
+
132
161
  it "retries failed jobs" do
133
- SecureRandom.should_receive(:hex).once.and_return(retried_job_id)
162
+ allow(SecureRandom).to receive(:hex).exactly(3).times.and_return(retried_job_id)
134
163
  start_server do
135
- capture_status_updates(5) {
136
- RetriedJob.perform_async().should == retried_job_id
137
- }.should == [retried_job_id] * 5
164
+ expect(capture_status_updates(3) {
165
+ expect(RetriedJob.perform_async()).to eq(retried_job_id)
166
+ }).to eq([retried_job_id] * 3)
167
+ expect(Sidekiq::Status.status(retried_job_id)).to eq(:retrying)
168
+ expect(Sidekiq::Status.working?(retried_job_id)).to be_falsey
169
+ expect(Sidekiq::Status::queued?(retried_job_id)).to be_falsey
170
+ expect(Sidekiq::Status::retrying?(retried_job_id)).to be_truthy
171
+ expect(Sidekiq::Status::failed?(retried_job_id)).to be_falsey
172
+ expect(Sidekiq::Status::complete?(retried_job_id)).to be_falsey
173
+ expect(Sidekiq::Status::stopped?(retried_job_id)).to be_falsey
174
+ expect(Sidekiq::Status::interrupted?(retried_job_id)).to be_falsey
175
+ end
176
+ expect(Sidekiq::Status.status(retried_job_id)).to eq(:retrying)
177
+ expect(Sidekiq::Status::retrying?(retried_job_id)).to be_truthy
178
+
179
+ # restarting and waiting for the job to complete
180
+ start_server do
181
+ expect(capture_status_updates(3) {}).to eq([retried_job_id] * 3)
182
+ expect(Sidekiq::Status.status(retried_job_id)).to eq(:complete)
183
+ expect(Sidekiq::Status.complete?(retried_job_id)).to be_truthy
184
+ expect(Sidekiq::Status::retrying?(retried_job_id)).to be_falsey
185
+ end
186
+ end
187
+
188
+ it "marks retried jobs as failed once they do eventually fail" do
189
+ allow(SecureRandom).to receive(:hex).and_return(retry_and_fail_job_id)
190
+ start_server do
191
+ expect(
192
+ capture_status_updates(3) {
193
+ expect(RetryAndFailJob.perform_async).to eq(retry_and_fail_job_id)
194
+ }
195
+ ).to eq([retry_and_fail_job_id] * 3)
196
+
197
+ expect(Sidekiq::Status.status(retry_and_fail_job_id)).to eq(:retrying)
198
+ end
199
+
200
+ # restarting and waiting for the job to fail
201
+ start_server do
202
+ expect(capture_status_updates(3) {}).to eq([retry_and_fail_job_id] * 3)
203
+
204
+ expect(Sidekiq::Status.status(retry_and_fail_job_id)).to eq(:failed)
205
+ expect(Sidekiq::Status.failed?(retry_and_fail_job_id)).to be_truthy
206
+ expect(Sidekiq::Status::retrying?(retry_and_fail_job_id)).to be_falsey
138
207
  end
139
- Sidekiq::Status.status(retried_job_id).should == :complete
140
208
  end
141
209
 
142
210
  context ":expiration param" do
@@ -149,40 +217,53 @@ describe Sidekiq::Status do
149
217
  expect_2_jobs_ttl_covers (Sidekiq::Status::DEFAULT_EXPIRY+1)..expiration_param
150
218
  end
151
219
 
152
- it "allow to overwrite :expiration parameter by .expiration method from worker" do
220
+ it "allow to overwrite :expiration parameter by #expiration method from worker" do
153
221
  overwritten_expiration = expiration_param * 100
154
- NoStatusConfirmationJob.any_instance.stub(:expiration => overwritten_expiration)
155
- StubJob.any_instance.stub(:expiration => overwritten_expiration)
222
+ allow_any_instance_of(NoStatusConfirmationJob).to receive(:expiration).
223
+ and_return(overwritten_expiration)
224
+ allow_any_instance_of(StubJob).to receive(:expiration).
225
+ and_return(overwritten_expiration)
156
226
  run_2_jobs!
157
227
  expect_2_jobs_are_done_and_status_eq :complete
158
228
  expect_2_jobs_ttl_covers (expiration_param+1)..overwritten_expiration
159
229
  end
230
+
231
+ it "reads #expiration from a method when defined" do
232
+ allow(SecureRandom).to receive(:hex).once.and_return(job_id, job_id_1)
233
+ start_server do
234
+ expect(StubJob.perform_async).to eq(job_id)
235
+ expect(ExpiryJob.perform_async).to eq(job_id_1)
236
+ expect(redis.ttl("sidekiq:status:#{job_id}")).to eq(30 * 60)
237
+ expect(redis.ttl("sidekiq:status:#{job_id_1}")).to eq(15)
238
+ end
239
+ end
160
240
  end
161
241
 
162
242
  def seed_secure_random_with_job_ids
163
- SecureRandom.should_receive(:hex).exactly(4).times.and_return(plain_sidekiq_job_id, plain_sidekiq_job_id, job_id_1, job_id_1)
243
+ allow(SecureRandom).to receive(:hex).exactly(4).times.
244
+ and_return(plain_sidekiq_job_id, plain_sidekiq_job_id, job_id_1, job_id_1)
164
245
  end
165
246
 
166
247
  def run_2_jobs!
167
248
  start_server(:expiration => expiration_param) do
168
- capture_status_updates(12) {
169
- StubJob.perform_async.should == plain_sidekiq_job_id
249
+ expect(capture_status_updates(6) {
250
+ expect(StubJob.perform_async).to eq(plain_sidekiq_job_id)
170
251
  NoStatusConfirmationJob.perform_async(1)
171
- StubJob.perform_async.should == job_id_1
252
+ expect(StubJob.perform_async).to eq(job_id_1)
172
253
  NoStatusConfirmationJob.perform_async(2)
173
- }.should =~ [plain_sidekiq_job_id, job_id_1] * 6
254
+ }).to match_array([plain_sidekiq_job_id, job_id_1] * 3)
174
255
  end
175
256
  end
176
257
 
177
258
  def expect_2_jobs_ttl_covers(range)
178
- range.should cover redis.ttl(plain_sidekiq_job_id)
179
- range.should cover redis.ttl(job_id_1)
259
+ expect(range).to cover redis.ttl("sidekiq:status:#{plain_sidekiq_job_id}")
260
+ expect(range).to cover redis.ttl("sidekiq:status:#{job_id_1}")
180
261
  end
181
262
 
182
263
  def expect_2_jobs_are_done_and_status_eq(status)
183
- redis.mget('NoStatusConfirmationJob_1', 'NoStatusConfirmationJob_2').should == %w(done)*2
184
- Sidekiq::Status.status(plain_sidekiq_job_id).should == status
185
- Sidekiq::Status.status(job_id_1).should == status
264
+ expect(redis.mget('NoStatusConfirmationJob_1', 'NoStatusConfirmationJob_2')).to eq(%w(done)*2)
265
+ expect(Sidekiq::Status.status(plain_sidekiq_job_id)).to eq(status)
266
+ expect(Sidekiq::Status.status(job_id_1)).to eq(status)
186
267
  end
187
268
  end
188
269