bullet 6.1.0 → 7.0.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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +82 -0
  3. data/CHANGELOG.md +31 -0
  4. data/Gemfile.rails-6.0 +1 -1
  5. data/Gemfile.rails-6.1 +15 -0
  6. data/Gemfile.rails-7.0 +10 -0
  7. data/README.md +17 -7
  8. data/lib/bullet/active_job.rb +5 -1
  9. data/lib/bullet/active_record41.rb +1 -0
  10. data/lib/bullet/active_record42.rb +1 -0
  11. data/lib/bullet/active_record52.rb +22 -17
  12. data/lib/bullet/active_record60.rb +22 -17
  13. data/lib/bullet/active_record61.rb +272 -0
  14. data/lib/bullet/active_record70.rb +261 -0
  15. data/lib/bullet/bullet_xhr.js +18 -17
  16. data/lib/bullet/dependency.rb +16 -0
  17. data/lib/bullet/detector/base.rb +2 -1
  18. data/lib/bullet/detector/counter_cache.rb +1 -1
  19. data/lib/bullet/detector/n_plus_one_query.rb +3 -3
  20. data/lib/bullet/detector/unused_eager_loading.rb +1 -1
  21. data/lib/bullet/mongoid4x.rb +1 -1
  22. data/lib/bullet/mongoid5x.rb +1 -1
  23. data/lib/bullet/mongoid6x.rb +1 -1
  24. data/lib/bullet/mongoid7x.rb +24 -7
  25. data/lib/bullet/notification.rb +2 -1
  26. data/lib/bullet/rack.rb +26 -18
  27. data/lib/bullet/stack_trace_filter.rb +7 -9
  28. data/lib/bullet/version.rb +1 -1
  29. data/lib/bullet.rb +69 -23
  30. data/lib/generators/bullet/install_generator.rb +23 -25
  31. data/perf/benchmark.rb +4 -1
  32. data/spec/bullet/detector/counter_cache_spec.rb +1 -1
  33. data/spec/bullet/detector/unused_eager_loading_spec.rb +10 -10
  34. data/spec/bullet/ext/object_spec.rb +1 -1
  35. data/spec/bullet/notification/n_plus_one_query_spec.rb +1 -3
  36. data/spec/bullet/rack_spec.rb +130 -3
  37. data/spec/bullet_spec.rb +39 -10
  38. data/spec/integration/active_record/association_spec.rb +54 -10
  39. data/spec/integration/counter_cache_spec.rb +4 -4
  40. data/spec/integration/mongoid/association_spec.rb +4 -4
  41. data/spec/models/attachment.rb +5 -0
  42. data/spec/models/deal.rb +5 -0
  43. data/spec/models/folder.rb +2 -1
  44. data/spec/models/group.rb +2 -1
  45. data/spec/models/page.rb +2 -1
  46. data/spec/models/post.rb +2 -0
  47. data/spec/models/submission.rb +1 -0
  48. data/spec/models/user.rb +1 -0
  49. data/spec/models/writer.rb +2 -1
  50. data/spec/spec_helper.rb +0 -2
  51. data/spec/support/mongo_seed.rb +1 -0
  52. data/spec/support/sqlite_seed.rb +20 -0
  53. data/test.sh +2 -0
  54. metadata +15 -7
  55. data/.travis.yml +0 -33
data/lib/bullet.rb CHANGED
@@ -20,9 +20,6 @@ module Bullet
20
20
  autoload :Registry, 'bullet/registry'
21
21
  autoload :NotificationCollector, 'bullet/notification_collector'
22
22
 
23
- BULLET_DEBUG = 'BULLET_DEBUG'
24
- TRUE = 'true'
25
-
26
23
  if defined?(Rails::Railtie)
27
24
  class BulletRailtie < Rails::Railtie
28
25
  initializer 'bullet.configure_rails_initialization' do |app|
@@ -38,10 +35,11 @@ module Bullet
38
35
  :stacktrace_includes,
39
36
  :stacktrace_excludes,
40
37
  :skip_html_injection
