bullet 5.9.0 → 7.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/main.yml +82 -0
  3. data/CHANGELOG.md +72 -0
  4. data/Gemfile.rails-4.0 +1 -1
  5. data/Gemfile.rails-4.1 +1 -1
  6. data/Gemfile.rails-4.2 +1 -1
  7. data/Gemfile.rails-5.0 +1 -1
  8. data/Gemfile.rails-5.1 +1 -1
  9. data/Gemfile.rails-5.2 +1 -1
  10. data/Gemfile.rails-6.0 +15 -0
  11. data/Gemfile.rails-6.1 +15 -0
  12. data/Gemfile.rails-7.0 +10 -0
  13. data/MIT-LICENSE +1 -1
  14. data/README.md +59 -33
  15. data/lib/bullet/active_job.rb +13 -0
  16. data/lib/bullet/active_record4.rb +9 -32
  17. data/lib/bullet/active_record41.rb +8 -27
  18. data/lib/bullet/active_record42.rb +9 -24
  19. data/lib/bullet/active_record5.rb +190 -179
  20. data/lib/bullet/active_record52.rb +184 -169
  21. data/lib/bullet/active_record60.rb +274 -0
  22. data/lib/bullet/active_record61.rb +274 -0
  23. data/lib/bullet/active_record70.rb +277 -0
  24. data/lib/bullet/bullet_xhr.js +64 -0
  25. data/lib/bullet/dependency.rb +60 -36
  26. data/lib/bullet/detector/association.rb +26 -20
  27. data/lib/bullet/detector/counter_cache.rb +15 -11
  28. data/lib/bullet/detector/n_plus_one_query.rb +24 -14
  29. data/lib/bullet/detector/unused_eager_loading.rb +8 -5
  30. data/lib/bullet/ext/object.rb +4 -2
  31. data/lib/bullet/mongoid4x.rb +3 -7
  32. data/lib/bullet/mongoid5x.rb +3 -7
  33. data/lib/bullet/mongoid6x.rb +3 -7
  34. data/lib/bullet/mongoid7x.rb +34 -23
  35. data/lib/bullet/notification/base.rb +14 -18
  36. data/lib/bullet/notification/n_plus_one_query.rb +2 -4
  37. data/lib/bullet/notification/unused_eager_loading.rb +2 -4
  38. data/lib/bullet/notification.rb +2 -1
  39. data/lib/bullet/rack.rb +55 -27
  40. data/lib/bullet/stack_trace_filter.rb +11 -19
  41. data/lib/bullet/version.rb +1 -1
  42. data/lib/bullet.rb +68 -42
  43. data/lib/generators/bullet/install_generator.rb +22 -23
  44. data/perf/benchmark.rb +11 -14
  45. data/spec/bullet/detector/counter_cache_spec.rb +6 -6
  46. data/spec/bullet/detector/n_plus_one_query_spec.rb +8 -4
  47. data/spec/bullet/detector/unused_eager_loading_spec.rb +25 -8
  48. data/spec/bullet/ext/object_spec.rb +10 -5
  49. data/spec/bullet/notification/base_spec.rb +5 -7
  50. data/spec/bullet/notification/n_plus_one_query_spec.rb +16 -3
  51. data/spec/bullet/notification/unused_eager_loading_spec.rb +5 -1
  52. data/spec/bullet/rack_spec.rb +161 -11
  53. data/spec/bullet/registry/association_spec.rb +2 -2
  54. data/spec/bullet/registry/base_spec.rb +1 -1
  55. data/spec/bullet_spec.rb +25 -44
  56. data/spec/integration/active_record/association_spec.rb +115 -144
  57. data/spec/integration/counter_cache_spec.rb +14 -34
  58. data/spec/integration/mongoid/association_spec.rb +19 -33
  59. data/spec/models/attachment.rb +5 -0
  60. data/spec/models/deal.rb +5 -0
  61. data/spec/models/post.rb +2 -0
  62. data/spec/models/role.rb +7 -0
  63. data/spec/models/submission.rb +1 -0
  64. data/spec/models/user.rb +2 -0
  65. data/spec/spec_helper.rb +4 -10
  66. data/spec/support/bullet_ext.rb +8 -9
  67. data/spec/support/mongo_seed.rb +3 -16
  68. data/spec/support/sqlite_seed.rb +38 -0
  69. data/test.sh +3 -0
  70. metadata +21 -8
  71. data/.travis.yml +0 -12
