resque-exceptional 0.0.1 → 0.1.0

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.
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: []