sidekiq-failures 0.4.5 → 1.1.0

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.
@@ -5,192 +5,247 @@ module Sidekiq
5
5
  describe "WebExtension" do
6
6
  include Rack::Test::Methods
7
7
 
8
+ TOKEN = SecureRandom.base64(defined?(Sidekiq::Web::TOKEN_LENGTH) ? Sidekiq::Web::TOKEN_LENGTH : 32).freeze
9
+
8
10
  def app
9
11
  Sidekiq::Web
10
12
  end
11
13
 
12
14
  before do
13
- Sidekiq.redis = REDIS
14
- Sidekiq.redis {|c| c.flushdb }
15
+ env 'rack.session', { csrf: TOKEN }
16
+ env 'HTTP_X_CSRF_TOKEN', TOKEN
17
+ Sidekiq.redis(&:flushdb)
15
18
  end
16
19
 
17
20
  it 'can display home with failures tab' do
18
21
  get '/'
19
22
 
20
- last_response.status.must_equal 200
21
- last_response.body.must_match /Sidekiq/
22
- last_response.body.must_match /Failures/
23
+ _(last_response.status).must_equal 200
24
+ _(last_response.body).must_match(/Sidekiq/)
25
+ _(last_response.body).must_match(/Failures/)
23
26
  end
24
27
 
25
28
  it 'can display failures page without any failures' do
26
29
  get '/failures'
27
- last_response.status.must_equal 200
28
- last_response.body.must_match /Failed Jobs/
29
- last_response.body.must_match /No failed jobs found/
30
+ _(last_response.status).must_equal 200
31
+ _(last_response.body).must_match(/Failed Jobs/)
32
+ _(last_response.body).must_match(/No failed jobs found/)
30
33
  end
31
34
 
32
35
  it 'has the reset counter form and action' do
33
36
  get '/failures'
34
- last_response.body.must_match /failures\/all\/reset/
35
- last_response.body.must_match /Reset Counter/
37
+ _(last_response.body).must_match(/failures\/all\/reset/)
38
+ _(last_response.body).must_match(/Reset Counter/)
36
39
  end
37
40
 
38
41
  describe 'when there are failures' do
39
42
  before do
40
- create_sample_failure
43
+ @failure = create_sample_failure
41
44
  get '/failures'
42
45
  end
43
46
 
44
47
  it 'should be successful' do
45
- last_response.status.must_equal 200
48
+ _(last_response.status).must_equal 200
46
49
  end
47
50
 
48
51
  it 'can display failures page with failures listed' do
49
- last_response.body.must_match /Failed Jobs/
50
- last_response.body.must_match /HardWorker/
51
- last_response.body.must_match /ArgumentError/
52
- last_response.body.wont_match /No failed jobs found/
52
+ _(last_response.body).must_match(/Failed Jobs/)
53
+ _(last_response.body).must_match(/HardWorker/)
54
+ _(last_response.body).must_match(/ArgumentError/)
53
55
  end
54
56
 
55
57
  it 'can reset counter' do
56
58
  assert_equal failed_count, "1"
57
59
 
58
- last_response.body.must_match /HardWorker/
60
+ _(last_response.body).must_match(/HardWorker/)
61
+ post '/failures/all/reset', { authenticity_token: TOKEN }
59
62
 
60
- post '/failures/all/reset'
61
- last_response.status.must_equal 302
62
- last_response.location.must_match /failures$/
63
+ _(last_response.status).must_equal 302
64
+ _(last_response.location).must_match(/failures$/)
63
65
 
64
66
  get '/failures'
65
- last_response.status.must_equal 200
66
- last_response.body.must_match /HardWorker/
67
+ _(last_response.status).must_equal 200
68
+ _(last_response.body).must_match(/HardWorker/)
67
69
 
68
70
  assert_equal failed_count, "0"
69
71
  end
70
72
 
71
73
  it 'has the delete all form and action' do
72
- last_response.body.must_match /failures\/all\/delete/
73
- last_response.body.must_match /Delete All/
74
+ _(last_response.body).must_match(/failures\/all\/delete/)
75
+ _(last_response.body).must_match(/Delete All/)
74
76
  end
75
77
 
76
78
  it 'can delete all failures' do
77
79
  assert_equal failed_count, "1"
78
80
 