@@ -31,12 +31,6 @@ module Bullet
31
31
  response = double(body: '<html><head></head><body></body></html>')
32
32
  expect(middleware).not_to be_html_request(headers, response)
33
33
  end
34
-
35
- it "should be false if response body doesn't contain html tag" do
36
- headers = { 'Content-Type' => 'text/html' }
37
- response = double(body: '<div>Partial</div>')
38
- expect(middleware).not_to be_html_request(headers, response)
39
- end
40
34
  end
41
35
 
42
36
  context 'empty?' do
@@ -45,15 +39,20 @@ module Bullet
45
39
  expect(middleware).not_to be_empty(response)
46
40
  end
47
41
 
48
- it 'should be true if response is not found' do
42
+ it 'should be false if response is not found' do
49
43
  response = ['Not Found']
50
- expect(middleware).to be_empty(response)
44
+ expect(middleware).not_to be_empty(response)
51
45
  end
52
46
 
53
47
  it 'should be true if response body is empty' do
54
48
  response = double(body: '')
55
49
  expect(middleware).to be_empty(response)
56
50
  end
51
+
52
+ it 'should be true if no response body' do
53
+ response = double()
54
+ expect(middleware).to be_empty(response)
55
+ end
57
56
  end
58
57
 
59
58
  context '#call' do
@@ -67,11 +66,12 @@ module Bullet
67
66
 
68
67
  it 'should change response body if notification is active' do
69
68
  expect(Bullet).to receive(:notification?).and_return(true)
69
+ expect(Bullet).to receive(:console_enabled?).and_return(true)
70
70
  expect(Bullet).to receive(:gather_inline_notifications).and_return('<bullet></bullet>')
71
71
  expect(Bullet).to receive(:perform_out_of_channel_notifications)
72
- status, headers, response = middleware.call('Content-Type' => 'text/html')
72
+ _, headers, response = middleware.call('Content-Type' => 'text/html')
73
73
  expect(headers['Content-Length']).to eq('56')
74
- expect(response).to eq(['<html><head></head><body><bullet></bullet></body></html>'])
74
+ expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])
75
75
  end
76
76
 
77
77
  it 'should set the right Content-Length if response body contains accents' do
@@ -79,10 +79,138 @@ module Bullet
79
79
  response.body = '<html><head></head><body>é</body></html>'
80
80
  app.response = response
81
81
  expect(Bullet).to receive(:notification?).and_return(true)
82
+ allow(Bullet).to receive(:console_enabled?).and_return(true)
82
83
  expect(Bullet).to receive(:gather_inline_notifications).and_return('<bullet></bullet>')
83
- status, headers, response = middleware.call('Content-Type' => 'text/html')
84
+ _, headers, response = middleware.call('Content-Type' => 'text/html')
84
85
  expect(headers['Content-Length']).to eq('58')
85
86
  end