41
- attr_reader :whitelist
42
- attr_accessor :add_footer, :orm_patches_applied
38
+ attr_reader :safelist
39
+ attr_accessor :add_footer, :orm_patches_applied, :skip_http_headers
43
40
 
44
- available_notifiers = UniformNotifier::AVAILABLE_NOTIFIERS.map { |notifier| "#{notifier}=" }
41
+ available_notifiers =
42
+ UniformNotifier::AVAILABLE_NOTIFIERS.select { |notifier| notifier != :raise }.map { |notifier| "#{notifier}=" }
45
43
  available_notifiers_options = { to: UniformNotifier }
46
44
  delegate(*available_notifiers, **available_notifiers_options)
47
45
 
@@ -59,7 +57,7 @@ module Bullet
59
57
  @enable = @n_plus_one_query_enable = @unused_eager_loading_enable = @counter_cache_enable = enable
60
58
 
61
59
  if enable?
62
- reset_whitelist
60
+ reset_safelist
63
61
  unless orm_patches_applied
64
62
  self.orm_patches_applied = true
65
63
  Bullet::Mongoid.enable if mongoid?
@@ -72,8 +70,9 @@ module Bullet
72
70
  !!@enable
73
71
  end
74
72
 
73
+ # Rails.root might be nil if `railties` is a dependency on a project that does not use Rails
75
74
  def app_root
76
- (defined?(::Rails.root) ? Rails.root.to_s : Dir.pwd).to_s
75
+ @app_root ||= (defined?(::Rails.root) && !::Rails.root.nil? ? Rails.root.to_s : Dir.pwd).to_s
77
76
  end
78
77
 
79
78
  def n_plus_one_query_enable?
@@ -89,36 +88,81 @@ module Bullet
89
88
  end
90
89
 
91
90
  def stacktrace_includes
92
- @stacktrace_includes || []
91
+ @stacktrace_includes ||= []
93
92
  end
94
93
 
95
94
  def stacktrace_excludes
96
- @stacktrace_excludes || []
95
+ @stacktrace_excludes ||= []
96
+ end
97
+
98
+ def add_safelist(options)
99
+ reset_safelist
100
+ @safelist[options[:type]][options[:class_name]] ||= []
101
+ @safelist[options[:type]][options[:class_name]] << options[:association].to_sym
102
+ end
103
+
104
+ def delete_safelist(options)
105
+ reset_safelist
106
+ @safelist[options[:type]][options[:class_name]] ||= []
107
+ @safelist[options[:type]][options[:class_name]].delete(options[:association].to_sym)
108
+ @safelist[options[:type]].delete_if { |_key, val| val.empty? }
109
+ end
110
+
111
+ def get_safelist_associations(type, class_name)
112
+ Array(@safelist[type][class_name])
113
+ end
114
+
115
+ def reset_safelist
116
+ @safelist ||= { n_plus_one_query: {}, unused_eager_loading: {}, counter_cache: {} }
117
+ end
118
+
119
+ def clear_safelist
120
+ @safelist = nil
97
121
  end
98
122
 
99
123
  def add_whitelist(options)
100
- reset_whitelist
101
- @whitelist[options[:type]][options[:class_name]] ||= []
102
- @whitelist[options[:type]][options[:class_name]] << options[:association].to_sym
124
+ ActiveSupport::Deprecation.warn(<<~WARN.strip
125
+ add_whitelist is deprecated in favor of add_safelist. It will be removed from the next major release.
126
+ WARN
127
+ )
128
+
129
+ add_safelist(options)
103
130
  end
104
131
 
105
132
  def delete_whitelist(options)
106
- reset_whitelist
107
- @whitelist[options[:type]][options[:class_name]] ||= []
108
- @whitelist[options[:type]][options[:class_name]].delete(options[:association].to_sym)
109
- @whitelist[options[:type]].delete_if { |_key, val| val.empty? }
133
+ ActiveSupport::Deprecation.warn(<<~WARN.strip
134
+ delete_whitelist is deprecated in favor of delete_safelist. It will be removed from the next major release.
135
+ WARN
136
+ )
137
+
138
+ delete_safelist(options)
110
139
  end
