bullet 5.9.0 → 6.1.1

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 (64) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +22 -1
  3. data/CHANGELOG.md +27 -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/README.md +31 -10
  13. data/lib/bullet.rb +50 -26
  14. data/lib/bullet/active_job.rb +13 -0
  15. data/lib/bullet/active_record4.rb +9 -32
  16. data/lib/bullet/active_record41.rb +7 -27
  17. data/lib/bullet/active_record42.rb +8 -24
  18. data/lib/bullet/active_record5.rb +188 -179
  19. data/lib/bullet/active_record52.rb +176 -168
  20. data/lib/bullet/active_record60.rb +267 -0
  21. data/lib/bullet/active_record61.rb +267 -0
  22. data/lib/bullet/bullet_xhr.js +63 -0
  23. data/lib/bullet/dependency.rb +50 -36
  24. data/lib/bullet/detector/association.rb +26 -20
  25. data/lib/bullet/detector/base.rb +1 -2
  26. data/lib/bullet/detector/counter_cache.rb +13 -9
  27. data/lib/bullet/detector/n_plus_one_query.rb +22 -12
  28. data/lib/bullet/detector/unused_eager_loading.rb +6 -3
  29. data/lib/bullet/ext/object.rb +4 -2
  30. data/lib/bullet/mongoid4x.rb +2 -6
  31. data/lib/bullet/mongoid5x.rb +2 -6
  32. data/lib/bullet/mongoid6x.rb +2 -6
  33. data/lib/bullet/mongoid7x.rb +2 -6
  34. data/lib/bullet/notification/base.rb +14 -18
  35. data/lib/bullet/notification/n_plus_one_query.rb +2 -4
  36. data/lib/bullet/notification/unused_eager_loading.rb +2 -4
  37. data/lib/bullet/rack.rb +50 -25
  38. data/lib/bullet/stack_trace_filter.rb +6 -12
  39. data/lib/bullet/version.rb +1 -1
  40. data/lib/generators/bullet/install_generator.rb +23 -23
  41. data/perf/benchmark.rb +8 -14
  42. data/spec/bullet/detector/counter_cache_spec.rb +6 -6
  43. data/spec/bullet/detector/n_plus_one_query_spec.rb +7 -3
  44. data/spec/bullet/detector/unused_eager_loading_spec.rb +19 -6
  45. data/spec/bullet/ext/object_spec.rb +9 -4
  46. data/spec/bullet/notification/base_spec.rb +1 -3
  47. data/spec/bullet/notification/n_plus_one_query_spec.rb +16 -3
  48. data/spec/bullet/notification/unused_eager_loading_spec.rb +5 -1
  49. data/spec/bullet/rack_spec.rb +86 -6
  50. data/spec/bullet/registry/association_spec.rb +2 -2
  51. data/spec/bullet/registry/base_spec.rb +1 -1
  52. data/spec/bullet_spec.rb +11 -30
  53. data/spec/integration/active_record/association_spec.rb +44 -136
  54. data/spec/integration/counter_cache_spec.rb +11 -31
  55. data/spec/integration/mongoid/association_spec.rb +18 -32
  56. data/spec/models/folder.rb +1 -2
  57. data/spec/models/group.rb +1 -2
  58. data/spec/models/page.rb +1 -2
  59. data/spec/models/writer.rb +1 -2
  60. data/spec/spec_helper.rb +6 -10
  61. data/spec/support/bullet_ext.rb +8 -9
  62. data/spec/support/mongo_seed.rb +2 -16
  63. data/test.sh +1 -0
  64. metadata +12 -7
@@ -5,12 +5,12 @@ module Bullet
5
5
  VENDOR_PATH = '/vendor'
6
6
 
7
7
  def caller_in_project
8
- app_root = rails? ? Rails.root.to_s : Dir.pwd
9
- vendor_root = app_root + VENDOR_PATH
8
+ vendor_root = Bullet.app_root + VENDOR_PATH
10
9
  bundler_path = Bundler.bundle_path.to_s
