honeybadger 1.8.1 → 1.9.0.beta1

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 (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