resque-exceptional 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/HISTORY.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## 0.1.0 (2010-10-16)
2
+
3
+ * Added more unit tests.
4
+ * Added yardoc code comments.
5
+ * Had a couple days production testing ;-) Initial version to be announced.
6
+
1
7
  ## 0.0.1 (2010-10-13)
2
8
 
3
9
  * Initial release.
data/README.md CHANGED
@@ -1,13 +1,13 @@
1
1
  resque-exceptional
2
2
  ==================
3
3
 
4
- resque-exceptional provides a Resque failure backend that sends exceptions
5
- raised by jobs to http://getexceptional.com
4
+ resque-exceptional provides a [Resque][re] failure backend that sends exceptions
5
+ raised by jobs to [getexceptional.com][ge]
6
6
 
7
7
  Install & Quick Start
8
8
  ---------------------
9
9
 
10
- Before you jump into code, you'll need a http://getexceptional.com account.
10
+ Before you jump into code, you'll need a getexceptional.com account.
11
11
 
12
12
  To install:
13
13
 
@@ -64,6 +64,18 @@ Configuration Options
64
64
  * `http_open_timeout` - timeout in seconds to establish the connection. (default: `2`)
65
65
  * `http_read_timeout` - timeout in seconds to wait for a reply. (default: `5`)
66
66
 
