bullet 7.0.7 → 7.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -1
  3. data/README.md +3 -0
  4. data/lib/bullet/active_record4.rb +9 -0
  5. data/lib/bullet/active_record41.rb +9 -0
  6. data/lib/bullet/active_record42.rb +9 -0
  7. data/lib/bullet/active_record5.rb +14 -0
  8. data/lib/bullet/active_record52.rb +14 -0
  9. data/lib/bullet/active_record60.rb +14 -0
  10. data/lib/bullet/active_record61.rb +14 -0
  11. data/lib/bullet/active_record70.rb +28 -8
  12. data/lib/bullet/active_record71.rb +304 -0
  13. data/lib/bullet/dependency.rb +12 -0
  14. data/lib/bullet/detector/n_plus_one_query.rb +2 -5
  15. data/lib/bullet/detector/unused_eager_loading.rb +5 -2
  16. data/lib/bullet/ext/object.rb +2 -1
  17. data/lib/bullet/mongoid8x.rb +59 -0
  18. data/lib/bullet/notification/base.rb +9 -8
  19. data/lib/bullet/notification/counter_cache.rb +1 -1
  20. data/lib/bullet/rack.rb +4 -2
  21. data/lib/bullet/registry/association.rb +2 -1
  22. data/lib/bullet/stack_trace_filter.rb +3 -2
  23. data/lib/bullet/version.rb +1 -1
  24. data/lib/bullet.rb +7 -2
  25. metadata +7 -155
  26. data/.github/workflows/main.yml +0 -82
  27. data/.gitignore +0 -15
  28. data/.rspec +0 -2
  29. data/Gemfile +0 -24
  30. data/Gemfile.mongoid +0 -12
  31. data/Gemfile.mongoid-4.0 +0 -15
  32. data/Gemfile.mongoid-5.0 +0 -15
  33. data/Gemfile.mongoid-6.0 +0 -15
  34. data/Gemfile.mongoid-7.0 +0 -15
  35. data/Gemfile.rails-4.0 +0 -16
  36. data/Gemfile.rails-4.1 +0 -16
  37. data/Gemfile.rails-4.2 +0 -16
  38. data/Gemfile.rails-5.0 +0 -15
  39. data/Gemfile.rails-5.1 +0 -15
  40. data/Gemfile.rails-5.2 +0 -15
  41. data/Gemfile.rails-6.0 +0 -15
  42. data/Gemfile.rails-6.1 +0 -15
  43. data/Gemfile.rails-7.0 +0 -10
  44. data/Guardfile +0 -8
  45. data/Hacking.md +0 -75
  46. data/Rakefile +0 -51
  47. data/bullet.gemspec +0 -33
  48. data/perf/benchmark.rb +0 -118
  49. data/rails/init.rb +0 -3
  50. data/spec/bullet/detector/association_spec.rb +0 -28
  51. data/spec/bullet/detector/base_spec.rb +0 -10
  52. data/spec/bullet/detector/counter_cache_spec.rb +0 -58
  53. data/spec/bullet/detector/n_plus_one_query_spec.rb +0 -150
  54. data/spec/bullet/detector/unused_eager_loading_spec.rb +0 -126
  55. data/spec/bullet/ext/object_spec.rb +0 -44
  56. data/spec/bullet/ext/string_spec.rb +0 -15
  57. data/spec/bullet/notification/base_spec.rb +0 -94
  58. data/spec/bullet/notification/counter_cache_spec.rb +0 -14
  59. data/spec/bullet/notification/n_plus_one_query_spec.rb +0 -29
  60. data/spec/bullet/notification/unused_eager_loading_spec.rb +0 -18
  61. data/spec/bullet/notification_collector_spec.rb +0 -34
  62. data/spec/bullet/rack_spec.rb +0 -296
  63. data/spec/bullet/registry/association_spec.rb +0 -28
  64. data/spec/bullet/registry/base_spec.rb +0 -46
  65. data/spec/bullet/registry/object_spec.rb +0 -26
  66. data/spec/bullet/stack_trace_filter_spec.rb +0 -26
  67. data/spec/bullet_spec.rb +0 -136
  68. data/spec/integration/active_record/association_spec.rb +0 -822
  69. data/spec/integration/counter_cache_spec.rb +0 -68
  70. data/spec/integration/mongoid/association_spec.rb +0 -246
  71. data/spec/models/address.rb +0 -5
  72. data/spec/models/attachment.rb +0 -5
  73. data/spec/models/author.rb +0 -5
  74. data/spec/models/base_user.rb +0 -7
  75. data/spec/models/category.rb +0 -12
  76. data/spec/models/city.rb +0 -5
  77. data/spec/models/client.rb +0 -8
  78. data/spec/models/comment.rb +0 -8
  79. data/spec/models/company.rb +0 -5
  80. data/spec/models/country.rb +0 -5
  81. data/spec/models/deal.rb +0 -5
  82. data/spec/models/document.rb +0 -7
  83. data/spec/models/entry.rb +0 -5
  84. data/spec/models/firm.rb +0 -7
  85. data/spec/models/folder.rb +0 -4
  86. data/spec/models/group.rb +0 -4
  87. data/spec/models/mongoid/address.rb +0 -9
  88. data/spec/models/mongoid/category.rb +0 -10
  89. data/spec/models/mongoid/comment.rb +0 -9
  90. data/spec/models/mongoid/company.rb +0 -9
  91. data/spec/models/mongoid/entry.rb +0 -9
  92. data/spec/models/mongoid/post.rb +0 -14
  93. data/spec/models/mongoid/user.rb +0 -7
  94. data/spec/models/newspaper.rb +0 -5
  95. data/spec/models/page.rb +0 -4
  96. data/spec/models/person.rb +0 -5
  97. data/spec/models/pet.rb +0 -5
  98. data/spec/models/post.rb +0 -34
  99. data/spec/models/relationship.rb +0 -6
  100. data/spec/models/reply.rb +0 -5
  101. data/spec/models/role.rb +0 -7
  102. data/spec/models/student.rb +0 -5
  103. data/spec/models/submission.rb +0 -7
  104. data/spec/models/teacher.rb +0 -5
  105. data/spec/models/user.rb +0 -8
  106. data/spec/models/writer.rb +0 -4
  107. data/spec/spec_helper.rb +0 -97
  108. data/spec/support/bullet_ext.rb +0 -56
  109. data/spec/support/mongo_seed.rb +0 -59
  110. data/spec/support/rack_double.rb +0 -49
  111. data/spec/support/sqlite_seed.rb +0 -284
  112. data/test.sh +0 -15
  113. data/update.sh +0 -10
