bullet 6.1.2 → 7.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +82 -0
  3. data/CHANGELOG.md +22 -0
  4. data/Gemfile.rails-7.0 +10 -0
  5. data/README.md +10 -7
  6. data/lib/bullet/active_record41.rb +1 -0
  7. data/lib/bullet/active_record42.rb +1 -0
  8. data/lib/bullet/active_record52.rb +22 -17
  9. data/lib/bullet/active_record60.rb +22 -17
  10. data/lib/bullet/active_record61.rb +22 -17
  11. data/lib/bullet/active_record70.rb +261 -0
  12. data/lib/bullet/bullet_xhr.js +1 -0
  13. data/lib/bullet/dependency.rb +10 -0
  14. data/lib/bullet/detector/base.rb +2 -1
  15. data/lib/bullet/detector/counter_cache.rb +1 -1
  16. data/lib/bullet/detector/n_plus_one_query.rb +3 -3
  17. data/lib/bullet/detector/unused_eager_loading.rb +1 -1
  18. data/lib/bullet/mongoid4x.rb +1 -1
  19. data/lib/bullet/mongoid5x.rb +1 -1
  20. data/lib/bullet/mongoid6x.rb +1 -1
  21. data/lib/bullet/mongoid7x.rb +24 -7
  22. data/lib/bullet/notification.rb +2 -1
  23. data/lib/bullet/rack.rb +5 -3
  24. data/lib/bullet/stack_trace_filter.rb +7 -9
  25. data/lib/bullet/version.rb +1 -1
  26. data/lib/bullet.rb +68 -22
  27. data/perf/benchmark.rb +4 -1
  28. data/spec/bullet/detector/unused_eager_loading_spec.rb +6 -2
  29. data/spec/bullet/ext/object_spec.rb +1 -1
  30. data/spec/bullet/rack_spec.rb +88 -17
  31. data/spec/bullet_spec.rb +39 -10
  32. data/spec/integration/active_record/association_spec.rb +53 -8
  33. data/spec/integration/counter_cache_spec.rb +4 -4
  34. data/spec/integration/mongoid/association_spec.rb +1 -1
  35. data/spec/models/attachment.rb +5 -0
  36. data/spec/models/deal.rb +5 -0
  37. data/spec/models/folder.rb +2 -1
  38. data/spec/models/group.rb +2 -1
  39. data/spec/models/page.rb +2 -1
  40. data/spec/models/post.rb +2 -0
  41. data/spec/models/submission.rb +1 -0
  42. data/spec/models/user.rb +1 -0
  43. data/spec/models/writer.rb +2 -1
  44. data/spec/spec_helper.rb +0 -2
  45. data/spec/support/mongo_seed.rb +1 -0
  46. data/spec/support/sqlite_seed.rb +20 -0
  47. data/test.sh +2 -0
  48. metadata +10 -4
  49. data/.travis.yml +0 -33
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Bullet
4
4
  module Detector
5
- class Base; end
5
+ class Base
6
+ end
6
7
  end
7
8
  end
@@ -54,7 +54,7 @@ module Bullet
54
54
  private
55
55
 
56
56
  def create_notification(klazz, associations)
57
- notify_associations = Array(associations) - Bullet.get_whitelist_associations(:counter_cache, klazz)
57
+ notify_associations = Array(associations) - Bullet.get_safelist_associations(:counter_cache, klazz)
58
58
 
59
59
  if notify_associations.present?
60
60
  notice = Bullet::Notification::CounterCache.new klazz, notify_associations
@@ -35,6 +35,7 @@ module Bullet
35
35
 
36
36
  objects = Array(object_or_objects)
37
37
  return if objects.map(&:bullet_primary_key_value).compact.empty?
38
+ return if objects.all? { |obj| obj.class.name =~ /^HABTM_/ }
38
39
 
