sidekiq 2.13.0 → 2.13.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sidekiq might be problematic. Click here for more details.

Files changed (72) hide show
  1. data/.gitignore +2 -0
  2. data/Changes.md +21 -0
  3. data/Contributing.md +26 -0
  4. data/Pro-Changes.md +22 -0
  5. data/lib/sidekiq/exception_handler.rb +1 -1
  6. data/lib/sidekiq/middleware/chain.rb +8 -1
  7. data/lib/sidekiq/middleware/server/retry_jobs.rb +14 -1
  8. data/lib/sidekiq/redis_connection.rb +14 -5
  9. data/lib/sidekiq/version.rb +1 -1
  10. data/lib/sidekiq/web.rb +33 -11
  11. data/test/test_exception_handler.rb +3 -7
  12. data/test/test_middleware.rb +9 -2
  13. data/test/test_redis_connection.rb +30 -0
  14. data/test/test_retry.rb +17 -0
  15. data/test/test_web.rb +41 -3
  16. data/web/assets/javascripts/dashboard.js +36 -3
  17. data/web/assets/javascripts/locales/README.md +27 -0
  18. data/web/assets/javascripts/locales/jquery.timeago.ar.js +96 -0
  19. data/web/assets/javascripts/locales/jquery.timeago.bg.js +18 -0
  20. data/web/assets/javascripts/locales/jquery.timeago.bs.js +49 -0
  21. data/web/assets/javascripts/locales/jquery.timeago.ca.js +18 -0
  22. data/web/assets/javascripts/locales/jquery.timeago.cy.js +20 -0
  23. data/web/assets/javascripts/locales/jquery.timeago.cz.js +18 -0
  24. data/web/assets/javascripts/locales/jquery.timeago.da.js +18 -0
  25. data/web/assets/javascripts/locales/jquery.timeago.de.js +18 -0
  26. data/web/assets/javascripts/locales/jquery.timeago.el.js +18 -0
  27. data/web/assets/javascripts/locales/jquery.timeago.en-short.js +20 -0
  28. data/web/assets/javascripts/locales/jquery.timeago.en.js +20 -0
  29. data/web/assets/javascripts/locales/jquery.timeago.es.js +18 -0
  30. data/web/assets/javascripts/locales/jquery.timeago.et.js +18 -0
  31. data/web/assets/javascripts/locales/jquery.timeago.fa.js +22 -0
  32. data/web/assets/javascripts/locales/jquery.timeago.fi.js +28 -0
  33. data/web/assets/javascripts/locales/jquery.timeago.fr-short.js +16 -0
  34. data/web/assets/javascripts/locales/jquery.timeago.fr.js +17 -0
  35. data/web/assets/javascripts/locales/jquery.timeago.he.js +18 -0
  36. data/web/assets/javascripts/locales/jquery.timeago.hr.js +49 -0
  37. data/web/assets/javascripts/locales/jquery.timeago.hu.js +18 -0
  38. data/web/assets/javascripts/locales/jquery.timeago.hy.js +18 -0
  39. data/web/assets/javascripts/locales/jquery.timeago.id.js +18 -0
  40. data/web/assets/javascripts/locales/jquery.timeago.it.js +16 -0
  41. data/web/assets/javascripts/locales/jquery.timeago.ja.js +19 -0
  42. data/web/assets/javascripts/locales/jquery.timeago.ko.js +17 -0
  43. data/web/assets/javascripts/locales/jquery.timeago.lt.js +20 -0
  44. data/web/assets/javascripts/locales/jquery.timeago.mk.js +20 -0
  45. data/web/assets/javascripts/locales/jquery.timeago.nl.js +20 -0
  46. data/web/assets/javascripts/locales/jquery.timeago.no.js +18 -0
  47. data/web/assets/javascripts/locales/jquery.timeago.pl.js +31 -0
  48. data/web/assets/javascripts/locales/jquery.timeago.pt-br.js +16 -0
  49. data/web/assets/javascripts/locales/jquery.timeago.pt.js +16 -0
  50. data/web/assets/javascripts/locales/jquery.timeago.ro.js +18 -0
  51. data/web/assets/javascripts/locales/jquery.timeago.rs.js +49 -0
  52. data/web/assets/javascripts/locales/jquery.timeago.ru.js +34 -0
  53. data/web/assets/javascripts/locales/jquery.timeago.sk.js +18 -0
  54. data/web/assets/javascripts/locales/jquery.timeago.sl.js +44 -0
  55. data/web/assets/javascripts/locales/jquery.timeago.sv.js +18 -0
  56. data/web/assets/javascripts/locales/jquery.timeago.th.js +20 -0
  57. data/web/assets/javascripts/locales/jquery.timeago.tr.js +16 -0
  58. data/web/assets/javascripts/locales/jquery.timeago.uk.js +34 -0
  59. data/web/assets/javascripts/locales/jquery.timeago.uz.js +19 -0
  60. data/web/assets/javascripts/locales/jquery.timeago.zh-CN.js +20 -0
  61. data/web/assets/javascripts/locales/jquery.timeago.zh-TW.js +20 -0
  62. data/web/assets/stylesheets/application.css +46 -1
  63. data/web/views/_job_info.slim +51 -0
  64. data/web/views/_summary.slim +8 -8
  65. data/web/views/index.slim +1 -1
  66. data/web/views/layout.slim +24 -22
  67. data/web/views/queue.slim +1 -1
  68. data/web/views/retries.slim +2 -1
  69. data/web/views/retry.slim +1 -44
  70. data/web/views/scheduled.slim +4 -2
  71. data/web/views/scheduled_job_info.slim +6 -0
  72. metadata +50 -2