11
10
  select_caller_locations do |location|
12
11
  caller_path = location_as_path(location)
13
- caller_path.include?(app_root) && !caller_path.include?(vendor_root) && !caller_path.include?(bundler_path) ||
12
+ caller_path.include?(Bullet.app_root) && !caller_path.include?(vendor_root) &&
13
+ !caller_path.include?(bundler_path) ||
14
14
  Bullet.stacktrace_includes.any? { |include_pattern| pattern_matches?(location, include_pattern) }
15
15
  end
16
16
  end
@@ -52,20 +52,14 @@ module Bullet
52
52
 
53
53
  def select_caller_locations
54
54
  if ruby_19?
55
- caller.select do |caller_path|
56
- yield caller_path
57
- end
55
+ caller.select { |caller_path| yield caller_path }
58
56
  else
59
- caller_locations.select do |location|
60
- yield location
61
- end
57
+ caller_locations.select { |location| yield location }
62
58
  end
63
59
  end
64
60
 
65
61
  def ruby_19?
66
- if @ruby_19.nil?
67
- @ruby_19 = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.0.0')
68
- end
62
+ @ruby_19 = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.0.0') if @ruby_19.nil?
69
63
  @ruby_19
70
64
  end
71
65
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bullet
4
- VERSION = '5.9.0'
4
+ VERSION = '6.1.1'
5
5
  end
@@ -10,17 +10,17 @@ module Bullet
10
10
 
11
11
  def enable_in_development
12
12
  environment(nil, env: 'development') do
13
- <<-"FILE".strip
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
25
  end
26
26
 
@@ -28,20 +28,20 @@ module Bullet
28
28
  end
29
29
 
30
30
  def enable_in_test
31
- if yes?('Would you like to enable bullet in test environment? (y/n)')
32
- environment(nil, env: 'test') do
33
- <<-"FILE".strip
34
-
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
- FILE
41
- end
31
+ return unless yes?('Would you like to enable bullet in test environment? (y/n)')
42
32
 
43
- 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
44
42
  end
43
+
44
+ say 'Enabled bullet in config/environments/test.rb'
45
45
  end
46
46
  end
47
47
  end
@@ -29,11 +29,11 @@ class User < ActiveRecord::Base
29
29
  end
30
30
 
31
31
  # create database bullet_benchmark;
32
- ActiveRecord::Base.establish_connection(adapter: 'mysql2', database: 'bullet_benchmark', server: '/tmp/mysql.socket', username: 'root')
32
+ ActiveRecord::Base.establish_connection(
33
+ adapter: 'mysql2', database: 'bullet_benchmark', server: '/tmp/mysql.socket', username: 'root'
34
+ )
33
35
 
34
- ActiveRecord::Base.connection.tables.each do |table|
35
- ActiveRecord::Base.connection.drop_table(table)
36
- end
36
+ ActiveRecord::Base.connection.tables.each { |table| ActiveRecord::Base.connection.drop_table(table) }
37
37
 
38
38
  ActiveRecord::Schema.define(version: 1) do
39
39
  create_table :posts do |t|
@@ -54,26 +54,20 @@ ActiveRecord::Schema.define(version: 1) do
54
54
  end
55
55
 
56
56
  users_size = 100
57
- posts_size = 1000
57
+ posts_size = 1_000
58
58
  comments_size = 10_000
59
59
  users = []
60
- users_size.times do |i|
61
- users << User.new(name: "user#{i}")
62
- end
60
+ users_size.times { |i| users << User.new(name: "user#{i}") }
63
61
  User.import users
64
62
  users = User.all
65
63
 
66
64
  posts = []
67
- posts_size.times do |i|
68
- posts << Post.new(title: "Title #{i}", body: "Body #{i}", user: users[i % 100])
69
- end
65
+ posts_size.times { |i| posts << Post.new(title: "Title #{i}", body: "Body #{i}", user: users[i % 100]) }
70
66
  Post.import posts