39
40
  Bullet.debug(
40
41
  'Detector::NPlusOneQuery#add_possible_objects',
@@ -84,8 +85,7 @@ module Bullet
84
85
  # associations == v comparison order is important here because
85
86
  # v variable might be a squeel node where :== method is redefined,
86
87
  # so it does not compare values at all and return unexpected results
87
- result =
88
- v.is_a?(Hash) ? v.key?(associations) : associations == v
88
+ result = v.is_a?(Hash) ? v.key?(associations) : associations == v
89
89
  return true if result
90
90
  end
91
91
 
@@ -95,7 +95,7 @@ module Bullet
95
95
  private
96
96
 
97
97
  def create_notification(callers, klazz, associations)
98
- notify_associations = Array(associations) - Bullet.get_whitelist_associations(:n_plus_one_query, klazz)
98
+ notify_associations = Array(associations) - Bullet.get_safelist_associations(:n_plus_one_query, klazz)
99
99
 
100
100
  if notify_associations.present?
101
101
  notice = Bullet::Notification::NPlusOneQuery.new(callers, klazz, notify_associations)
@@ -65,7 +65,7 @@ module Bullet
65
65
  private
66
66
 
67
67
  def create_notification(callers, klazz, associations)
68
- notify_associations = Array(associations) - Bullet.get_whitelist_associations(:unused_eager_loading, klazz)
68
+ notify_associations = Array(associations) - Bullet.get_safelist_associations(:unused_eager_loading, klazz)
69
69
 
70
70
  if notify_associations.present?
71
71
  notice = Bullet::Notification::UnusedEagerLoading.new(callers, klazz, notify_associations)
@@ -23,7 +23,7 @@ module Bullet
23
23
  end
24
24
 
25
25
  def each(&block)
26
- return to_enum unless block_given?
26
+ return to_enum unless block
27
27
 
28
28
  records = []
29
29
  origin_each { |record| records << record }
@@ -23,7 +23,7 @@ module Bullet
23
23
  end
24
24
 
25
25
  def each(&block)
26
- return to_enum unless block_given?
26
+ return to_enum unless block
27
27
 
28
28
  records = []
29
29
  origin_each { |record| records << record }
@@ -23,7 +23,7 @@ module Bullet
23
23
  end
24
24
 
25
25
  def each(&block)
26
- return to_enum unless block_given?
26
+ return to_enum unless block
27
27
 
28
28
  records = []
29
29
  origin_each { |record| records << record }
@@ -25,14 +25,31 @@ module Bullet
25
25
  def each(&block)
26
26
  return to_enum unless block_given?
27
27
 
28
- records = []
29
- origin_each { |record| records << record }
30
- if records.length > 1
31
- Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
32
- elsif records.size == 1
33
- Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
28
+ first_document = nil
29
+ document_count = 0
30
+
31
+ origin_each do |document|
32
+ document_count += 1
33
+
34
+ if document_count == 1
35
+ first_document = document
36
+ elsif document_count == 2
37
+ Bullet::Detector::NPlusOneQuery.add_possible_objects([first_document, document])
38
+ yield(first_document)
39
+ first_document = nil
40
+ yield(document)
41
+ else
42
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(document)
43
+ yield(document)
44
+ end
34
45
  end
35
- records.each(&block)
46
+
47
+ if document_count == 1
48
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(first_document)
49
+ yield(first_document)
50
+ end
51
+
52
+ self
36
53
  end
37
54
 
38
55
  def eager_load(docs)
@@ -7,6 +7,7 @@ module Bullet
7
7
  autoload :NPlusOneQuery, 'bullet/notification/n_plus_one_query'
8
8
  autoload :CounterCache, 'bullet/notification/counter_cache'
9
9
 
10
- class UnoptimizedQueryError < StandardError; end
10
+ class UnoptimizedQueryError < StandardError
11
+ end
11
12
  end
12
13
  end
data/lib/bullet/rack.rb CHANGED
@@ -22,9 +22,11 @@ module Bullet
22
22
  response_body = response_body(response)
23
23
  response_body = append_to_html_body(response_body, footer_note) if Bullet.add_footer
24
24
  response_body = append_to_html_body(response_body, Bullet.gather_inline_notifications)
25
- response_body = append_to_html_body(response_body, xhr_script)
25
+ if Bullet.add_footer && !Bullet.skip_http_headers
26
+ response_body = append_to_html_body(response_body, xhr_script)
27
+ end
26
28
  headers['Content-Length'] = response_body.bytesize.to_s
27
- else
29
+ elsif !Bullet.skip_http_headers
28
30
  set_header(headers, 'X-bullet-footer-text', Bullet.footer_info.uniq) if Bullet.add_footer
29
31
  set_header(headers, 'X-bullet-console-text', Bullet.text_notifications) if Bullet.console_enabled?
30
32
  end
@@ -82,7 +84,7 @@ module Bullet
82
84
  def response_body(response)
83
85
  if response.respond_to?(:body)
84
86
  Array === response.body ? response.body.first : response.body
85
- else
87
+ elsif response.respond_to?(:first)
86
88
  response.first
87
89
  end
88
90
  end
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
+ require "bundler"
2
3
 
3
4
  module Bullet
4
5
  module StackTraceFilter
5
6
  VENDOR_PATH = '/vendor'
7
+ IS_RUBY_19 = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.0.0')
6
8
 
7
9
  def caller_in_project
8
10
  vendor_root = Bullet.app_root + VENDOR_PATH
@@ -10,8 +12,9 @@ module Bullet
10
12
  select_caller_locations do |location|
11
13
  caller_path = location_as_path(location)
12
14
  caller_path.include?(Bullet.app_root) && !caller_path.include?(vendor_root) &&
13
- !caller_path.include?(bundler_path) ||
14
- Bullet.stacktrace_includes.any? { |include_pattern| pattern_matches?(location, include_pattern) }
15
+ !caller_path.include?(bundler_path) || Bullet.stacktrace_includes.any? { |include_pattern|
16
+ pattern_matches?(location, include_pattern)
17
+ }
15
18
  end
16
19
  end
17
20
 
@@ -47,20 +50,15 @@ module Bullet
47
50
  end
48
51
 
49
52
  def location_as_path(location)
50
- ruby_19? ? location : location.absolute_path.to_s
53
+ IS_RUBY_19 ? location : location.absolute_path.to_s
51
54
  end
52
55
 
53
56
  def select_caller_locations
54
- if ruby_19?
57
+ if IS_RUBY_19
55
58
  caller.select { |caller_path| yield caller_path }
56
59
  else
57
60
  caller_locations.select { |location| yield location }
58
61
  end
59
62
  end
60
-
61
- def ruby_19?
62
- @ruby_19 = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.0.0') if @ruby_19.nil?
63
- @ruby_19
64
- end
65
63
  end
66
64
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bullet
4
- VERSION = '6.1.2'
4
+ VERSION = '7.0.0'
5
5
  end
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
@@ -241,7 +285,9 @@ module Bullet
241
285
  end
242
286
 
243
287
  def inject_into_page?
244
- !@skip_html_injection && (console_enabled? || add_footer)
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
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) }
@@ -19,7 +19,9 @@ module Bullet
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([:association]))).to eq([:association])
22
+ expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new([:association]))).to eq(
23
+ [:association]
24
+ )
23
25
  end