79
- last_response.body.must_match /HardWorker/
81
+ _(last_response.body).must_match(/HardWorker/)
80
82
 
81
- post '/failures/all/delete'
82
- last_response.status.must_equal 302
83
- last_response.location.must_match /failures$/
83
+ post '/failures/all/delete', { authenticity_token: TOKEN }
84
+ _(last_response.status).must_equal 302
85
+ _(last_response.location).must_match(/failures$/)
84
86
 
85
87
  get '/failures'
86
- last_response.status.must_equal 200
87
- last_response.body.must_match /No failed jobs found/
88
+ _(last_response.status).must_equal 200
89
+ _(last_response.body).must_match(/No failed jobs found/)
88
90
 
89
91
  assert_equal failed_count, "1"
90
92
  end
91
93
 
92
94
  it 'has the retry all form and action' do
93
- last_response.body.must_match /failures\/all\/retry/
94
- last_response.body.must_match /Retry All/
95
+ _(last_response.body).must_match(/failures\/all\/retry/)
96
+ _(last_response.body).must_match(/Retry All/)
95
97
  end
96
98
 
97
99
  it 'can retry all failures' do
98
100
  assert_equal failed_count, "1"
99
101
 
100
- last_response.body.must_match /HardWorker/
101
- post '/failures/all/retry'
102
- last_response.status.must_equal 302
103
- last_response.location.must_match /failures/
102
+ _(last_response.body).must_match(/HardWorker/)
103
+ post '/failures/all/retry', { authenticity_token: TOKEN }
104
+ _(last_response.status).must_equal 302
105
+ _(last_response.location).must_match(/failures/)
104
106
 
105
107
  get '/failures'
106
- last_response.status.must_equal 200
107
- last_response.body.must_match(/No failed jobs found/)
108
+ _(last_response.status).must_equal 200
109
+ _(last_response.body).must_match(/No failed jobs found/)
108
110
  end
109
111
 
110
112
  it 'can delete failure from the list' do
111
113
  assert_equal failed_count, "1"
112
114
 
113
- last_response.body.must_match /HardWorker/
115
+ _(last_response.body).must_match(/HardWorker/)
114
116
 
115
- post '/failures', { :key => [failure_score], :delete => 'Delete' }
116
- last_response.status.must_equal 302
117
- last_response.location.must_match /failures/
117
+ post '/failures', { authenticity_token: TOKEN, :key => [build_param_key(@failure)], :delete => 'Delete' }
118
+ _(last_response.status).must_equal 302
119
+ _(last_response.location).must_match(/failures/)
118
120
 
119
121
  get '/failures'
120
- last_response.status.must_equal 200
121
- last_response.body.must_match /No failed jobs found/
122
+ _(last_response.status).must_equal 200
123
+ _(last_response.body).must_match(/No failed jobs found/)
122
124
  end
123
125
 
124
126
  it 'can retry failure from the list' do
125
127
  assert_equal failed_count, "1"
126
128
 
127
- last_response.body.must_match /HardWorker/
129
+ _(last_response.body).must_match(/HardWorker/)
130
+
131
+ post '/failures', { authenticity_token: TOKEN, :key => [build_param_key(@failure)], :retry => 'Retry Now' }
132
+ _(last_response.status).must_equal 302
133
+ _(last_response.location).must_match(/failures/)
134
+
135
+ get '/failures'
136
+ _(last_response.status).must_equal 200
137
+ _(last_response.body).must_match(/No failed jobs found/)
138
+ end
128
139
 
129
- post '/failures', { :key => [failure_score], :retry => 'Retry Now' }
130
- last_response.status.must_equal 302
131
- last_response.location.must_match /failures/
140
+ it 'can handle failures with nil error_message' do
141
+ create_sample_failure(error_message: nil)
132
142
 
133
143
  get '/failures'
134
- last_response.status.must_equal 200
135
- last_response.body.must_match /No failed jobs found/
144
+
145
+ _(last_response.status).must_equal 200
136
146
  end
137
147
  end
138
148
 
139
149
  describe 'when there is failure' do
140
150
  before do
141
- create_sample_failure
142
- get "/failures/#{failure_score}"
151
+ @failure = create_sample_failure
152
+ get "/failures/#{build_param_key(@failure)}"
143
153
  end