87
+
88
+ context 'with injection notifiers' do
89
+ before do
90
+ expect(Bullet).to receive(:notification?).and_return(true)
91
+ allow(Bullet).to receive(:gather_inline_notifications).and_return('<bullet></bullet>')
92
+ allow(middleware).to receive(:xhr_script).and_return('<script></script>')
93
+ allow(middleware).to receive(:footer_note).and_return('footer')
94
+ expect(Bullet).to receive(:perform_out_of_channel_notifications)
95
+ end
96
+
97
+ it 'should change response body if add_footer is true' do
98
+ expect(Bullet).to receive(:add_footer).exactly(3).times.and_return(true)
99
+ _, headers, response = middleware.call('Content-Type' => 'text/html')
100
+
101
+ expect(headers['Content-Length']).to eq((73 + middleware.send(:footer_note).length).to_s)
102
+ expect(response).to eq(%w[<html><head></head><body>footer<bullet></bullet><script></script></body></html>])
103
+ end
104
+
105
+ it 'should change response body for html safe string if add_footer is true' do
106
+ expect(Bullet).to receive(:add_footer).exactly(3).times.and_return(true)
107
+ app.response =
108
+ Support::ResponseDouble.new.tap do |response|
109
+ response.body = ActiveSupport::SafeBuffer.new('<html><head></head><body></body></html>')
110
+ end
111
+ _, headers, response = middleware.call('Content-Type' => 'text/html')
112
+
113
+ expect(headers['Content-Length']).to eq((73 + middleware.send(:footer_note).length).to_s)
114
+ expect(response).to eq(%w[<html><head></head><body>footer<bullet></bullet><script></script></body></html>])
115
+ end
116
+
117
+ it 'should add the footer-text header for non-html requests when add_footer is true' do
118
+ allow(Bullet).to receive(:add_footer).at_least(:once).and_return(true)
119
+ allow(Bullet).to receive(:footer_info).and_return(['footer text'])
120
+ app.headers = { 'Content-Type' => 'application/json' }
121
+ _, headers, _response = middleware.call({})
122
+ expect(headers).to include('X-bullet-footer-text' => '["footer text"]')
123
+ end
124
+
125
+ it 'should change response body if console_enabled is true' do
126
+ expect(Bullet).to receive(:console_enabled?).and_return(true)
127
+ _, headers, response = middleware.call('Content-Type' => 'text/html')
128
+ expect(headers['Content-Length']).to eq('56')
129
+ expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])
130
+ end
131
+
132
+ it 'should change response body for html safe string if console_enabled is true' do
133
+ expect(Bullet).to receive(:console_enabled?).and_return(true)
134
+ app.response =
135
+ Support::ResponseDouble.new.tap do |response|
136
+ response.body = ActiveSupport::SafeBuffer.new('<html><head></head><body></body></html>')
137
+ end
138
+ _, headers, response = middleware.call('Content-Type' => 'text/html')
139
+ expect(headers['Content-Length']).to eq('56')
140
+ expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])
141
+ end
142
+
143
+ it 'should add headers for non-html requests when console_enabled is true' do
144
+ allow(Bullet).to receive(:console_enabled?).at_least(:once).and_return(true)
145
+ allow(Bullet).to receive(:text_notifications).and_return(['text notifications'])
146
+ app.headers = { 'Content-Type' => 'application/json' }
147
+ _, headers, _response = middleware.call({})
148
+ expect(headers).to include('X-bullet-console-text' => '["text notifications"]')
149
+ end
150
+
151
+ it "shouldn't change response body unnecessarily" do
152
+ expected_response = Support::ResponseDouble.new 'Actual body'
153
+ app.response = expected_response
154
+ _, _, response = middleware.call({})
155
+ expect(response).to eq(expected_response)
156
+ end
157
+
158
+ it "shouldn't add headers unnecessarily" do
159
+ app.headers = { 'Content-Type' => 'application/json' }
160
+ _, headers, _response = middleware.call({})
161
+ expect(headers).not_to include('X-bullet-footer-text')
162
+ expect(headers).not_to include('X-bullet-console-text')
163
+ end
164
+
165
+ context 'when skip_http_headers is enabled' do
166
+ before do
167
+ allow(Bullet).to receive(:skip_http_headers).and_return(true)
168
+ end
169
+
170
+ it 'should include the footer but not the xhr script tag if add_footer is true' do
171
+ expect(Bullet).to receive(:add_footer).at_least(:once).and_return(true)
172
+ _, headers, response = middleware.call({})
173
+
174
+ expect(headers['Content-Length']).to eq((56 + middleware.send(:footer_note).length).to_s)
175
+ expect(response).to eq(%w[<html><head></head><body>footer<bullet></bullet></body></html>])
176
+ end
177
+
178
+ it 'should not include the xhr script tag if console_enabled is true' do
179
+ expect(Bullet).to receive(:console_enabled?).and_return(true)
180
+ _, headers, response = middleware.call({})
181
+ expect(headers['Content-Length']).to eq('56')
182
+ expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])
183
+ end
184
+
185
+ it 'should not add the footer-text header for non-html requests when add_footer is true' do
186
+ allow(Bullet).to receive(:add_footer).at_least(:once).and_return(true)
187
+ app.headers = { 'Content-Type' => 'application/json' }
188
+ _, headers, _response = middleware.call({})
189
+ expect(headers).not_to include('X-bullet-footer-text')
190
+ end
191
+
192
+ it 'should not add headers for non-html requests when console_enabled is true' do
193
+ allow(Bullet).to receive(:console_enabled?).at_least(:once).and_return(true)
194
+ app.headers = { 'Content-Type' => 'application/json' }
195
+ _, headers, _response = middleware.call({})
196
+ expect(headers).not_to include('X-bullet-console-text')
197
+ end
198
+ end
199
+ end
200
+
201
+ context 'when skip_html_injection is enabled' do
202
+ it 'should not try to inject html' do
203
+ expected_response = Support::ResponseDouble.new 'Actual body'
204
+ app.response = expected_response
205
+ allow(Bullet).to receive(:notification?).and_return(true)
206
+ allow(Bullet).to receive(:skip_html_injection?).and_return(true)
207
+ expect(Bullet).to receive(:gather_inline_notifications).never
208
+ expect(middleware).to receive(:xhr_script).never
209
+ expect(Bullet).to receive(:perform_out_of_channel_notifications)
210
+ _, _, response = middleware.call('Content-Type' => 'text/html')
211
+ expect(response).to eq(expected_response)
212
+ end
213
+ end
86
214
  end