111
140
 
112
141
  def get_whitelist_associations(type, class_name)
113
- Array(@whitelist[type][class_name])
142
+ ActiveSupport::Deprecation.warn(<<~WARN.strip
143
+ get_whitelist_associations is deprecated in favor of get_safelist_associations. It will be removed from the next major release.
144
+ WARN
145
+ )
146
+
147
+ get_safelist_associations(type, class_name)
114
148
  end
115
149
 
116
150
  def reset_whitelist
117
- @whitelist ||= { n_plus_one_query: {}, unused_eager_loading: {}, counter_cache: {} }
151
+ ActiveSupport::Deprecation.warn(<<~WARN.strip
152
+ reset_whitelist is deprecated in favor of reset_safelist. It will be removed from the next major release.
153
+ WARN
154
+ )
155
+
156
+ reset_safelist
118
157
  end
119
158
 
120
159
  def clear_whitelist
121
- @whitelist = nil
160
+ ActiveSupport::Deprecation.warn(<<~WARN.strip
161
+ clear_whitelist is deprecated in favor of clear_safelist. It will be removed from the next major release.
162
+ WARN
163
+ )
164
+
165
+ clear_safelist
122
166
  end
123
167
 
124
168
  def bullet_logger=(active)
@@ -132,7 +176,7 @@ module Bullet
132
176
  end
133
177
 
134
178
  def debug(title, message)
135
- puts "[Bullet][#{title}] #{message}" if ENV[BULLET_DEBUG] == TRUE
179
+ puts "[Bullet][#{title}] #{message}" if ENV['BULLET_DEBUG'] == 'true'
136
180
  end
137
181
 
138
182
  def start_request
@@ -240,8 +284,10 @@ module Bullet
240
284
  UniformNotifier.active_notifiers.include?(UniformNotifier::JavascriptConsole)
241
285
  end
242
286
 
243
- def skip_html_injection?
244
- @skip_html_injection || false
287
+ def inject_into_page?
288
+ return false if defined?(@skip_html_injection) && @skip_html_injection
289
+
290
+ console_enabled? || add_footer
245
291
  end
246
292
 
247
293
  private
@@ -10,40 +10,38 @@ module Bullet
10
10
 
11
11
  def enable_in_development
12
12
  environment(nil, env: 'development') do
13
- <<-"FILE"
14
-
15
- config.after_initialize do
16
- Bullet.enable = true
17
- Bullet.alert = true
18
- Bullet.bullet_logger = true
19
- Bullet.console = true
20
- # Bullet.growl = true
21
- Bullet.rails_logger = true
22
- Bullet.add_footer = true
23
- end
13
+ <<~FILE
14
+ config.after_initialize do
15
+ Bullet.enable = true
16
+ Bullet.alert = true
17
+ Bullet.bullet_logger = true
18
+ Bullet.console = true
19
+ # Bullet.growl = true
20
+ Bullet.rails_logger = true
21
+ Bullet.add_footer = true
22
+ end
23
+
24
24
  FILE
25
- .strip
26
25
  end
27
26
 
28
27
  say 'Enabled bullet in config/environments/development.rb'
29
28
  end
30
29
 
31
30
  def enable_in_test
32
- if yes?('Would you like to enable bullet in test environment? (y/n)')
33
- environment(nil, env: 'test') do
34
- <<-"FILE"
35
-
36
- config.after_initialize do
37
- Bullet.enable = true
38
- Bullet.bullet_logger = true
39
- Bullet.raise = true # raise an error if n+1 query occurs
40
- end
41
- FILE
42
- .strip
43
- end
31
+ return unless yes?('Would you like to enable bullet in test environment? (y/n)')
44
32
 
45
- say 'Enabled bullet in config/environments/test.rb'
33
+ environment(nil, env: 'test') do
34
+ <<~FILE
35
+ config.after_initialize do
36
+ Bullet.enable = true
37
+ Bullet.bullet_logger = true
38
+ Bullet.raise = true # raise an error if n+1 query occurs
39
+ end
40
+
41
+ FILE
46
42
  end