144
154
 
145
155
  it 'should be successful' do
146
- last_response.status.must_equal 200
156
+ _(last_response.status).must_equal 200
147
157
  end
148
158
 
149
159
  it 'can display failure page' do
150
- last_response.body.must_match /Job/
151
- last_response.body.must_match /HardWorker/
152
- last_response.body.must_match /ArgumentError/
153
- last_response.body.must_match /file1/
160
+ _(last_response.body).must_match(/Job/)
161
+ _(last_response.body).must_match(/HardWorker/)
162
+ _(last_response.body).must_match(/ArgumentError/)
163
+ _(last_response.body).must_match(/file1/)
154
164
  end
155
165
 
156
166
  it 'can delete failure' do
157
- last_response.body.must_match /HardWorker/
167
+ _(last_response.body).must_match(/HardWorker/)
158
168
 
159
- post "/failures/#{failure_score}", :delete => 'Delete'
160
- last_response.status.must_equal 302
161
- last_response.location.must_match /failures/
169
+ post "/failures/#{build_param_key(@failure)}", { authenticity_token: TOKEN, :delete => 'Delete' }
170
+ _(last_response.status).must_equal 302
171
+ _(last_response.location).must_match(/failures/)
162
172
 
163
- get "/failures/#{failure_score}"
164
- last_response.status.must_equal 302
165
- last_response.location.must_match /failures/
173
+ get "/failures/#{build_param_key(@failure)}"
174
+ _(last_response.status).must_equal 302
175
+ _(last_response.location).must_match(/failures/)
166
176
  end
167
177
 
168
178
  it 'can retry failure' do
169
- last_response.body.must_match /HardWorker/
179
+ _(last_response.body).must_match(/HardWorker/)
180
+
181
+ post "/failures/#{build_param_key(@failure)}", { authenticity_token: TOKEN, :retry => 'Retry Now' }
182
+ _(last_response.status).must_equal 302
183
+ _(last_response.location).must_match(/failures/)
170
184
 
171
- post "/failures/#{failure_score}", :retry => 'Retry Now'
172
- last_response.status.must_equal 302
173
- last_response.location.must_match /failures/
185
+ get "/failures/#{build_param_key(@failure)}"
186
+ _(last_response.status).must_equal 302
187
+ _(last_response.location).must_match(/failures/)
188
+ end
174
189
 
175
- get "/failures/#{failure_score}"
176
- last_response.status.must_equal 302
177
- last_response.location.must_match /failures/
190
+ if defined? Sidekiq::Pro
191
+ it 'can filter failure' do
192
+ create_sample_failure
193
+ post '/filter/failures', { authenticity_token: TOKEN, substr: 'new' }
194
+
195
+ _(last_response.status).must_equal 200
196
+ end
178
197
  end
179
198
  end
180
199
 
181
200
  describe 'when there is specific failure' do
182
201
  describe 'with unescaped data' do
183
- before do
184
- create_sample_failure(args: ['<h1>omg</h1>'], error_message: '<p>wow</p>')
185
- get '/failures'
202
+ describe 'the index page' do
203
+ before do
204
+ create_sample_failure(args: ['<h1>omg</h1>'], error_message: '<p>wow</p>', error_class: '<script>alert("xss")</script>ArgumentError')
205
+ get '/failures'
206
+ end
207
+
208
+ it 'can escape arguments' do
209
+ _(last_response.body).must_match(/&quot;&lt;h1&gt;omg&lt;\/h1&gt;&quot;/)
210
+ _(last_response.body).wont_match(/<h1>omg<\/h1>/)
211
+ end
212
+
213
+ it 'can escape error message' do
214
+ _(last_response.body).must_match(/&lt;script&gt;alert\(&quot;xss&quot;\)&lt;\/script&gt;ArgumentError: &lt;p&gt;wow&lt;\/p&gt;/)
215
+ _(last_response.body).wont_match(/<script>alert\("xss"\)<\/script>ArgumentError/)
216
+ _(last_response.body).wont_match(/<p>wow<\/p>/)
217
+ end
186
218
  end
187
219
 