@@ -1,126 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- module Bullet
6
- module Detector
7
- describe UnusedEagerLoading do
8
- before(:all) do
9
- @post = Post.first
10
- @post2 = Post.all[1]
11
- @post3 = Post.last
12
- end
13
-
14
- context '.call_associations' do
15
- it 'should get empty array if eager_loadings' do
16
- expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new([:association]))).to be_empty
17
- end
18
-
19
- it 'should get call associations if object and association are both in eager_loadings and call_object_associations' do
20
- UnusedEagerLoading.add_eager_loadings([@post], :association)
21
- UnusedEagerLoading.add_call_object_associations(@post, :association)
22
- expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new([:association]))).to eq(
23
- [:association]
24
- )
25
- end
26
-
27
- it 'should not get call associations if not exist in call_object_associations' do
28
- UnusedEagerLoading.add_eager_loadings([@post], :association)
29
- expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new([:association]))).to be_empty
30
- end
31
- end
32
-
33
- context '.diff_object_associations' do
34
- it 'should return associations not exist in call_association' do
35
- expect(UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new([:association]))).to eq(
36
- [:association]
37
- )
38
- end
39
-
40
- it 'should return empty if associations exist in call_association' do
41
- UnusedEagerLoading.add_eager_loadings([@post], :association)
42
- UnusedEagerLoading.add_call_object_associations(@post, :association)
43
- expect(
44
- UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new([:association]))
45
- ).to be_empty
46
- end
47
- end
48
-
49
- context '.check_unused_preload_associations' do
50
- let(:paths) { %w[/dir1 /dir1/subdir] }
51
- it 'should create notification if object_association_diff is not empty' do
52
- UnusedEagerLoading.add_object_associations(@post, :association)
53
- allow(UnusedEagerLoading).to receive(:caller_in_project).and_return(paths)
54
- expect(UnusedEagerLoading).to receive(:create_notification).with(paths, 'Post', [:association])
55
- UnusedEagerLoading.check_unused_preload_associations
56
- end
57
-
58
- it 'should not create notification if object_association_diff is empty' do
59
- UnusedEagerLoading.add_object_associations(@post, :association)
60
- UnusedEagerLoading.add_eager_loadings([@post], :association)
61
- UnusedEagerLoading.add_call_object_associations(@post, :association)
62
- expect(
63
- UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new([:association]))
64
- ).to be_empty
65
- expect(UnusedEagerLoading).not_to receive(:create_notification).with('Post', [:association])
66
- UnusedEagerLoading.check_unused_preload_associations
67
- end
68
-
69
- it 'should create call stack for notification' do
70
- UnusedEagerLoading.add_object_associations(@post, :association)
71
- expect(UnusedEagerLoading.send(:call_stacks).registry).not_to be_empty
72
- end
73
- end
74
-
75
- context '.add_eager_loadings' do
76
- it 'should add objects, associations pair when eager_loadings are empty' do
77
- UnusedEagerLoading.add_eager_loadings([@post, @post2], :associations)
78
- expect(UnusedEagerLoading.send(:eager_loadings)).to be_include(
79
- [@post.bullet_key, @post2.bullet_key],
80
- :associations
81
- )
82
- end
83
-
84
- it 'should add objects, associations pair for existing eager_loadings' do
85
- UnusedEagerLoading.add_eager_loadings([@post, @post2], :association1)
86
- UnusedEagerLoading.add_eager_loadings([@post, @post2], :association2)
87
- expect(UnusedEagerLoading.send(:eager_loadings)).to be_include(
88
- [@post.bullet_key, @post2.bullet_key],
89
- :association1
90
- )
91
- expect(UnusedEagerLoading.send(:eager_loadings)).to be_include(
92
- [@post.bullet_key, @post2.bullet_key],
93
- :association2
94
- )
95
- end
96
-
97
- it 'should merge objects, associations pair for existing eager_loadings' do
98
- UnusedEagerLoading.add_eager_loadings([@post], :association1)
99
- UnusedEagerLoading.add_eager_loadings([@post, @post2], :association2)
100
- expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post.bullet_key], :association1)
101
- expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post.bullet_key], :association2)
102
- expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post2.bullet_key], :association2)
103
- end
104
-
105
- it 'should vmerge objects recursively, associations pair for existing eager_loadings' do
106
- UnusedEagerLoading.add_eager_loadings([@post, @post2], :association1)
107
- UnusedEagerLoading.add_eager_loadings([@post, @post3], :association1)
108
- UnusedEagerLoading.add_eager_loadings([@post, @post3], :association2)
109
- expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post.bullet_key], :association1)
110
- expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post.bullet_key], :association2)
111
- expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post2.bullet_key], :association1)
112
- expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post3.bullet_key], :association1)
113
- expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post3.bullet_key], :association2)
114
- end
115
-
116
- it 'should delete objects, associations pair for existing eager_loadings' do
117
- UnusedEagerLoading.add_eager_loadings([@post, @post2], :association1)
118
- UnusedEagerLoading.add_eager_loadings([@post], :association2)
119
- expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post.bullet_key], :association1)
120
- expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post.bullet_key], :association2)
121
- expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post2.bullet_key], :association1)
122
- end
123
- end
124
- end
125
- end
126
- end
@@ -1,44 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- describe Object do
6
- context 'bullet_key' do
7
- it 'should return class and id composition' do
8
- post = Post.first
9
- expect(post.bullet_key).to eq("Post:#{post.id}")
10
- end
11
-
12
- if mongoid?
13
- it 'should return class with namespace and id composition' do
14
- post = Mongoid::Post.first
15
- expect(post.bullet_key).to eq("Mongoid::Post:#{post.id}")
16
- end
17
- end
18
- end
19
-
20
- context 'bullet_primary_key_value' do
21
- it 'should return id' do
22
- post = Post.first
23
- expect(post.bullet_primary_key_value).to eq(post.id)
24
- end
25
-
26
- it 'should return primary key value' do
27
- post = Post.first
28
- Post.primary_key = 'name'
29
- expect(post.bullet_primary_key_value).to eq(post.name)
30
- Post.primary_key = 'id'
31
- end
32
-
33
- it 'should return value for multiple primary keys' do
34
- post = Post.first
35
- allow(Post).to receive(:primary_keys).and_return(%i[category_id 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
42
- end
43
- end
44
- end
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- describe String do
6
- context 'bullet_class_name' do
7
- it 'should only return class name' do
8
- expect('Post:1'.bullet_class_name).to eq('Post')
9
- end
10
-
11
- it 'should return class name with namespace' do
12
- expect('Mongoid::Post:1234567890'.bullet_class_name).to eq('Mongoid::Post')
13
- end
14
- end
15
- end
@@ -1,94 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- module Bullet
6
- module Notification
7
- describe Base do
8
- subject { Base.new(Post, %i[comments votes]) }
9
-
10
- context '#title' do
11
- it 'should raise NoMethodError' do
12
- expect { subject.title }.to raise_error(NoMethodError)
13
- end
14
- end
15
-
16
- context '#body' do
17
- it 'should raise NoMethodError' do
18
- expect { subject.body }.to raise_error(NoMethodError)
19
- end
20
- end
21
-
22
- context '#whoami' do
23
- it 'should display user name' do
24
- user = `whoami`.chomp
25
- expect(subject.whoami).to eq("user: #{user}")
26
- end
27
-
28
- it 'should leverage ENV parameter' do
29
- temp_env_variable('USER', 'bogus') { expect(subject.whoami).to eq('user: bogus') }
30
- end
31
-
32
- it 'should return blank if no user available' do
33
- temp_env_variable('USER', '') do
34
- expect(subject).to receive(:`).with('whoami').and_return('')
35
- expect(subject.whoami).to eq('')
36
- end
37
- end
38
-
39
- it 'should return blank if whoami is not available' do
40
- temp_env_variable('USER', '') do
41
- expect(subject).to receive(:`).with('whoami').and_raise(Errno::ENOENT)
42
- expect(subject.whoami).to eq('')
43
- end
44
- end
45
-
46
- def temp_env_variable(name, value)
47
- old_value = ENV[name]
48
- ENV[name] = value
49
- yield
50
- ensure
51
- ENV[name] = old_value
52
- end
53
- end
54
-
55
- context '#body_with_caller' do
56
- it 'should return body' do
57
- allow(subject).to receive(:body).and_return('body')
58
- allow(subject).to receive(:call_stack_messages).and_return('call_stack_messages')
59
- expect(subject.body_with_caller).to eq("body\ncall_stack_messages\n")
60
- end
61
- end
62
-
63
- context '#notification_data' do
64
- it 'should return notification data' do
65
- allow(subject).to receive(:whoami).and_return('whoami')
66
- allow(subject).to receive(:url).and_return('url')
67
- allow(subject).to receive(:title).and_return('title')
68
- allow(subject).to receive(:body_with_caller).and_return('body_with_caller')
69
- expect(subject.notification_data).to eq(user: 'whoami', url: 'url', title: 'title', body: 'body_with_caller')
70
- end
71
- end
72
-
73
- context '#notify_inline' do
74
- it 'should send full_notice to notifier' do
75
- notifier = double
76
- allow(subject).to receive(:notifier).and_return(notifier)
77
- allow(subject).to receive(:notification_data).and_return({ foo: :bar })
78
- expect(notifier).to receive(:inline_notify).with({ foo: :bar })
79
- subject.notify_inline
80
- end
81
- end
82
-
83
- context '#notify_out_of_channel' do
84
- it 'should send full_out_of_channel to notifier' do
85
- notifier = double
86
- allow(subject).to receive(:notifier).and_return(notifier)
87
- allow(subject).to receive(:notification_data).and_return({ foo: :bar })
88
- expect(notifier).to receive(:out_of_channel_notify).with({ foo: :bar })
89
- subject.notify_out_of_channel
90
- end
91
- end
92
- end
93
- end
94
- end
@@ -1,14 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- module Bullet
6
- module Notification
7
- describe CounterCache do
8
- subject { CounterCache.new(Post, %i[comments votes]) }
9
-
10
- it { expect(subject.body).to eq(' Post => [:comments, :votes]') }
11
- it { expect(subject.title).to eq('Need Counter Cache') }
12
- end
13
- end
14
- end
@@ -1,29 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- module Bullet
6
- module Notification
7
- describe NPlusOneQuery do
8
- subject { NPlusOneQuery.new([%w[caller1 caller2]], Post, %i[comments votes], 'path') }
9
-
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
26
- it { expect(subject.title).to eq('USE eager loading in path') }
27
- end
28
- end
29
- end
@@ -1,18 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- module Bullet
6
- module Notification
7
- describe UnusedEagerLoading do
8
- subject { UnusedEagerLoading.new([''], Post, %i[comments votes], 'path') }
9
-
10
- it do
11
- expect(subject.body).to eq(
12
- " Post => [:comments, :votes]\n Remove from your query: .includes([:comments, :votes])"
13
- )
14
- end
15
- it { expect(subject.title).to eq('AVOID eager loading in path') }
16
- end
17
- end
18
- end
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- module Bullet
6
- describe NotificationCollector do
7
- subject { NotificationCollector.new.tap { |collector| collector.add('value') } }
8
-
9
- context '#add' do
10
- it 'should add a value' do
11
- subject.add('value1')
12
- expect(subject.collection).to be_include('value1')
13
- end
14
- end
15
-
16
- context '#reset' do
17
- it 'should reset collector' do
18
- subject.reset
19
- expect(subject.collection).to be_empty
20
- end
21
- end
22
-
23
- context '#notifications_present?' do
24
- it 'should be true if collection is not empty' do
25
- expect(subject).to be_notifications_present
26
- end
27
-
28
- it 'should be false if collection is empty' do
29
- subject.reset
30
- expect(subject).not_to be_notifications_present
31
- end
32
- end
33
- end
34
- end
@@ -1,296 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- module Bullet
6
- describe Rack do
7
- let(:middleware) { Bullet::Rack.new app }
8
- let(:app) { Support::AppDouble.new }
9
-
10
- context '#html_request?' do
11
- it 'should be true if Content-Type is text/html and http body contains html tag' do
12
- headers = { 'Content-Type' => 'text/html' }
13
- response = double(body: '<html><head></head><body></body></html>')
14
- expect(middleware).to be_html_request(headers, response)
15
- end
16
-
17
- it 'should be true if Content-Type is text/html and http body contains html tag with attributes' do
18
- headers = { 'Content-Type' => 'text/html' }
19
- response = double(body: "<html attr='hello'><head></head><body></body></html>")
20
- expect(middleware).to be_html_request(headers, response)
21
- end
22
-
23
- it 'should be false if there is no Content-Type header' do
24
- headers = {}
25
- response = double(body: '<html><head></head><body></body></html>')
26
- expect(middleware).not_to be_html_request(headers, response)
27
- end
28
-
29
- it 'should be false if Content-Type is javascript' do
30
- headers = { 'Content-Type' => 'text/javascript' }
31
- response = double(body: '<html><head></head><body></body></html>')
32
- expect(middleware).not_to be_html_request(headers, response)
33
- end
34
- end
35
-
36
- context 'empty?' do
37
- it 'should be false if response is a string and not empty' do
38
- response = double(body: '<html><head></head><body></body></html>')
39
- expect(middleware).not_to be_empty(response)
40
- end
41
-
42
- it 'should be false if response is not found' do
43
- response = ['Not Found']
44
- expect(middleware).not_to be_empty(response)
45
- end
46
-
47
- it 'should be true if response body is empty' do
48
- response = double(body: '')
49
- expect(middleware).to be_empty(response)
50
- end
51
-
52
- it 'should be true if no response body' do
53
- response = double
54
- expect(middleware).to be_empty(response)
55
- end
56
- end
57
-
58
- context '#call' do
59
- context 'when Bullet is enabled' do
60
- it 'should return original response body' do
61
- expected_response = Support::ResponseDouble.new 'Actual body'
62
- app.response = expected_response
63
- _, _, response = middleware.call({})
64
- expect(response).to eq(expected_response)
65
- end
66
-
67
- it 'should change response body if notification is active' do
68
- expect(Bullet).to receive(:notification?).and_return(true)
69
- expect(Bullet).to receive(:console_enabled?).and_return(true)
70
- expect(Bullet).to receive(:gather_inline_notifications).and_return('<bullet></bullet>')
71
- expect(Bullet).to receive(:perform_out_of_channel_notifications)
72
- _, headers, response = middleware.call('Content-Type' => 'text/html')
73
- expect(headers['Content-Length']).to eq('56')
74
- expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])
75
- end
76
-
77
- it 'should set the right Content-Length if response body contains accents' do
78
- response = Support::ResponseDouble.new
79
- response.body = '<html><head></head><body>é</body></html>'
80
- app.response = response
81
- expect(Bullet).to receive(:notification?).and_return(true)
82
- allow(Bullet).to receive(:console_enabled?).and_return(true)
83
- expect(Bullet).to receive(:gather_inline_notifications).and_return('<bullet></bullet>')
84
- _, headers, response = middleware.call('Content-Type' => 'text/html')
85
- expect(headers['Content-Length']).to eq('58')
86
- end
87
-
88
- context 'with injection notifiers' do
89
- before do
90
- expect(Bullet).to receive(:notification?).and_return(true)
91
- allow(Bullet).to receive(:gather_inline_notifications).and_return('<bullet></bullet>')
92
- allow(middleware).to receive(:xhr_script).and_return('<script></script>')
93
- allow(middleware).to receive(:footer_note).and_return('footer')
94
- expect(Bullet).to receive(:perform_out_of_channel_notifications)
95
- end
96
-
97
- it 'should change response body if add_footer is true' do
98
- expect(Bullet).to receive(:add_footer).exactly(3).times.and_return(true)
99
- _, headers, response = middleware.call('Content-Type' => 'text/html')
100
-
101
- expect(headers['Content-Length']).to eq((73 + middleware.send(:footer_note).length).to_s)
102
- expect(response).to eq(%w[<html><head></head><body>footer<bullet></bullet><script></script></body></html>])
103
- end
104
-
105
- it 'should change response body for html safe string if add_footer is true' do
106
- expect(Bullet).to receive(:add_footer).exactly(3).times.and_return(true)
107
- app.response =
108
- Support::ResponseDouble.new.tap do |response|
109
- response.body = ActiveSupport::SafeBuffer.new('<html><head></head><body></body></html>')
110
- end
111
- _, headers, response = middleware.call('Content-Type' => 'text/html')
112
-
113
- expect(headers['Content-Length']).to eq((73 + middleware.send(:footer_note).length).to_s)
114
- expect(response).to eq(%w[<html><head></head><body>footer<bullet></bullet><script></script></body></html>])
115
- end
116
-
117
- it 'should add the footer-text header for non-html requests when add_footer is true' do
118
- allow(Bullet).to receive(:add_footer).at_least(:once).and_return(true)
119
- allow(Bullet).to receive(:footer_info).and_return(['footer text'])
120
- app.headers = { 'Content-Type' => 'application/json' }
121
- _, headers, _response = middleware.call({})
122
- expect(headers).to include('X-bullet-footer-text' => '["footer text"]')
123
- end
124
-
125
- it 'should change response body if console_enabled is true' do
126
- expect(Bullet).to receive(:console_enabled?).and_return(true)
127
- _, headers, response = middleware.call('Content-Type' => 'text/html')
128
- expect(headers['Content-Length']).to eq('56')
129
- expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])
130
- end
131
-
132
- it 'should include CSP nonce in inline script if console_enabled and a CSP is applied' do
133
- allow(Bullet).to receive(:add_footer).at_least(:once).and_return(true)
134
- expect(Bullet).to receive(:console_enabled?).and_return(true)
135
- allow(middleware).to receive(:xhr_script).and_call_original
136
-
137
- nonce = '+t9/wTlgG6xbHxXYUaDNzQ=='
138
- app.headers = {
139
- 'Content-Type' => 'text/html',
140
- 'Content-Security-Policy' => "default-src 'self' https:; script-src 'self' https: 'nonce-#{nonce}'"
141
- }
142
-
143
- _, headers, response = middleware.call('Content-Type' => 'text/html')
144
-
145
- size = 56 + middleware.send(:footer_note).length + middleware.send(:xhr_script, nonce).length
146
- expect(headers['Content-Length']).to eq(size.to_s)
147
- end
148
-
149
- it 'should change response body for html safe string if console_enabled is true' do
150
- expect(Bullet).to receive(:console_enabled?).and_return(true)
151
- app.response =
152
- Support::ResponseDouble.new.tap do |response|
153
- response.body = ActiveSupport::SafeBuffer.new('<html><head></head><body></body></html>')
154
- end
155
- _, headers, response = middleware.call('Content-Type' => 'text/html')
156
- expect(headers['Content-Length']).to eq('56')
157
- expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])
158
- end
159
-
160
- it 'should add headers for non-html requests when console_enabled is true' do
161
- allow(Bullet).to receive(:console_enabled?).at_least(:once).and_return(true)
162
- allow(Bullet).to receive(:text_notifications).and_return(['text notifications'])
163
- app.headers = { 'Content-Type' => 'application/json' }
164
- _, headers, _response = middleware.call({})
165
- expect(headers).to include('X-bullet-console-text' => '["text notifications"]')
166
- end
167
-
168
- it "shouldn't change response body unnecessarily" do
169
- expected_response = Support::ResponseDouble.new 'Actual body'
170
- app.response = expected_response
171
- _, _, response = middleware.call({})
172
- expect(response).to eq(expected_response)
173
- end
174
-
175
- it "shouldn't add headers unnecessarily" do
176
- app.headers = { 'Content-Type' => 'application/json' }
177
- _, headers, _response = middleware.call({})
178
- expect(headers).not_to include('X-bullet-footer-text')
179
- expect(headers).not_to include('X-bullet-console-text')
180
- end
181
-
182
- context 'when skip_http_headers is enabled' do
183
- before do
184
- allow(Bullet).to receive(:skip_http_headers).and_return(true)
185
- end
186
-
187
- it 'should include the footer but not the xhr script tag if add_footer is true' do
188
- expect(Bullet).to receive(:add_footer).at_least(:once).and_return(true)
189
- _, headers, response = middleware.call({})
190
-
191
- expect(headers['Content-Length']).to eq((56 + middleware.send(:footer_note).length).to_s)
192
- expect(response).to eq(%w[<html><head></head><body>footer<bullet></bullet></body></html>])
193
- end
194
-
195
- it 'should not include the xhr script tag if console_enabled is true' do
196
- expect(Bullet).to receive(:console_enabled?).and_return(true)
197
- _, headers, response = middleware.call({})
198
- expect(headers['Content-Length']).to eq('56')
199
- expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])
200
- end
201
-
202
- it 'should not add the footer-text header for non-html requests when add_footer is true' do
203
- allow(Bullet).to receive(:add_footer).at_least(:once).and_return(true)
204
- app.headers = { 'Content-Type' => 'application/json' }
205
- _, headers, _response = middleware.call({})
206
- expect(headers).not_to include('X-bullet-footer-text')
207
- end
208
-
209
- it 'should not add headers for non-html requests when console_enabled is true' do
210
- allow(Bullet).to receive(:console_enabled?).at_least(:once).and_return(true)
211
- app.headers = { 'Content-Type' => 'application/json' }
212
- _, headers, _response = middleware.call({})
213
- expect(headers).not_to include('X-bullet-console-text')
214
- end
215
- end
216
- end
217
-
218
- context 'when skip_html_injection is enabled' do
219
- it 'should not try to inject html' do
220
- expected_response = Support::ResponseDouble.new 'Actual body'
221
- app.response = expected_response
222
- allow(Bullet).to receive(:notification?).and_return(true)
223
- allow(Bullet).to receive(:skip_html_injection?).and_return(true)
224
- expect(Bullet).to receive(:gather_inline_notifications).never
225
- expect(middleware).to receive(:xhr_script).never
226
- expect(Bullet).to receive(:perform_out_of_channel_notifications)
227
- _, _, response = middleware.call('Content-Type' => 'text/html')
228
- expect(response).to eq(expected_response)
229
- end
230
- end
231
- end
232
-
233
- context 'when Bullet is disabled' do
234
- before(:each) { allow(Bullet).to receive(:enable?).and_return(false) }
235
-
236
- it 'should not call Bullet.start_request' do
237
- expect(Bullet).not_to receive(:start_request)
238
- middleware.call({})
239
- end
240
- end
241
- end
242
-
243
- context '#set_header' do
244
- it 'should truncate headers to under 8kb' do
245
- long_header = ['a' * 1_024] * 10
246
- expected_res = (['a' * 1_024] * 7).to_json
247
- expect(middleware.set_header({}, 'Dummy-Header', long_header)).to eq(expected_res)
248
- end
249
- end
250
-
251
- describe '#response_body' do
252
- let(:response) { double }
253
- let(:body_string) { '<html><body>My Body</body></html>' }
254
-
255
- context 'when `response` responds to `body`' do
256
- before { allow(response).to receive(:body).and_return(body) }
257
-
258
- context 'when `body` returns an Array' do
259
- let(:body) { [body_string, 'random string'] }
260
- it 'should return the plain body string' do
261
- expect(middleware.response_body(response)).to eq body_string
262
- end
263
- end
264
-
265
- context 'when `body` does not return an Array' do
266
- let(:body) { body_string }
267
- it 'should return the plain body string' do
268
- expect(middleware.response_body(response)).to eq body_string
269
- end
270
- end
271
- end
272
-
273
- context 'when `response` does not respond to `body`' do
274
- before { allow(response).to receive(:first).and_return(body_string) }
275
-
276
- it 'should return the plain body string' do
277
- expect(middleware.response_body(response)).to eq body_string
278
- end
279
- end
280
-
281
- begin
282
- require 'rack/files'
283
-
284
- context 'when `response` is a Rack::Files::Iterator' do
285
- let(:response) { instance_double(::Rack::Files::Iterator) }
286
- before { allow(response).to receive(:is_a?).with(::Rack::Files::Iterator) { true } }
287
-
288
- it 'should return nil' do
289
- expect(middleware.response_body(response)).to be_nil
290
- end
291
- end
292
- rescue LoadError
293
- end
294
- end
295
- end
296
- end