71
67
  posts = Post.all
72
68
 
73
69
  comments = []
74
- comments_size.times do |i|
75
- comments << Comment.new(body: "Comment #{i}", post: posts[i % 1000], user: users[i % 100])
76
- end
70
+ comments_size.times { |i| comments << Comment.new(body: "Comment #{i}", post: posts[i % 1_000], user: users[i % 100]) }
77
71
  Comment.import comments
78
72
 
79
73
  puts 'Start benchmarking...'
@@ -12,15 +12,15 @@ module Bullet
12
12
 
13
13
  context '.add_counter_cache' do
14
14
  it 'should create notification if conditions met' do
15
- expect(CounterCache).to receive(:conditions_met?).with(@post1, [:comments]).and_return(true)
16
- expect(CounterCache).to receive(:create_notification).with('Post', [:comments])
17
- CounterCache.add_counter_cache(@post1, [:comments])
15
+ expect(CounterCache).to receive(:conditions_met?).with(@post1, %i[comments]).and_return(true)
16
+ expect(CounterCache).to receive(:create_notification).with('Post', %i[comments])
17
+ CounterCache.add_counter_cache(@post1, %i[comments])
18
18
  end
19
19
 
20
20
  it 'should not create notification if conditions not met' do
21
- expect(CounterCache).to receive(:conditions_met?).with(@post1, [:comments]).and_return(false)
21
+ expect(CounterCache).to receive(:conditions_met?).with(@post1, %i[comments]).and_return(false)
22
22
  expect(CounterCache).to receive(:create_notification).never
23
- CounterCache.add_counter_cache(@post1, [:comments])
23
+ CounterCache.add_counter_cache(@post1, %i[comments])
24
24
  end
25
25
  end
26
26
 
@@ -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
@@ -76,8 +76,8 @@ module Bullet
76
76
  context '.call_association' do
77
77
  it 'should create notification if conditions met' do
78
78
  expect(NPlusOneQuery).to receive(:conditions_met?).with(@post, :association).and_return(true)
79
- expect(NPlusOneQuery).to receive(:caller_in_project).and_return(['caller'])
80
- expect(NPlusOneQuery).to receive(:create_notification).with(['caller'], 'Post', :association)
79
+ expect(NPlusOneQuery).to receive(:caller_in_project).and_return(%w[caller])
80
+ expect(NPlusOneQuery).to receive(:create_notification).with(%w[caller], 'Post', :association)
81
81
  NPlusOneQuery.call_association(@post, :association)
82
82
  end
83
83
 
@@ -149,7 +149,11 @@ module Bullet
149
149
 
150
150
  expect(NPlusOneQuery).to receive(:caller_locations).and_return([in_project, *included_gems, excluded_gem])
151
151
  expect(NPlusOneQuery).to receive(:conditions_met?).with(@post, :association).and_return(true)
152
- expect(NPlusOneQuery).to receive(:create_notification).with([in_project, *included_gems], 'Post', :association)
152
+ expect(NPlusOneQuery).to receive(:create_notification).with(
153
+ [in_project, *included_gems],
154
+ 'Post',
155
+ :association
156
+ )
153
157
  NPlusOneQuery.call_association(@post, :association)
154
158
  end
155
159
  end
@@ -36,12 +36,14 @@ module Bullet
36
36
  it 'should return empty if associations exist in call_association' do
37
37
  UnusedEagerLoading.add_eager_loadings([@post], :association)
38
38
  UnusedEagerLoading.add_call_object_associations(@post, :association)
39
- expect(UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new([:association]))).to be_empty
39
+ expect(
40
+ UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new([:association]))
41
+ ).to be_empty
40
42
  end
41
43
  end
42
44
 
43
45
  context '.check_unused_preload_associations' do
44
- let(:paths) { ['/dir1', '/dir1/subdir'] }
46
+ let(:paths) { %w[/dir1 /dir1/subdir] }
45
47
  it 'should create notification if object_association_diff is not empty' do
