resque-integration 3.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/.drone.yml +28 -0
  3. data/.gitignore +21 -0
  4. data/.rspec +3 -0
  5. data/Appraisals +27 -0
  6. data/CHANGELOG.md +311 -0
  7. data/Gemfile +4 -0
  8. data/README.md +281 -0
  9. data/Rakefile +1 -0
  10. data/app/assets/javascripts/standalone/progress_bar.js +47 -0
  11. data/app/controllers/resque/jobs_controller.rb +42 -0
  12. data/app/controllers/resque/queues/info_controller.rb +9 -0
  13. data/app/controllers/resque/queues/status_controller.rb +38 -0
  14. data/app/views/shared/job_progress_bar.html.haml +17 -0
  15. data/bin/resque-status +59 -0
  16. data/config/routes.rb +13 -0
  17. data/config.ru +9 -0
  18. data/dip.yml +44 -0
  19. data/docker-compose.development.yml +12 -0
  20. data/docker-compose.drone.yml +6 -0
  21. data/docker-compose.yml +10 -0
  22. data/lib/generators/resque/integration/install/install_generator.rb +38 -0
  23. data/lib/resque/integration/backtrace.rb +29 -0
  24. data/lib/resque/integration/configuration.rb +238 -0
  25. data/lib/resque/integration/continuous.rb +75 -0
  26. data/lib/resque/integration/engine.rb +103 -0
  27. data/lib/resque/integration/extensions/job.rb +17 -0
  28. data/lib/resque/integration/extensions/worker.rb +17 -0
  29. data/lib/resque/integration/extensions.rb +8 -0
  30. data/lib/resque/integration/failure_backends/queues_totals.rb +37 -0
  31. data/lib/resque/integration/failure_backends.rb +7 -0
  32. data/lib/resque/integration/god.erb +99 -0
  33. data/lib/resque/integration/hooks.rb +72 -0
  34. data/lib/resque/integration/logs_rotator.rb +95 -0
  35. data/lib/resque/integration/monkey_patch/verbose_formatter.rb +10 -0
  36. data/lib/resque/integration/ordered.rb +142 -0
  37. data/lib/resque/integration/priority.rb +89 -0
  38. data/lib/resque/integration/queues_info/age.rb +53 -0
  39. data/lib/resque/integration/queues_info/config.rb +96 -0
  40. data/lib/resque/integration/queues_info/size.rb +33 -0
  41. data/lib/resque/integration/queues_info.rb +55 -0
  42. data/lib/resque/integration/tasks/hooks.rake +49 -0
  43. data/lib/resque/integration/tasks/lock.rake +37 -0
  44. data/lib/resque/integration/tasks/resque.rake +101 -0
  45. data/lib/resque/integration/unique.rb +218 -0
  46. data/lib/resque/integration/version.rb +5 -0
  47. data/lib/resque/integration.rb +146 -0
  48. data/lib/resque-integration.rb +1 -0
  49. data/resque-integration.gemspec +40 -0
  50. data/spec/fixtures/resque_queues.yml +45 -0
  51. data/spec/resque/controllers/jobs_controller_spec.rb +65 -0
  52. data/spec/resque/integration/configuration_spec.rb +147 -0
  53. data/spec/resque/integration/continuous_spec.rb +122 -0
  54. data/spec/resque/integration/failure_backends/queues_totals_spec.rb +105 -0
  55. data/spec/resque/integration/ordered_spec.rb +87 -0
  56. data/spec/resque/integration/priority_spec.rb +94 -0
  57. data/spec/resque/integration/queues_info_spec.rb +402 -0
  58. data/spec/resque/integration/unique_spec.rb +184 -0
  59. data/spec/resque/integration_spec.rb +105 -0
  60. data/spec/shared/resque_inline.rb +10 -0
  61. data/spec/spec_helper.rb +28 -0
  62. data/vendor/assets/images/progressbar/white.gif +0 -0
  63. data/vendor/assets/javascripts/jquery.progressbar.js +177 -0
  64. data/vendor/assets/stylesheets/jquery.progressbar.css.erb +33 -0
  65. data/vendor/assets/stylesheets/jquery.progressbar.no_pipeline.css +33 -0
  66. metadata +402 -0