67
+ Screenshots
68
+ -----------
69
+
70
+ Below are some screenshots of the getexceptional.com web interface, showing
71
+ Resque exceptions.
72
+
73
+ **App Overview**
74
+ ![Get Exceptional - Overview](http://img.skitch.com/20101013-k7hgurmaqew6sn8cik5gywbt2.png)
75
+
76
+ **Detailed Information**
77
+ ![Get Exceptional - Details](http://img.skitch.com/20101013-ftjrjhh3fegcqr9mig9kttmwi4.png)
78
+
67
79
  Note on Patches/Pull Requests
68
80
  -----------------------------
69
81
 
@@ -79,4 +91,8 @@ Note on Patches/Pull Requests
79
91
  Author
80
92
  ------
81
93
 
82
- Luke Antins :: http://lividpenguin.com :: @lantins
94
+ Luke Antins :: [http://lividpenguin.com][lp] :: @lantins
95
+
96
+ [re]: http://github.com/defunkt/resque
97
+ [lp]: http://lividpenguin.com
98
+ [ge]: http://getexceptional.com
data/Rakefile CHANGED
@@ -18,8 +18,8 @@ end
18
18
  # docs task.
19
19
  YARD::Rake::YardocTask.new :yardoc do |t|
20
20
  t.files = ['lib/**/*.rb']
21
- t.options = ['--output-dir', "doc/",
22
- '--files', 'LICENSE HISTORY.md',
21
+ t.options = ['--output-dir', 'doc/',
22
+ '--files', 'LICENSE,HISTORY.md',
23
23
  '--readme', 'README.md',
24
24
  '--title', 'resque-exceptional documentation']
25
25
  end
@@ -1,26 +1,30 @@
1
1
  module Resque
2
2
  module Failure
3
-
4
- # A resque failure backend that sends exception data to getexceptional.com
3
+ # A Resque failure backend that sends exception data to getexceptional.com
5
4
  class Exceptional < Base
5
+ Version = '0.1.0' # Failure backend version number.
6
+
6
7
  # Raised if the api_key is not set.
7
8
  class APIKeyError < StandardError
8
9
  end
9
10
 
10
- # Our version number =)
11
- Version = '0.0.1'
12
-
13
11
  class << self
14
- # API Settings.
15
- attr_accessor :api_key
16
- # HTTP Proxy Options.
12
+ attr_accessor :api_key # your getexceptional api key.
13
+ attr_accessor :use_ssl # enable/disable SSL.
14
+ # HTTP proxy option
17
15
  attr_accessor :proxy_host, :proxy_port, :proxy_user, :proxy_pass
18
- # HTTP Client Options.
19
- attr_accessor :use_ssl, :http_open_timeout, :http_read_timeout
16
+ # HTTP client option
17
+ attr_accessor :http_open_timeout, :http_read_timeout
20
18
  end
21
19
 
22
20
  # Configures the failure backend. At a minimum you will need to set
23
21
  # an api_key.
22
+ #
23
+ # @example Setting your API Key and enabling SSL:
24
+ # Resque::Failure::Exceptional.configure do |config|
25
+ # config.api_key = '505f2518c41866bb0be7ba434bb2b079'
26
+ # config.use_ssl = true
27
+ # end
24
28
  def self.configure
25
29
  yield self
26
30
  end
@@ -60,7 +64,11 @@ module Resque
60
64
  #
61
65
  # @return [Hash] http headers.
62
66
  def http_headers
63
- { 'Content-Type' => 'application/json', 'Accept' => 'application/json' }
67
+ {
68
+ 'Content-Type' => 'application/json',
69
+ 'Accept' => 'application/json',
70
+ 'User-Agent' => "resque-exceptional/#{Version}"
71
+ }
64
72
  end
65
73
 
66
74
  # Returns the compressed request data.
@@ -82,6 +90,9 @@ module Resque
82
90
  #
83
91
  # nb. this isn't documented in the public api... not sure if we should
84
92
  # use it or not...
93
+ #
94
+ # @return [String] md5sum of the backtrace.
95
+ # @return [nil] if we don't have a backtrace available.
85
96
  def uniqueness_hash
86
97
  return nil if (exception.backtrace.nil? || exception.backtrace.empty?)
87
98
  Digest::MD5.hexdigest(exception.backtrace.join)
@@ -104,17 +115,24 @@ module Resque
104
115
  http
105
116
  end
106
117
 
107
- # Helper method to return the correct HTTP port number.
118
+ # Helper method to return the correct HTTP port number, depending on
119
+ # if were using SSL or not.
120
+ #
121
+ # @return [Fixnum] HTTP port number.
108
122
  def http_port
109
123
  use_ssl? ? 443 : 80
110
124
  end
111
125
 
112
126
  # Helper method to check if were using SSL or not.
127
+ #
128
+ # @return [Boolean] true if ssl is enabled.
113
129
  def use_ssl?
114
130
  self.class.use_ssl || false
115
131
  end
116
132
 
117
133
  # Adds a prefix to log messages.
134
+ #
135
+ # @param [String] msg your log message.
118
136
  def log(msg)
119
137
  super("resque-exception - #{msg}")
120
138
  end
@@ -154,6 +172,5 @@ module Resque
154
172
  end
155
173
 
156
174
  end
157
-
158
175
  end
159
- end
176
+ end
@@ -8,6 +8,8 @@ class ExceptionalTest < Test::Unit::TestCase
8
8
  @queue = 'test_queue'
9
9
  @payload = { 'class' => 'TestJob', 'args' => ['foo', 'bar'] }
10
10
  @failure = Resque::Failure::Exceptional.new(@exception, @worker, @queue, @payload)
11
+ WebMock.disable_net_connect!
12
+ WebMock.reset_webmock
11
13
  end
12
14
 
13
15
  # test we can build a hash to send to the api.
@@ -90,29 +92,161 @@ class ExceptionalTest < Test::Unit::TestCase
90
92
  assert_equal true, @failure.respond_to?(:save)
91
93
  end
92
94
 
93
- # we need a Net::HTTP client setup to send the data.
94
- def test_http_client
95
-
95
+ # test save success.
96
+ def test_save_success
97
+ with_api_key '27810b263f0e11eef2f1d29be75d2f39' do
98
+ stub_request(:post, /.*api.getexceptional.com.*/)
99
+ @failure.save
100
+ assert_requested(:post, /.*api.getexceptional.com.*/)
101
+ assert_match /^(resque-exception).*(success).*$/, @worker.log_history.first
102
+ end
96
103
  end
97
104
 
98
- # perform a test with the real api.
99
- def test_live_fire
100
- #omit 'comment this line, set your api key, test with real api!'
101
- Resque::Failure::Exceptional.configure { |c| c.api_key = 'e6bee67c9a45ae41c8318e7d9a0e9d8d0dd9bc20' }
102
- @failure.save
103
- assert_match /^(resque-exception).*(success).*$/, @worker.log_history.first
104
- # reset.
105
- Resque::Failure::Exceptional.configure { |c| c.api_key = nil }
105
+ # test save fail.
106
+ def test_save_fail
107
+ with_api_key '27810b263f0e11eef2f1d29be75d2f39' do
108
+ stub_request(:post, /.*api.getexceptional.com.*/).to_return(:status => [500, 'Internal Server Error'])
109
+ @failure.save
110
+ assert_requested(:post, /.*api.getexceptional.com.*/)
111
+ assert_match /^(resque-exception).*(fail).*(500).*$/, @worker.log_history.first
112
+ end
113
+ end
114
+
115
+ # we need a Net::HTTP client setup to send the data.
116
+ def test_http_client
117
+ assert_kind_of Net::HTTP, @failure.http_client
106
118
  end
107
119
 
108
120
  # we should fail if the api_key is not set.
109
121
  def test_fail_if_api_key_nil
110
122
  # should already be nil, but lets just be sure...
111
- Resque::Failure::Exceptional.configure { |c| c.api_key = nil }
112
- @failure.save
113
- assert_match /^(resque-exception).*(error).*(api_key).*$/, @worker.log_history.first
123
+ with_api_key nil do
124
+ @failure.save
125
+ assert_match /^(resque-exception).*(error).*(api_key).*$/, @worker.log_history.first
126
+ end
127
+ end
128
+
129
+ # test we prefix our log messages.
130
+ def test_log_adds_prefix
131
+ @failure.log('test message')
132
+ @failure.log('123 another msg bud!')
133
+ assert_match /^resque-exception - .*/, @worker.log_history.first
134
+ assert_match /^resque-exception - .*/, @worker.log_history.last
135
+ end
136
+
137
+ # test our `#use_ssl?` and `#http_port` helper methods.
138
+ def test_helper_methods
139
+ # check defaults
140
+ assert_equal false, @failure.use_ssl?, 'use_ssl? should default to false.'
141
+ assert_equal 80, @failure.http_port, 'http_port should default to 80.'
142
+
143
+ # enable ssl.
144
+ Resque::Failure::Exceptional.configure { |c| c.use_ssl = true }
145
+ assert_equal true, @failure.use_ssl?, 'use_ssl? should now be true'
146
+ assert_equal 443, @failure.http_port, 'http_port should now be 443.'
147
+
148
+ # put the config back.
149
+ Resque::Failure::Exceptional.configure { |c| c.use_ssl = false }
150
+ end
151
+
152
+ # returns nil if the backtrace is empty.
153
+ def test_uniqueness_hash_returns_nil_when_empty_backtrace
154
+ mock(@failure.exception).backtrace.times(any_times) { Array.new }
155
+ assert_equal nil, @failure.uniqueness_hash
156
+ end
157
+
158
+ # returns nil if the backtrace is empty.
159
+ def test_uniqueness_hash_returns_nil_when_nil_backtrace
160
+ mock(@failure.exception).backtrace.times(any_times) { nil }
161
+ assert_equal nil, @failure.uniqueness_hash
162
+ end
163
+
164
+ # uniqueness_hash builds a md5sum.
165
+ def test_uniqueness_hash_returns_a_md5_of_the_backtrace
166
+ # fake backtrace.
167
+ fake_backtrace = ['fake', 'backtrace', 'that_wont_change']
168
+ mock(@failure.exception).backtrace.times(any_times) { fake_backtrace }
169
+
170
+ assert_equal '27810b263f0e11eef2f1d29be75d2f39', @failure.uniqueness_hash
171
+ end
172
+
173
+ # return the HTTP path and query string with uniqueness hash.
174
+ def test_http_path_query
175
+ # fake backtrace.
176
+ fake_backtrace = ['fake', 'backtrace', 'that_wont_change']
177
+ mock(@failure.exception).backtrace.times(any_times) { fake_backtrace }
178
+
179
+ with_api_key '27810b263f0e11eef2f1d29be75d2f39' do
180
+ path, query = *@failure.http_path_query.split('?', 2)
181
+ assert_match /^api_key=27810b263f0e11eef2f1d29be75d2f39/, query, 'query should have api_key.'
182
+ assert_match /protocol_version=\d{1}/, query, 'query should have protocol_version.'
183
+ assert_match /hash=27810b263f0e11eef2f1d29be75d2f39$/, query, 'query should have a uniqueness hash.'
184
+ end
185
+ end
186
+
187
+ # build a path & query without a uniqueness hash.
188
+ def test_http_path_query_without_uniqueness_hash
189
+ # fake empty backtrace.
190
+ mock(@failure.exception).backtrace.times(any_times) { Array.new }
191
+
192
+ with_api_key '27810b263f0e11eef2f1d29be75d2f39' do
193
+ path, query = *@failure.http_path_query.split('?', 2)
194
+ assert_match /^api_key=27810b263f0e11eef2f1d29be75d2f39/, query, 'query should have api_key.'
195
+ assert_match /protocol_version=\d{1}$/, query, 'query should have protocol_version.'
196
+ end
197
+ end
198
+
199
+ # raise exception if api key is not set.
200
+ def test_http_path_query_without_api_key_raises_exception
201
+ assert_raise Resque::Failure::Exceptional::APIKeyError, 'should raise APIKeyError if api key is not set' do
202
+ @failure.http_path_query
203
+ end
204
+ end
205
+
206
+ # should return http response if successful.
207
+ def test_http_post_request
208
+ with_api_key '27810b263f0e11eef2f1d29be75d2f39' do
209
+ stub_request(:post, /.*api.getexceptional.com.*/)
210
+
211
+ response = @failure.http_post_request
212
+ assert_requested(:post, /.*api.getexceptional.com.*/)
213
+ assert_equal '200', response.code, 'should be a successful http request'
214
+ end
114
215
  end
115
216
 
116
217
  # should handle exceptions raised during the HTTP Post.
117
- # should return and not raise anything if we were successful.
218
+ def test_http_post_request_handles_exceptions_and_returns_nil
219
+ response = @failure.http_post_request
220
+ assert_equal nil, @failure.http_post_request, 'should be nil, APIKeyError should have been caught.'
221
+
222
+ with_api_key '27810b263f0e11eef2f1d29be75d2f39' do
223
+ WebMock.reset_webmock
224
+ stub_request(:post, /.*api.getexceptional.com.*/).to_raise(StandardError)
225
+ assert_equal nil, @failure.http_post_request, 'should be nil, StandardError should have been caught.'
226
+ assert_requested(:post, /.*api.getexceptional.com.*/)
227
+ end
228
+ end
229
+
230
+ # make sure we catch timeout errors.
231
+ def test_http_post_request_timeout
232
+ with_api_key '27810b263f0e11eef2f1d29be75d2f39' do
233
+ stub_request(:post, /.*api.getexceptional.com.*/).to_timeout
234
+ assert_equal nil, @failure.http_post_request, 'should be nil, TimeoutError should have been caught.'
235
+ assert_requested(:post, /.*api.getexceptional.com.*/)
236
+ end
237
+ end
238
+
239
+ # perform a test with the real api.
240
+ def test_live_fire_with_real_api!
241
+ unless ENV['EXCEPTIONAL_API_KEY']
242
+ omit 'Test with the REAL API. Example: `EXCEPTIONAL_API_KEY=27810b263f0e11eef2f1d29be75d2f39 rake test`'
243
+ end
244
+
245
+ with_api_key ENV['EXCEPTIONAL_API_KEY'] do
246
+ WebMock.allow_net_connect!
247
+ @failure.save
248
+ assert_match /^(resque-exception).*(success).*$/, @worker.log_history.first
249
+ end
250
+ end
251
+
118
252
  end
data/test/test_helper.rb CHANGED
@@ -1,22 +1,32 @@
1
+ require 'simplecov'
2
+ SimpleCov.start do
3
+ add_filter '/test/'
4
+ end
5
+
1
6
  dir = File.dirname(File.expand_path(__FILE__))
2
7
  $LOAD_PATH.unshift dir + '/../lib'
3
8
  $TESTING = true
4
9
 
5
10
  require 'test/unit'
6
11
  require 'rubygems'
7
- require 'simplecov'
8
12
  require 'rr'
13
+ require 'webmock'
14
+ require 'webmock/test_unit'
9
15
 
10
- SimpleCov.start do
11
- add_filter "/test/"
12
- end
16
+ # require our failure backend to test.
17
+ require 'resque-exceptional'
13
18
 
14
19
  class Test::Unit::TestCase
15
20
  include RR::Adapters::TestUnit
16
- end
21
+ include WebMock::API
17
22
 
18
- # require our failure backend to test.
19
- require 'resque-exceptional'
23
+ # periodicly set the api key.
24
+ def with_api_key(key, &block)
25
+ Resque::Failure::Exceptional.api_key = key
26
+ yield
27
+ Resque::Failure::Exceptional.api_key = nil
28
+ end
29
+ end
20
30
 
21
31
  # fake worker.
22
32
  class FakeWorker
@@ -28,7 +38,7 @@ class FakeWorker
28
38
 
29
39
  def log(msg)
30
40
  @log_history << msg
31
- p msg
41
+ p msg if ENV['VERBOSE']
32
42
  end
33
43
 
34
44
  def to_s
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: resque-exceptional
3
3
  version: !ruby/object:Gem::Version
4
- hash: 29
4
+ hash: 27
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 0
9
8
  - 1
10
- version: 0.0.1
9
+ - 0
10
+ version: 0.1.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Luke Antins
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-10-13 00:00:00 +01:00
18
+ date: 2010-10-16 00:00:00 +01:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -65,7 +65,7 @@ dependencies:
65
65
  type: :development
66
66
  version_requirements: *id003
67
67
  - !ruby/object:Gem::Dependency
68
- name: yard
68
+ name: webmock
69
69
  prerelease: false
70
70
  requirement: &id004 !ruby/object:Gem::Requirement
71
71
  none: false
@@ -79,9 +79,23 @@ dependencies:
79
79
  type: :development
80
80
  version_requirements: *id004
81
81
  - !ruby/object:Gem::Dependency
82
- name: simplecov
82
+ name: yard
83
83
  prerelease: false
84
84
  requirement: &id005 !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ hash: 3
90
+ segments:
91
+ - 0
92
+ version: "0"
93
+ type: :development
94
+ version_requirements: *id005
95
+ - !ruby/object:Gem::Dependency
96
+ name: simplecov
97
+ prerelease: false
98
+ requirement: &id006 !ruby/object:Gem::Requirement
85
99
  none: false
86
100
  requirements:
87
101
  - - ">="
@@ -93,7 +107,7 @@ dependencies:
93
107
  - 0
94
108
  version: 0.3.0
95
109
  type: :development
96
- version_requirements: *id005
110
+ version_requirements: *id006
97
111
  description: " resque-exceptional provides a Resque failure backend that sends exceptions\n raised by jobs to getexceptional.com.\n"
98
112
  email: luke@lividpenguin.com
99
113
  executables: []