sidekiq-undertaker 1.0.0.rc01
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.
- checksums.yaml +7 -0
- data/.codeclimate.yml +6 -0
- data/.gitignore +22 -0
- data/.rubocop.yml +5 -0
- data/.rubocop_todo.yml +23 -0
- data/.travis.yml +53 -0
- data/Demo_Filter.png +0 -0
- data/Demo_Job_Filter.png +0 -0
- data/Demo_Morgue_1_Job.png +0 -0
- data/Demo_Morgue_all.png +0 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +21 -0
- data/README.md +95 -0
- data/Rakefile +34 -0
- data/lib/sidekiq/undertaker.rb +16 -0
- data/lib/sidekiq/undertaker/bucket.rb +29 -0
- data/lib/sidekiq/undertaker/dead_job.rb +64 -0
- data/lib/sidekiq/undertaker/job_distributor.rb +64 -0
- data/lib/sidekiq/undertaker/job_filter.rb +46 -0
- data/lib/sidekiq/undertaker/version.rb +7 -0
- data/lib/sidekiq/undertaker/web_extension.rb +37 -0
- data/lib/sidekiq/undertaker/web_extension/api_helpers.rb +120 -0
- data/sidekiq-undertaker.gemspec +55 -0
- data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_filter/when_filter_page_is_called/behaves_like_a_page/the_displayed_page_is_correct.approved.txt +240 -0
- data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_filter/when_job_classbucket_page_is_called/behaves_like_a_page/the_displayed_page_is_correct.approved.txt +241 -0
- data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_filter/when_job_classbucket_page_is_polled/behaves_like_a_page/the_displayed_page_is_correct.approved.txt +241 -0
- data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_morgue/when_job_classerrorbucket_is_called/with_all_failures_and_errors/behaves_like_a_page/the_displayed_page_is_correct.approved.txt +316 -0
- data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_morgue/when_job_classerrorbucket_is_called/with_specific_job_class_and_a_specific_error/behaves_like_a_page/the_displayed_page_is_correct.approved.txt +274 -0
- data/spec/sidekiq/undertaker/bucket_spec.rb +26 -0
- data/spec/sidekiq/undertaker/dead_jobs_spec.rb +90 -0
- data/spec/sidekiq/undertaker/job_distributor_spec.rb +104 -0
- data/spec/sidekiq/undertaker/job_filter_spec.rb +105 -0
- data/spec/sidekiq/undertaker/web_extension_spec.rb +270 -0
- data/spec/spec_helper.rb +76 -0
- data/web/locales/en.yml +5 -0
- data/web/views/filter.erb +34 -0
- data/web/views/filter_job_class.erb +35 -0
- data/web/views/morgue.erb +75 -0
- metadata +378 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
module Sidekiq
|
6
|
+
module Undertaker
|
7
|
+
describe Bucket do
|
8
|
+
describe ".bucket_names" do
|
9
|
+
let(:expected_bucket_names) { %w[1_hour 3_hours 1_day 3_days 1_week older] }
|
10
|
+
|
11
|
+
it { expect(described_class.bucket_names).to eq expected_bucket_names }
|
12
|
+
end
|
13
|
+
|
14
|
+
describe ".for_elapsed_time" do
|
15
|
+
let(:expectation) { %w[1_hour 3_hours 1_day 3_days 1_week older] }
|
16
|
+
let(:elapsed_times) { [1, 3601, 10801, 86401, 259201, 604801] }
|
17
|
+
|
18
|
+
it "maps elapsed_time to bucket name" do
|
19
|
+
elapsed_times.each_with_index do |elapsed_time, index|
|
20
|
+
expect(described_class.for_elapsed_time(elapsed_time)).to eq expectation[index]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
module Sidekiq
|
6
|
+
module Undertaker
|
7
|
+
describe DeadJob do
|
8
|
+
let(:job) do
|
9
|
+
build_job(
|
10
|
+
"class" => "HardWorkTask",
|
11
|
+
"failed_at" => Time.now,
|
12
|
+
"error_class" => "NoMethodError",
|
13
|
+
"queue" => "SomeQueue"
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "attributes" do
|
18
|
+
let(:expected_attributes) do
|
19
|
+
{
|
20
|
+
job_class: "HardWorkTask",
|
21
|
+
time_elapsed_since_failure: 9,
|
22
|
+
error_class: "NoMethodError",
|
23
|
+
bucket_name: "1_hour",
|
24
|
+
job: job
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
context "when all attributes are given" do
|
29
|
+
let(:args) { expected_attributes }
|
30
|
+
|
31
|
+
it { expect(described_class.new(args)).to have_attributes expected_attributes }
|
32
|
+
end
|
33
|
+
|
34
|
+
context "when some attributes are derived" do
|
35
|
+
let(:args) do
|
36
|
+
{
|
37
|
+
time_elapsed_since_failure: 9,
|
38
|
+
bucket_name: "1_hour",
|
39
|
+
job: job
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
it do
|
44
|
+
expect(
|
45
|
+
described_class.new(args)
|
46
|
+
).to have_attributes expected_attributes
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe ".for_each" do
|
52
|
+
let(:expected_dead_job) do
|
53
|
+
DeadJob.new(
|
54
|
+
job_class: "HardWorkTask",
|
55
|
+
time_elapsed_since_failure: time_elapsed,
|
56
|
+
error_class: "NoMethodError",
|
57
|
+
bucket_name: "1_hour",
|
58
|
+
job: killed_job
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
62
|
+
let(:killed_job) do
|
63
|
+
Sidekiq::DeadSet.new.find_job(job.jid)
|
64
|
+
end
|
65
|
+
|
66
|
+
let(:time_elapsed) { Time.now.to_i - job.item["failed_at"].to_i }
|
67
|
+
|
68
|
+
before do
|
69
|
+
Timecop.freeze
|
70
|
+
|
71
|
+
kill_job(job)
|
72
|
+
end
|
73
|
+
|
74
|
+
after do
|
75
|
+
Timecop.return
|
76
|
+
end
|
77
|
+
|
78
|
+
it "returns job with appropriate metadata filled" do
|
79
|
+
cnt = 0
|
80
|
+
described_class.for_each do |dead_job|
|
81
|
+
# It returns only one dead_job
|
82
|
+
cnt += 1
|
83
|
+
expect(dead_job).to eql expected_dead_job
|
84
|
+
end
|
85
|
+
expect(cnt).to be 1
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
module Sidekiq
|
6
|
+
module Undertaker
|
7
|
+
describe JobDistributor do
|
8
|
+
let(:job1) do
|
9
|
+
instance_double(Sidekiq::Job, item: {
|
10
|
+
"class" => "A",
|
11
|
+
"failed_at" => 1,
|
12
|
+
"error_class" => "E1"
|
13
|
+
})
|
14
|
+
end
|
15
|
+
let(:job2) do
|
16
|
+
instance_double(Sidekiq::Job, item: {
|
17
|
+
"class" => "A",
|
18
|
+
"failed_at" => 1,
|
19
|
+
"error_class" => "E1"
|
20
|
+
})
|
21
|
+
end
|
22
|
+
let(:job3) do
|
23
|
+
instance_double(Sidekiq::Job, item: {
|
24
|
+
"class" => "B",
|
25
|
+
"failed_at" => 1,
|
26
|
+
"error_class" => "E1"
|
27
|
+
})
|
28
|
+
end
|
29
|
+
let(:job4) do
|
30
|
+
instance_double(Sidekiq::Job, item: {
|
31
|
+
"class" => "B",
|
32
|
+
"failed_at" => 1,
|
33
|
+
"error_class" => "E2"
|
34
|
+
})
|
35
|
+
end
|
36
|
+
|
37
|
+
let(:dead_job1) do
|
38
|
+
DeadJob.new(
|
39
|
+
job: job1, # 'A', 'E1'
|
40
|
+
time_elapsed_since_failure: 10,
|
41
|
+
bucket_name: "1_hour"
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
let(:dead_job2) do
|
46
|
+
DeadJob.new(
|
47
|
+
job: job2, # 'A', 'E1'
|
48
|
+
time_elapsed_since_failure: 10 + 60 * 60,
|
49
|
+
bucket_name: "3_hours"
|
50
|
+
)
|
51
|
+
end
|
52
|
+
let(:dead_job3) do
|
53
|
+
DeadJob.new(
|
54
|
+
job: job3, # 'B', 'E1'
|
55
|
+
time_elapsed_since_failure: 10 + 60 * 60,
|
56
|
+
bucket_name: "3_hours"
|
57
|
+
)
|
58
|
+
end
|
59
|
+
let(:dead_job4) do
|
60
|
+
DeadJob.new(
|
61
|
+
job: job4, # 'B', 'E2'
|
62
|
+
time_elapsed_since_failure: 10 + 60 * 60 * 24,
|
63
|
+
bucket_name: "1_day"
|
64
|
+
)
|
65
|
+
end
|
66
|
+
|
67
|
+
let(:dead_jobs) { [dead_job1, dead_job2, dead_job3, dead_job4] }
|
68
|
+
|
69
|
+
# rubocop:disable Metrics/LineLength
|
70
|
+
describe "#group_by_job_class" do
|
71
|
+
subject(:distribution) { described_class.new(dead_jobs).group_by_job_class }
|
72
|
+
|
73
|
+
let(:expected_distribution) do
|
74
|
+
[
|
75
|
+
["all", { "1_hour" => 1, "3_hours" => 2, "1_day" => 1, "3_days" => 0, "1_week" => 0, "older" => 0, "total_dead" => 4 }],
|
76
|
+
["B", { "1_hour" => 0, "3_hours" => 1, "1_day" => 1, "3_days" => 0, "1_week" => 0, "older" => 0, "total_dead" => 2 }],
|
77
|
+
["A", { "1_hour" => 1, "3_hours" => 1, "1_day" => 0, "3_days" => 0, "1_week" => 0, "older" => 0, "total_dead" => 2 }]
|
78
|
+
]
|
79
|
+
end
|
80
|
+
|
81
|
+
it "distributes the dead jobs into buckets and groups them by job_class" do
|
82
|
+
expect(distribution).to eq expected_distribution
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "#group_by_error_class" do
|
87
|
+
subject(:distribution) { described_class.new(dead_jobs).group_by_error_class }
|
88
|
+
|
89
|
+
let(:expected_distribution) do
|
90
|
+
[
|
91
|
+
["all", { "1_hour" => 1, "3_hours" => 2, "1_day" => 1, "3_days" => 0, "1_week" => 0, "older" => 0, "total_dead" => 4 }],
|
92
|
+
["E1", { "1_hour" => 1, "3_hours" => 2, "1_day" => 0, "3_days" => 0, "1_week" => 0, "older" => 0, "total_dead" => 3 }],
|
93
|
+
["E2", { "1_hour" => 0, "3_hours" => 0, "1_day" => 1, "3_days" => 0, "1_week" => 0, "older" => 0, "total_dead" => 1 }]
|
94
|
+
]
|
95
|
+
end
|
96
|
+
|
97
|
+
it "distributes the dead jobs into buckets and groups them by error_class" do
|
98
|
+
expect(distribution).to eq expected_distribution
|
99
|
+
end
|
100
|
+
end
|
101
|
+
# rubocop:enable Metrics/LineLength
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
module Sidekiq
|
6
|
+
module Undertaker
|
7
|
+
describe JobFilter do
|
8
|
+
describe ".filter_dead_jobs" do
|
9
|
+
let(:job1) do
|
10
|
+
instance_double(Sidekiq::Job, item: {
|
11
|
+
"class" => "HardWorkTask",
|
12
|
+
"failed_at" => Time.now.to_i - 5 * 60,
|
13
|
+
"error_class" => "NoMethodError"
|
14
|
+
})
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:job2) do
|
18
|
+
instance_double(Sidekiq::Job, item: {
|
19
|
+
"class" => "HardWorkTask",
|
20
|
+
"failed_at" => Time.now.to_i - 2 * 60 * 60,
|
21
|
+
"error_class" => "RandomError"
|
22
|
+
})
|
23
|
+
end
|
24
|
+
|
25
|
+
let(:job3) do
|
26
|
+
instance_double(Sidekiq::Job, item: {
|
27
|
+
"class" => "LazyWorkTask",
|
28
|
+
"failed_at" => Time.now.to_i - 2 * 60 * 60,
|
29
|
+
"error_class" => "NoMethodError"
|
30
|
+
})
|
31
|
+
end
|
32
|
+
|
33
|
+
before do
|
34
|
+
Timecop.freeze
|
35
|
+
|
36
|
+
dead_set = instance_double(Sidekiq::DeadSet)
|
37
|
+
|
38
|
+
allow(dead_set).to receive(:each)
|
39
|
+
.and_yield(job1)
|
40
|
+
.and_yield(job2)
|
41
|
+
.and_yield(job3)
|
42
|
+
|
43
|
+
allow(Sidekiq::DeadSet).to receive(:new).and_return(dead_set)
|
44
|
+
end
|
45
|
+
|
46
|
+
after { Timecop.return }
|
47
|
+
|
48
|
+
context "when the job_class filter is given" do
|
49
|
+
subject(:dead_jobs) { described_class.filter_dead_jobs("job_class" => "HardWorkTask") }
|
50
|
+
|
51
|
+
it "filters jobs based on job_class" do
|
52
|
+
dead_jobs.each do |dead_job|
|
53
|
+
expect(dead_job.job_class).to eq "HardWorkTask"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
it { expect(dead_jobs.size).to eq 2 }
|
58
|
+
end
|
59
|
+
|
60
|
+
context "when the job_class and bucket_name filters are given" do
|
61
|
+
subject(:dead_jobs) do
|
62
|
+
described_class.filter_dead_jobs("job_class" => "HardWorkTask",
|
63
|
+
"bucket_name" => "3_hours")
|
64
|
+
end
|
65
|
+
|
66
|
+
it "filters based on multiple filter attributes" do
|
67
|
+
dead_jobs.each do |dead_job|
|
68
|
+
expect(dead_job.job_class).to eq "HardWorkTask"
|
69
|
+
expect(dead_job.bucket_name).to eq "3_hours"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
it { expect(dead_jobs.size).to eq 1 }
|
74
|
+
end
|
75
|
+
|
76
|
+
context "when the error_class filter is given" do
|
77
|
+
subject(:dead_jobs) { described_class.filter_dead_jobs("error_class" => "NoMethodError") }
|
78
|
+
|
79
|
+
it "filters jobs based on error_class" do
|
80
|
+
dead_jobs.each do |dead_job|
|
81
|
+
expect(dead_job.error_class).to eq "NoMethodError"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
it { expect(dead_jobs.size).to eq 2 }
|
85
|
+
end
|
86
|
+
|
87
|
+
context "when no filters are applied" do
|
88
|
+
subject(:dead_jobs) { described_class.filter_dead_jobs }
|
89
|
+
|
90
|
+
it { expect(dead_jobs.size).to eq 3 }
|
91
|
+
end
|
92
|
+
|
93
|
+
context "when all filters are nil" do
|
94
|
+
subject(:dead_jobs) do
|
95
|
+
described_class.filter_dead_jobs("job_class" => nil,
|
96
|
+
"bucket_name" => nil,
|
97
|
+
"error_class" => nil)
|
98
|
+
end
|
99
|
+
|
100
|
+
it { expect(dead_jobs.size).to eq 3 }
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,270 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
require "sidekiq/api"
|
6
|
+
require "sidekiq/web"
|
7
|
+
require "sinatra"
|
8
|
+
require "rack/test"
|
9
|
+
|
10
|
+
module Sidekiq
|
11
|
+
# rubocop:disable Metrics/ModuleLength
|
12
|
+
module Undertaker
|
13
|
+
describe WebExtension, type: :controller do
|
14
|
+
include Rack::Test::Methods
|
15
|
+
|
16
|
+
let(:app) { Sidekiq::Web }
|
17
|
+
let(:job_refs) { [] }
|
18
|
+
|
19
|
+
let(:jid1) { "4416aa76eb8cf03f56a49220" }
|
20
|
+
let(:jid2) { "34e79a46b1956d3a1180767b" }
|
21
|
+
let(:jid3) { "8d08674fce759ac75d1a6e75" }
|
22
|
+
let(:jid4) { "bfa4a272cdcac8bfac7b9f1a" }
|
23
|
+
|
24
|
+
let(:default_job_opts) do
|
25
|
+
{
|
26
|
+
"class" => "HardWorker",
|
27
|
+
"args" => ["asdf", 1234],
|
28
|
+
"queue" => "foo",
|
29
|
+
"error_message" => "Some fake message",
|
30
|
+
"error_class" => "RuntimeError",
|
31
|
+
"retry_count" => 0,
|
32
|
+
"failed_at" => Time.now.utc
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
# rubocop:disable RSpec/AnyInstance
|
37
|
+
before do
|
38
|
+
Timecop.freeze(Time.gm(2018, 12, 16, 20, 57))
|
39
|
+
|
40
|
+
job_refs.push add_dead("jid" => jid1)
|
41
|
+
job_refs.push add_dead("jid" => jid2)
|
42
|
+
job_refs.push add_dead("jid" => jid3, "error_class" => "NoMethodError")
|
43
|
+
job_refs.push add_dead("jid" => jid4, "class" => "HardWorker1", "error_class" => "NoMethodError")
|
44
|
+
|
45
|
+
allow_any_instance_of(Sidekiq::WebAction).to receive(:root_path).and_return("/sidekiq/")
|
46
|
+
end
|
47
|
+
# rubocop:enable RSpec/AnyInstance
|
48
|
+
|
49
|
+
after { Timecop.return }
|
50
|
+
|
51
|
+
def add_dead(opts = {})
|
52
|
+
opts = default_job_opts.merge!(opts)
|
53
|
+
|
54
|
+
job = build_job(opts)
|
55
|
+
killed_job = kill_job(job)
|
56
|
+
|
57
|
+
"#{killed_job.score}-#{job.jid}"
|
58
|
+
end
|
59
|
+
|
60
|
+
shared_examples "a page" do
|
61
|
+
it "the displayed page is correct" do
|
62
|
+
subject
|
63
|
+
|
64
|
+
expect(last_response.status).to eq 200
|
65
|
+
verify do
|
66
|
+
apply_custom_excludes(last_response.body)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "show filter" do
|
72
|
+
# /undertaker/filter
|
73
|
+
context "when filter page is called" do
|
74
|
+
subject { get "/undertaker/filter" }
|
75
|
+
|
76
|
+
it_behaves_like "a page"
|
77
|
+
end
|
78
|
+
|
79
|
+
# /undertaker/filter/:job_class/:bucket_name
|
80
|
+
context "when job-class/bucket page is called" do
|
81
|
+
subject { get "/undertaker/filter/HardWorker/1_hour" }
|
82
|
+
|
83
|
+
it_behaves_like "a page"
|
84
|
+
end
|
85
|
+
|
86
|
+
# /undertaker/filter/:job_class/:bucket_name?poll=true
|
87
|
+
context "when job-class/bucket page is polled" do
|
88
|
+
subject { get "/undertaker/filter/HardWorker/1_hour?poll=true" }
|
89
|
+
|
90
|
+
it_behaves_like "a page"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe "show morgue" do
|
95
|
+
# /undertaker/morgue/:job_class/:error_class/:bucket_name
|
96
|
+
context "when job-class/error/bucket is called" do
|
97
|
+
context "with specific job-class and a specific error" do
|
98
|
+
subject { get "/undertaker/morgue/HardWorker/RuntimeError/1_hour" }
|
99
|
+
|
100
|
+
it_behaves_like "a page"
|
101
|
+
end
|
102
|
+
|
103
|
+
context "with all failures and errors" do
|
104
|
+
subject { get "/undertaker/morgue/all/all/total_dead" }
|
105
|
+
|
106
|
+
it_behaves_like "a page"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
describe "delete" do
|
112
|
+
context "when job-class, error and bucket are given" do
|
113
|
+
subject { post "/undertaker/morgue/HardWorker/RuntimeError/1_hour/delete" }
|
114
|
+
|
115
|
+
let(:expected_redirect_url) { "http://example.org/undertaker/morgue/HardWorker/RuntimeError/1_hour" }
|
116
|
+
|
117
|
+
let(:params) { { "job_class" => "HardWorker", "error_class" => "RuntimeError", "bucket_name" => "1_hour" } }
|
118
|
+
let(:dead_jobs_set) { [dead_job1, dead_job2] }
|
119
|
+
let(:dead_job1) { Sidekiq::Undertaker::DeadJob.to_dead_job(Sidekiq::DeadSet.new.find_job(jid1)) }
|
120
|
+
let(:dead_job2) { Sidekiq::Undertaker::DeadJob.to_dead_job(Sidekiq::DeadSet.new.find_job(jid2)) }
|
121
|
+
|
122
|
+
before do
|
123
|
+
allow(Sidekiq::Undertaker::JobFilter).to receive(:filter_dead_jobs).with(params)
|
124
|
+
.and_return(dead_jobs_set)
|
125
|
+
end
|
126
|
+
|
127
|
+
it "deletes the dead jobs" do
|
128
|
+
expect(dead_job1.job).to receive(:delete).and_call_original
|
129
|
+
expect(dead_job2.job).to receive(:delete).and_call_original
|
130
|
+
subject
|
131
|
+
end
|
132
|
+
|
133
|
+
it "reduces the DeadSet" do
|
134
|
+
expect { subject }.to change { Sidekiq::DeadSet.new.size }.from(4).to(2)
|
135
|
+
end
|
136
|
+
|
137
|
+
it "redirects to /undertaker/morgue/HardWorker/RuntimeError/1_hour after the delete" do
|
138
|
+
subject
|
139
|
+
expect(last_response.status).to eq 302
|
140
|
+
|
141
|
+
# Redirect
|
142
|
+
follow_redirect!
|
143
|
+
expect(last_request.url).to eq expected_redirect_url
|
144
|
+
expect(last_response.status).to eq 200
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
context "when referer given" do
|
149
|
+
subject do
|
150
|
+
post("/undertaker/morgue",
|
151
|
+
"key[]=#{job_refs[0]}&delete=Delete",
|
152
|
+
"HTTP_REFERER" => "/undertaker/morgue/all/all/total_dead")
|
153
|
+
end
|
154
|
+
|
155
|
+
it "redirects back to referer after delete" do
|
156
|
+
subject
|
157
|
+
expect(last_response.status).to eq 302
|
158
|
+
expect(last_response.header["Location"]).to include "/undertaker/morgue/all/all/total_dead"
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
context "when /undertaker/morgue is called" do
|
163
|
+
context "when a key is given" do
|
164
|
+
subject { post "/undertaker/morgue", "key[]=#{job_refs[0]}&delete=Delete" }
|
165
|
+
|
166
|
+
it "deletes specific dead job now" do
|
167
|
+
expect { subject }.to change { Sidekiq::DeadSet.new.size }.from(4).to(3)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
context "when a key is missing" do
|
172
|
+
subject { post "/undertaker/morgue" }
|
173
|
+
|
174
|
+
it "returns 400 Bad Request" do
|
175
|
+
subject
|
176
|
+
expect(last_response.status).to eq 400
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
describe "retry" do
|
183
|
+
context "when job class, error and bucket are given" do
|
184
|
+
subject { post "/undertaker/morgue/HardWorker/RuntimeError/1_hour/retry" }
|
185
|
+
|
186
|
+
let(:expected_redirect_url) { "http://example.org/undertaker/morgue/HardWorker/RuntimeError/1_hour" }
|
187
|
+
|
188
|
+
let(:params) { { "job_class" => "HardWorker", "error_class" => "RuntimeError", "bucket_name" => "1_hour" } }
|
189
|
+
let(:dead_jobs_set) { [dead_job1, dead_job2] }
|
190
|
+
let(:dead_job1) { Sidekiq::Undertaker::DeadJob.to_dead_job(Sidekiq::DeadSet.new.find_job(jid1)) }
|
191
|
+
let(:dead_job2) { Sidekiq::Undertaker::DeadJob.to_dead_job(Sidekiq::DeadSet.new.find_job(jid2)) }
|
192
|
+
|
193
|
+
before do
|
194
|
+
allow(Sidekiq::Undertaker::JobFilter).to receive(:filter_dead_jobs).with(params)
|
195
|
+
.and_return(dead_jobs_set)
|
196
|
+
end
|
197
|
+
|
198
|
+
it "retries the dead jobs" do
|
199
|
+
expect(dead_job1.job).to receive(:retry).and_call_original
|
200
|
+
expect(dead_job2.job).to receive(:retry).and_call_original
|
201
|
+
subject
|
202
|
+
end
|
203
|
+
|
204
|
+
it "reduces DeadSet" do
|
205
|
+
expect { subject }.to change { Sidekiq::DeadSet.new.size }.from(4).to(2)
|
206
|
+
end
|
207
|
+
|
208
|
+
it "redirects to /undertaker/morgue/HardWorker/RuntimeError/1_hour" do
|
209
|
+
subject
|
210
|
+
expect(last_response.status).to eq 302
|
211
|
+
|
212
|
+
# Redirect
|
213
|
+
follow_redirect!
|
214
|
+
expect(last_request.url).to eq expected_redirect_url
|
215
|
+
expect(last_response.status).to eq 200
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
context "when /undertaker/morgue is called" do
|
220
|
+
context "when a key is given" do
|
221
|
+
subject { post "/undertaker/morgue", "key[]=#{job_refs[0]}&retry=Retry+Now" }
|
222
|
+
|
223
|
+
it "reduces DeadSet" do
|
224
|
+
expect { subject }.to change { Sidekiq::DeadSet.new.size }.from(4).to(3)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
context "when a key is missing" do
|
229
|
+
subject { post "/undertaker/morgue" }
|
230
|
+
|
231
|
+
it "returns 400 Bad Request" do
|
232
|
+
subject
|
233
|
+
expect(last_response.status).to eq 400
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
describe "specific jobs" do
|
240
|
+
let(:dead_job) { Sidekiq::DeadSet.new.find_job(jid1) }
|
241
|
+
|
242
|
+
before do
|
243
|
+
score, jid = job_refs[0].split("-")
|
244
|
+
|
245
|
+
dead_set = instance_double(Sidekiq::DeadSet)
|
246
|
+
|
247
|
+
allow(dead_set).to receive(:fetch)
|
248
|
+
.with(score.to_f, jid)
|
249
|
+
.and_return([dead_job])
|
250
|
+
|
251
|
+
allow(Sidekiq::DeadSet).to receive(:new).and_return(dead_set)
|
252
|
+
end
|
253
|
+
|
254
|
+
it "retries specific dead job now" do
|
255
|
+
expect(dead_job).to receive(:retry)
|
256
|
+
post "/undertaker/morgue", "key[]=#{job_refs[0]}&retry=Retry+Now"
|
257
|
+
end
|
258
|
+
|
259
|
+
it "redirects on specific retry post" do
|
260
|
+
post("/undertaker/morgue",
|
261
|
+
"key[]=#{job_refs[0]}&retry=Retry+Now",
|
262
|
+
"HTTP_REFERER" => "/undertaker/morgue/all/all/total_dead")
|
263
|
+
expect(last_response.status).to eq 302
|
264
|
+
expect(last_response.header["Location"]).to include("/undertaker/morgue/all/all/total_dead")
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
# rubocop:enable Metrics/ModuleLength
|
270
|
+
end
|