@@ -0,0 +1,147 @@
1
+ # coding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Resque::Integration::Configuration do
6
+ let(:config) do
7
+ File.stub(:exists? => true)
8
+ File.stub(:read)
9
+ ERB.stub_chain(:new, :result)
10
+ YAML.stub(:load => config_yaml)
11
+ described_class.new('path/to/config.yml')
12
+ end
13
+
14
+ let(:config_yaml) { {} }
15
+
16
+ describe '#schedule_file' do
17
+ let(:config_yaml) { {'resque' => {'schedule_file' => 'schedule.yml'}} }
18
+
19
+ it { expect(config.schedule_file).to eq 'schedule.yml' }
20
+ end
21
+
22
+ describe '#log_level' do
23
+ context 'when default' do
24
+ it { expect(config.log_level).to eq 1 }
25
+ end
26
+
27
+ context 'when defined' do
28
+ let(:config_yaml) { {'resque' => {'log_level' => 2}} }
29
+
30
+ it { expect(config.log_level).to eq 2 }
31
+ end
32
+ end
33
+
34
+ describe '#resque_scheduler?' do
35
+ context 'when default' do
36
+ it { expect(config.resque_scheduler?).to be_truthy }
37
+ end
38
+
39
+ context 'when defined' do
40
+ let(:config_yaml) { {'resque' => {'scheduler' => 'no'}} }
41
+
42
+ it { expect(config.resque_scheduler?).to be_falsey }
43
+ end
44
+ end
45
+
46
+ describe '#run_at_exit_hooks?' do
47
+ context 'when default' do
48
+ it { expect(config.resque_scheduler?).to be_truthy }
49
+ end
50
+
51
+ context 'when defined' do
52
+ let(:config_yaml) { {'resque' => {'run_at_exit_hooks' => 'no'}} }
53
+
54
+ it { expect(config.run_at_exit_hooks?).to be_falsey }
55
+ end
56
+ end
57
+ end
58
+
59
+ describe Resque::Integration::Configuration::Notifier do
60
+ context 'when NilClass given as config' do
61
+ subject(:config) { described_class::new(nil) }
62
+
63
+ it { should_not be_enabled }
64
+ its(:include_payload?) { should be_truthy }
65
+ its(:to) { should be_empty }
66
+ its(:from) { should eq 'no_reply@gmail.com' }
67
+ its(:mail) { should eq :alert }
68
+ its(:mailer) { should eq 'ResqueFailedJobMailer::Mailer' }
69
+ end
70
+
71
+ context 'when Hash given as config' do
72
+ let :configuration do
73
+ {to: ['to1@mail', 'to2@mail'],
74
+ from: 'from@mail',
75
+ enabled: false,
76
+ include_payload: false,
77
+ mail: 'notify',
78
+ mailer: 'MyMailer'}
79
+ end
80
+
81
+ subject(:config) { described_class::new(configuration) }
82
+
83
+ it { should_not be_enabled }
84
+ its(:include_payload?) { should be_falsey }
85
+ its(:to) { should include 'to1@mail' }
86
+ its(:to) { should include 'to2@mail' }
87
+ its(:from) { should eq 'from@mail' }
88
+ its(:mail) { should eq :notify }
89
+ its(:mailer) { should eq 'MyMailer' }
90
+ end
91
+ end
92
+
93
+ describe Resque::Integration::Configuration::Worker do
94
+ describe '.new' do
95
+ context 'when Integer given as config' do
96
+ subject(:config) { described_class::new(:default, 2) }
97
+
98
+ its(:queue) { should eq :default }
99
+ its(:count) { should eq 2 }
100
+ end
101
+
102
+ context 'when Hash given as config' do
103
+ subject(:config) { described_class::new(:default, :count => 2) }
104
+
105
+ its(:queue) { should eq :default }
106
+ its(:count) { should eq 2 }
107
+ end
108
+ end
109
+
110
+ describe '#count' do
111
+ context 'when initialized without count paramter' do
112
+ subject { described_class::new(:default, {}) }
113
+
114
+ its(:count) { should eq 1 }
115
+ end
116
+
117
+ context 'when initialized with count <= 0' do
118
+ subject { described_class::new(:default, :count => 0) }
119
+
120
+ its(:count) { should eq 1 }
121
+ end
122
+ end
123
+
124
+ describe '#env' do
125
+ let :config do
126
+ described_class.new(:default,
127
+ :count => 2,
128
+ :jobs_per_fork => 10,
129
+ :minutes_per_fork => 5,
130
+ :shuffle => true,
131
+ :env => {:VAR => 2})
132
+ end
133
+
134
+ subject { config.env }
135
+
136
+ its([:QUEUE]) { should eq 'default' }
137
+ its([:JOBS_PER_FORK]) { should eq '10' }
138
+ its([:MINUTES_PER_FORK]) { should eq '5' }
139
+ its([:SHUFFLE]) { should eq '1' }
140
+ its([:VAR]) { should eq '2' }
141
+
142
+ context "when shuffle is disabled" do
143
+ let(:config) { described_class.new(:default, shuffle: false) }
144
+ its([:SHUFFLE]) { should be_nil }
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,122 @@
1
+ # coding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Resque::Integration::Continuous do
6
+ context 'when applied to non-unique job' do
7
+ class ContinuousJobTest
8
+ include Resque::Integration
9
+
10
+ queue :continuous_test
11
+ continuous
12
+
13
+ def self.perform(id)
14
+ continue(id + 1)
15
+ end
16
+ end
17
+
18
+ it 'should re-enqueue the job' do
19
+ Resque.enqueue(ContinuousJobTest, 1)
20
+
21
+ job = Resque.reserve(ContinuousJobTest.queue)
22
+ job.should be_a(Resque::Job)
23
+ job.payload['args'].should eq [1]
24
+ job.perform
25
+
26
+ job2 = Resque.reserve(ContinuousJobTest.queue)
27
+ job2.should be_a(Resque::Job)
28
+ job2.payload['args'].should eq [2]
29
+ end
30
+ end
31
+
32
+ context 'when applied to unique job' do
33
+ class ContinuousUniqueJobTest
34
+ include Resque::Integration
35
+
36
+ queue :unique_continuous_test
37
+ continuous
38
+ unique { |x, y| x }
39
+
40
+ def self.execute(x, y)
41
+ continue(x, y + 1)
42
+ end
43
+ end
44
+
45
+ it 'should re-enqueue the job regardless of any locks' do
46
+ ContinuousUniqueJobTest.enqueue(1, 1)
47
+
48
+ job = Resque.reserve(ContinuousUniqueJobTest.queue)
49
+ job.should be_a(Resque::Job)
50
+
51
+ # meta prepend meta_id arg, so we just ignore it here
52
+ job.payload['args'][1..-1].should eq [1, 1]
53
+ job.perform
54
+
55
+ ContinuousUniqueJobTest.should be_locked(1, 2)
56
+ ContinuousUniqueJobTest.should be_enqueued(1, 2)
57
+
58
+ job2 = Resque.reserve(ContinuousUniqueJobTest.queue)
59
+ job2.should be_a(Resque::Job)
60
+ job2.payload['args'][1..-1].should eq [1, 2]
61
+
62
+ # clean the queue
63
+ ContinuousUniqueJobTest.dequeue(1)
64
+ end
65
+
66
+ it 'should not finish meta' do
67
+ meta = ContinuousUniqueJobTest.enqueue(2, 1)
68
+
69
+ job = Resque.reserve(ContinuousUniqueJobTest.queue)
70
+ job.perform
71
+ meta.reload!
72
+
73
+ meta2 = ContinuousUniqueJobTest.enqueued?(2, 2)
74
+
75
+ meta2.started_at.should eq meta.started_at
76
+ meta2.enqueued_at.should eq meta.enqueued_at
77
+ meta2.data.should eq meta.data
78
+ meta2.should be_working
79
+
80
+ # clean the queue
81
+ ContinuousUniqueJobTest.dequeue(2)
82
+ end
83
+
84
+ it 'should enqueue job with the same meta_id' do
85
+ ContinuousUniqueJobTest.enqueue(3, 1)
86
+
87
+ job1 = Resque.reserve(ContinuousUniqueJobTest.queue)
88
+ meta1 = job1.payload['args'].first
89
+
90
+ job1.perform
91
+
92
+ job2 = Resque.reserve(ContinuousUniqueJobTest.queue)
93
+ meta2 = job2.payload['args'].first
94
+
95
+ meta1.should eq meta2
96
+ end
97
+ end
98
+
99
+ context 'when called without arguments' do
100
+ class ContinuousWithoutArgsJobTest
101
+ include Resque::Integration
102
+
103
+ queue :continuous_without_args_test
104
+ continuous
105
+ unique
106
+
107
+ def self.execute(x)
108
+ continue
109
+ end
110
+ end
111
+
112
+ it 'should re-enqueue job with same arguments' do
113
+ ContinuousWithoutArgsJobTest.enqueue(1)
114
+
115
+ job = Resque.reserve(ContinuousWithoutArgsJobTest.queue)
116
+ job.should be_a(Resque::Job)
117
+ job.perform
118
+
119
+ ContinuousWithoutArgsJobTest.should be_enqueued(1)
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,105 @@
1
+ require 'spec_helper'
2
+
3
+ describe Resque::Integration::FailureBackends::QueuesTotals do
4
+ let(:failure) { double('UnbelievableError') }
5
+ let(:worker) { double('Worker') }
6
+ let(:payload) { double('Payload') }
7
+
8
+ describe '#save' do
9
+ let(:queue) { 'images' }
10
+ let(:backend) { described_class.new(failure, worker, queue, payload) }
11
+
12
+ before { stub_const('Resque::Integration::FailureBackends::QueuesTotals::MAX_COUNTER_VALUE', 3) }
13
+
14
+ it 'increments failures count for specified queue' do
15
+ expect do
16
+ 2.times { backend.save }
17
+ end.to change { described_class.count(queue) }.from(0).to(2)
18
+ end
19
+
20
+ context 'when counter overflows' do
21
+ it 'resets failures count for specified queue to 1' do
22
+ expect do
23
+ 3.times { backend.save }
24
+ end.to change { described_class.count(queue) }.from(0).to(1)
25
+ end
26
+ end
27
+ end
28
+
29
+ describe '.count' do
30
+ let(:images_queue) { 'images' }
31
+ let(:products_queue) { 'products' }
32
+ let(:images_failure_backend) { described_class.new(failure, worker, images_queue, payload) }
33
+ let(:products_failure_backend) { described_class.new(failure, worker, products_queue, payload) }
34
+
35
+ before do
36
+ 2.times { images_failure_backend.save }
37
+ 3.times { products_failure_backend.save }
38
+ end
39
+
40
+ context 'with specified queue' do
41
+ it 'returns failures count for specified queue' do
42
+ expect(described_class.count(images_queue)).to eq(2)
43
+ expect(described_class.count(products_queue)).to eq(3)
44
+ end
45
+ end
46
+
47
+ context 'with queue which has no failures' do
48
+ it 'returns 0' do
49
+ expect(described_class.count('not_failed')).to eq(0)
50
+ end
51
+ end
52
+
53
+ context 'without specified queue' do
54
+ it 'returns aggregated failures count from all queues' do
55
+ expect(described_class.count).to eq(5)
56
+ end
57
+ end
58
+ end
59
+
60
+ describe '.queues' do
61
+ context 'when has failures data' do
62
+ let(:images_queue) { 'images' }
63
+ let(:products_queue) { 'products' }
64
+
65
+ before do
66
+ described_class.new(failure, worker, images_queue, payload).save
67
+ described_class.new(failure, worker, products_queue, payload).save
68
+ end
69
+
70
+ it 'returns names of failed queues' do
71
+ expect(described_class.queues).to match_array([images_queue, products_queue])
72
+ end
73
+ end
74
+
75
+ context 'when does not have failures data' do
76
+ it { expect(described_class.queues).to be_empty }
77
+ end
78
+ end
79
+
80
+ describe '.clear' do
81
+ let(:images_queue) { 'images' }
82
+ let(:products_queue) { 'products' }
83
+
84
+ before do
85
+ described_class.new(failure, worker, images_queue, payload).save
86
+ described_class.new(failure, worker, products_queue, payload).save
87
+ end
88
+
89
+ context 'with specified queue' do
90
+ it 'deletes counter data for specified queue' do
91
+ expect { described_class.clear(products_queue) }.to change { described_class.count }.from(2).to(1)
92
+ expect(described_class.count(images_queue)).to eq(1)
93
+ expect(described_class.count(products_queue)).to eq(0)
94
+ end
95
+ end
96
+
97
+ context 'without specified queue' do
98
+ it 'deletes counter data for all queues' do
99
+ expect { described_class.clear }.to change { described_class.count }.from(2).to(0)
100
+ expect(described_class.count(images_queue)).to eq(0)
101
+ expect(described_class.count(products_queue)).to eq(0)
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,87 @@
1
+ require "spec_helper"
2
+
3
+ describe Resque::Integration::Ordered do
4
+ class TestJob
5
+ include Resque::Integration
6
+
7
+ unique { |company_id, param1| [company_id] }
8
+ ordered max_iterations: 2
9
+ end
10
+
11
+ class UniqueTestJob
12
+ include Resque::Integration
13
+
14
+ unique { |company_id, param1| [company_id] }
15
+ ordered max_iterations: 2, unique: ->(_company_id, param1) { [param1] }
16
+ end
17
+
18
+ it "push args to separate queue" do
19
+ ordered_meta1 = TestJob.enqueue(1, 10)
20
+ ordered_meta2 = TestJob.enqueue(1, 20)
21
+
22
+ meta_id = TestJob.meta_id(1, 10)
23
+ args_key = TestJob.ordered_queue_key(meta_id)
24
+
25
+ expect(TestJob).to be_enqueued(1)
26
+ expect(TestJob.ordered_queue_size(meta_id)).to eq 2
27
+
28
+ job_args = Resque.decode(Resque.redis.lpop(args_key))
29
+ expect(job_args[0]).to eq ordered_meta1.meta_id
30
+
31
+ job_args = Resque.decode(Resque.redis.lpop(args_key))
32
+ expect(job_args[0]).to eq ordered_meta2.meta_id
33
+ end
34
+
35
+ it "execute jobs by each args" do
36
+ TestJob.enqueue(1, 10)
37
+ TestJob.enqueue(1, 20)
38
+
39
+ expect(TestJob).to receive(:execute).with(kind_of(Resque::Plugins::Meta::Metadata), 1, 10).ordered
40
+ expect(TestJob).to receive(:execute).with(kind_of(Resque::Plugins::Meta::Metadata), 1, 20).ordered
41
+ expect(TestJob).to_not receive(:continue)
42
+
43
+ meta_id = TestJob.meta_id(1, 10)
44
+ TestJob.perform(meta_id)
45
+ expect(TestJob.ordered_queue_size(meta_id)).to eq 0
46
+ end
47
+
48
+ it "reenqueue job after max iterations reached" do
49
+ TestJob.enqueue(1, 10)
50
+ TestJob.enqueue(1, 20)
51
+ TestJob.enqueue(1, 30)
52
+ TestJob.enqueue(1, 40)
53
+
54
+ expect(TestJob).to receive(:execute).with(kind_of(Resque::Plugins::Meta::Metadata), 1, 10).ordered
55
+ expect(TestJob).to receive(:execute).with(kind_of(Resque::Plugins::Meta::Metadata), 1, 20).ordered
56
+ expect(TestJob).to_not receive(:execute).with(kind_of(Resque::Plugins::Meta::Metadata), 1, 30).ordered
57
+ expect(TestJob).to_not receive(:execute).with(kind_of(Resque::Plugins::Meta::Metadata), 1, 40).ordered
58
+ expect(TestJob).to receive(:continue)
59
+
60
+ meta_id = TestJob.meta_id(1, 10)
61
+ TestJob.perform(meta_id)
62
+ expect(TestJob.ordered_queue_size(meta_id)).to eq 2
63
+ end
64
+
65
+ context 'uniqueness' do
66
+ it 'perform with unique args only once' do
67
+ UniqueTestJob.enqueue(1, 10)
68
+ UniqueTestJob.enqueue(1, 20)
69
+ UniqueTestJob.enqueue(1, 10)
70
+
71
+ expect(UniqueTestJob).to receive(:execute).once.with(kind_of(Resque::Plugins::Meta::Metadata), 1, 10).ordered
72
+ expect(UniqueTestJob).to receive(:execute).once.with(kind_of(Resque::Plugins::Meta::Metadata), 1, 20).ordered
73
+ expect(UniqueTestJob).to_not receive(:continue)
74
+
75
+ meta_id = UniqueTestJob.meta_id(1, 10)
76
+ UniqueTestJob.perform(meta_id)
77
+ expect(UniqueTestJob.ordered_queue_size(meta_id)).to eq 0
78
+ expect(UniqueTestJob.uniqueness.size(meta_id)).to eq 0
79
+ end
80
+
81
+ it 'enqueue unique jobs with equal meta' do
82
+ meta = UniqueTestJob.enqueue(1, 10)
83
+ expect(meta.meta_id).to eq UniqueTestJob.enqueue(1, 10).meta_id
84
+ expect(meta.meta_id).to_not eq UniqueTestJob.enqueue(1, 20).meta_id
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,94 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Resque::Integration::Priority do
4
+ class JobWithPriority
5
+ include Resque::Integration
6
+
7
+ queue :foo
8
+ prioritized
9
+
10
+ def self.perform(id, params)
11
+ end
12
+ end
13
+
14
+ class UniqueJobWithPriority
15
+ include Resque::Integration
16
+
17
+ queue :foo
18
+ unique do |id, params|
19
+ [id, params["param"]]
20
+ end
21
+ prioritized
22
+
23
+ def self.execute(id, params)
24
+ end
25
+ end
26
+
27
+ describe '#enqueue' do
28
+ it 'enqueue to priority queue' do
29
+ JobWithPriority.enqueue_with_priority(:high, 1, param: 'one')
30
+
31
+ expect(Resque.size(:foo)).to eq(0)
32
+ expect(Resque.size(:foo_low)).to eq(0)
33
+ expect(Resque.size(:foo_high)).to eq(1)
34
+ end
35
+
36
+ it 'enqueue to default queue' do
37
+ JobWithPriority.enqueue(1, param: 'one')
38
+
39
+ expect(Resque.size(:foo)).to eq(1)
40
+ expect(Resque.size(:foo_low)).to eq(0)
41
+ expect(Resque.size(:foo_high)).to eq(0)
42
+ end
43
+
44
+ it 'enqueue only one job' do
45
+ meta1 = UniqueJobWithPriority.enqueue_with_priority(:high, 1, param: 'one')
46
+ meta2 = UniqueJobWithPriority.enqueue_with_priority(:high, 1, param: 'one')
47
+
48
+ expect(meta1.meta_id).to eq(meta2.meta_id)
49
+
50
+ expect(Resque.size(:foo)).to eq(0)
51
+ expect(Resque.size(:foo_low)).to eq(0)
52
+ expect(Resque.size(:foo_high)).to eq(1)
53
+ end
54
+ end
55
+
56
+ describe '#dequeue' do
57
+ it 'dequeue simple job with high priority' do
58
+ JobWithPriority.enqueue_with_priority(:high, 1, param: 'one')
59
+ JobWithPriority.enqueue_with_priority(:high, 2, param: 'two')
60
+ expect(Resque.size(:foo_high)).to eq(2)
61
+
62
+ JobWithPriority.dequeue(:high, 1, param: 'one')
63
+ expect(Resque.size(:foo_high)).to eq(1)
64
+ end
65
+
66
+ it 'dequeue unique job with high priority' do
67
+ UniqueJobWithPriority.enqueue_with_priority(:high, 1, param: 'one')
68
+ UniqueJobWithPriority.enqueue_with_priority(:high, 2, param: 'two')
69
+ expect(Resque.size(:foo_high)).to eq(2)
70
+
71
+ UniqueJobWithPriority.dequeue(:high, 1, param: 'one')
72
+ expect(Resque.size(:foo_high)).to eq(1)
73
+ end
74
+ end
75
+
76
+ describe '#perform' do
77
+ include_context 'resque inline'
78
+
79
+ it 'executes job' do
80
+ expect(JobWithPriority).to receive(:perform).with(1, 'param' => 'one')
81
+ expect(JobWithPriority).to receive(:perform).with(2, 'param' => 'two')
82
+
83
+ JobWithPriority.enqueue_with_priority(:high, 1, param: 'one')
84
+ JobWithPriority.enqueue_with_priority(:high, 2, param: 'two')
85
+ end
86
+
87
+ it 'executes job' do
88
+ expect(UniqueJobWithPriority).to receive(:execute).twice.and_call_original
89
+
90
+ UniqueJobWithPriority.enqueue_with_priority(:high, 1, param: 'one')
91
+ UniqueJobWithPriority.enqueue_with_priority(:high, 1, param: 'one')
92
+ end
93
+ end
94
+ end