43
+
44
+ say 'Enabled bullet in config/environments/test.rb'
47
45
  end
48
46
  end
49
47
  end
data/perf/benchmark.rb CHANGED
@@ -30,7 +30,10 @@ end
30
30
 
31
31
  # create database bullet_benchmark;
32
32
  ActiveRecord::Base.establish_connection(
33
- adapter: 'mysql2', database: 'bullet_benchmark', server: '/tmp/mysql.socket', username: 'root'
33
+ adapter: 'mysql2',
34
+ database: 'bullet_benchmark',
35
+ server: '/tmp/mysql.socket',
36
+ username: 'root'
34
37
  )
35
38
 
36
39
  ActiveRecord::Base.connection.tables.each { |table| ActiveRecord::Base.connection.drop_table(table) }
@@ -47,7 +47,7 @@ module Bullet
47
47
  expect(CounterCache.conditions_met?(@post1, :associations)).to eq false
48
48
  end
49
49
 
50
- it 'should be true when object is possible, and impossible' do
50
+ it 'should be false when object is possible, and impossible' do
51
51
  CounterCache.add_possible_objects(@post1)
52
52
  CounterCache.add_impossible_object(@post1)
53
53
  expect(CounterCache.conditions_met?(@post1, :associations)).to eq false
@@ -13,27 +13,27 @@ module Bullet
13
13
 
14
14
  context '.call_associations' do
15
15
  it 'should get empty array if eager_loadings' do
16
- expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new(%i[association]))).to be_empty
16
+ expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new([:association]))).to be_empty
17
17
  end
18
18
 
19
19
  it 'should get call associations if object and association are both in eager_loadings and call_object_associations' do
20
20
  UnusedEagerLoading.add_eager_loadings([@post], :association)
21
21
  UnusedEagerLoading.add_call_object_associations(@post, :association)
22
- expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new(%i[association]))).to eq(
23
- %i[association]
22
+ expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new([:association]))).to eq(
23
+ [:association]
24
24
  )
25
25
  end
26
26
 
27
27
  it 'should not get call associations if not exist in call_object_associations' do
28
28
  UnusedEagerLoading.add_eager_loadings([@post], :association)
29
- expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new(%i[association]))).to be_empty
29
+ expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new([:association]))).to be_empty
30
30
  end
31
31
  end
32
32
 
33
33
  context '.diff_object_associations' do
34
34
  it 'should return associations not exist in call_association' do
35
- expect(UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new(%i[association]))).to eq(
36
- %i[association]
35
+ expect(UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new([:association]))).to eq(
36
+ [:association]
37
37
  )
38
38
  end
39
39
 
@@ -41,7 +41,7 @@ module Bullet
41
41
  UnusedEagerLoading.add_eager_loadings([@post], :association)
42
42
  UnusedEagerLoading.add_call_object_associations(@post, :association)
43
43
  expect(
44
- UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new(%i[association]))
44
+ UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new([:association]))
45
45
  ).to be_empty
46
46
  end
47
47
  end
@@ -51,7 +51,7 @@ module Bullet
51
51
  it 'should create notification if object_association_diff is not empty' do
52
52
  UnusedEagerLoading.add_object_associations(@post, :association)
53
53
  allow(UnusedEagerLoading).to receive(:caller_in_project).and_return(paths)
54
- expect(UnusedEagerLoading).to receive(:create_notification).with(paths, 'Post', %i[association])
54
+ expect(UnusedEagerLoading).to receive(:create_notification).with(paths, 'Post', [:association])
55
55
  UnusedEagerLoading.check_unused_preload_associations
56
56
  end
57
57
 
@@ -60,9 +60,9 @@ module Bullet
60
60
  UnusedEagerLoading.add_eager_loadings([@post], :association)
61
61
  UnusedEagerLoading.add_call_object_associations(@post, :association)
62
62
  expect(
63
- UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new(%i[association]))
63
+ UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new([:association]))
64
64
  ).to be_empty
65
- expect(UnusedEagerLoading).not_to receive(:create_notification).with('Post', %i[association])
65
+ expect(UnusedEagerLoading).not_to receive(:create_notification).with('Post', [:association])
66
66
  UnusedEagerLoading.check_unused_preload_associations