46
48
  UnusedEagerLoading.add_object_associations(@post, :association)
47
49
  allow(UnusedEagerLoading).to receive(:caller_in_project).and_return(paths)
@@ -53,7 +55,9 @@ module Bullet
53
55
  UnusedEagerLoading.add_object_associations(@post, :association)
54
56
  UnusedEagerLoading.add_eager_loadings([@post], :association)
55
57
  UnusedEagerLoading.add_call_object_associations(@post, :association)
56
- expect(UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new([:association]))).to be_empty
58
+ expect(
59
+ UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new([:association]))
60
+ ).to be_empty
57
61
  expect(UnusedEagerLoading).not_to receive(:create_notification).with('Post', [:association])
58
62
  UnusedEagerLoading.check_unused_preload_associations
59
63
  end
@@ -62,14 +66,23 @@ module Bullet
62
66
  context '.add_eager_loadings' do
63
67
  it 'should add objects, associations pair when eager_loadings are empty' do
64
68
  UnusedEagerLoading.add_eager_loadings([@post, @post2], :associations)
65
- expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post.bullet_key, @post2.bullet_key], :associations)
69
+ expect(UnusedEagerLoading.send(:eager_loadings)).to be_include(
70
+ [@post.bullet_key, @post2.bullet_key],
71
+ :associations
72
+ )
66
73
  end
67
74
 
68
75
  it 'should add objects, associations pair for existing eager_loadings' do
69
76
  UnusedEagerLoading.add_eager_loadings([@post, @post2], :association1)
70
77
  UnusedEagerLoading.add_eager_loadings([@post, @post2], :association2)
71
- expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post.bullet_key, @post2.bullet_key], :association1)
72
- expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post.bullet_key, @post2.bullet_key], :association2)
78
+ expect(UnusedEagerLoading.send(:eager_loadings)).to be_include(
79
+ [@post.bullet_key, @post2.bullet_key],
80
+ :association1
81
+ )
82
+ expect(UnusedEagerLoading.send(:eager_loadings)).to be_include(
83
+ [@post.bullet_key, @post2.bullet_key],
84
+ :association2
85
+ )
73
86
  end
74
87
 
75
88
  it 'should merge objects, associations pair for existing eager_loadings' do
@@ -17,23 +17,28 @@ describe Object do
17
17
  end
18
18
  end
19
19
 
20
- context 'primary_key_value' do
20
+ context 'bullet_primary_key_value' do
21
21
  it 'should return id' do
22
22
  post = Post.first
23
- expect(post.primary_key_value).to eq(post.id)
23
+ expect(post.bullet_primary_key_value).to eq(post.id)
24
24
  end
25
25
 
26
26
  it 'should return primary key value' do
27
27
  post = Post.first
28
28
  Post.primary_key = 'name'
29
- expect(post.primary_key_value).to eq(post.name)
29
+ expect(post.bullet_primary_key_value).to eq(post.name)
30
30
  Post.primary_key = 'id'
31
31
  end
32
32
 
33
33
  it 'should return value for multiple primary keys' do
34
34
  post = Post.first
35
35
  allow(Post).to receive(:primary_keys).and_return(%i[category_id writer_id])
36
- expect(post.primary_key_value).to eq("#{post.category_id},#{post.writer_id}")
36
+ expect(post.bullet_primary_key_value).to eq("#{post.category_id},#{post.writer_id}")
37
+ end
38
+
39
+ it 'it should return nil for unpersisted records' do
40
+ post = Post.new(id: 123)
41
+ expect(post.bullet_primary_key_value).to be_nil
37
42
  end
38
43
  end
39
44
  end
@@ -26,9 +26,7 @@ module Bullet
26
26
  end
27
27
 
28
28
  it 'should leverage ENV parameter' do
29
- temp_env_variable('USER', 'bogus') do
30
- expect(subject.whoami).to eq('user: bogus')
31
- end
29
+ temp_env_variable('USER', 'bogus') { expect(subject.whoami).to eq('user: bogus') }
32
30
  end