24
26
 
25
27
  it 'should not get call associations if not exist in call_object_associations' do
@@ -30,7 +32,9 @@ module Bullet
30
32
 
31
33
  context '.diff_object_associations' do
32
34
  it 'should return associations not exist in call_association' do
33
- expect(UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new([:association]))).to eq([:association])
35
+ expect(UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new([:association]))).to eq(
36
+ [:association]
37
+ )
34
38
  end
35
39
 
36
40
  it 'should return empty if associations exist in call_association' do
@@ -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
@@ -69,7 +69,6 @@ module Bullet
69
69
  expect(Bullet).to receive(:notification?).and_return(true)
70
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')
@@ -84,37 +83,44 @@ module Bullet
84
83
  allow(Bullet).to receive(:console_enabled?).and_return(true)
85
84
  expect(Bullet).to receive(:gather_inline_notifications).and_return('<bullet></bullet>')
86
85
  _, headers, response = middleware.call('Content-Type' => 'text/html')
87
- expect(headers['Content-Length']).to eq((58 + middleware.send(:xhr_script).length).to_s)
86
+ expect(headers['Content-Length']).to eq('58')
88
87
  end
89
88
 
90
89
  context 'with injection notifiers' do
91
90
  before do
92
91
  expect(Bullet).to receive(:notification?).and_return(true)