67
67
  end
68
68
  end
@@ -10,7 +10,7 @@ describe Object do
10
10
  end
11
11
 
12
12
  if mongoid?
13
- it 'should return class with namesapce and id composition' do
13
+ it 'should return class with namespace and id composition' do
14
14
  post = Mongoid::Post.first
15
15
  expect(post.bullet_key).to eq("Mongoid::Post:#{post.id}")
16
16
  end
@@ -21,9 +21,7 @@ module Bullet
21
21
  )
22
22
  end
23
23
  it do
24
- expect(subject.body).to eq(
25
- " Post => [:comments, :votes]\n Add to your query: .includes([:comments, :votes])"
26
- )
24
+ expect(subject.body).to eq(" Post => [:comments, :votes]\n Add to your query: .includes([:comments, :votes])")
27
25
  end
28
26
  it { expect(subject.title).to eq('USE eager loading in path') }
29
27
  end
@@ -67,9 +67,8 @@ module Bullet
67
67
 
68
68
  it 'should change response body if notification is active' do
69
69
  expect(Bullet).to receive(:notification?).and_return(true)
70
- allow(Bullet).to receive(:skip_html_injection?).and_return(false)
70
+ expect(Bullet).to receive(:console_enabled?).and_return(true)
71
71
  expect(Bullet).to receive(:gather_inline_notifications).and_return('<bullet></bullet>')
72
- expect(middleware).to receive(:xhr_script).and_return('')
73
72
  expect(Bullet).to receive(:perform_out_of_channel_notifications)
74
73
  _, headers, response = middleware.call('Content-Type' => 'text/html')
75
74
  expect(headers['Content-Length']).to eq('56')
@@ -81,9 +80,123 @@ module Bullet
81
80
  response.body = '<html><head></head><body>é</body></html>'
82
81
  app.response = response
83
82
  expect(Bullet).to receive(:notification?).and_return(true)
83
+ allow(Bullet).to receive(:console_enabled?).and_return(true)
84
84
  expect(Bullet).to receive(:gather_inline_notifications).and_return('<bullet></bullet>')
85
85
  _, headers, response = middleware.call('Content-Type' => 'text/html')