188
- it 'can escape arguments' do
189
- last_response.body.must_match /&quot;&lt;h1&gt;omg&lt;&#x2F;h1&gt;&quot;/
190
- end
191
-
192
- it 'can escape error message' do
193
- last_response.body.must_match /ArgumentError: &lt;p&gt;wow&lt;&#x2F;p&gt;/
220
+ describe 'the details page' do
221
+ before do
222
+ @failure = create_sample_failure(
223
+ args: ['<h1>omg</h1>', '<script>alert("xss2")</script>'],
224
+ error_message: '<p>wow</p>',
225
+ error_class: '<script>alert("xss")</script>ArgumentError'
226
+ )
227
+ get "/failures/#{build_param_key(@failure)}"
228
+ end
229
+
230
+ it 'can escape error class' do
231
+ _(last_response.status).must_equal 200
232
+ _(last_response.body).must_match(/&lt;script&gt;alert\(&quot;xss&quot;\)&lt;\/script&gt;ArgumentError/)
233
+ _(last_response.body).wont_match(/<script>alert\("xss"\)<\/script>ArgumentError/)
234
+ end
235
+
236
+ it 'can escape error message' do
237
+ _(last_response.status).must_equal 200
238
+ _(last_response.body).must_match(/&lt;p&gt;wow&lt;\/p&gt;/)
239
+ _(last_response.body).wont_match(/<p>wow<\/p>/)
240
+ end
241
+
242
+ it 'can escape arguments' do
243
+ _(last_response.status).must_equal 200
244
+ _(last_response.body).must_match(/&quot;&lt;h1&gt;omg&lt;\/h1&gt;&quot;/)
245
+ # _(last_response.body).must_match(/&quot;&lt;script&gt;alert\(&quot;xss2&quot;\)&lt;\/script&gt;&quot;/)
246
+ _(last_response.body).wont_match(/<h1>omg<\/h1>/)
247
+ _(last_response.body).wont_match(/<script>alert\("xss2"\)<\/script>/)
248
+ end
194
249
  end
195
250
  end
196
251
 
@@ -201,8 +256,7 @@ module Sidekiq
201
256
  end
202
257
 
203
258
  it 'should be successful' do
204
- last_response.status.must_equal 200
205
- last_response.body.wont_match /No failed jobs found/
259
+ _(last_response.status).must_equal 200
206
260
  end
207
261
  end
208
262
 
@@ -213,31 +267,44 @@ module Sidekiq
213
267
  end
214
268
 
215
269
  it 'should be successful' do
216
- last_response.status.must_equal 200
217
- last_response.body.wont_match /No failed jobs found/
270
+ _(last_response.status).must_equal 200
218
271
  end
219
272
  end
220
273
  end
221
274
 
222
275
  def create_sample_failure(data = {})
276
+ failed_at = Time.now.utc.to_i - 10000
277
+ enqueued_at = failed_at - 1000
278
+
223
279
  data = {
224
280
  :queue => 'default',
225
281
  :class => 'HardWorker',
226
282
  :args => ['bob', 5],
227
- :jid => 1,
228
- :enqueued_at => Time.now.utc.to_f,
229
- :failed_at => Time.now.utc.to_f,
283
+ :jid => SecureRandom.hex(12),
284
+ :enqueued_at => failed_at.to_f,
285
+ :failed_at => enqueued_at.to_f,
230
286
  :error_class => 'ArgumentError',
231
287
  :error_message => 'Some new message',
232
288
  :error_backtrace => ["path/file1.rb", "path/file2.rb"]
233
289
  }.merge(data)
234
290
 
291
+ # Store the score so we can use it for retrieving the failure later
292
+ score = failure_score
293
+
235
294
  Sidekiq.redis do |c|
236
295
  c.multi do
237
- c.zadd(Sidekiq::Failures::LIST_KEY, failure_score, Sidekiq.dump_json(data))
296
+ c.zadd(Sidekiq::Failures::LIST_KEY, score, Sidekiq.dump_json(data))
238
297
  c.set("stat:failed", 1)
239
298
  end
240
299
  end
300
+
301
+ # Update data with the score used so tests can reference it
302
+ data[:score] = score
303
+ data
304
+ end
305
+
306
+ def build_param_key(failure)
307
+ "#{failure[:score]}-#{failure[:jid]}"
241
308
  end
242
309
 
243
310
  def failed_count
@@ -245,7 +312,7 @@ module Sidekiq
245
312
  end
246
313
 
