bullet_instructure 4.0.2

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 (118) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.rspec +2 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +20 -0
  6. data/CHANGELOG.md +75 -0
  7. data/Gemfile +19 -0
  8. data/Gemfile.mongoid +14 -0
  9. data/Gemfile.mongoid-2.4 +19 -0
  10. data/Gemfile.mongoid-2.5 +19 -0
  11. data/Gemfile.mongoid-2.6 +19 -0
  12. data/Gemfile.mongoid-2.7 +19 -0
  13. data/Gemfile.mongoid-2.8 +19 -0
  14. data/Gemfile.mongoid-3.0 +19 -0
  15. data/Gemfile.mongoid-3.1 +19 -0
  16. data/Gemfile.mongoid-4.0 +19 -0
  17. data/Gemfile.rails-3.0 +19 -0
  18. data/Gemfile.rails-3.1 +19 -0
  19. data/Gemfile.rails-3.2 +19 -0
  20. data/Gemfile.rails-4.0 +19 -0
  21. data/Gemfile.rails-4.1 +19 -0
  22. data/Guardfile +8 -0
  23. data/Hacking.md +74 -0
  24. data/MIT-LICENSE +20 -0
  25. data/README.md +428 -0
  26. data/Rakefile +52 -0
  27. data/bullet_instructure.gemspec +27 -0
  28. data/lib/bullet.rb +196 -0
  29. data/lib/bullet/active_record3.rb +148 -0
  30. data/lib/bullet/active_record3x.rb +128 -0
  31. data/lib/bullet/active_record4.rb +128 -0
  32. data/lib/bullet/active_record41.rb +121 -0
  33. data/lib/bullet/dependency.rb +81 -0
  34. data/lib/bullet/detector.rb +9 -0
  35. data/lib/bullet/detector/association.rb +67 -0
  36. data/lib/bullet/detector/base.rb +6 -0
  37. data/lib/bullet/detector/counter_cache.rb +59 -0
  38. data/lib/bullet/detector/n_plus_one_query.rb +89 -0
  39. data/lib/bullet/detector/unused_eager_loading.rb +84 -0
  40. data/lib/bullet/ext/object.rb +9 -0
  41. data/lib/bullet/ext/string.rb +5 -0
  42. data/lib/bullet/mongoid2x.rb +56 -0
  43. data/lib/bullet/mongoid3x.rb +56 -0
  44. data/lib/bullet/mongoid4x.rb +56 -0
  45. data/lib/bullet/notification.rb +10 -0
  46. data/lib/bullet/notification/base.rb +97 -0
  47. data/lib/bullet/notification/counter_cache.rb +13 -0
  48. data/lib/bullet/notification/n_plus_one_query.rb +28 -0
  49. data/lib/bullet/notification/unused_eager_loading.rb +13 -0
  50. data/lib/bullet/notification_collector.rb +24 -0
  51. data/lib/bullet/rack.rb +81 -0
  52. data/lib/bullet/registry.rb +7 -0
  53. data/lib/bullet/registry/association.rb +13 -0
  54. data/lib/bullet/registry/base.rb +40 -0
  55. data/lib/bullet/registry/object.rb +13 -0
  56. data/lib/bullet/version.rb +4 -0
  57. data/perf/benchmark.rb +121 -0
  58. data/rails/init.rb +1 -0
  59. data/spec/bullet/detector/association_spec.rb +26 -0
  60. data/spec/bullet/detector/base_spec.rb +8 -0
  61. data/spec/bullet/detector/counter_cache_spec.rb +56 -0
  62. data/spec/bullet/detector/n_plus_one_query_spec.rb +138 -0
  63. data/spec/bullet/detector/unused_eager_loading_spec.rb +88 -0
  64. data/spec/bullet/ext/object_spec.rb +17 -0
  65. data/spec/bullet/ext/string_spec.rb +13 -0
  66. data/spec/bullet/notification/base_spec.rb +83 -0
  67. data/spec/bullet/notification/counter_cache_spec.rb +12 -0
  68. data/spec/bullet/notification/n_plus_one_query_spec.rb +14 -0
  69. data/spec/bullet/notification/unused_eager_loading_spec.rb +12 -0
  70. data/spec/bullet/notification_collector_spec.rb +32 -0
  71. data/spec/bullet/rack_spec.rb +97 -0
  72. data/spec/bullet/registry/association_spec.rb +26 -0
  73. data/spec/bullet/registry/base_spec.rb +44 -0
  74. data/spec/bullet/registry/object_spec.rb +24 -0
  75. data/spec/bullet_spec.rb +41 -0
  76. data/spec/integration/active_record3/association_spec.rb +651 -0
  77. data/spec/integration/active_record4/association_spec.rb +649 -0
  78. data/spec/integration/counter_cache_spec.rb +63 -0
  79. data/spec/integration/mongoid/association_spec.rb +258 -0
  80. data/spec/models/address.rb +3 -0
  81. data/spec/models/author.rb +3 -0
  82. data/spec/models/base_user.rb +5 -0
  83. data/spec/models/category.rb +7 -0
  84. data/spec/models/city.rb +3 -0
  85. data/spec/models/client.rb +4 -0
  86. data/spec/models/comment.rb +4 -0
  87. data/spec/models/company.rb +3 -0
  88. data/spec/models/country.rb +3 -0
  89. data/spec/models/document.rb +5 -0
  90. data/spec/models/entry.rb +3 -0
  91. data/spec/models/firm.rb +4 -0
  92. data/spec/models/folder.rb +2 -0
  93. data/spec/models/mongoid/address.rb +7 -0
  94. data/spec/models/mongoid/category.rb +8 -0
  95. data/spec/models/mongoid/comment.rb +7 -0
  96. data/spec/models/mongoid/company.rb +7 -0
  97. data/spec/models/mongoid/entry.rb +7 -0
  98. data/spec/models/mongoid/post.rb +12 -0
  99. data/spec/models/mongoid/user.rb +5 -0
  100. data/spec/models/newspaper.rb +3 -0
  101. data/spec/models/page.rb +2 -0
  102. data/spec/models/person.rb +3 -0
  103. data/spec/models/pet.rb +3 -0
  104. data/spec/models/post.rb +10 -0
  105. data/spec/models/relationship.rb +4 -0
  106. data/spec/models/student.rb +3 -0
  107. data/spec/models/submission.rb +4 -0
  108. data/spec/models/teacher.rb +3 -0
  109. data/spec/models/user.rb +4 -0
  110. data/spec/models/writer.rb +2 -0
  111. data/spec/spec_helper.rb +103 -0
  112. data/spec/support/bullet_ext.rb +55 -0
  113. data/spec/support/mongo_seed.rb +65 -0
  114. data/spec/support/rack_double.rb +55 -0
  115. data/spec/support/sqlite_seed.rb +229 -0
  116. data/tasks/bullet_tasks.rake +9 -0
  117. data/test.sh +15 -0
  118. metadata +246 -0
