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.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +82 -0
- data/CHANGELOG.md +31 -0
- data/Gemfile.rails-6.0 +1 -1
- data/Gemfile.rails-6.1 +15 -0
- data/Gemfile.rails-7.0 +10 -0
- data/README.md +17 -7
- data/lib/bullet/active_job.rb +5 -1
- data/lib/bullet/active_record41.rb +1 -0
- data/lib/bullet/active_record42.rb +1 -0
- data/lib/bullet/active_record52.rb +22 -17
- data/lib/bullet/active_record60.rb +22 -17
- data/lib/bullet/active_record61.rb +272 -0
- data/lib/bullet/active_record70.rb +261 -0
- data/lib/bullet/bullet_xhr.js +18 -17
- data/lib/bullet/dependency.rb +16 -0
- data/lib/bullet/detector/base.rb +2 -1
- data/lib/bullet/detector/counter_cache.rb +1 -1
- data/lib/bullet/detector/n_plus_one_query.rb +3 -3
- data/lib/bullet/detector/unused_eager_loading.rb +1 -1
- data/lib/bullet/mongoid4x.rb +1 -1
- data/lib/bullet/mongoid5x.rb +1 -1
- data/lib/bullet/mongoid6x.rb +1 -1
- data/lib/bullet/mongoid7x.rb +24 -7
- data/lib/bullet/notification.rb +2 -1
- data/lib/bullet/rack.rb +26 -18
- data/lib/bullet/stack_trace_filter.rb +7 -9
- data/lib/bullet/version.rb +1 -1
- data/lib/bullet.rb +69 -23
- data/lib/generators/bullet/install_generator.rb +23 -25
- data/perf/benchmark.rb +4 -1
- data/spec/bullet/detector/counter_cache_spec.rb +1 -1
- data/spec/bullet/detector/unused_eager_loading_spec.rb +10 -10
- data/spec/bullet/ext/object_spec.rb +1 -1
- data/spec/bullet/notification/n_plus_one_query_spec.rb +1 -3
- data/spec/bullet/rack_spec.rb +130 -3
- data/spec/bullet_spec.rb +39 -10
- data/spec/integration/active_record/association_spec.rb +54 -10
- data/spec/integration/counter_cache_spec.rb +4 -4
- data/spec/integration/mongoid/association_spec.rb +4 -4
- data/spec/models/attachment.rb +5 -0
- data/spec/models/deal.rb +5 -0
- data/spec/models/folder.rb +2 -1
- data/spec/models/group.rb +2 -1
- data/spec/models/page.rb +2 -1
- data/spec/models/post.rb +2 -0
- data/spec/models/submission.rb +1 -0
- data/spec/models/user.rb +1 -0
- data/spec/models/writer.rb +2 -1
- data/spec/spec_helper.rb +0 -2
- data/spec/support/mongo_seed.rb +1 -0
- data/spec/support/sqlite_seed.rb +20 -0
- data/test.sh +2 -0
- metadata +15 -7
- 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 :
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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] ==
|
|
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
|
|
244
|
-
@skip_html_injection
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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',
|
|
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
|
|
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(
|
|
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(
|
|
23
|
-
|
|
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(
|
|
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(
|
|
36
|
-
|
|
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(
|
|
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',
|
|
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(
|
|
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',
|
|
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
|
|
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
|
data/spec/bullet/rack_spec.rb
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
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
|
|
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.
|
|
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
|
|
89
|
-
Bullet.
|
|
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.
|
|
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
|
|
97
|
-
Bullet.
|
|
98
|
-
Bullet.
|
|
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.
|
|
101
|
-
expect(Bullet.
|
|
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
|