86
- expect(headers['Content-Length']).to eq((58 + middleware.send(:xhr_script).length).to_s)
86
+ expect(headers['Content-Length']).to eq('58')
87
+ end
88
+
89
+ context 'with injection notifiers' do
90
+ before do
91
+ expect(Bullet).to receive(:notification?).and_return(true)
92
+ allow(Bullet).to receive(:gather_inline_notifications).and_return('<bullet></bullet>')
93
+ allow(middleware).to receive(:xhr_script).and_return('<script></script>')
94
+ allow(middleware).to receive(:footer_note).and_return('footer')
95
+ expect(Bullet).to receive(:perform_out_of_channel_notifications)
96
+ end
97
+
98
+ it 'should change response body if add_footer is true' do
99
+ expect(Bullet).to receive(:add_footer).exactly(3).times.and_return(true)
100
+ _, headers, response = middleware.call('Content-Type' => 'text/html')
101
+
102
+ expect(headers['Content-Length']).to eq((73 + middleware.send(:footer_note).length).to_s)
103
+ expect(response).to eq(%w[<html><head></head><body>footer<bullet></bullet><script></script></body></html>])
104
+ end
105
+
106
+ it 'should change response body for html safe string if add_footer is true' do
107
+ expect(Bullet).to receive(:add_footer).exactly(3).times.and_return(true)
108
+ app.response =
109
+ Support::ResponseDouble.new.tap do |response|
110
+ response.body = ActiveSupport::SafeBuffer.new('<html><head></head><body></body></html>')
111
+ end
112
+ _, headers, response = middleware.call('Content-Type' => 'text/html')
113
+
114
+ expect(headers['Content-Length']).to eq((73 + middleware.send(:footer_note).length).to_s)
115
+ expect(response).to eq(%w[<html><head></head><body>footer<bullet></bullet><script></script></body></html>])
116
+ end
117
+
118
+ it 'should add the footer-text header for non-html requests when add_footer is true' do
119
+ allow(Bullet).to receive(:add_footer).at_least(:once).and_return(true)
120
+ allow(Bullet).to receive(:footer_info).and_return(['footer text'])
121
+ app.headers = { 'Content-Type' => 'application/json' }
122
+ _, headers, _response = middleware.call({})
123
+ expect(headers).to include('X-bullet-footer-text' => '["footer text"]')
124
+ end
125
+
126
+ it 'should change response body if console_enabled is true' do
127
+ expect(Bullet).to receive(:console_enabled?).and_return(true)
128
+ _, headers, response = middleware.call('Content-Type' => 'text/html')
129
+ expect(headers['Content-Length']).to eq('56')
130
+ expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])
131
+ end
132
+
133
+ it 'should change response body for html safe string if console_enabled is true' do
134
+ expect(Bullet).to receive(:console_enabled?).and_return(true)
135
+ app.response =
136
+ Support::ResponseDouble.new.tap do |response|
137
+ response.body = ActiveSupport::SafeBuffer.new('<html><head></head><body></body></html>')
138
+ end
139
+ _, headers, response = middleware.call('Content-Type' => 'text/html')
140
+ expect(headers['Content-Length']).to eq('56')
141
+ expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])
142
+ end
143
+
144
+ it 'should add headers for non-html requests when console_enabled is true' do
145
+ allow(Bullet).to receive(:console_enabled?).at_least(:once).and_return(true)
146
+ allow(Bullet).to receive(:text_notifications).and_return(['text notifications'])
147
+ app.headers = { 'Content-Type' => 'application/json' }
148
+ _, headers, _response = middleware.call({})
149
+ expect(headers).to include('X-bullet-console-text' => '["text notifications"]')
150
+ end
151
+
152
+ it "shouldn't change response body unnecessarily" do
153
+ expected_response = Support::ResponseDouble.new 'Actual body'
154
+ app.response = expected_response
155
+ _, _, response = middleware.call({})
156
+ expect(response).to eq(expected_response)
157
+ end
158
+
159
+ it "shouldn't add headers unnecessarily" do
160
+ app.headers = { 'Content-Type' => 'application/json' }
161
+ _, headers, _response = middleware.call({})
162
+ expect(headers).not_to include('X-bullet-footer-text')
163
+ expect(headers).not_to include('X-bullet-console-text')
164
+ end
165
+
166
+ context 'when skip_http_headers is enabled' do
167
+ before do
168
+ allow(Bullet).to receive(:skip_http_headers).and_return(true)
169
+ end
170
+
171
+ it 'should include the footer but not the xhr script tag if add_footer is true' do
172
+ expect(Bullet).to receive(:add_footer).at_least(:once).and_return(true)
173
+ _, headers, response = middleware.call({})
174
+
175
+ expect(headers['Content-Length']).to eq((56 + middleware.send(:footer_note).length).to_s)
176
+ expect(response).to eq(%w[<html><head></head><body>footer<bullet></bullet></body></html>])
177
+ end
178
+
179
+ it 'should not include the xhr script tag if console_enabled is true' do
180
+ expect(Bullet).to receive(:console_enabled?).and_return(true)
181
+ _, headers, response = middleware.call({})
182
+ expect(headers['Content-Length']).to eq('56')
183
+ expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])
184
+ end
185
+
186
+ it 'should not add the footer-text header for non-html requests when add_footer is true' do
187
+ allow(Bullet).to receive(:add_footer).at_least(:once).and_return(true)
188
+ app.headers = { 'Content-Type' => 'application/json' }
189
+ _, headers, _response = middleware.call({})
190
+ expect(headers).not_to include('X-bullet-footer-text')
191
+ end
192
+
193
+ it 'should not add headers for non-html requests when console_enabled is true' do
194
+ allow(Bullet).to receive(:console_enabled?).at_least(:once).and_return(true)
195
+ app.headers = { 'Content-Type' => 'application/json' }
196
+ _, headers, _response = middleware.call({})
197
+ expect(headers).not_to include('X-bullet-console-text')
198
+ end
199
+ end
87
200
  end