87
215
 
88
216
  context 'when Bullet is disabled' do
@@ -95,6 +223,14 @@ module Bullet
95
223
  end
96
224
  end
97
225
 
226
+ context '#set_header' do
227
+ it 'should truncate headers to under 8kb' do
228
+ long_header = ['a' * 1_024] * 10
229
+ expected_res = (['a' * 1_024] * 7).to_json
230
+ expect(middleware.set_header({}, 'Dummy-Header', long_header)).to eq(expected_res)
231
+ end
232
+ end
233
+
98
234
  describe '#response_body' do
99
235
  let(:response) { double }
100
236
  let(:body_string) { '<html><body>My Body</body></html>' }
@@ -124,6 +260,20 @@ module Bullet
124
260
  expect(middleware.response_body(response)).to eq body_string
125
261
  end
126
262
  end
263
+
264
+ begin
265
+ require 'rack/files'
266
+
267
+ context 'when `response` is a Rack::Files::Iterator' do
268
+ let(:response) { instance_double(::Rack::Files::Iterator) }
269
+ before { allow(response).to receive(:is_a?).with(::Rack::Files::Iterator) { true } }
270
+
271
+ it 'should return nil' do
272
+ expect(middleware.response_body(response)).to be_nil
273
+ end
274
+ end
275
+ rescue LoadError
276
+ end
127
277
  end
128
278
  end
129
279
  end
@@ -16,11 +16,11 @@ module Bullet
16
16
 
17
17
  context '#similarly_associated' do
18
18
  it 'should return similarly associated keys' do
19
- expect(subject.similarly_associated('key1', Set.new(['value']))).to eq(%w[key1 key2])
19
+ expect(subject.similarly_associated('key1', Set.new(%w[value]))).to eq(%w[key1 key2])
20
20
  end
21
21
 
22
22
  it 'should return empty if key does not exist' do
23
- expect(subject.similarly_associated('key3', Set.new(['value']))).to be_empty
23
+ expect(subject.similarly_associated('key3', Set.new(%w[value]))).to be_empty
24
24
  end
25
25
  end
26
26
  end
@@ -9,7 +9,7 @@ module Bullet
9
9
 
10
10
  context '#[]' do
11
11
  it 'should get value by key' do
12
- expect(subject['key']).to eq(Set.new(['value']))
12
+ expect(subject['key']).to eq(Set.new(%w[value]))
13
13
  end
14
14
  end
15
15
 
data/spec/bullet_spec.rb CHANGED
@@ -17,9 +17,7 @@ describe Bullet, focused: true do
17
17
  end
18
18
 
19
19
  context 'disable Bullet' do
20
- before do
21
- Bullet.enable = false
22
- end
20
+ before { Bullet.enable = false }
23
21
 
24
22
  it 'should be disabled' do
25
23
  expect(subject).to_not be_enable
@@ -27,8 +25,8 @@ describe Bullet, focused: true do
27
25
 
28
26
  context 'enable Bullet again without patching again the orms' do
29
27
  before do
30
- expect(Bullet::Mongoid).not_to receive(:enable) if defined? Bullet::Mongoid
31
- expect(Bullet::ActiveRecord).not_to receive(:enable) if defined? Bullet::ActiveRecord
28
+ expect(Bullet::Mongoid).not_to receive(:enable) if defined?(Bullet::Mongoid)
29
+ expect(Bullet::ActiveRecord).not_to receive(:enable) if defined?(Bullet::ActiveRecord)
32
30
  Bullet.enable = true
33
31
  end
34
32
 
@@ -42,9 +40,7 @@ describe Bullet, focused: true do
42
40
 
43
41
  describe '#start?' do
44
42
  context 'when bullet is disabled' do
