sr-sidekiq 4.1.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (186) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/3.0-Upgrade.md +70 -0
  4. data/4.0-Upgrade.md +50 -0
  5. data/COMM-LICENSE (sidekiq) +95 -0
  6. data/Changes.md +1241 -0
  7. data/Ent-Changes.md +112 -0
  8. data/Gemfile +29 -0
  9. data/LICENSE (sidekiq) +9 -0
  10. data/LICENSE (sr-sidekiq) +5 -0
  11. data/Pro-2.0-Upgrade.md +138 -0
  12. data/Pro-3.0-Upgrade.md +44 -0
  13. data/Pro-Changes.md +539 -0
  14. data/README.md +8 -0
  15. data/Rakefile +9 -0
  16. data/bin/sidekiq +18 -0
  17. data/bin/sidekiqctl +99 -0
  18. data/bin/sidekiqload +167 -0
  19. data/code_of_conduct.md +50 -0
  20. data/lib/generators/sidekiq/templates/worker.rb.erb +9 -0
  21. data/lib/generators/sidekiq/templates/worker_spec.rb.erb +6 -0
  22. data/lib/generators/sidekiq/templates/worker_test.rb.erb +8 -0
  23. data/lib/generators/sidekiq/worker_generator.rb +49 -0
  24. data/lib/sidekiq.rb +237 -0
  25. data/lib/sidekiq/api.rb +844 -0
  26. data/lib/sidekiq/cli.rb +389 -0
  27. data/lib/sidekiq/client.rb +260 -0
  28. data/lib/sidekiq/core_ext.rb +106 -0
  29. data/lib/sidekiq/exception_handler.rb +31 -0
  30. data/lib/sidekiq/extensions/action_mailer.rb +57 -0
  31. data/lib/sidekiq/extensions/active_record.rb +40 -0
  32. data/lib/sidekiq/extensions/class_methods.rb +40 -0
  33. data/lib/sidekiq/extensions/generic_proxy.rb +25 -0
  34. data/lib/sidekiq/fetch.rb +81 -0
  35. data/lib/sidekiq/launcher.rb +160 -0
  36. data/lib/sidekiq/logging.rb +106 -0
  37. data/lib/sidekiq/manager.rb +137 -0
  38. data/lib/sidekiq/middleware/chain.rb +150 -0
  39. data/lib/sidekiq/middleware/i18n.rb +42 -0
  40. data/lib/sidekiq/middleware/server/active_record.rb +13 -0
  41. data/lib/sidekiq/middleware/server/logging.rb +40 -0
  42. data/lib/sidekiq/middleware/server/retry_jobs.rb +205 -0
  43. data/lib/sidekiq/paginator.rb +43 -0
  44. data/lib/sidekiq/processor.rb +186 -0
  45. data/lib/sidekiq/rails.rb +39 -0
  46. data/lib/sidekiq/redis_connection.rb +97 -0
  47. data/lib/sidekiq/scheduled.rb +146 -0
  48. data/lib/sidekiq/testing.rb +316 -0
  49. data/lib/sidekiq/testing/inline.rb +29 -0
  50. data/lib/sidekiq/util.rb +62 -0
  51. data/lib/sidekiq/version.rb +4 -0
  52. data/lib/sidekiq/web.rb +278 -0
  53. data/lib/sidekiq/web_helpers.rb +255 -0
  54. data/lib/sidekiq/worker.rb +121 -0
  55. data/sidekiq.gemspec +26 -0
  56. data/sr-sidekiq-4.1.3.gem +0 -0
  57. data/sr-sidekiq-4.1.4.gem +0 -0
  58. data/sr-sidekiq-4.1.5.gem +0 -0
  59. data/test/config.yml +9 -0
  60. data/test/env_based_config.yml +11 -0
  61. data/test/fake_env.rb +1 -0
  62. data/test/fixtures/en.yml +2 -0
  63. data/test/helper.rb +75 -0
  64. data/test/test_actors.rb +138 -0
  65. data/test/test_api.rb +528 -0
  66. data/test/test_cli.rb +406 -0
  67. data/test/test_client.rb +262 -0
  68. data/test/test_exception_handler.rb +56 -0
  69. data/test/test_extensions.rb +127 -0
  70. data/test/test_fetch.rb +50 -0
  71. data/test/test_launcher.rb +85 -0
  72. data/test/test_logging.rb +35 -0
  73. data/test/test_manager.rb +50 -0
  74. data/test/test_middleware.rb +158 -0
  75. data/test/test_processor.rb +201 -0
  76. data/test/test_rails.rb +22 -0
  77. data/test/test_redis_connection.rb +127 -0
  78. data/test/test_retry.rb +326 -0
  79. data/test/test_retry_exhausted.rb +149 -0
  80. data/test/test_scheduled.rb +115 -0
  81. data/test/test_scheduling.rb +50 -0
  82. data/test/test_sidekiq.rb +107 -0
  83. data/test/test_testing.rb +143 -0
  84. data/test/test_testing_fake.rb +357 -0
  85. data/test/test_testing_inline.rb +94 -0
  86. data/test/test_util.rb +13 -0
  87. data/test/test_web.rb +614 -0
  88. data/test/test_web_helpers.rb +54 -0
  89. data/web/assets/images/bootstrap/glyphicons-halflings-white.png +0 -0
  90. data/web/assets/images/bootstrap/glyphicons-halflings.png +0 -0
  91. data/web/assets/images/favicon.ico +0 -0
  92. data/web/assets/images/logo.png +0 -0
  93. data/web/assets/images/status-sd8051fd480.png +0 -0
  94. data/web/assets/images/status/active.png +0 -0
  95. data/web/assets/images/status/idle.png +0 -0
  96. data/web/assets/javascripts/application.js +88 -0
  97. data/web/assets/javascripts/dashboard.js +300 -0
  98. data/web/assets/javascripts/locales/README.md +27 -0
  99. data/web/assets/javascripts/locales/jquery.timeago.ar.js +96 -0
  100. data/web/assets/javascripts/locales/jquery.timeago.bg.js +18 -0
  101. data/web/assets/javascripts/locales/jquery.timeago.bs.js +49 -0
  102. data/web/assets/javascripts/locales/jquery.timeago.ca.js +18 -0
  103. data/web/assets/javascripts/locales/jquery.timeago.cs.js +18 -0
  104. data/web/assets/javascripts/locales/jquery.timeago.cy.js +20 -0
  105. data/web/assets/javascripts/locales/jquery.timeago.da.js +18 -0
  106. data/web/assets/javascripts/locales/jquery.timeago.de.js +18 -0
  107. data/web/assets/javascripts/locales/jquery.timeago.el.js +18 -0
  108. data/web/assets/javascripts/locales/jquery.timeago.en-short.js +20 -0
  109. data/web/assets/javascripts/locales/jquery.timeago.en.js +20 -0
  110. data/web/assets/javascripts/locales/jquery.timeago.es.js +18 -0
  111. data/web/assets/javascripts/locales/jquery.timeago.et.js +18 -0
  112. data/web/assets/javascripts/locales/jquery.timeago.fa.js +22 -0
  113. data/web/assets/javascripts/locales/jquery.timeago.fi.js +28 -0
  114. data/web/assets/javascripts/locales/jquery.timeago.fr-short.js +16 -0
  115. data/web/assets/javascripts/locales/jquery.timeago.fr.js +17 -0
  116. data/web/assets/javascripts/locales/jquery.timeago.he.js +18 -0
  117. data/web/assets/javascripts/locales/jquery.timeago.hr.js +49 -0
  118. data/web/assets/javascripts/locales/jquery.timeago.hu.js +18 -0
  119. data/web/assets/javascripts/locales/jquery.timeago.hy.js +18 -0
  120. data/web/assets/javascripts/locales/jquery.timeago.id.js +18 -0
  121. data/web/assets/javascripts/locales/jquery.timeago.it.js +16 -0
  122. data/web/assets/javascripts/locales/jquery.timeago.ja.js +19 -0
  123. data/web/assets/javascripts/locales/jquery.timeago.ko.js +17 -0
  124. data/web/assets/javascripts/locales/jquery.timeago.lt.js +20 -0
  125. data/web/assets/javascripts/locales/jquery.timeago.mk.js +20 -0
  126. data/web/assets/javascripts/locales/jquery.timeago.nl.js +20 -0
  127. data/web/assets/javascripts/locales/jquery.timeago.no.js +18 -0
  128. data/web/assets/javascripts/locales/jquery.timeago.pl.js +31 -0
  129. data/web/assets/javascripts/locales/jquery.timeago.pt-br.js +16 -0
  130. data/web/assets/javascripts/locales/jquery.timeago.pt.js +16 -0
  131. data/web/assets/javascripts/locales/jquery.timeago.ro.js +18 -0
  132. data/web/assets/javascripts/locales/jquery.timeago.rs.js +49 -0
  133. data/web/assets/javascripts/locales/jquery.timeago.ru.js +34 -0
  134. data/web/assets/javascripts/locales/jquery.timeago.sk.js +18 -0
  135. data/web/assets/javascripts/locales/jquery.timeago.sl.js +44 -0
  136. data/web/assets/javascripts/locales/jquery.timeago.sv.js +18 -0
  137. data/web/assets/javascripts/locales/jquery.timeago.th.js +20 -0
  138. data/web/assets/javascripts/locales/jquery.timeago.tr.js +16 -0
  139. data/web/assets/javascripts/locales/jquery.timeago.uk.js +34 -0
  140. data/web/assets/javascripts/locales/jquery.timeago.uz.js +19 -0
  141. data/web/assets/javascripts/locales/jquery.timeago.zh-cn.js +20 -0
  142. data/web/assets/javascripts/locales/jquery.timeago.zh-tw.js +20 -0
  143. data/web/assets/stylesheets/application.css +754 -0
  144. data/web/assets/stylesheets/bootstrap.css +9 -0
  145. data/web/locales/cs.yml +78 -0
  146. data/web/locales/da.yml +68 -0
  147. data/web/locales/de.yml +69 -0
  148. data/web/locales/el.yml +68 -0
  149. data/web/locales/en.yml +79 -0
  150. data/web/locales/es.yml +69 -0
  151. data/web/locales/fr.yml +78 -0
  152. data/web/locales/hi.yml +75 -0
  153. data/web/locales/it.yml +69 -0
  154. data/web/locales/ja.yml +78 -0
  155. data/web/locales/ko.yml +68 -0
  156. data/web/locales/nb.yml +77 -0
  157. data/web/locales/nl.yml +68 -0
  158. data/web/locales/pl.yml +59 -0
  159. data/web/locales/pt-br.yml +68 -0
  160. data/web/locales/pt.yml +67 -0
  161. data/web/locales/ru.yml +78 -0
  162. data/web/locales/sv.yml +68 -0
  163. data/web/locales/ta.yml +75 -0
  164. data/web/locales/uk.yml +76 -0
  165. data/web/locales/zh-cn.yml +68 -0
  166. data/web/locales/zh-tw.yml +68 -0
  167. data/web/views/_footer.erb +17 -0
  168. data/web/views/_job_info.erb +88 -0
  169. data/web/views/_nav.erb +66 -0
  170. data/web/views/_paging.erb +23 -0
  171. data/web/views/_poll_js.erb +5 -0
  172. data/web/views/_poll_link.erb +7 -0
  173. data/web/views/_status.erb +4 -0
  174. data/web/views/_summary.erb +40 -0
  175. data/web/views/busy.erb +94 -0
  176. data/web/views/dashboard.erb +75 -0
  177. data/web/views/dead.erb +34 -0
  178. data/web/views/layout.erb +32 -0
  179. data/web/views/morgue.erb +71 -0
  180. data/web/views/queue.erb +45 -0
  181. data/web/views/queues.erb +28 -0
  182. data/web/views/retries.erb +74 -0
  183. data/web/views/retry.erb +34 -0
  184. data/web/views/scheduled.erb +54 -0
  185. data/web/views/scheduled_job_info.erb +8 -0
  186. metadata +408 -0
