honeybadger 1.8.1 → 1.9.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/CHANGELOG.md +14 -0
  2. data/Gemfile.lock +61 -19
  3. data/Guardfile +4 -4
  4. data/MIT-LICENSE +1 -0
  5. data/README.md +2 -2
  6. data/Rakefile +4 -14
  7. data/features/rails.feature +1 -1
  8. data/gemfiles/rack.gemfile.lock +62 -27
  9. data/gemfiles/rails2.3.gemfile.lock +73 -36
  10. data/gemfiles/rails3.0.gemfile.lock +59 -26
  11. data/gemfiles/rails3.1.gemfile.lock +59 -26
  12. data/gemfiles/rails3.2.gemfile.lock +63 -30
  13. data/gemfiles/rails4.gemfile.lock +69 -36
  14. data/gemfiles/rake.gemfile.lock +62 -27
  15. data/gemfiles/sinatra.gemfile.lock +62 -27
  16. data/honeybadger.gemspec +31 -17
  17. data/lib/honeybadger.rb +2 -3
  18. data/lib/honeybadger/array.rb +53 -0
  19. data/lib/honeybadger/configuration.rb +19 -2
  20. data/lib/honeybadger/monitor.rb +16 -0
  21. data/lib/honeybadger/monitor/railtie.rb +52 -0
  22. data/lib/honeybadger/monitor/sender.rb +33 -0
  23. data/lib/honeybadger/monitor/worker.rb +71 -0
  24. data/lib/honeybadger/railtie.rb +10 -0
  25. data/lib/honeybadger/sender.rb +60 -41
  26. data/{test/unit/backtrace_test.rb → spec/honeybadger/backtrace_spec.rb} +69 -71
  27. data/{test/unit/capistrano_test.rb → spec/honeybadger/capistrano_spec.rb} +8 -9
  28. data/{test/unit/configuration_test.rb → spec/honeybadger/configuration_spec.rb} +85 -59
  29. data/spec/honeybadger/logger_spec.rb +65 -0
  30. data/spec/honeybadger/monitor/worker_spec.rb +189 -0
  31. data/{test/unit/notice_test.rb → spec/honeybadger/notice_spec.rb} +169 -185
  32. data/spec/honeybadger/notifier_spec.rb +252 -0
  33. data/spec/honeybadger/rack_spec.rb +84 -0
  34. data/{test/unit/rails/action_controller_catcher_test.rb → spec/honeybadger/rails/action_controller_spec.rb} +65 -57
  35. data/{test/unit/rails_test.rb → spec/honeybadger/rails_spec.rb} +8 -8
  36. data/spec/honeybadger/sender_spec.rb +249 -0
  37. data/spec/honeybadger_tasks_spec.rb +165 -0
  38. data/spec/spec_helper.rb +18 -0
  39. data/spec/support/array_including.rb +31 -0
  40. data/spec/support/backtraced_exception.rb +9 -0
  41. data/spec/support/collected_sender.rb +12 -0
  42. data/spec/support/defines_constants.rb +18 -0
  43. data/{test/test_helper.rb → spec/support/helpers.rb} +8 -61
  44. metadata +93 -45
  45. data/test/unit/honeybadger_tasks_test.rb +0 -167
  46. data/test/unit/logger_test.rb +0 -74
  47. data/test/unit/notifier_test.rb +0 -265
  48. data/test/unit/rack_test.rb +0 -88
  49. data/test/unit/sender_test.rb +0 -290
@@ -1,10 +1,10 @@
1
- require 'test_helper'
1
+ require 'spec_helper'
2
2
  require 'honeybadger/rails'
3
3
 
4
- class RailsInitializerTest < Test::Unit::TestCase
4
+ describe Honeybadger::Rails do
5
5
  include DefinesConstants
6
6
 
7
- should "trigger use of Rails' logger if logger isn't set and Rails' logger exists" do
7
+ it "triggers use of Rails' logger if logger isn't set and Rails' logger exists" do
8
8
  rails = Module.new do