247
314
  def failure_score
248
- Time.at(1).to_f
315
+ Time.now.to_f
249
316
  end
250
317
  end
251
318
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sidekiq-failures
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.5
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marcelo Silveira
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2015-07-21 00:00:00.000000000 Z
10
+ date: 2025-08-21 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: sidekiq
@@ -16,14 +15,28 @@ dependencies:
16
15
  requirements:
17
16
  - - ">="
18
17
  - !ruby/object:Gem::Version
19
- version: 2.16.0
18
+ version: 4.0.0
20
19
  type: :runtime
21
20
  prerelease: false
22
21
  version_requirements: !ruby/object:Gem::Requirement
23
22
  requirements:
24
23
  - - ">="
25
24
  - !ruby/object:Gem::Version
26
- version: 2.16.0
25
+ version: 4.0.0
26
+ - !ruby/object:Gem::Dependency
27
+ name: minitest
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
27
40
  - !ruby/object:Gem::Dependency
28
41
  name: rake
29
42
  requirement: !ruby/object:Gem::Requirement
@@ -87,8 +100,9 @@ executables: []
87
100
  extensions: []
88
101
  extra_rdoc_files: []
89
102
  files:
103
+ - ".github/dependabot.yml"
104
+ - ".github/workflows/ci.yml"
90
105
  - ".gitignore"
91
- - ".travis.yml"
92
106
  - CHANGELOG.md
93
107
  - Gemfile
94
108
  - LICENSE
@@ -98,20 +112,26 @@ files:
98
112
  - lib/sidekiq/failures.rb
99
113
  - lib/sidekiq/failures/failure_set.rb
100
114
  - lib/sidekiq/failures/locales/en.yml
115
+ - lib/sidekiq/failures/locales/ja.yml
116
+ - lib/sidekiq/failures/locales/pt-BR.yml
117
+ - lib/sidekiq/failures/locales/zh-cn.yml
101
118
  - lib/sidekiq/failures/middleware.rb
102
119
  - lib/sidekiq/failures/sorted_entry.rb
103
120
  - lib/sidekiq/failures/version.rb
104
121
  - lib/sidekiq/failures/views/failure.erb
122
+ - lib/sidekiq/failures/views/failure_legacy.erb
105
123
  - lib/sidekiq/failures/views/failures.erb
124
+ - lib/sidekiq/failures/views/failures_legacy.erb
106
125
  - lib/sidekiq/failures/web_extension.rb
107
126
  - sidekiq-failures.gemspec
127
+ - test/failures_test.rb
108
128
  - test/middleware_test.rb
109
129
  - test/test_helper.rb
110
130
  - test/web_extension_test.rb
111
131
  homepage: https://github.com/mhfs/sidekiq-failures/
112
- licenses: []
132
+ licenses:
133
+ - MIT
113
134
  metadata: {}
114
- post_install_message:
115
135
  rdoc_options: []
116
136
  require_paths:
117
137
  - lib
@@ -126,13 +146,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
126
146
  - !ruby/object:Gem::Version
127
147
  version: '0'
128
148
  requirements: []
129
- rubyforge_project:
130
- rubygems_version: 2.4.5
131
- signing_key:
149
+ rubygems_version: 3.6.2
132
150
  specification_version: 4
133
151
  summary: Keeps track of Sidekiq failed jobs and adds a tab to the Web UI to let you
134
152
  browse them. Makes use of Sidekiq's custom tabs and middleware chain.
135
- test_files:
136
- - test/middleware_test.rb
137
- - test/test_helper.rb
138
- - test/web_extension_test.rb
153
+ test_files: []
data/.travis.yml DELETED
@@ -1,18 +0,0 @@
1
- language: ruby
2
- services:
3
- - redis-server
4
- rvm:
5
- - 1.9.3
6
- - jruby-19mode
7
- - 2.0.0
8
- - 2.1
9
- env:
10
- matrix:
11
- - SIDEKIQ_VERSION="~> 2.16"
12
- - SIDEKIQ_VERSION="~> 2.17"
13
- - SIDEKIQ_VERSION="~> 3.0"
14
- - SIDEKIQ_VERSION="~> 3.1"
15
- matrix:
16
- allow_failures:
17
- - rvm: 1.9.3
18
- - rvm: jruby-19mode