@@ -0,0 +1,138 @@
1
+ require 'spec_helper'
2
+
3
+ module Bullet
4
+ module Detector
5
+ describe NPlusOneQuery do
6
+ before(:all) do
7
+ @post = Post.first
8
+ @post2 = Post.last
9
+ end
10
+
11
+ context ".call_association" do
12
+ it "should add call_object_associations" do
13
+ expect(NPlusOneQuery).to receive(:add_call_object_associations).with(@post, :associations)
14
+ NPlusOneQuery.call_association(@post, :associations)
15
+ end
16
+ end
17
+
18
+ context ".possible?" do
19
+ it "should be true if possible_objects contain" do
20
+ NPlusOneQuery.add_possible_objects(@post)
21
+ expect(NPlusOneQuery.send(:possible?, @post.bullet_key)).to eq true
22
+ end
23
+ end
24
+
25
+ context ".impossible?" do
26
+ it "should be true if impossible_objects contain" do
27
+ NPlusOneQuery.add_impossible_object(@post)
28
+ expect(NPlusOneQuery.send(:impossible?, @post.bullet_key)).to eq true
29
+ end
30
+ end
31
+
32
+ context ".association?" do
33
+ it "should be true if object, associations pair is already existed" do
34
+ NPlusOneQuery.add_object_associations(@post, :association)
35
+ expect(NPlusOneQuery.send(:association?, @post.bullet_key, :association)).to eq true
36
+ end
37
+
38
+ it "should be false if object, association pair is not existed" do
39
+ NPlusOneQuery.add_object_associations(@post, :association1)
40
+ expect(NPlusOneQuery.send(:association?, @post.bullet_key, :associatio2)).to eq false
41
+ end
42
+ end
43
+
44
+ context ".conditions_met?" do
45
+ it "should be true if object is possible, not impossible and object, associations pair is not already existed" do
46
+ allow(NPlusOneQuery).to receive(:possible?).with(@post.bullet_key).and_return(true)
47
+ allow(NPlusOneQuery).to receive(:impossible?).with(@post.bullet_key).and_return(false)
48
+ allow(NPlusOneQuery).to receive(:association?).with(@post.bullet_key, :associations).and_return(false)
49
+ expect(NPlusOneQuery.send(:conditions_met?, @post.bullet_key, :associations)).to eq true
50
+ end
51
+
52
+ it "should be false if object is not possible, not impossible and object, associations pair is not already existed" do
53
+ allow(NPlusOneQuery).to receive(:possible?).with(@post.bullet_key).and_return(false)
54
+ allow(NPlusOneQuery).to receive(:impossible?).with(@post.bullet_key).and_return(false)
55
+ allow(NPlusOneQuery).to receive(:association?).with(@post.bullet_key, :associations).and_return(false)
56
+ expect(NPlusOneQuery.send(:conditions_met?, @post.bullet_key, :associations)).to eq false
57
+ end
58
+
59
+ it "should be false if object is possible, but impossible and object, associations pair is not already existed" do
60
+ allow(NPlusOneQuery).to receive(:possible?).with(@post.bullet_key).and_return(true)
61
+ allow(NPlusOneQuery).to receive(:impossible?).with(@post.bullet_key).and_return(true)
62
+ allow(NPlusOneQuery).to receive(:association?).with(@post.bullet_key, :associations).and_return(false)
63
+ expect(NPlusOneQuery.send(:conditions_met?, @post.bullet_key, :associations)).to eq false
64
+ end
65
+
66
+ it "should be false if object is possible, not impossible and object, associations pair is already existed" do
67
+ allow(NPlusOneQuery).to receive(:possible?).with(@post.bullet_key).and_return(true)
68
+ allow(NPlusOneQuery).to receive(:impossible?).with(@post.bullet_key).and_return(false)
69
+ allow(NPlusOneQuery).to receive(:association?).with(@post.bullet_key, :associations).and_return(true)
70
+ expect(NPlusOneQuery.send(:conditions_met?, @post.bullet_key, :associations)).to eq false
71
+ end
72
+ end
73
+
74
+ context ".call_association" do
75
+ it "should create notification if conditions met" do
76
+ expect(NPlusOneQuery).to receive(:conditions_met?).with(@post.bullet_key, :association).and_return(true)
77
+ expect(NPlusOneQuery).to receive(:caller_in_project).and_return(["caller"])
78
+ expect(NPlusOneQuery).to receive(:create_notification).with(["caller"], "Post", :association)
79
+ NPlusOneQuery.call_association(@post, :association)
80
+ end
81
+
82
+ it "should not create notification if conditions not met" do
83
+ expect(NPlusOneQuery).to receive(:conditions_met?).with(@post.bullet_key, :association).and_return(false)
84
+ expect(NPlusOneQuery).not_to receive(:caller_in_project!)
85
+ expect(NPlusOneQuery).not_to receive(:create_notification).with("Post", :association)
86
+ NPlusOneQuery.call_association(@post, :association)
87
+ end
88
+ end
89
+
90
+ context ".caller_in_project" do
91
+ it "should include only paths that are in the project" do
92
+ in_project = File.join(Dir.pwd, 'abc', 'abc.rb')
93
+ not_in_project = '/def/def.rb'
94
+
95
+ expect(NPlusOneQuery).to receive(:caller).and_return([in_project, not_in_project])
96
+ expect(NPlusOneQuery).to receive(:conditions_met?).with(@post.bullet_key, :association).and_return(true)
97
+ expect(NPlusOneQuery).to receive(:create_notification).with([in_project], "Post", :association)
98
+ NPlusOneQuery.call_association(@post, :association)
99
+ end
100
+
101
+ context "stacktrace_includes" do
102
+ before { Bullet.stacktrace_includes = [ 'def' ] }
103
+ after { Bullet.stacktrace_includes = nil }
104
+
105
+ it "should include paths that are in the stacktrace_include list" do
106
+ in_project = File.join(Dir.pwd, 'abc', 'abc.rb')
107
+ included_gem = '/def/def.rb'
108
+ excluded_gem = '/ghi/ghi.rb'
109
+
110
+ expect(NPlusOneQuery).to receive(:caller).and_return([in_project, included_gem, excluded_gem])
111
+ expect(NPlusOneQuery).to receive(:conditions_met?).with(@post.bullet_key, :association).and_return(true)
112
+ expect(NPlusOneQuery).to receive(:create_notification).with([in_project, included_gem], "Post", :association)
113
+ NPlusOneQuery.call_association(@post, :association)
114
+ end
115
+ end
116
+ end
117
+
118
+ context ".add_possible_objects" do
119
+ it "should add possible objects" do
120
+ NPlusOneQuery.add_possible_objects([@post, @post2])
121
+ expect(NPlusOneQuery.send(:possible_objects)).to be_include(@post.bullet_key)
122
+ expect(NPlusOneQuery.send(:possible_objects)).to be_include(@post2.bullet_key)
123
+ end
124
+
125
+ it "should not raise error if object is nil" do
126
+ expect { NPlusOneQuery.add_possible_objects(nil) }.not_to raise_error
127
+ end
128
+ end
129
+
130
+ context ".add_impossible_object" do
131
+ it "should add impossible object" do
132
+ NPlusOneQuery.add_impossible_object(@post)
133
+ expect(NPlusOneQuery.send(:impossible_objects)).to be_include(@post.bullet_key)
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,88 @@
1
+ require 'spec_helper'
2
+
3
+ module Bullet
4
+ module Detector
5
+ describe UnusedEagerLoading do
6
+ before(:all) do
7
+ @post = Post.first
8
+ @post2 = Post.last
9
+ end
10
+
11
+ context ".call_associations" do
12
+ it "should get empty array if eager_loadings" do
13
+ expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new([:association]))).to be_empty
14
+ end
15
+
16
+ it "should get call associations if object and association are both in eager_loadings and call_object_associations" do
17
+ UnusedEagerLoading.add_eager_loadings([@post], :association)
18
+ UnusedEagerLoading.add_call_object_associations(@post, :association)
19
+ expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new([:association]))).to eq([:association])
20
+ end
21
+
22
+ it "should not get call associations if not exist in call_object_associations" do
23
+ UnusedEagerLoading.add_eager_loadings([@post], :association)
24
+ expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new([:association]))).to be_empty
25
+ end
26
+ end
27
+
28
+ context ".diff_object_associations" do
29
+ it "should return associations not exist in call_association" do
30
+ expect(UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new([:association]))).to eq([:association])
31
+ end
32
+
33
+ it "should return empty if associations exist in call_association" do
34
+ UnusedEagerLoading.add_eager_loadings([@post], :association)
35
+ UnusedEagerLoading.add_call_object_associations(@post, :association)
36
+ expect(UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new([:association]))).to be_empty
37
+ end
38
+ end
39
+
40
+ context ".check_unused_preload_associations" do
41
+ it "should create notification if object_association_diff is not empty" do
42
+ UnusedEagerLoading.add_object_associations(@post, :association)
43
+ expect(UnusedEagerLoading).to receive(:create_notification).with("Post", [:association])
44
+ UnusedEagerLoading.check_unused_preload_associations
45
+ end
46
+
47
+ it "should not create notification if object_association_diff is empty" do
48
+ UnusedEagerLoading.add_object_associations(@post, :association)
49
+ UnusedEagerLoading.add_eager_loadings([@post], :association)
50
+ UnusedEagerLoading.add_call_object_associations(@post, :association)
51
+ expect(UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new([:association]))).to be_empty
52
+ expect(UnusedEagerLoading).not_to receive(:create_notification).with("Post", [:association])
53
+ UnusedEagerLoading.check_unused_preload_associations
54
+ end
55
+ end
56
+
57
+ context ".add_eager_loadings" do
58
+ it "should add objects, associations pair when eager_loadings are empty" do
59
+ UnusedEagerLoading.add_eager_loadings([@post, @post2], :associations)
60
+ expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post.bullet_key, @post2.bullet_key], :associations)
61
+ end
62
+
63
+ it "should add objects, associations pair for existing eager_loadings" do
64
+ UnusedEagerLoading.add_eager_loadings([@post, @post2], :association1)
65
+ UnusedEagerLoading.add_eager_loadings([@post, @post2], :association2)
66
+ expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post.bullet_key, @post2.bullet_key], :association1)
67
+ expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post.bullet_key, @post2.bullet_key], :association2)
68
+ end
69
+
70
+ it "should merge objects, associations pair for existing eager_loadings" do
71
+ UnusedEagerLoading.add_eager_loadings([@post], :association1)
72
+ UnusedEagerLoading.add_eager_loadings([@post, @post2], :association2)
73
+ expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post.bullet_key], :association1)
74
+ expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post.bullet_key], :association2)
75
+ expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post.bullet_key, @post2.bullet_key], :association2)
76
+ end
77
+
78
+ it "should delete objects, associations pair for existing eager_loadings" do
79
+ UnusedEagerLoading.add_eager_loadings([@post, @post2], :association1)
80
+ UnusedEagerLoading.add_eager_loadings([@post], :association2)
81
+ expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post.bullet_key], :association1)
82
+ expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post.bullet_key], :association2)
83
+ expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post2.bullet_key], :association1)
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ describe Object do
4
+ context "bullet_key" do
5
+ it "should return class and id composition" do
6
+ post = Post.first
7
+ expect(post.bullet_key).to eq("Post:#{post.id}")
8
+ end
9
+
10
+ if mongoid?
11
+ it "should return class with namesapce and id composition" do
12
+ post = Mongoid::Post.first
13
+ expect(post.bullet_key).to eq("Mongoid::Post:#{post.id}")
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ describe String do
4
+ context "bullet_class_name" do
5
+ it "should only return class name" do
6
+ expect("Post:1".bullet_class_name).to eq("Post")
7
+ end
8
+
9
+ it "should return class name with namespace" do
10
+ expect("Mongoid::Post:1234567890".bullet_class_name).to eq("Mongoid::Post")
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,83 @@
1
+ require 'spec_helper'
2
+
3
+ module Bullet
4
+ module Notification
5
+ describe Base do
6
+ subject { Base.new(Post, [:comments, :votes]) }
7
+
8
+ context "#title" do
9
+ it "should raise NoMethodError" do
10
+ expect { subject.title }.to raise_error(NoMethodError)
11
+ end
12
+ end
13
+
14
+ context "#body" do
15
+ it "should raise NoMethodError" do
16
+ expect { subject.body }.to raise_error(NoMethodError)
17
+ end
18
+ end
19
+
20
+ context "#whoami" do
21
+ it "should display user name" do
22
+ user = `whoami`.chomp
23
+ expect(subject.whoami).to eq("user: #{user}")
24
+ end
25
+ end
26
+
27
+ context "#body_with_caller" do
28
+ it "should return body" do
29
+ allow(subject).to receive(:body).and_return("body")
30
+ expect(subject.body_with_caller).to eq("body")
31
+ end
32
+ end
33
+
34
+ context "#standard_notice" do
35
+ it "should return title + body" do
36
+ allow(subject).to receive(:title).and_return("title")
37
+ allow(subject).to receive(:body).and_return("body")
38
+ expect(subject.standard_notice).to eq("title\nbody")
39
+ end
40
+ end
41
+
42
+ context "#full_notice" do
43
+ it "should return whoami + url + title + body_with_caller" do
44
+ allow(subject).to receive(:whoami).and_return("whoami")
45
+ allow(subject).to receive(:url).and_return("url")
46
+ allow(subject).to receive(:title).and_return("title")
47
+ allow(subject).to receive(:body_with_caller).and_return("body_with_caller")
48
+ expect(subject.full_notice).to eq("whoami\nurl\ntitle\nbody_with_caller")
49
+ end
50
+ end
51
+
52
+ context "#notification_data" do
53
+ it "should return notification data" do
54
+ allow(subject).to receive(:whoami).and_return("whoami")
55
+ allow(subject).to receive(:url).and_return("url")
56
+ allow(subject).to receive(:title).and_return("title")
57
+ allow(subject).to receive(:body_with_caller).and_return("body_with_caller")
58
+ expect(subject.notification_data).to eq(:user => "whoami", :url => "url", :title => "title", :body => "body_with_caller")
59
+ end
60
+ end
61
+
62
+ context "#notify_inline" do
63
+ it "should send full_notice to notifier" do
64
+ notifier = double
65
+ allow(subject).to receive(:notifier).and_return(notifier)
66
+ allow(subject).to receive(:notification_data).and_return(:foo => :bar)
67
+ expect(notifier).to receive(:inline_notify).with(:foo => :bar)
68
+ subject.notify_inline
69
+ end
70
+ end
71
+
72
+ context "#notify_out_of_channel" do
73
+ it "should send full_out_of_channel to notifier" do
74
+ notifier = double
75
+ allow(subject).to receive(:notifier).and_return(notifier)
76
+ allow(subject).to receive(:notification_data).and_return(:foo => :bar)
77
+ expect(notifier).to receive(:out_of_channel_notify).with(:foo => :bar)
78
+ subject.notify_out_of_channel
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+
3
+ module Bullet
4
+ module Notification
5
+ describe CounterCache do
6
+ subject { CounterCache.new(Post, [:comments, :votes]) }
7
+
8
+ it { expect(subject.body).to eq(" Post => [:comments, :votes]") }
9
+ it { expect(subject.title).to eq("Need Counter Cache") }
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ module Bullet
4
+ module Notification
5
+ describe NPlusOneQuery do
6
+ subject { NPlusOneQuery.new([["caller1", "caller2"]], Post, [:comments, :votes], "path") }
7
+
8
+ it { expect(subject.body_with_caller).to eq(" Post => [:comments, :votes]\n Add to your finder: :include => [:comments, :votes]\nN+1 Query method call stack\n caller1\n caller2") }
9
+ it { expect([ subject.body_with_caller, subject.body_with_caller]).to eq([ " Post => [:comments, :votes]\n Add to your finder: :include => [:comments, :votes]\nN+1 Query method call stack\n caller1\n caller2", " Post => [:comments, :votes]\n Add to your finder: :include => [:comments, :votes]\nN+1 Query method call stack\n caller1\n caller2" ]) }
10
+ it { expect(subject.body).to eq(" Post => [:comments, :votes]\n Add to your finder: :include => [:comments, :votes]") }
11
+ it { expect(subject.title).to eq("N+1 Query in path") }
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+
3
+ module Bullet
4
+ module Notification
5
+ describe UnusedEagerLoading do
6
+ subject { UnusedEagerLoading.new(Post, [:comments, :votes], "path") }
7
+
8
+ it { expect(subject.body).to eq(" Post => [:comments, :votes]\n Remove from your finder: :include => [:comments, :votes]") }
9
+ it { expect(subject.title).to eq("Unused Eager Loading in path") }
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ module Bullet
4
+ describe NotificationCollector do
5
+ subject { NotificationCollector.new.tap { |collector| collector.add("value") } }
6
+
7
+ context "#add" do
8
+ it "should add a value" do
9
+ subject.add("value1")
10
+ expect(subject.collection).to be_include("value1")
11
+ end
12
+ end
13
+
14
+ context "#reset" do
15
+ it "should reset collector" do
16
+ subject.reset
17
+ expect(subject.collection).to be_empty
18
+ end
19
+ end
20
+
21
+ context "#notifications_present?" do
22
+ it "should be true if collection is not empty" do
23
+ expect(subject).to be_notifications_present
24
+ end
25
+
26
+ it "should be false if collection is empty" do
27
+ subject.reset
28
+ expect(subject).not_to be_notifications_present
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,97 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ module Bullet
5
+ describe Rack do
6
+ let(:middleware) { Bullet::Rack.new app }
7
+ let(:app) { Support::AppDouble.new }
8
+
9
+ context "#html_request?" do
10
+ it "should be true if Content-Type is text/html and http body contains html tag" do
11
+ headers = {"Content-Type" => "text/html"}
12
+ response = double(:body => "<html><head></head><body></body></html>")
13
+ expect(middleware).to be_html_request(headers, response)
14
+ end
15
+
16
+ it "should be true if Content-Type is text/html and http body contains html tag with attributes" do
17
+ headers = {"Content-Type" => "text/html"}
18
+ response = double(:body => "<html attr='hello'><head></head><body></body></html>")
19
+ expect(middleware).to be_html_request(headers, response)
20
+ end
21
+
22
+ it "should be false if there is no Content-Type header" do
23
+ headers = {}
24
+ response = double(:body => "<html><head></head><body></body></html>")
25
+ expect(middleware).not_to be_html_request(headers, response)
26
+ end
27
+
28
+ it "should be false if Content-Type is javascript" do
29
+ headers = {"Content-Type" => "text/javascript"}
30
+ response = double(:body => "<html><head></head><body></body></html>")
31
+ expect(middleware).not_to be_html_request(headers, response)
32
+ end
33
+
34
+ it "should be false if response body doesn't contain html tag" do
35
+ headers = {"Content-Type" => "text/html"}
36
+ response = double(:body => "<div>Partial</div>")
37
+ expect(middleware).not_to be_html_request(headers, response)
38
+ end
39
+ end
40
+
41
+ context "empty?" do
42
+ it "should be false if response is a string and not empty" do
43
+ response = double(:body => "<html><head></head><body></body></html>")
44
+ expect(middleware).not_to be_empty(response)
45
+ end
46
+
47
+ it "should be true if response is not found" do
48
+ response = ["Not Found"]
49
+ expect(middleware).to be_empty(response)
50
+ end
51
+
52
+ it "should be true if response body is empty" do
53
+ response = double(:body => "")
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).twice
69
+ expect(Bullet).to receive(:gather_inline_notifications).and_return("<bullet></bullet>")
70
+ expect(Bullet).to receive(:perform_out_of_channel_notifications)
71
+ status, headers, response = middleware.call([200, {"Content-Type" => "text/html"}])
72
+ expect(headers["Content-Length"]).to eq("56")
73
+ expect(response).to eq(["<html><head></head><body></body></html><bullet></bullet>"])
74
+ end
75
+
76
+ it "should set the right Content-Length if response body contains accents" do
77
+ response = Support::ResponseDouble.new
78
+ response.body = "<html><head></head><body>é</body></html>"
79
+ app.response = response
80
+ expect(Bullet).to receive(:notification?).and_return(true).twice
81
+ expect(Bullet).to receive(:gather_inline_notifications).and_return("<bullet></bullet>")
82
+ status, headers, response = middleware.call([200, {"Content-Type" => "text/html"}])
83
+ expect(headers["Content-Length"]).to eq("58")
84
+ end
85
+ end
86
+
87
+ context "when Bullet is disabled" do
88
+ before(:each) { allow(Bullet).to receive(:enable?).and_return(false) }
89
+
90
+ it "should not call Bullet.start_request" do
91
+ expect(Bullet).not_to receive(:start_request)
92
+ middleware.call([])
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end