9
9
  def self.logger
10
10
  "RAILS LOGGER"
@@ -12,17 +12,17 @@ class RailsInitializerTest < Test::Unit::TestCase
12
12
  end
13
13
  define_constant("Rails", rails)
14
14
  Honeybadger::Rails.initialize
15
- assert_equal "RAILS LOGGER", Honeybadger.logger
15
+ expect(Honeybadger.logger).to eq "RAILS LOGGER"
16
16
  end
17
17
 
18
- should "trigger use of Rails' default logger if logger isn't set and Rails.logger doesn't exist" do
18
+ it "triggers use of Rails' default logger if logger isn't set and Rails.logger doesn't exist" do
19
19
  define_constant("RAILS_DEFAULT_LOGGER", "RAILS DEFAULT LOGGER")
20
20
 
21
21
  Honeybadger::Rails.initialize
22
- assert_equal "RAILS DEFAULT LOGGER", Honeybadger.logger
22
+ expect(Honeybadger.logger).to eq "RAILS DEFAULT LOGGER"
23
23
  end
24
24
 
25
- should "allow overriding of the logger if already assigned" do
25
+ it "allows overriding of the logger if already assigned" do
26
26
  define_constant("RAILS_DEFAULT_LOGGER", "RAILS DEFAULT LOGGER")
27
27
  Honeybadger::Rails.initialize
28
28
 
@@ -30,6 +30,6 @@ class RailsInitializerTest < Test::Unit::TestCase
30
30
  config.logger = "OVERRIDDEN LOGGER"
31
31
  end
32
32
 
33
- assert_equal "OVERRIDDEN LOGGER", Honeybadger.logger
33
+ expect(Honeybadger.logger).to eq "OVERRIDDEN LOGGER"
34
34
  end
35
35
  end