45
- before(:each) do
46
- Bullet.enable = false
47
- end
43
+ before(:each) { Bullet.enable = false }
48
44
 
49
45
  it 'should not be started' do
50
46
  expect(Bullet).not_to be_start
@@ -53,28 +49,19 @@ describe Bullet, focused: true do
53
49
  end
54
50
 
55
51
  describe '#debug' do
56
- before(:each) do
57
- $stdout = StringIO.new
58
- end
52
+ before(:each) { $stdout = StringIO.new }
59
53
 
60
- after(:each) do
61
- $stdout = STDOUT
62
- end
54
+ after(:each) { $stdout = STDOUT }
63
55
 
64
56
  context 'when debug is enabled' do
65
- before(:each) do
66
- ENV['BULLET_DEBUG'] = 'true'
67
- end
57
+ before(:each) { ENV['BULLET_DEBUG'] = 'true' }
68
58
 
69
- after(:each) do
70
- ENV['BULLET_DEBUG'] = 'false'
71
- end
59
+ after(:each) { ENV['BULLET_DEBUG'] = 'false' }
72
60
 
73
61
  it 'should output debug information' do
74
62
  Bullet.debug('debug_message', 'this is helpful information')
75
63
 
76
- expect($stdout.string)
77
- .to eq("[Bullet][debug_message] this is helpful information\n")
64
+ expect($stdout.string).to eq("[Bullet][debug_message] this is helpful information\n")
78
65
  end
79
66
  end
80
67
 
@@ -87,31 +74,31 @@ describe Bullet, focused: true do
87
74
  end
88
75
  end
89
76
 
90
- describe '#add_whitelist' do
77
+ describe '#add_safelist' do
91
78
  context "for 'special' class names" do
92
- it 'is added to the whitelist successfully' do
93
- Bullet.add_whitelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
94
- expect(Bullet.get_whitelist_associations(:n_plus_one_query, 'Klass')).to include :department
79
+ it 'is added to the safelist successfully' do
80
+ Bullet.add_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
81
+ expect(Bullet.get_safelist_associations(:n_plus_one_query, 'Klass')).to include :department
95
82
  end
96
83
  end
97
84
  end
98
85
 
99
- describe '#delete_whitelist' do
86
+ describe '#delete_safelist' do
100
87
  context "for 'special' class names" do
101
- it 'is deleted from the whitelist successfully' do
102
- Bullet.add_whitelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
103
- Bullet.delete_whitelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
104
- expect(Bullet.whitelist[:n_plus_one_query]).to eq({})
88
+ it 'is deleted from the safelist successfully' do
89
+ Bullet.add_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
90
+ Bullet.delete_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
91
+ expect(Bullet.safelist[:n_plus_one_query]).to eq({})
105
92
  end
106
93
  end
107
94
 
108
95
  context 'when exists multiple definitions' do
109
- it 'is deleted from the whitelist successfully' do
110
- Bullet.add_whitelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
111
- Bullet.add_whitelist(type: :n_plus_one_query, class_name: 'Klass', association: :team)
112
- Bullet.delete_whitelist(type: :n_plus_one_query, class_name: 'Klass', association: :team)
113
- expect(Bullet.get_whitelist_associations(:n_plus_one_query, 'Klass')).to include :department
114
- expect(Bullet.get_whitelist_associations(:n_plus_one_query, 'Klass')).to_not include :team
96
+ it 'is deleted from the safelist successfully' do
97
+ Bullet.add_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
98
+ Bullet.add_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :team)
99
+ Bullet.delete_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :team)
100
+ expect(Bullet.get_safelist_associations(:n_plus_one_query, 'Klass')).to include :department
101
+ expect(Bullet.get_safelist_associations(:n_plus_one_query, 'Klass')).to_not include :team
115
102
  end
116
103
  end
117
104
  end
@@ -125,13 +112,7 @@ describe Bullet, focused: true do
125
112
  end
126
113
 
127
114
  context 'when called with Rack environment hash' do
128
- let(:env) {
129
- {
130
- 'REQUEST_METHOD' => 'GET',
131
- 'PATH_INFO' => '/path',
132
- 'QUERY_STRING' => 'foo=bar'
133
- }
134
- }
115
+ let(:env) { { 'REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/path', 'QUERY_STRING' => 'foo=bar' } }
135
116
 
136
117
  context "when env['REQUEST_URI'] is nil" do
137
118
  before { env['REQUEST_URI'] = nil }