88
201
 
89
202
  context 'when skip_html_injection is enabled' do
@@ -148,6 +261,20 @@ module Bullet
148
261
  expect(middleware.response_body(response)).to eq body_string
149
262
  end
150
263
  end
264
+
265
+ begin
266
+ require 'rack/files'
267
+
268
+ context 'when `response` is a Rack::Files::Iterator' do
269
+ let(:response) { instance_double(::Rack::Files::Iterator) }
270
+ before { allow(response).to receive(:is_a?).with(::Rack::Files::Iterator) { true } }
271
+
272
+ it 'should return nil' do
273
+ expect(middleware.response_body(response)).to be_nil
274
+ end
275
+ end
276
+ rescue LoadError
277
+ end
151
278
  end
152
279
  end
153
280
  end
data/spec/bullet_spec.rb CHANGED
@@ -74,31 +74,60 @@ describe Bullet, focused: true do
74
74
  end
75
75
  end
76
76
 
77
+ describe '#add_safelist' do
78
+ context "for 'special' class names" do
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
82
+ end
83
+ end
84
+ end
85
+
77
86
  describe '#add_whitelist' do
78
87
  context "for 'special' class names" do
79
- it 'is added to the whitelist successfully' do
88
+ it 'is added to the safelist successfully' do
80
89
  Bullet.add_whitelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
81
- expect(Bullet.get_whitelist_associations(:n_plus_one_query, 'Klass')).to include :department
90
+ expect(Bullet.get_safelist_associations(:n_plus_one_query, 'Klass')).to include :department
91
+ end
92
+ end
93
+ end
94
+
95
+ describe '#delete_safelist' do
96
+ context "for 'special' class names" do
97
+ it 'is deleted from the safelist successfully' do
98
+ Bullet.add_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
99
+ Bullet.delete_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
100
+ expect(Bullet.safelist[:n_plus_one_query]).to eq({})
101
+ end
102
+ end
103
+
104
+ context 'when exists multiple definitions' do
105
+ it 'is deleted from the safelist successfully' do
106
+ Bullet.add_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
107
+ Bullet.add_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :team)
108
+ Bullet.delete_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :team)
109
+ expect(Bullet.get_safelist_associations(:n_plus_one_query, 'Klass')).to include :department
110
+ expect(Bullet.get_safelist_associations(:n_plus_one_query, 'Klass')).to_not include :team
82
111
  end
83
112
  end
84
113
  end
85
114
 
86
115
  describe '#delete_whitelist' do
87
116
  context "for 'special' class names" do
88
- it 'is deleted from the whitelist successfully' do
89
- Bullet.add_whitelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
117
+ it 'is deleted from the safelist successfully' do
118
+ Bullet.add_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
90
119
  Bullet.delete_whitelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
91
- expect(Bullet.whitelist[:n_plus_one_query]).to eq({})
120
+ expect(Bullet.safelist[:n_plus_one_query]).to eq({})
92
121
  end
93
122
  end
94
123
 
95
124
  context 'when exists multiple definitions' do
96
- it 'is deleted from the whitelist successfully' do
97
- Bullet.add_whitelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
98
- Bullet.add_whitelist(type: :n_plus_one_query, class_name: 'Klass', association: :team)
125
+ it 'is deleted from the safelist successfully' do
126
+ Bullet.add_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
127
+ Bullet.add_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :team)
99
128
  Bullet.delete_whitelist(type: :n_plus_one_query, class_name: 'Klass', association: :team)
100
- expect(Bullet.get_whitelist_associations(:n_plus_one_query, 'Klass')).to include :department
101
- expect(Bullet.get_whitelist_associations(:n_plus_one_query, 'Klass')).to_not include :team
129
+ expect(Bullet.get_safelist_associations(:n_plus_one_query, 'Klass')).to include :department
130
+ expect(Bullet.get_safelist_associations(:n_plus_one_query, 'Klass')).to_not include :team
102
131
  end
103
132
  end
104
133
  end