data/test/test_util.rb ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'helper'
3
+
4
+ class TestUtil < Sidekiq::Test
5
+
6
+ class Helpers
7
+ include Sidekiq::Util
8
+ end
9
+
10
+ def test_nothing_atm
11
+ assert true
12
+ end
13
+ end
data/test/test_web.rb ADDED
@@ -0,0 +1,614 @@
1
+ # frozen_string_literal: true
2
+ # encoding: utf-8
3
+ require_relative 'helper'
4
+ require 'sidekiq/web'
5
+ require 'rack/test'
6
+ require 'tilt/erubis'
7
+
8
+ class TestWeb < Sidekiq::Test
9
+
10
+ describe 'sidekiq web' do
11
+ include Rack::Test::Methods
12
+
13
+ def app
14
+ Sidekiq::Web
15
+ end
16
+
17
+ def job_params(job, score)
18
+ "#{score}-#{job['jid']}"
19
+ end
20
+
21
+ before do
22
+ Sidekiq.redis {|c| c.flushdb }
23
+ end
24
+
25
+ class WebWorker
26
+ include Sidekiq::Worker
27
+
28
+ def perform(a, b)
29
+ a + b
30
+ end
31
+ end
32
+
33
+ it 'can show text with any locales' do
34
+ rackenv = {'HTTP_ACCEPT_LANGUAGE' => 'ru,en'}
35
+ get '/', {}, rackenv
36
+ assert_match(/Панель управления/, last_response.body)
37
+ rackenv = {'HTTP_ACCEPT_LANGUAGE' => 'es,en'}
38
+ get '/', {}, rackenv
39
+ assert_match(/Panel de Control/, last_response.body)
40
+ rackenv = {'HTTP_ACCEPT_LANGUAGE' => 'en-us'}
41
+ get '/', {}, rackenv
42
+ assert_match(/Dashboard/, last_response.body)
43
+ rackenv = {'HTTP_ACCEPT_LANGUAGE' => 'zh-cn'}
44
+ get '/', {}, rackenv
45
+ assert_match(/信息板/, last_response.body)
46
+ rackenv = {'HTTP_ACCEPT_LANGUAGE' => 'zh-tw'}
47
+ get '/', {}, rackenv
48
+ assert_match(/資訊主頁/, last_response.body)
49
+ rackenv = {'HTTP_ACCEPT_LANGUAGE' => 'nb'}
50
+ get '/', {}, rackenv
51
+ assert_match(/Oversikt/, last_response.body)
52
+ end
53
+
54
+ describe 'busy' do
55
+
56
+ it 'can display workers' do
57
+ Sidekiq.redis do |conn|
58
+ conn.incr('busy')
59
+ conn.sadd('processes', 'foo:1234')
60
+ conn.hmset('foo:1234', 'info', Sidekiq.dump_json('hostname' => 'foo', 'started_at' => Time.now.to_f, "queues" => []), 'at', Time.now.to_f, 'busy', 4)
61
+ identity = 'foo:1234:workers'
62
+ hash = {:queue => 'critical', :payload => { 'class' => WebWorker.name, 'args' => [1,'abc'] }, :run_at => Time.now.to_i }
63
+ conn.hmset(identity, 1001, Sidekiq.dump_json(hash))
64
+ end
65
+ assert_equal ['1001'], Sidekiq::Workers.new.map { |pid, tid, data| tid }
66
+
67
+ get '/busy'
68
+ assert_equal 200, last_response.status
69
+ assert_match(/status-active/, last_response.body)
70
+ assert_match(/critical/, last_response.body)
71
+ assert_match(/WebWorker/, last_response.body)
72
+ end
73
+
74
+ it 'can quiet a process' do
75
+ identity = 'identity'
76
+ signals_key = "#{identity}-signals"
77
+
78
+ assert_nil Sidekiq.redis { |c| c.lpop signals_key }
79
+ post '/busy', 'quiet' => '1', 'identity' => identity
80
+ assert_equal 302, last_response.status
81
+ assert_equal 'USR1', Sidekiq.redis { |c| c.lpop signals_key }
82
+ end
83
+
84
+ it 'can stop a process' do
85
+ identity = 'identity'
86
+ signals_key = "#{identity}-signals"
87
+
88
+ assert_nil Sidekiq.redis { |c| c.lpop signals_key }
89
+ post '/busy', 'stop' => '1', 'identity' => identity
90
+ assert_equal 302, last_response.status
91
+ assert_equal 'TERM', Sidekiq.redis { |c| c.lpop signals_key }
92
+ end
93
+ end
94
+
95
+ it 'can display queues' do
96
+ assert Sidekiq::Client.push('queue' => :foo, 'class' => WebWorker, 'args' => [1, 3])
97
+
98
+ get '/queues'
99
+ assert_equal 200, last_response.status
100
+ assert_match(/foo/, last_response.body)
101
+ refute_match(/HardWorker/, last_response.body)
102
+ end
103
+
104
+ it 'handles queue view' do
105
+ get '/queues/default'
106
+ assert_equal 200, last_response.status
107
+ end
108
+
109
+ it 'can delete a queue' do
110
+ Sidekiq.redis do |conn|
111
+ conn.rpush('queue:foo', '{}')
112
+ conn.sadd('queues', 'foo')
113
+ end
114
+
115
+ get '/queues/foo'
116
+ assert_equal 200, last_response.status
117
+
118
+ post '/queues/foo'
119
+ assert_equal 302, last_response.status
120
+
121
+ Sidekiq.redis do |conn|
122
+ refute conn.smembers('queues').include?('foo')
123
+ refute conn.exists('queue:foo')
124
+ end
125
+ end
126
+
127
+ it 'can delete a job' do
128
+ Sidekiq.redis do |conn|
129
+ conn.rpush('queue:foo', "{}")
130
+ conn.rpush('queue:foo', "{\"foo\":\"bar\"}")
131
+ conn.rpush('queue:foo', "{\"foo2\":\"bar2\"}")
132
+ end
133
+
134
+ get '/queues/foo'
135
+ assert_equal 200, last_response.status
136
+
137
+ post '/queues/foo/delete', key_val: "{\"foo\":\"bar\"}"
138
+ assert_equal 302, last_response.status
139
+
140
+ Sidekiq.redis do |conn|
141
+ refute conn.lrange('queue:foo', 0, -1).include?("{\"foo\":\"bar\"}")
142
+ end
143
+ end
144
+
145
+ it 'can display retries' do
146
+ get '/retries'
147
+ assert_equal 200, last_response.status
148
+ assert_match(/found/, last_response.body)
149
+ refute_match(/HardWorker/, last_response.body)
150
+
151
+ add_retry
152
+
153
+ get '/retries'
154
+ assert_equal 200, last_response.status
155
+ refute_match(/found/, last_response.body)
156
+ assert_match(/HardWorker/, last_response.body)
157
+ end
158
+
159
+ it 'can display a single retry' do
160
+ params = add_retry
161
+ get '/retries/0-shouldntexist'
162
+ assert_equal 302, last_response.status
163
+ get "/retries/#{job_params(*params)}"
164
+ assert_equal 200, last_response.status
165
+ assert_match(/HardWorker/, last_response.body)
166
+ end
167
+
168
+ it 'handles missing retry' do
169
+ get "/retries/0-shouldntexist"
170
+ assert_equal 302, last_response.status
171
+ end
172
+
173
+ it 'can delete a single retry' do
174
+ params = add_retry
175
+ post "/retries/#{job_params(*params)}", 'delete' => 'Delete'
176
+ assert_equal 302, last_response.status
177
+ assert_equal 'http://example.org/retries', last_response.header['Location']
178
+
179
+ get "/retries"
180
+ assert_equal 200, last_response.status
181
+ refute_match(/#{params.first['args'][2]}/, last_response.body)
182
+ end
183
+
184
+ it 'can delete all retries' do
185
+ 3.times { add_retry }
186
+
187
+ post "/retries/all/delete", 'delete' => 'Delete'
188
+ assert_equal 0, Sidekiq::RetrySet.new.size
189
+ assert_equal 302, last_response.status
190
+ assert_equal 'http://example.org/retries', last_response.header['Location']
191
+ end
192
+
193
+ it 'can retry a single retry now' do
194
+ params = add_retry
195
+ post "/retries/#{job_params(*params)}", 'retry' => 'Retry'
196
+ assert_equal 302, last_response.status
197
+ assert_equal 'http://example.org/retries', last_response.header['Location']
198
+
199
+ get '/queues/default'
200
+ assert_equal 200, last_response.status
201
+ assert_match(/#{params.first['args'][2]}/, last_response.body)
202
+ end
203
+
204
+ it 'can kill a single retry now' do
205
+ params = add_retry
206
+ post "/retries/#{job_params(*params)}", 'kill' => 'Kill'
207
+ assert_equal 302, last_response.status
208
+ assert_equal 'http://example.org/retries', last_response.header['Location']
209
+
210
+ get '/morgue'
211
+ assert_equal 200, last_response.status
212
+ assert_match(/#{params.first['args'][2]}/, last_response.body)
213
+ end
214
+
215
+ it 'can display scheduled' do
216
+ get '/scheduled'
217
+ assert_equal 200, last_response.status
218
+ assert_match(/found/, last_response.body)
219
+ refute_match(/HardWorker/, last_response.body)
220
+
221
+ add_scheduled
222
+
223
+ get '/scheduled'
224
+ assert_equal 200, last_response.status
225
+ refute_match(/found/, last_response.body)
226
+ assert_match(/HardWorker/, last_response.body)
227
+ end
228
+
229
+ it 'can display a single scheduled job' do
230
+ params = add_scheduled
231
+ get '/scheduled/0-shouldntexist'
232
+ assert_equal 302, last_response.status
233
+ get "/scheduled/#{job_params(*params)}"
234
+ assert_equal 200, last_response.status
235
+ assert_match(/HardWorker/, last_response.body)
236
+ end
237
+
238
+ it 'handles missing scheduled job' do
239
+ get "/scheduled/0-shouldntexist"
240
+ assert_equal 302, last_response.status
241
+ end
242
+
243
+ it 'can add to queue a single scheduled job' do
244
+ params = add_scheduled
245
+ post "/scheduled/#{job_params(*params)}", 'add_to_queue' => true
246
+ assert_equal 302, last_response.status
247
+ assert_equal 'http://example.org/scheduled', last_response.header['Location']
248
+
249
+ get '/queues/default'
250
+ assert_equal 200, last_response.status
251
+ assert_match(/#{params.first['args'][2]}/, last_response.body)
252
+ end
253
+
254
+ it 'can delete a single scheduled job' do
255
+ params = add_scheduled
256
+ post "/scheduled/#{job_params(*params)}", 'delete' => 'Delete'
257
+ assert_equal 302, last_response.status
258
+ assert_equal 'http://example.org/scheduled', last_response.header['Location']
259
+
260
+ get "/scheduled"
261
+ assert_equal 200, last_response.status
262
+ refute_match(/#{params.first['args'][2]}/, last_response.body)
263
+ end
264
+
265
+ it 'can delete scheduled' do
266
+ params = add_scheduled
267
+ Sidekiq.redis do |conn|
268
+ assert_equal 1, conn.zcard('schedule')
269
+ post '/scheduled', 'key' => [job_params(*params)], 'delete' => 'Delete'
270
+ assert_equal 302, last_response.status
271
+ assert_equal 'http://example.org/scheduled', last_response.header['Location']
272
+ assert_equal 0, conn.zcard('schedule')
273
+ end
274
+ end
275
+
276
+ it "can move scheduled to default queue" do
277
+ q = Sidekiq::Queue.new
278
+ params = add_scheduled
279
+ Sidekiq.redis do |conn|
280
+ assert_equal 1, conn.zcard('schedule')
281
+ assert_equal 0, q.size
282
+ post '/scheduled', 'key' => [job_params(*params)], 'add_to_queue' => 'AddToQueue'
283
+ assert_equal 302, last_response.status
284
+ assert_equal 'http://example.org/scheduled', last_response.header['Location']
285
+ assert_equal 0, conn.zcard('schedule')
286
+ assert_equal 1, q.size
287
+ get '/queues/default'
288
+ assert_equal 200, last_response.status
289
+ assert_match(/#{params[0]['args'][2]}/, last_response.body)
290
+ end
291
+ end
292
+
293
+ it 'can retry all retries' do
294
+ msg = add_retry.first
295
+ add_retry
296
+
297
+ post "/retries/all/retry", 'retry' => 'Retry'
298
+ assert_equal 302, last_response.status
299
+ assert_equal 'http://example.org/retries', last_response.header['Location']
300
+ assert_equal 2, Sidekiq::Queue.new("default").size
301
+
302
+ get '/queues/default'
303
+ assert_equal 200, last_response.status
304
+ assert_match(/#{msg['args'][2]}/, last_response.body)
305
+ end
306
+
307
+ it 'calls updatePage() once when polling' do
308
+ get '/busy?poll=true'
309
+ assert_equal 200, last_response.status
310
+ assert_equal 1, last_response.body.scan('updatePage(').count
311
+ end
312
+
313
+ it 'escape job args and error messages' do
314
+ # on /retries page
315
+ params = add_xss_retry
316
+ get '/retries'
317
+ assert_equal 200, last_response.status
318
+ assert_match(/FailWorker/, last_response.body)
319
+
320
+ assert last_response.body.include?( "fail message: &lt;a&gt;hello&lt;&#x2F;a&gt;" )
321
+ assert !last_response.body.include?( "fail message: <a>hello</a>" )
322
+
323
+ assert last_response.body.include?( "args\">&quot;&lt;a&gt;hello&lt;&#x2F;a&gt;&quot;<" )
324
+ assert !last_response.body.include?( "args\"><a>hello</a><" )
325
+
326
+ # on /workers page
327
+ Sidekiq.redis do |conn|
328
+ pro = 'foo:1234'
329
+ conn.sadd('processes', pro)
330
+ conn.hmset(pro, 'info', Sidekiq.dump_json('started_at' => Time.now.to_f, 'labels' => ['frumduz'], 'queues' =>[]), 'busy', 1, 'beat', Time.now.to_f)
331
+ identity = "#{pro}:workers"
332
+ hash = {:queue => 'critical', :payload => { 'class' => "FailWorker", 'args' => ["<a>hello</a>"] }, :run_at => Time.now.to_i }
333
+ conn.hmset(identity, 100001, Sidekiq.dump_json(hash))
334
+ conn.incr('busy')
335
+ end
336
+
337
+ get '/busy'
338
+ assert_equal 200, last_response.status
339
+ assert_match(/FailWorker/, last_response.body)
340
+ assert_match(/frumduz/, last_response.body)
341
+ assert last_response.body.include?( "&lt;a&gt;hello&lt;&#x2F;a&gt;" )
342
+ assert !last_response.body.include?( "<a>hello</a>" )
343
+
344
+
345
+ # on /queues page
346
+ params = add_xss_retry # sorry, don't know how to easily make this show up on queues page otherwise.
347
+ post "/retries/#{job_params(*params)}", 'retry' => 'Retry'
348
+ assert_equal 302, last_response.status
349
+
350
+ get '/queues/foo'
351
+ assert_equal 200, last_response.status
352
+ assert last_response.body.include?( "&lt;a&gt;hello&lt;&#x2F;a&gt;" )
353
+ assert !last_response.body.include?( "<a>hello</a>" )
354
+ end
355
+
356
+ it 'can show user defined tab' do
357
+ begin
358
+ Sidekiq::Web.tabs['Custom Tab'] = '/custom'
359
+
360
+ get '/'
361
+ assert_match 'Custom Tab', last_response.body
362
+
363
+ ensure
364
+ Sidekiq::Web.tabs.delete 'Custom Tab'
365
+ end
366
+ end
367
+
368
+ it 'can display home' do
369
+ get '/'
370
+ assert_equal 200, last_response.status
371
+ end
372
+
373
+ describe 'custom locales' do
374
+ before do
375
+ Sidekiq::Web.settings.locales << File.join(File.dirname(__FILE__), "fixtures")
376
+ Sidekiq::Web.tabs['Custom Tab'] = '/custom'
377
+ Sidekiq::Web.get('/custom') do
378
+ clear_caches # ugly hack since I can't figure out how to access WebHelpers outside of this context
379
+ t('translated_text')
380
+ end
381
+ end
382
+
383
+ after do
384
+ Sidekiq::Web.tabs.delete 'Custom Tab'
385
+ Sidekiq::Web.settings.locales.pop
386
+ end
387
+
388
+ it 'can show user defined tab with custom locales' do
389
+ get '/custom'
390
+ assert_match(/Changed text/, last_response.body)
391
+ end
392
+ end
393
+
394
+ describe 'dashboard/stats' do
395
+ it 'redirects to stats' do
396
+ get '/dashboard/stats'
397
+ assert_equal 302, last_response.status
398
+ assert_equal 'http://example.org/stats', last_response.header['Location']
399
+ end
400
+ end
401
+
402
+ describe 'stats' do
403
+ include Sidekiq::Util
404
+
405
+ before do
406
+ Sidekiq.redis do |conn|
407
+ conn.set("stat:processed", 5)
408
+ conn.set("stat:failed", 2)
409
+ conn.sadd("queues", "default")
410
+ end
411
+ 2.times { add_retry }
412
+ 3.times { add_scheduled }
413
+ 4.times { add_worker }
414
+
415
+ get '/stats'
416
+ @response = Sidekiq.load_json(last_response.body)
417
+ end
418
+
419
+ it 'can refresh dashboard stats' do
420
+ assert_equal 200, last_response.status
421
+ end
422
+
423
+ describe "for sidekiq" do
424
+ it 'are namespaced' do
425
+ assert_includes @response.keys, "sidekiq"
426
+ end
427
+
428
+ it 'reports processed' do
429
+ assert_equal 5, @response["sidekiq"]["processed"]
430
+ end
431
+
432
+ it 'reports failed' do
433
+ assert_equal 2, @response["sidekiq"]["failed"]
434
+ end
435
+
436
+ it 'reports busy' do
437
+ assert_equal 4, @response["sidekiq"]["busy"]
438
+ end
439
+
440
+ it 'reports processes' do
441
+ assert_equal 1, @response["sidekiq"]["processes"]
442
+ end
443
+
444
+ it 'reports retries' do
445
+ assert_equal 2, @response["sidekiq"]["retries"]
446
+ end
447
+
448
+ it 'reports scheduled' do
449
+ assert_equal 3, @response["sidekiq"]["scheduled"]
450
+ end
451
+
452
+ it 'reports latency' do
453
+ assert_equal 0, @response["sidekiq"]["default_latency"]
454
+ end
455
+ end
456
+
457
+ describe "for redis" do
458
+ it 'are namespaced' do
459
+ assert_includes @response.keys, "redis"
460
+ end
461
+
462
+ it 'reports version' do
463
+ assert_includes @response["redis"].keys, "redis_version"
464
+ end
465
+
466
+ it 'reports uptime' do
467
+ assert_includes @response["redis"].keys, "uptime_in_days"
468
+ end
469
+
470
+ it 'reports connected clients' do
471
+ assert_includes @response["redis"].keys, "connected_clients"
472
+ end
473
+
474
+ it 'reports user memory' do
475
+ assert_includes @response["redis"].keys, "used_memory_human"
476
+ end
477
+
478
+ it 'reports memory peak' do
479
+ assert_includes @response["redis"].keys, "used_memory_peak_human"
480
+ end
481
+ end
482
+ end
483
+
484
+ describe 'stats/queues' do
485
+ include Sidekiq::Util
486
+
487
+ before do
488
+ Sidekiq.redis do |conn|
489
+ conn.set("stat:processed", 5)
490
+ conn.set("stat:failed", 2)
491
+ conn.sadd("queues", "default")
492
+ conn.sadd("queues", "queue2")
493
+ end
494
+ 2.times { add_retry }
495
+ 3.times { add_scheduled }
496
+ 4.times { add_worker }
497
+
498
+ get '/stats/queues'
499
+ @response = Sidekiq.load_json(last_response.body)
500
+ end
501
+
502
+ it 'reports the queue depth' do
503
+ assert_equal 0, @response["default"]
504
+ assert_equal 0, @response["queue2"]
505
+ end
506
+ end
507
+
508
+ describe 'dead jobs' do
509
+ it 'shows empty index' do
510
+ get 'morgue'
511
+ assert_equal 200, last_response.status
512
+ end
513
+
514
+ it 'shows index with jobs' do
515
+ (_, score) = add_dead
516
+ get 'morgue'
517
+ assert_equal 200, last_response.status
518
+ assert_match(/#{score}/, last_response.body)
519
+ end
520
+
521
+ it 'can delete all dead' do
522
+ 3.times { add_dead }
523
+
524
+ assert_equal 3, Sidekiq::DeadSet.new.size
525
+ post "/morgue/all/delete", 'delete' => 'Delete'
526
+ assert_equal 0, Sidekiq::DeadSet.new.size
527
+ assert_equal 302, last_response.status
528
+ assert_equal 'http://example.org/morgue', last_response.header['Location']
529
+ end
530
+
531
+ it 'can retry a dead job' do
532
+ params = add_dead
533
+ post "/morgue/#{job_params(*params)}", 'retry' => 'Retry'
534
+ assert_equal 302, last_response.status
535
+ assert_equal 'http://example.org/morgue', last_response.header['Location']
536
+
537
+ get '/queues/foo'
538
+ assert_equal 200, last_response.status
539
+ assert_match(/#{params.first['args'][2]}/, last_response.body)
540
+ end
541
+ end
542
+
543
+ def add_scheduled
544
+ score = Time.now.to_f
545
+ msg = { 'class' => 'HardWorker',
546
+ 'args' => ['bob', 1, Time.now.to_f],
547
+ 'jid' => SecureRandom.hex(12) }
548
+ Sidekiq.redis do |conn|
549
+ conn.zadd('schedule', score, Sidekiq.dump_json(msg))
550
+ end
551
+ [msg, score]
552
+ end
553
+
554
+ def add_retry
555
+ msg = { 'class' => 'HardWorker',
556
+ 'args' => ['bob', 1, Time.now.to_f],
557
+ 'queue' => 'default',
558
+ 'error_message' => 'Some fake message',
559
+ 'error_class' => 'RuntimeError',
560
+ 'retry_count' => 0,
561
+ 'failed_at' => Time.now.to_f,
562
+ 'jid' => SecureRandom.hex(12) }
563
+ score = Time.now.to_f
564
+ Sidekiq.redis do |conn|
565
+ conn.zadd('retry', score, Sidekiq.dump_json(msg))
566
+ end
567
+ [msg, score]
568
+ end
569
+
570
+ def add_dead
571
+ msg = { 'class' => 'HardWorker',
572
+ 'args' => ['bob', 1, Time.now.to_f],
573
+ 'queue' => 'foo',
574
+ 'error_message' => 'Some fake message',
575
+ 'error_class' => 'RuntimeError',
576
+ 'retry_count' => 0,
577
+ 'failed_at' => Time.now.utc,
578
+ 'jid' => SecureRandom.hex(12) }
579
+ score = Time.now.to_f
580
+ Sidekiq.redis do |conn|
581
+ conn.zadd('dead', score, Sidekiq.dump_json(msg))
582
+ end
583
+ [msg, score]
584
+ end
585
+
586
+ def add_xss_retry
587
+ msg = { 'class' => 'FailWorker',
588
+ 'args' => ['<a>hello</a>'],
589
+ 'queue' => 'foo',
590
+ 'error_message' => 'fail message: <a>hello</a>',
591
+ 'error_class' => 'RuntimeError',
592
+ 'retry_count' => 0,
593
+ 'failed_at' => Time.now.to_f,
594
+ 'jid' => SecureRandom.hex(12) }
595
+ score = Time.now.to_f
596
+ Sidekiq.redis do |conn|
597
+ conn.zadd('retry', score, Sidekiq.dump_json(msg))
598
+ end
599
+ [msg, score]
600
+ end
601
+
602
+ def add_worker
603
+ key = "#{hostname}:#{$$}"
604
+ msg = "{\"queue\":\"default\",\"payload\":{\"retry\":true,\"queue\":\"default\",\"timeout\":20,\"backtrace\":5,\"class\":\"HardWorker\",\"args\":[\"bob\",10,5],\"jid\":\"2b5ad2b016f5e063a1c62872\"},\"run_at\":1361208995}"
605
+ Sidekiq.redis do |conn|
606
+ conn.multi do
607
+ conn.sadd("processes", key)
608
+ conn.hmset(key, 'info', Sidekiq.dump_json('hostname' => 'foo', 'started_at' => Time.now.to_f, "queues" => []), 'at', Time.now.to_f, 'busy', 4)
609
+ conn.hmset("#{key}:workers", Time.now.to_f, msg)
610
+ end
611
+ end
612
+ end
613
+ end
614
+ end