93
92
  allow(Bullet).to receive(:gather_inline_notifications).and_return('<bullet></bullet>')
94
- allow(middleware).to receive(:xhr_script).and_return('')
93
+ allow(middleware).to receive(:xhr_script).and_return('<script></script>')
95
94
  allow(middleware).to receive(:footer_note).and_return('footer')
96
95
  expect(Bullet).to receive(:perform_out_of_channel_notifications)
97
96
  end
98
97
 
99
98
  it 'should change response body if add_footer is true' do
100
- expect(Bullet).to receive(:add_footer).twice.and_return(true)
99
+ expect(Bullet).to receive(:add_footer).exactly(3).times.and_return(true)
101
100
  _, headers, response = middleware.call('Content-Type' => 'text/html')
102
101
 
103
- expect(headers['Content-Length']).to eq((56 + middleware.send(:footer_note).length).to_s)
104
- expect(response.first).to start_with('<html><head></head><body>')
105
- expect(response.first).to include('<bullet></bullet><')
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>])
106
104
  end
107
105
 
108
106
  it 'should change response body for html safe string if add_footer is true' do
109
- expect(Bullet).to receive(:add_footer).twice.and_return(true)
110
- app.response = Support::ResponseDouble.new.tap do |response|
111
- response.body = ActiveSupport::SafeBuffer.new('<html><head></head><body></body></html>')
112
- end
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
113
112
  _, headers, response = middleware.call('Content-Type' => 'text/html')
114
113
 
115
- expect(headers['Content-Length']).to eq((56 + middleware.send(:footer_note).length).to_s)
116
- expect(response.first).to start_with('<html><head></head><body>')
117
- expect(response.first).to include('<bullet></bullet><')
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"]')
118
124
  end
119
125
 
120
126
  it 'should change response body if console_enabled is true' do
@@ -126,20 +132,71 @@ module Bullet
126
132
 
127
133
  it 'should change response body for html safe string if console_enabled is true' do
128
134
  expect(Bullet).to receive(:console_enabled?).and_return(true)
129
- app.response = Support::ResponseDouble.new.tap do |response|
130
- response.body = ActiveSupport::SafeBuffer.new('<html><head></head><body></body></html>')
131
- end
135
+ app.response =
136
+ Support::ResponseDouble.new.tap do |response|
137
+ response.body = ActiveSupport::SafeBuffer.new('<html><head></head><body></body></html>')
138
+ end
132
139
  _, headers, response = middleware.call('Content-Type' => 'text/html')
133
140
  expect(headers['Content-Length']).to eq('56')
134
141
  expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])
135
142
  end
136
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
+
137
152
  it "shouldn't change response body unnecessarily" do
138
153
  expected_response = Support::ResponseDouble.new 'Actual body'
139
154
  app.response = expected_response
140
155
  _, _, response = middleware.call({})
141
156
  expect(response).to eq(expected_response)
142
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
143
200
  end
144
201
 
145
202
  context 'when skip_html_injection is enabled' do
@@ -204,6 +261,20 @@ module Bullet
204
261
  expect(middleware.response_body(response)).to eq body_string
205
262
  end
206
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
207
278
  end
208
279
  end
209
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