@@ -0,0 +1,249 @@
1
+ require 'spec_helper'
2
+
3
+ describe Honeybadger::Sender do
4
+ before(:each) { reset_config }
5
+
6
+ it "posts to Honeybadger when using an HTTP proxy" do
7
+ post = double()
8
+ http = double(:adapter => nil,
9
+ :url_prefix= => nil,
10
+ :headers => nil)
11
+ http.stub(:post).and_yield(post).and_return(false)
12
+
13
+ url = "http://api.honeybadger.io:80#{Honeybadger::Sender::NOTICES_URI}"
14
+ uri = URI.parse(url)
15
+
16
+ post.should_receive(:url).with(uri.path)
17
+ post.should_receive(:body=).with('asdf')
18
+
19
+ Faraday.should_receive(:new).
20
+ with(hash_including(:request => hash_including({ :proxy => { :uri => 'https://some.host:88', :user => 'login', :password => 'passwd' } }))).and_return(http)
21
+
22
+ send_exception(:proxy_host => 'some.host',
23
+ :proxy_port => 88,
24
+ :proxy_user => 'login',
25
+ :proxy_pass => 'passwd',
26
+ :notice => 'asdf')
27
+ end
28
+
29
+ it "returns the created group's id on successful posting" do
30
+ stub_http(:body => '{"id":"3799307"}')
31
+ expect(send_exception(:secure => false)).to eq '3799307'
32
+ end
33
+
34
+ it "returns nil on failed posting" do
35
+ stub_http(:response => Faraday::Response.new(:status => 500))
36
+ expect(send_exception(:secure => false)).to be_nil
37
+ end
38
+
39
+ it "logs missing API key and return nil" do
40
+ sender = build_sender(:api_key => nil)
41
+ sender.should_receive(:log).with(:error, /API key/)
42
+ expect(send_exception(:sender => sender, :secure => false)).to be_nil
43
+ end
44
+
45
+ it "logs success" do
46
+ stub_http
47
+ sender = build_sender
48
+ sender.should_receive(:log).with(:debug, /Success/, kind_of(Faraday::Response), kind_of(String))
49
+ send_exception(:sender => sender, :secure => false)
50
+ end
51
+
52
+ it "logs failure" do
53
+ stub_http(:response => Faraday::Response.new(:status => 500))
54
+ sender = build_sender
55
+ sender.should_receive(:log).with(:error, /Failure/, kind_of(Faraday::Response), kind_of(String))
56
+ send_exception(:sender => sender, :secure => false)
57
+ end
58
+
59
+ context "when encountering exceptions" do
60
+ # TODO: Figure out why nested groups aren't running
61
+ context "HTTP connection setup problems" do
62
+ it "should not be rescued" do
63
+ Faraday.should_receive(:new).and_raise(NoMemoryError)
64
+ expect { build_sender.send(:client) }.to raise_error(NoMemoryError)
65
+ end
66
+
67
+ it "should be logged" do
68
+ Faraday.should_receive(:new).and_raise(RuntimeError)
69
+
70
+ sender = build_sender
71
+ sender.should_receive(:log).with(:error, /Failure initializing the HTTP connection/)
72
+
73
+ expect { sender.send(:client) }.to raise_error(RuntimeError)
74
+ end
75
+ end
76
+
77
+ context "unexpected exception sending problems" do
78
+ it "should be logged" do
79
+ sender = build_sender
80
+ sender.should_receive(:client).and_raise(RuntimeError)
81
+
82
+ sender.should_receive(:log).with(:error, /Error/)
83
+ sender.send_to_honeybadger("stuff")
84
+ end
85
+
86
+ it "returns nil no matter what" do
87
+ sender = build_sender
88
+ sender.should_receive(:client).and_raise(LocalJumpError)
89
+
90
+ expect { sender.send_to_honeybadger("stuff").should be_nil }.not_to raise_error
91
+ end
92
+ end
93
+
94
+ it "returns nil on failed posting" do
95
+ http = stub_http
96
+ http.should_receive(:post).and_raise(Errno::ECONNREFUSED)
97
+ expect(send_exception(:secure => false)).to be_nil
98
+ end
99
+
100
+ it "not fail when posting and a timeout exception occurs" do
101
+ http = stub_http
102
+ http.should_receive(:post).and_raise(TimeoutError)
103
+ expect { send_exception(:secure => false) }.not_to raise_error
104
+ end
105
+
106
+ it "not fail when posting and a connection refused exception occurs" do
107
+ http = stub_http
108
+ http.should_receive(:post).and_raise(Errno::ECONNREFUSED)
109
+ expect { send_exception(:secure => false) }.not_to raise_error
110
+ end
111
+
112
+ it "not fail when posting any http exception occurs" do
113
+ http = stub_http
114
+ Honeybadger::Sender::HTTP_ERRORS.each do |error|
115
+ http.stub(:post).and_raise(error)
116
+ expect { send_exception(:secure => false) }.not_to raise_error
117
+ end
118
+ end
119
+ end
120
+
121
+ context "SSL" do
122
+ it "posts to the right url for non-ssl" do
123
+ http = stub_http
124
+ url = "http://api.honeybadger.io:80#{Honeybadger::Sender::NOTICES_URI}"
125
+ uri = URI.parse(url)
126
+ post = double(:body= => nil)
127
+ http.should_receive(:post).and_yield(post)
128
+ post.should_receive(:url).with(uri.path)
129
+ send_exception(:secure => false)
130
+ end
131
+
132
+ it "post to the right path for ssl" do
133
+ http = stub_http
134
+ post = double(:body= => nil)
135
+ http.should_receive(:post).and_yield(post)
136
+ post.should_receive(:url).with(Honeybadger::Sender::NOTICES_URI)
137
+ send_exception(:secure => true)
138
+ end
139
+
140
+ it "verifies the SSL peer when the use_ssl option is set to true" do
141
+ url = "https://api.honeybadger.io#{Honeybadger::Sender::NOTICES_URI}"
142
+
143
+ real_http = Faraday.new(:url => url)
144
+ real_http.stub(:post => nil)
145
+ Faraday.stub(:new).and_yield(real_http)
146
+
147
+ File.stub(:exist?).with(OpenSSL::X509::DEFAULT_CERT_FILE).and_return(false)
148
+
149
+ send_exception(:secure => true)
150
+
151
+ expect(real_http.ssl).not_to be_empty
152
+ expect(real_http.ssl[:verify_mode]).to eq OpenSSL::SSL::VERIFY_PEER
153
+ expect(real_http.ssl[:ca_file]).to eq Honeybadger.configuration.local_cert_path
154
+ end
155
+
156
+ it "uses the default DEFAULT_CERT_FILE if asked to" do
157
+ File.should_receive(:exist?).with(OpenSSL::X509::DEFAULT_CERT_FILE).and_return(true)
158
+
159
+ Honeybadger.configure do |config|
160
+ config.secure = true
161
+ config.use_system_ssl_cert_chain = true
162
+ end
163
+
164
+ sender = Honeybadger::Sender.new(Honeybadger.configuration)
165
+
166
+ expect(sender.use_system_ssl_cert_chain?).to be_true
167
+
168
+ http = sender.send(:client)
169
+ expect(http.ssl[:ca_file]).not_to eq Honeybadger.configuration.local_cert_path
170
+ end
171
+
172
+ it "verifies the connection when the use_ssl option is set (VERIFY_PEER)" do
173
+ sender = build_sender(:secure => true)
174
+ http = sender.send(:client)
175
+ expect(http.ssl[:verify_mode]).to eq OpenSSL::SSL::VERIFY_PEER
176
+ end
177
+
178
+ it "uses the default cert (OpenSSL::X509::DEFAULT_CERT_FILE) only if explicitly told to" do
179
+ sender = build_sender(:secure => true)
180
+ http = sender.send(:client)
181
+
182
+ expect(http.ssl[:ca_file]).to eq Honeybadger.configuration.local_cert_path
183
+
184
+ File.stub(:exist?).with(OpenSSL::X509::DEFAULT_CERT_FILE).and_return(true)
185
+ sender = build_sender(:secure => true, :use_system_ssl_cert_chain => true)
186
+ http = sender.send(:client)
187
+
188
+ expect(http.ssl[:ca_file]).not_to eq Honeybadger.configuration.local_cert_path
189
+ expect(http.ssl[:ca_file]).to eq OpenSSL::X509::DEFAULT_CERT_FILE
190
+ end
191
+
192
+ it "uses ssl if secure" do
193
+ sender = build_sender(:secure => true)
194
+ http = sender.send(:client)
195
+ expect(http.port).to eq 443
196
+ end
197
+
198
+ it "does not use ssl if not secure" do
199
+ sender = build_sender(:secure => false)
200
+ http = sender.send(:client)
201
+ expect(http.port).to eq 80
202
+ end
203
+ end
204
+
205
+ context "network timeouts" do
206
+ it "default the open timeout to 2 seconds" do
207
+ Faraday.should_receive(:new).with(hash_including({ :request => hash_including({ :open_timeout => 2 }) }))
208
+ send_exception
209
+ end
210
+
211
+ it "default the read timeout to 5 seconds" do
212
+ Faraday.should_receive(:new).with(hash_including({ :request => hash_including({ :timeout => 5 }) }))
213
+ send_exception
214
+ end
215
+
216
+ it "allow override of the open timeout" do
217
+ Faraday.should_receive(:new).with(hash_including({ :request => hash_including({ :open_timeout => 4 }) }))
218
+ send_exception(:http_open_timeout => 4)
219
+ end
220
+
221
+ it "allow override of the read timeout" do
222
+ Faraday.should_receive(:new).with(hash_including({ :request => hash_including({ :timeout => 10 }) }))
223
+ send_exception(:http_read_timeout => 10)
224
+ end
225
+ end
226
+
227
+ def build_sender(opts = {})
228
+ Honeybadger.configure do |conf|
229
+ opts.each {|opt, value| conf.send(:"#{opt}=", value) }
230
+ end
231
+ end
232
+
233
+ def send_exception(args = {})
234
+ notice = args.delete(:notice) || build_notice_data
235
+ sender = args.delete(:sender) || build_sender(args)
236
+ sender.send_to_honeybadger(notice)
237
+ end
238
+
239
+ def stub_http(options = {})
240
+ response = options[:response] || Faraday::Response.new(:status => 200)
241
+ response.stub(:body => options[:body] || '{"id":"1234"}')
242
+ http = double(:post => response,
243
+ :adapter => nil,
244
+ :url_prefix= => nil,
245
+ :headers => nil)
246
+ Faraday.stub(:new => http)
247
+ http
248
+ end
249
+ end
@@ -0,0 +1,165 @@
1
+ require 'spec_helper'
2
+ require 'rubygems'
3
+
4
+ require File.expand_path('../../lib/honeybadger_tasks', __FILE__)
5
+ require 'fakeweb'
6
+
7
+ FakeWeb.allow_net_connect = false
8
+
9
+ describe HoneybadgerTasks do
10
+ def successful_response(body = "")
11
+ response = Net::HTTPSuccess.new('1.2', '200', 'OK')
12
+ response.stub(:body).and_return(body)
13
+ response
14
+ end
15
+
16
+ def unsuccessful_response(body = "")
17
+ response = Net::HTTPClientError.new('1.2', '200', 'OK')
18
+ response.stub(:body).and_return(body)
19
+ response
20
+ end
21
+
22
+ context "being quiet" do
23
+ before(:each) { HoneybadgerTasks.stub(:puts) }
24
+
25
+ context "in a configured project" do
26
+ before(:each) { Honeybadger.configure { |config| config.api_key = "1234123412341234" } }
27
+
28
+ context "on deploy({})" do
29
+ it "complains about missing rails env" do
30
+ HoneybadgerTasks.should_receive(:puts).with(/which environment/i)
31
+ HoneybadgerTasks.deploy({})
32
+ end
33
+
34
+ it "return false" do
35
+ expect(HoneybadgerTasks.deploy({})).to be_false
36
+ end
37
+ end
38
+
39
+ context "given an optional HTTP proxy and valid options" do
40
+ before(:each) do
41
+ @response = double("response", :body => "stub body")
42
+ @http_proxy = double("proxy", :request => @response,
43
+ :use_ssl= => nil,
44
+ :ca_file= => nil,
45
+ :verify_mode= => nil)
46
+ @http_proxy_class = double("proxy_class", :new => @http_proxy)
47
+ @post = double("post", :set_form_data => nil)
48
+
49
+ @post.stub(:[]=).with('X-API-Key', '1234123412341234').and_return(true)
50
+
51
+ Net::HTTP.should_receive(:Proxy).
52
+ with(Honeybadger.configuration.proxy_host,
53
+ Honeybadger.configuration.proxy_port,
54
+ Honeybadger.configuration.proxy_user,
55
+ Honeybadger.configuration.proxy_pass).
56
+ and_return(@http_proxy_class)
57
+ Net::HTTP::Post.should_receive(:new).with("/v1/deploys").and_return(@post)
58
+
59
+ @options = { :environment => "staging", :dry_run => false }
60
+ end
61
+
62
+ context "performing a dry run" do
63
+ before(:each) { @output = HoneybadgerTasks.deploy(@options.merge(:dry_run => true)) }
64
+
65
+ it "return true without performing any actual request" do
66
+ @http_proxy.should_receive(:request).never
67
+ @output.should be_true
68
+ end
69
+ end
70
+
71
+ context "on deploy(options)" do
72
+ it "posts to https://api.honeybadger.io:443/v1/deploys" do
73
+ @http_proxy_class.should_receive(:new).with("api.honeybadger.io", 443).and_return(@http_proxy)
74
+ @post.should_receive(:set_form_data).with(kind_of(Hash))
75
+ @http_proxy.should_receive(:request).with(anything).and_return(successful_response)
76
+ HoneybadgerTasks.deploy(@options)
77
+ end
78
+
79
+ it "uses send the environment param" do
80
+ @post.should_receive(:set_form_data).
81
+ with(hash_including("deploy[environment]" => "staging"))
82
+ HoneybadgerTasks.deploy(@options)
83
+ end
84
+
85
+ [:local_username, :repository, :revision].each do |key|
86
+ it "uses send the #{key} param if it's passed in." do
87
+ @options[key] = "value"
88
+ @post.should_receive(:set_form_data).
89
+ with(hash_including("deploy[#{key}]" => "value"))
90
+ HoneybadgerTasks.deploy(@options)
91
+ end
92
+ end
93
+
94
+ it "puts the response body on success" do
95
+ HoneybadgerTasks.should_receive(:puts).with("Succesfully recorded deployment")
96
+ @http_proxy.should_receive(:request).with(anything).and_return(successful_response('body'))
97
+ HoneybadgerTasks.deploy(@options)
98
+ end
99
+
100
+ it "puts the response body on failure" do
101
+ HoneybadgerTasks.should_receive(:puts).with("body")
102
+ @http_proxy.should_receive(:request).with(anything).and_return(unsuccessful_response('body'))
103
+ HoneybadgerTasks.deploy(@options)
104
+ end
105
+
106
+ it "returns false on failure" do
107
+ @http_proxy.should_receive(:request).with(anything).and_return(unsuccessful_response('body'))
108
+ output = HoneybadgerTasks.deploy(@options)
109
+ expect(output).to be_false
110
+ end
111
+
112
+ it "return true on success" do
113
+ @http_proxy.should_receive(:request).with(anything).and_return(successful_response('body'))
114
+ output = HoneybadgerTasks.deploy(@options)
115
+ expect(output).to be_true
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ context "in a configured project with custom host" do
122
+ before(:each) do
123
+ Honeybadger.configure do |config|
124
+ config.api_key = "1234123412341234"
125
+ config.host = "custom.host"
126
+ config.secure = false
127
+ end
128
+ end
129
+
130
+ context "on deploy(:environment => 'staging')" do
131
+ it "posts to the custom host" do
132
+ @post = double("post", :set_form_data => nil)
133
+ @http_proxy = double("proxy", :request => @response)
134
+
135
+ @post.stub(:[]=).with('X-API-Key', '1234123412341234').and_return(true)
136
+
137
+ @http_proxy_class = double("proxy_class", :new => @http_proxy)
138
+ @http_proxy_class.should_receive(:new).with("custom.host", 80).and_return(@http_proxy)
139
+ Net::HTTP.should_receive(:Proxy).with(any_args).and_return(@http_proxy_class)
140
+ Net::HTTP::Post.should_receive(:new).with("/v1/deploys").and_return(@post)
141
+ @post.should_receive(:set_form_data).with(kind_of(Hash))
142
+ @http_proxy.should_receive(:request).with(any_args).and_return(successful_response)
143
+
144
+ HoneybadgerTasks.deploy(:environment => "staging")
145
+ end
146
+ end
147
+ end
148
+
149
+ context "when not configured" do
150
+ before(:each) { Honeybadger.configure { |config| config.api_key = "" } }
151
+
152
+ context "on deploy(:environment => 'staging')" do
153
+ it "complains about missing api key" do
154
+ HoneybadgerTasks.should_receive(:puts).with(/api key/i)
155
+ HoneybadgerTasks.deploy(:environment => "staging")
156
+ end
157
+
158
+ it "return false" do
159
+ @output = HoneybadgerTasks.deploy(:environment => "staging")
160
+ @output.should be_false
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end