data/.gitignore CHANGED
@@ -6,4 +6,6 @@ Gemfile.lock
6
6
  dump.rdb
7
7
  .rbx
8
8
  coverage/
9
+ vendor/
10
+ .bundle/
9
11
  .sass-cache/
data/Changes.md CHANGED
@@ -1,5 +1,26 @@
1
+ 2.13.1
2
+ -----------
3
+
4
+ - Make Sidekiq::Middleware::Chain Enumerable
5
+ - Make summary bar and graphs responsive [manishval, #1025]
6
+ - Adds a job status page for scheduled jobs [jonhyman]
7
+ - Handle race condition in retrying and deleting jobs in the Web UI
8
+ - The Web UI relative times are now i18n. [MadRabbit, #1088]
9
+ - Allow for default number of retry attempts to be set for
10
+ `Sidekiq::Middleware::Server::RetryJobs` middleware. [czarneckid] [#1091]
11
+
12
+ ```ruby
13
+ Sidekiq.configure_server do |config|
14
+ config.server_middleware do |chain|
15
+ chain.add Sidekiq::Middleare::Server::RetryJobs, :max_retries => 10
16
+ end
17
+ end
18
+ ```
19
+
20
+
1
21
  2.13.0
2
22
  -----------
23
+
3
24
  - Adding button to move scheduled job to main queue [guiceolin, #1020]
4
25
  - fix i18n support resetting saved locale when job is retried [#1011]
5
26
  - log rotation via USR2 now closes the old logger [#1008]
@@ -0,0 +1,26 @@
1
+ # Contributing
2
+
3
+ First of all, thank you for even opening this file up! I hope you find
4
+ it worthwhile to help out with Sidekiq.
5
+
6
+
7
+ ## Code
8
+
9
+ If you're interested in helping or contributing to Sidekiq, here's
10
+ some known areas which need improvement:
11
+
12
+ * The Web UI and sidekiq.org website could always use more polish. If you have an eye for design and an idea for improvement, please open an issue and let us know.
13
+ * The Sidekiq API has a serious issue: deleting elements and iterating
14
+ at the same time is broken, see #866, #1060.
15
+ * Make normal testing and inline testing dynamic: #1053
16
+ * Your brilliant idea which I haven't listed!
17
+
18
+ It's always best to open an issue before investing a lot of time into a
19
+ fix or new functionality. Functionality must meet my design goals and
20
+ vision for the project to be accepted; I would be happy to discuss how
21
+ your idea can best fit into Sidekiq.
22
+
23
+
24
+ ## Sponsorship
25
+
26
+ If you've got more money than time and want to sponsor Sidekiq's continued support, your company can buy [Sidekiq Pro](http://sidekiq.org/pro). You get great functionality, I continue to fix bugs and enhance Sidekiq for years to come.
@@ -3,6 +3,28 @@ Sidekiq Pro Changelog
3
3
 
4
4
  Please see http://sidekiq.org/pro for more details and how to buy.
5
5
 
6
+ 1.2.0
7
+ -----------
8
+
9
+ - **LEAK** Fix batch key which didn't expire in Redis. Keys match
10
+ /b-[a-f0-9]{16}-pending/, e.g. "b-4f55163ddba10aa0-pending" [#1057]
11
+ - **Reliable fetch now supports multiple queues**, using the algorithm spec'd
12
+ by @jackrg [#1102]
13
+ - Fix issue with reliable\_push where it didn't return the JID for a pushed
14
+ job when sending previously cached jobs to Redis.
15
+ - Add fast Sidekiq::Queue#delete\_job(jid) API which leverages Lua so job lookup is
16
+ 100% server-side. Benchmark vs Sidekiq's Job#delete API:
17
+
18
+ ```
19
+ Sidekiq Pro API
20
+ 0.030000 0.020000 0.050000 ( 1.640659)
21
+ Sidekiq API
22
+ 17.250000 2.220000 19.470000 ( 22.193300)
23
+ ```
24
+
25
+ - Add fast Sidekiq::Queue#delete\_by\_class(klass) API to remove all
26
+ jobs of a given type. Uses server-side Lua for performance.
27
+
6
28
  1.1.0
7
29
  -----------
8
30
 
@@ -33,7 +33,7 @@ module Sidekiq
33
33
  end
34
34
 
35
35
  def send_to_exception_notifier(msg, ex)
36
- ::ExceptionNotifier::Notifier.background_exception_notification(ex, :data => { :message => msg }).deliver
36
+ ::ExceptionNotifier.notify_exception(ex, :data => {:message => msg})
37
37
  end
38
38
  end
39
39
  end
@@ -61,8 +61,13 @@ module Sidekiq
61
61
  #
62
62
  module Middleware
63
63
  class Chain
64
+ include Enumerable
64
65
  attr_reader :entries
65
66
 
67
+ def each(&block)
68
+ entries.each(&block)
69
+ end
70
+
66
71
  def initialize
67
72
  @entries = []
68
73
  yield self if block_given?
@@ -73,7 +78,8 @@ module Sidekiq
73
78
  end
74
79
 
75
80
  def add(klass, *args)
76
- entries << Entry.new(klass, *args) unless exists?(klass)
81
+ remove(klass) if exists?(klass)
82
+ entries << Entry.new(klass, *args)
77
83
  end
78
84
 
79
85
  def insert_before(oldklass, newklass, *args)
@@ -117,6 +123,7 @@ module Sidekiq
117
123
 
118
124
  class Entry
119
125
  attr_reader :klass
126
+
120
127
  def initialize(klass, *args)
121
128
  @klass = klass
122
129
  @args = args
@@ -40,11 +40,24 @@ module Sidekiq
40
40
  #
41
41
  # We don't store the backtrace as that can add a lot of overhead
42
42
  # to the message and everyone is using Airbrake, right?
43
+ #
44
+ # The default number of retry attempts is 25. You can pass a value for the
45
+ # number of retry attempts when adding the middleware using the options hash:
46
+ #
47
+ # Sidekiq.configure_server do |config|
48
+ # config.server_middleware do |chain|
49
+ # chain.add Middleware::Server::RetryJobs, {:max_retries => 7}
50
+ # end
51
+ # end
43
52
  class RetryJobs
44
53
  include Sidekiq::Util
45
54
 
46
55
  DEFAULT_MAX_RETRY_ATTEMPTS = 25
47
56
 
57
+ def initialize(options = {})
58
+ @max_retries = options.fetch(:max_retries, DEFAULT_MAX_RETRY_ATTEMPTS)
59
+ end
60
+
48
61
  def call(worker, msg, queue)
49
62
  yield
50
63
  rescue Sidekiq::Shutdown
@@ -52,7 +65,7 @@ module Sidekiq
52
65
  raise
53
66
  rescue Exception => e
54
67
  raise e unless msg['retry']
55
- max_retry_attempts = retry_attempts_from(msg['retry'], DEFAULT_MAX_RETRY_ATTEMPTS)
68
+ max_retry_attempts = retry_attempts_from(msg['retry'], @max_retries)
56
69
 
57
70
  msg['queue'] = if msg['retry_queue']
58
71
  msg['retry_queue']
@@ -9,18 +9,19 @@ module Sidekiq
9
9
  url = options[:url] || determine_redis_provider || 'redis://localhost:6379/0'
10
10
  # need a connection for Fetcher and Retry
11
11
  size = options[:size] || (Sidekiq.server? ? (Sidekiq.options[:concurrency] + 2) : 5)
12
+ pool_timeout = options[:pool_timeout] || 1
12
13
 
13
14
  log_info(url, options)
14
15
 
15
- ConnectionPool.new(:timeout => 1, :size => size) do
16
- build_client(url, options[:namespace], options[:driver] || 'ruby')
16
+ ConnectionPool.new(:timeout => pool_timeout, :size => size) do
17
+ build_client(url, options[:namespace], options[:driver] || 'ruby', options[:network_timeout])
17
18
  end
18
19
  end
19
20
 
20
21
  private
21
22
 
22
- def build_client(url, namespace, driver)
23
- client = Redis.connect(:url => url, :driver => driver)
23
+ def build_client(url, namespace, driver, network_timeout)
24
+ client = Redis.new client_opts(url, driver, network_timeout)
24
25
  if namespace
25
26
  require 'redis/namespace'
26
27
  Redis::Namespace.new(namespace, :redis => client)
@@ -29,11 +30,19 @@ module Sidekiq
29
30
  end
30
31
  end
31
32
 
33
+ def client_opts(url, driver, timeout)
34
+ if timeout
35
+ { :url => url, :driver => driver, :timeout => timeout }
36
+ else
37
+ { :url => url, :driver => driver }
38
+ end
39
+ end
40
+
32
41
  def log_info(url, options)
33
42
  opts = options.dup
34
43
  opts.delete(:url)
35
44
  if Sidekiq.server?
36
- Sidekiq.logger.info("Booting #{Sidekiq::NAME} #{Sidekiq::VERSION} using #{url} with options #{opts}")
45
+ Sidekiq.logger.info("Booting Sidekiq #{Sidekiq::VERSION} using #{url} with options #{opts}")
37
46
  else
38
47
  Sidekiq.logger.info("#{Sidekiq::NAME} client using #{url} with options #{opts}")
39
48
  end
@@ -1,3 +1,3 @@
1
1
  module Sidekiq
2
- VERSION = "2.13.0"
2
+ VERSION = "2.13.1"
3
3
  end
@@ -12,25 +12,28 @@ module Sidekiq
12
12
  class Web < Sinatra::Base
13
13
  include Sidekiq::Paginator
14
14
 
15
- dir = File.expand_path(File.dirname(__FILE__) + "/../../web")
16
-
17
- set :public_folder, "#{dir}/assets"
18
- set :views, "#{dir}/views"
19
- set :root, "#{dir}/public"
20
- set :locales, "#{dir}/locales"
15
+ set :root, File.expand_path(File.dirname(__FILE__) + "/../../web")
16
+ set :public_folder, Proc.new { "#{root}/assets" }
17
+ set :views, Proc.new { "#{root}/views" }
18
+ set :locales, Proc.new { "#{root}/locales" }
21
19
  set :slim, :pretty => true
22
20
 
23
21
  helpers do
24
22
  def strings
25
23
  @strings ||= begin
26
24
  Dir["#{settings.locales}/*.yml"].inject({}) do |memo, file|
27
- memo.merge(YAML.load(File.read(file)))
25
+ memo.merge(YAML.load(File.open(file)))
28
26
  end
29
27
  end
30
28
  end
31
29
 
30
+ def locale
31
+ lang = (request.env["HTTP_ACCEPT_LANGUAGE"] || 'en')[0,2]
32
+ strings[lang] ? lang : 'en'
33
+ end
34
+
32
35
  def get_locale
33
- strings[(request.env["HTTP_ACCEPT_LANGUAGE"] || 'en')[0,2]] || strings['en']
36
+ strings[locale]
34
37
  end
35
38
 
36
39
  def t(msg, options={})
@@ -46,7 +49,7 @@ module Sidekiq
46
49
  end
47
50
 
48
51
  def workers_size
49
- Sidekiq.redis do |conn|
52
+ @workers_size ||= Sidekiq.redis do |conn|
50
53
  conn.scard('workers')
51
54
  end
52
55
  end
@@ -107,10 +110,10 @@ module Sidekiq
107
110
  [score.to_f, jid]
108
111
  end
109
112
 
110
- def display_args(args, count=100)
113
+ def display_args(args, truncate_after_chars = 2000)
111
114
  args.map do |arg|
112
115
  a = arg.inspect
113
- a.size > count ? "#{a[0..count]}..." : a
116
+ truncate_after_chars && a.size > truncate_after_chars ? "#{a[0..truncate_after_chars]}..." : a
114
117
  end.join(", ")
115
118
  end
116
119
 
@@ -212,6 +215,7 @@ module Sidekiq
212
215
 
213
216
  params['key'].each do |key|
214
217
  job = Sidekiq::RetrySet.new.fetch(*parse_params(key)).first
218
+ next unless job
215
219
  if params['retry']
216
220
  job.retry
217
221
  elsif params['delete']
@@ -249,6 +253,13 @@ module Sidekiq
249
253
  slim :scheduled
250
254
  end
251
255
 
256
+ get "/scheduled/:key" do
257
+ halt 404 unless params['key']
258
+ @job = Sidekiq::ScheduledSet.new.fetch(*parse_params(params['key'])).first
259
+ redirect "#{root_path}scheduled" if @job.nil?
260
+ slim :scheduled_job_info
261
+ end
262
+
252
263
  post '/scheduled' do
253
264
  halt 404 unless params['key']
254
265
 
@@ -263,6 +274,17 @@ module Sidekiq
263
274
  redirect "#{root_path}scheduled"
264
275
  end
265
276
 
277
+ post "/scheduled/:key" do
278
+ halt 404 unless params['key']
279
+ job = Sidekiq::ScheduledSet.new.fetch(*parse_params(params['key'])).first
280
+ if params['add_to_queue']
281
+ job.add_to_queue
282
+ elsif params['delete']
283
+ job.delete
284
+ end
285
+ redirect "#{root_path}scheduled"
286
+ end
287
+
266
288
  get '/' do
267
289
  @redis_info = Sidekiq.redis { |conn| conn.info }.select{ |k, v| redis_keys.include? k }
268
290
  stats_history = Sidekiq::Stats::History.new((params[:days] || 30).to_i)
@@ -73,8 +73,7 @@ class TestExceptionHandler < Minitest::Test
73
73
 
74
74
  describe "with fake ExceptionNotifier" do
75
75
  before do
76
- ::ExceptionNotifier = Module.new
77
- ::ExceptionNotifier::Notifier = MiniTest::Mock.new
76
+ ::ExceptionNotifier = MiniTest::Mock.new
78
77
  end
79
78
 
80
79
  after do
@@ -82,12 +81,9 @@ class TestExceptionHandler < Minitest::Test
82
81
  end
83
82
 
84
83
  it "notifies ExceptionNotifier" do
85
- mail = MiniTest::Mock.new
86
- mail.expect(:deliver,nil)
87
- ::ExceptionNotifier::Notifier.expect(:background_exception_notification,mail,[TEST_EXCEPTION, :data => { :message => { :b => 2 } }])
84
+ ::ExceptionNotifier.expect(:notify_exception,true,[TEST_EXCEPTION, :data => { :message => { :b => 2 } }])
88
85
  Component.new.invoke_exception(:b => 2)
89
- ::ExceptionNotifier::Notifier.verify
90
- mail.verify
86
+ ::ExceptionNotifier.verify
91
87
  end
92
88
  end
93
89
 
@@ -72,7 +72,7 @@ class TestMiddleware < Minitest::Test
72
72
  msg = Sidekiq.dump_json({ 'class' => CustomWorker.to_s, 'args' => [$recorder] })
73
73
 
74
74
  Sidekiq.server_middleware do |chain|
75
- # should only add once, second should be ignored
75
+ # should only add once, second should replace the first
76
76
  2.times { |i| chain.add CustomMiddleware, i.to_s, $recorder }
77
77
  chain.insert_before CustomMiddleware, AnotherCustomMiddleware, '2', $recorder
78
78
  chain.insert_after AnotherCustomMiddleware, YetAnotherCustomMiddleware, '3', $recorder
@@ -86,7 +86,14 @@ class TestMiddleware < Minitest::Test
86
86
  boss.expect(:async, actor, [])
87
87
  boss.expect(:async, actor, [])
88
88
  processor.process(Sidekiq::BasicFetch::UnitOfWork.new('queue:default', msg))
89
- assert_equal %w(2 before 3 before 0 before work_performed 0 after 3 after 2 after), $recorder.flatten
89
+ assert_equal %w(2 before 3 before 1 before work_performed 1 after 3 after 2 after), $recorder.flatten
90
+ end
91
+
92
+ it 'correctly replaces middleware when using middleware with options in the initializer' do
93
+ chain = Sidekiq::Middleware::Chain.new
94
+ chain.add Sidekiq::Middleware::Server::RetryJobs
95
+ chain.add Sidekiq::Middleware::Server::RetryJobs, {:max_retries => 5}
96
+ assert_equal 1, chain.count
90
97
  end
91
98
 
92
99
  it 'allows middleware to abruptly stop processing rest of chain' do
@@ -10,6 +10,22 @@ class TestRedisConnection < Minitest::Test
10
10
  assert_equal Redis, pool.checkout.class
11
11
  end
12
12
 
13
+ describe "network_timeout" do
14
+ it "sets a custom network_timeout if specified" do
15
+ pool = Sidekiq::RedisConnection.create(:network_timeout => 8)
16
+ redis = pool.checkout
17
+
18
+ assert_equal 8, redis.client.timeout
19
+ end
20
+
21
+ it "uses the default network_timeout if none specified" do
22
+ pool = Sidekiq::RedisConnection.create
23
+ redis = pool.checkout
24
+
25
+ assert_equal 5, redis.client.timeout
26
+ end
27
+ end
28
+
13
29
  describe "namespace" do
14
30
  it "uses a given :namespace" do
15
31
  pool = Sidekiq::RedisConnection.create(:namespace => "xxx")
@@ -22,6 +38,20 @@ class TestRedisConnection < Minitest::Test
22
38
  assert_equal "yyy", pool.checkout.namespace
23
39
  end
24
40
  end
41
+
42
+ describe "pool_timeout" do
43
+ it "uses a given :timeout over the default of 1" do
44
+ pool = Sidekiq::RedisConnection.create(:pool_timeout => 5)
45
+
46
+ assert_equal 5, pool.instance_eval{ @timeout }
47
+ end
48
+
49
+ it "uses the default timeout of 1 if no override" do
50
+ pool = Sidekiq::RedisConnection.create
51
+
52
+ assert_equal 1, pool.instance_eval{ @timeout }
53
+ end
54
+ end
25
55
  end
26
56
 
27
57
  describe ".determine_redis_provider" do
@@ -45,6 +45,23 @@ class TestRetry < Minitest::Test
45
45
  @redis.verify
46
46
  end
47
47
 
48
+ it 'allows a max_retries option in initializer' do
49
+ max_retries = 7
50
+ 1.upto(max_retries) do
51
+ @redis.expect :zadd, 1, ['retry', String, String]
52
+ end
53
+ msg = { 'class' => 'Bob', 'args' => [1,2,'foo'], 'retry' => true }
54
+ handler = Sidekiq::Middleware::Server::RetryJobs.new({:max_retries => max_retries})
55
+ 1.upto(max_retries + 1) do
56
+ assert_raises RuntimeError do
57
+ handler.call(worker, msg, 'default') do
58
+ raise "kerblammo!"
59
+ end
60
+ end
61
+ end
62
+ @redis.verify
63
+ end
64
+
48
65
  it 'saves backtraces' do
49
66
  @redis.expect :zadd, 1, ['retry', String, String]
50
67
  msg = { 'class' => 'Bob', 'args' => [1,2,'foo'], 'retry' => true, 'backtrace' => true }
@@ -130,7 +130,7 @@ class TestWeb < Minitest::Test
130
130
 
131
131
  it 'can display a single retry' do
132
132
  params = add_retry
133
- get '/retries/2c4c17969825a384a92f023b'
133
+ get '/retries/0-shouldntexist'
134
134
  assert_equal 302, last_response.status
135
135
  get "/retries/#{job_params(*params)}"
136
136
  assert_equal 200, last_response.status
@@ -138,7 +138,7 @@ class TestWeb < Minitest::Test
138
138
  end
139
139
 
140
140
  it 'handles missing retry' do
141
- get "/retries/2c4c17969825a384a92f023b"
141
+ get "/retries/0-shouldntexist"
142
142
  assert_equal 302, last_response.status
143
143
  end
144
144
 
@@ -187,6 +187,42 @@ class TestWeb < Minitest::Test
187
187
  assert_match /HardWorker/, last_response.body
188
188
  end
189
189
 
190
+ it 'can display a single scheduled job' do
191
+ params = add_scheduled
192
+ get '/scheduled/0-shouldntexist'
193
+ assert_equal 302, last_response.status
194
+ get "/scheduled/#{job_params(*params)}"
195
+ assert_equal 200, last_response.status
196
+ assert_match /HardWorker/, last_response.body
197
+ end
198
+
199
+ it 'handles missing scheduled job' do
200
+ get "/scheduled/0-shouldntexist"
201
+ assert_equal 302, last_response.status
202
+ end
203
+
204
+ it 'can add to queue a single scheduled job' do
205
+ params = add_scheduled
206
+ post "/scheduled/#{job_params(*params)}", 'add_to_queue' => true
207
+ assert_equal 302, last_response.status
208
+ assert_equal 'http://example.org/scheduled', last_response.header['Location']
209
+
210
+ get '/queues/default'
211
+ assert_equal 200, last_response.status
212
+ assert_match /#{params.first['args'][2]}/, last_response.body
213
+ end
214
+
215
+ it 'can delete a single scheduled job' do
216
+ params = add_scheduled
217
+ post "/scheduled/#{job_params(*params)}", 'delete' => 'Delete'
218
+ assert_equal 302, last_response.status
219
+ assert_equal 'http://example.org/scheduled', last_response.header['Location']
220
+
221
+ get "/scheduled"
222
+ assert_equal 200, last_response.status
223
+ refute_match /#{params.first['args'][2]}/, last_response.body
224
+ end
225
+
190
226
  it 'can delete scheduled' do
191
227
  params = add_scheduled
192
228
  Sidekiq.redis do |conn|
@@ -199,14 +235,16 @@ class TestWeb < Minitest::Test
199
235
  end
200
236
 
201
237
  it "can move scheduled to default queue" do
238
+ q = Sidekiq::Queue.new
202
239
  params = add_scheduled
203
240
  Sidekiq.redis do |conn|
204
241
  assert_equal 1, conn.zcard('schedule')
205
- assert_equal 0, conn.zcard('default')
242
+ assert_equal 0, q.size
206
243
  post '/scheduled', 'key' => [job_params(*params)], 'add_to_queue' => 'AddToQueue'
207
244
  assert_equal 302, last_response.status
208
245
  assert_equal 'http://example.org/scheduled', last_response.header['Location']
209
246
  assert_equal 0, conn.zcard('schedule')
247
+ assert_equal 1, q.size
210
248
  get '/queues/default'
211
249
  assert_equal 200, last_response.status
212
250
  assert_match /#{params[0]['args'][2]}/, last_response.body