33
31
 
34
32
  it 'should return blank if no user available' do
@@ -7,9 +7,22 @@ module Bullet
7
7
  describe NPlusOneQuery do
8
8
  subject { NPlusOneQuery.new([%w[caller1 caller2]], Post, %i[comments votes], 'path') }
9
9
 
10
- it { expect(subject.body_with_caller).to eq(" Post => [:comments, :votes]\n Add to your finder: :includes => [:comments, :votes]\nCall stack\n caller1\n caller2\n") }
11
- it { expect([subject.body_with_caller, subject.body_with_caller]).to eq([" Post => [:comments, :votes]\n Add to your finder: :includes => [:comments, :votes]\nCall stack\n caller1\n caller2\n", " Post => [:comments, :votes]\n Add to your finder: :includes => [:comments, :votes]\nCall stack\n caller1\n caller2\n"]) }
12
- it { expect(subject.body).to eq(" Post => [:comments, :votes]\n Add to your finder: :includes => [:comments, :votes]") }
10
+ it do
11
+ expect(subject.body_with_caller).to eq(
12
+ " Post => [:comments, :votes]\n Add to your query: .includes([:comments, :votes])\nCall stack\n caller1\n caller2\n"
13
+ )
14
+ end
15
+ it do
16
+ expect([subject.body_with_caller, subject.body_with_caller]).to eq(
17
+ [
18
+ " Post => [:comments, :votes]\n Add to your query: .includes([:comments, :votes])\nCall stack\n caller1\n caller2\n",
19
+ " Post => [:comments, :votes]\n Add to your query: .includes([:comments, :votes])\nCall stack\n caller1\n caller2\n"
20
+ ]
21
+ )
22
+ end
23
+ it do
24
+ expect(subject.body).to eq(" Post => [:comments, :votes]\n Add to your query: .includes([:comments, :votes])")
25
+ end
13
26
  it { expect(subject.title).to eq('USE eager loading in path') }
14
27
  end
15
28
  end
@@ -7,7 +7,11 @@ module Bullet
7
7
  describe UnusedEagerLoading do
8
8
  subject { UnusedEagerLoading.new([''], Post, %i[comments votes], 'path') }
9
9
 
10
- it { expect(subject.body).to eq(" Post => [:comments, :votes]\n Remove from your finder: :includes => [:comments, :votes]") }
10
+ it do
11
+ expect(subject.body).to eq(
12
+ " Post => [:comments, :votes]\n Remove from your query: .includes([:comments, :votes])"
13
+ )
14
+ end
11
15
  it { expect(subject.title).to eq('AVOID eager loading in path') }
12
16
  end
13
17
  end
@@ -45,9 +45,9 @@ module Bullet
45
45
  expect(middleware).not_to be_empty(response)
46
46
  end
47
47
 
48
- it 'should be true if response is not found' do
48
+ it 'should be false if response is not found' do
49
49
  response = ['Not Found']
50
- expect(middleware).to be_empty(response)
50
+ expect(middleware).not_to be_empty(response)
51
51
  end
52
52
 
53
53
  it 'should be true if response body is empty' do
@@ -67,11 +67,13 @@ 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
+ expect(Bullet).to receive(:console_enabled?).and_return(true)
70
71
  expect(Bullet).to receive(:gather_inline_notifications).and_return('<bullet></bullet>')
72
+ expect(middleware).to receive(:xhr_script).and_return('')
71
73
  expect(Bullet).to receive(:perform_out_of_channel_notifications)
72
- status, headers, response = middleware.call('Content-Type' => 'text/html')
74
+ _, headers, response = middleware.call('Content-Type' => 'text/html')
73
75
  expect(headers['Content-Length']).to eq('56')
74
- expect(response).to eq(['<html><head></head><body><bullet></bullet></body></html>'])
76
+ expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])
75
77
  end
76
78
 
77
79
  it 'should set the right Content-Length if response body contains accents' do
@@ -79,9 +81,79 @@ module Bullet
79
81
  response.body = '<html><head></head><body>é</body></html>'
80
82
  app.response = response
81
83
  expect(Bullet).to receive(:notification?).and_return(true)
84
+ allow(Bullet).to receive(:console_enabled?).and_return(true)
82
85
  expect(Bullet).to receive(:gather_inline_notifications).and_return('<bullet></bullet>')
83
- status, headers, response = middleware.call('Content-Type' => 'text/html')
84
- expect(headers['Content-Length']).to eq('58')
86
+ _, headers, response = middleware.call('Content-Type' => 'text/html')
87
+ expect(headers['Content-Length']).to eq((58 + middleware.send(:xhr_script).length).to_s)
88
+ end
89
+
90
+ context 'with injection notifiers' do
91
+ before do
92
+ expect(Bullet).to receive(:notification?).and_return(true)
93
+ allow(Bullet).to receive(:gather_inline_notifications).and_return('<bullet></bullet>')
94
+ allow(middleware).to receive(:xhr_script).and_return('')
95
+ allow(middleware).to receive(:footer_note).and_return('footer')
96
+ expect(Bullet).to receive(:perform_out_of_channel_notifications)
97
+ end
98
+
99
+ it 'should change response body if add_footer is true' do
100
+ expect(Bullet).to receive(:add_footer).twice.and_return(true)
101
+ _, headers, response = middleware.call('Content-Type' => 'text/html')
102
+
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><')
106
+ end
107
+
108
+ 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
113
+ _, headers, response = middleware.call('Content-Type' => 'text/html')
114
+
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><')
118
+ end
119
+
120
+ it 'should change response body if console_enabled is true' do
121
+ expect(Bullet).to receive(:console_enabled?).and_return(true)
122
+ _, headers, response = middleware.call('Content-Type' => 'text/html')
123
+ expect(headers['Content-Length']).to eq('56')
124
+ expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])
125
+ end
126
+
127
+ it 'should change response body for html safe string if console_enabled is true' do
128
+ 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
132
+ _, headers, response = middleware.call('Content-Type' => 'text/html')
133
+ expect(headers['Content-Length']).to eq('56')
134
+ expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])
135
+ end
136
+
137
+ it "shouldn't change response body unnecessarily" do
138
+ expected_response = Support::ResponseDouble.new 'Actual body'
139
+ app.response = expected_response
140
+ _, _, response = middleware.call({})
141
+ expect(response).to eq(expected_response)
142
+ end
143
+ end
144
+
145
+ context 'when skip_html_injection is enabled' do
146
+ it 'should not try to inject html' do
147
+ expected_response = Support::ResponseDouble.new 'Actual body'
148
+ app.response = expected_response
149
+ allow(Bullet).to receive(:notification?).and_return(true)
150
+ allow(Bullet).to receive(:skip_html_injection?).and_return(true)
151
+ expect(Bullet).to receive(:gather_inline_notifications).never
152
+ expect(middleware).to receive(:xhr_script).never
153
+ expect(Bullet).to receive(:perform_out_of_channel_notifications)
154
+ _, _, response = middleware.call('Content-Type' => 'text/html')
155
+ expect(response).to eq(expected_response)
156
+ end
85
157
  end
86
158
  end
87
159
 
@@ -95,6 +167,14 @@ module Bullet
95
167
  end
96
168
  end
97
169
 
170
+ context '#set_header' do
171
+ it 'should truncate headers to under 8kb' do
172
+ long_header = ['a' * 1_024] * 10
173
+ expected_res = (['a' * 1_024] * 7).to_json
174
+ expect(middleware.set_header({}, 'Dummy-Header', long_header)).to eq(expected_res)
175
+ end
176
+ end
177
+
98
178
  describe '#response_body' do
99
179
  let(:response) { double }
100
180
  let(:body_string) { '<html><body>My Body</body></html>' }