errornot_notifier 1.0.2 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +4 -0
- data/README.rdoc +26 -4
- data/Rakefile +2 -2
- data/SUPPORTED_RAILS_VERSIONS +2 -0
- data/generators/errornot/errornot_generator.rb +15 -6
- data/generators/errornot/templates/errornot_notifier_tasks.rake +14 -1
- data/generators/errornot/templates/initializer.rb +3 -3
- data/lib/errornot_notifier.rb +4 -1
- data/lib/errornot_notifier/configuration.rb +5 -1
- data/lib/errornot_notifier/notice.rb +37 -8
- data/lib/errornot_notifier/rails.rb +3 -2
- data/lib/errornot_notifier/rails/controller_methods.rb +3 -1
- data/lib/errornot_notifier/rails/javascript_notifier.rb +43 -0
- data/lib/errornot_notifier/rails3_tasks.rb +86 -0
- data/lib/errornot_notifier/railtie.rb +31 -0
- data/lib/errornot_notifier/version.rb +1 -1
- data/lib/errornot_tasks.rb +7 -1
- data/lib/rails/generators/errornot/errornot_generator.rb +51 -0
- data/lib/templates/javascript_notifier.erb +6 -0
- data/test/catcher_test.rb +9 -5
- data/test/configuration_test.rb +2 -1
- data/test/errornot_tasks_test.rb +47 -0
- data/test/notice_test.rb +44 -4
- data/test/sender_test.rb +2 -2
- metadata +46 -22
data/CHANGELOG
CHANGED
data/README.rdoc
CHANGED
@@ -17,6 +17,19 @@ In your config/environment* files, remove all references to ExceptionNotifier
|
|
17
17
|
|
18
18
|
Remove the vendor/plugins/exception_notifier directory.
|
19
19
|
|
20
|
+
=== Rails 3.x
|
21
|
+
|
22
|
+
Add the errornot_notifier gem to your Gemfile. In Gemfile:
|
23
|
+
|
24
|
+
gem 'errornot_notifier'
|
25
|
+
|
26
|
+
Then from your project's RAILS_ROOT, run:
|
27
|
+
|
28
|
+
bundle install
|
29
|
+
script/rails generate errornot --api-key your_key_here --server your_host
|
30
|
+
|
31
|
+
That's it!
|
32
|
+
|
20
33
|
=== Rails 2.x
|
21
34
|
|
22
35
|
Add the errornot_notifier gem to your app. In config/environment.rb:
|
@@ -26,11 +39,8 @@ Add the errornot_notifier gem to your app. In config/environment.rb:
|
|
26
39
|
Then from your project's RAILS_ROOT, run:
|
27
40
|
|
28
41
|
rake gems:install
|
29
|
-
script/generate errornot --api-key your_key_here --server your_host
|
30
|
-
|
31
|
-
Once installed, you should vendor the errornot_notifier gem.
|
32
|
-
|
33
42
|
rake gems:unpack GEM=errornot_notifier
|
43
|
+
script/generate errornot --api-key your_key_here --server your_host
|
34
44
|
|
35
45
|
As always, if you choose not to vendor the errornot_notifier gem, make sure
|
36
46
|
every server you deploy to has the gem installed or your application won't start.
|
@@ -300,6 +310,18 @@ Please open up a support ticket on Tender ( http://help.Errornotapp.com ) if
|
|
300
310
|
you're using a version of Rails that is not listed above and the notifier is
|
301
311
|
not working properly.
|
302
312
|
|
313
|
+
== Javascript Notifer
|
314
|
+
|
315
|
+
To automatically include the Javascript node on every page, set the
|
316
|
+
:js_notifier to true:
|
317
|
+
|
318
|
+
ErrornotNotifier.configure do |config|
|
319
|
+
config.js_notifier = true
|
320
|
+
end
|
321
|
+
|
322
|
+
It automatically uses the API key, host, and port specified in the
|
323
|
+
configuration.
|
324
|
+
|
303
325
|
== Thanks
|
304
326
|
|
305
327
|
Thanks to Eugene Bolshakov for the excellent write-up on GOING BEYOND
|
data/Rakefile
CHANGED
@@ -195,10 +195,10 @@ def define_rails_cucumber_tasks(additional_cucumber_args = '')
|
|
195
195
|
namespace :rails do
|
196
196
|
RAILS_VERSIONS.each do |version|
|
197
197
|
desc "Test integration of the gem with Rails #{version}"
|
198
|
-
task version do
|
198
|
+
task version => [:gemspec, :vendor_test_gems] do
|
199
199
|
puts "Testing Rails #{version}"
|
200
200
|
ENV['RAILS_VERSION'] = version
|
201
|
-
system("cucumber --format progress #{additional_cucumber_args} features/rails.feature")
|
201
|
+
system("cucumber --format #{ENV['CUCUMBER_FORMAT'] || 'progress'} #{additional_cucumber_args} features/rails.feature features/rails_with_js_notifier.feature")
|
202
202
|
end
|
203
203
|
end
|
204
204
|
|
data/SUPPORTED_RAILS_VERSIONS
CHANGED
@@ -5,11 +5,12 @@ class ErrornotGenerator < Rails::Generator::Base
|
|
5
5
|
def add_options!(opt)
|
6
6
|
opt.on('-k', '--api-key=key', String, "Your ErrorNot API key") {|v| options[:api_key] = v}
|
7
7
|
opt.on('-s', '--server=host', String, "Your host with errorNot is installed") {|v| options[:host] = v}
|
8
|
+
opt.on('-h', '--heroku', "Use the Heroku addon to provide your Hoptoad API key") {|v| options[:heroku] = v}
|
8
9
|
end
|
9
10
|
|
10
11
|
def manifest
|
11
|
-
if !api_key_configured? && !options[:api_key]
|
12
|
-
puts "Must pass --api-key or create config/initializers/errornot.rb"
|
12
|
+
if !api_key_configured? && !options[:api_key] && !options[:heroku]
|
13
|
+
puts "Must pass --api-key or --heroku or create config/initializers/errornot.rb"
|
13
14
|
exit
|
14
15
|
end
|
15
16
|
|
@@ -23,22 +24,30 @@ class ErrornotGenerator < Rails::Generator::Base
|
|
23
24
|
if ['config/deploy.rb', 'Capfile'].all? { |file| File.exists?(file) }
|
24
25
|
m.append_to 'config/deploy.rb', capistrano_hook
|
25
26
|
end
|
26
|
-
if
|
27
|
+
if api_key_expression
|
27
28
|
if use_initializer?
|
28
29
|
m.template 'initializer.rb', 'config/initializers/errornot.rb',
|
29
|
-
:assigns => {:api_key =>
|
30
|
+
:assigns => {:api_key => api_key_expression,
|
30
31
|
:host => options[:host]}
|
31
32
|
else
|
32
33
|
m.template 'initializer.rb', 'config/errornot.rb',
|
33
|
-
:assigns => {:api_key =>
|
34
|
+
:assigns => {:api_key => api_key_expression,
|
34
35
|
:host => options[:host]}
|
35
36
|
m.append_to 'config/environment.rb', "require 'config/errornot'"
|
36
37
|
end
|
37
38
|
end
|
38
|
-
m.rake "errornot:test", :generate_only => true
|
39
|
+
m.rake "errornot:test --trace", :generate_only => true
|
39
40
|
end
|
40
41
|
end
|
41
42
|
|
43
|
+
def api_key_expression
|
44
|
+
s = if options[:api_key]
|
45
|
+
"'#{options[:api_key]}'"
|
46
|
+
elsif options[:heroku]
|
47
|
+
"ENV['HOPTOAD_API_KEY']"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
42
51
|
def use_initializer?
|
43
52
|
Rails::VERSION::MAJOR > 1
|
44
53
|
end
|
@@ -6,5 +6,18 @@ unless ARGV.any? {|a| a =~ /^gems/}
|
|
6
6
|
$: << File.join(vendored_notifier, 'lib')
|
7
7
|
end
|
8
8
|
|
9
|
-
|
9
|
+
begin
|
10
|
+
require 'errornot_notifier/tasks'
|
11
|
+
rescue LoadError => exception
|
12
|
+
namespace :errornot do
|
13
|
+
%w(test log_stdout).each do |task_name|
|
14
|
+
desc "Missing dependency for errornot:#{task_name}"
|
15
|
+
task task_name do
|
16
|
+
$stderr.puts "Failed to run errornot:#{task_name} because of missing dependency."
|
17
|
+
$stderr.puts "You probably need to run `rake gems:install` to install the errornot_notifier gem"
|
18
|
+
abort exception.inspect
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
10
23
|
end
|
@@ -1,7 +1,7 @@
|
|
1
|
-
<% if Rails::VERSION::MINOR < 2 -%>
|
1
|
+
<% if Rails::VERSION::MAJOR < 3 && Rails::VERSION::MINOR < 2 -%>
|
2
2
|
require 'errornot_notifier/rails'
|
3
3
|
<% end -%>
|
4
4
|
ErrornotNotifier.configure do |config|
|
5
|
-
config.api_key =
|
6
|
-
config.host = '<%=
|
5
|
+
config.api_key = <%= api_key_expression %>
|
6
|
+
config.host = '<%= options[:server] %>'
|
7
7
|
end
|
data/lib/errornot_notifier.rb
CHANGED
@@ -9,6 +9,9 @@ require 'errornot_notifier/sender'
|
|
9
9
|
require 'errornot_notifier/backtrace'
|
10
10
|
require 'errornot_notifier/rack'
|
11
11
|
|
12
|
+
#TODO: Not sure if this needs to be removed to support 2.x or not
|
13
|
+
require 'errornot_notifier/railtie' if defined?(Rails::Railtie)
|
14
|
+
|
12
15
|
# Gem for applications to automatically post errors to the Errornot of their choice.
|
13
16
|
module ErrornotNotifier
|
14
17
|
|
@@ -125,7 +128,7 @@ module ErrornotNotifier
|
|
125
128
|
def build_notice_for(exception, opts = {})
|
126
129
|
exception = unwrap_exception(exception)
|
127
130
|
if exception.respond_to?(:to_hash)
|
128
|
-
opts = opts.merge(exception)
|
131
|
+
opts = opts.merge(exception.to_hash)
|
129
132
|
else
|
130
133
|
opts = opts.merge(:exception => exception)
|
131
134
|
end
|
@@ -7,7 +7,7 @@ module ErrornotNotifier
|
|
7
7
|
:http_open_timeout, :http_read_timeout, :ignore, :ignore_by_filters,
|
8
8
|
:ignore_user_agent, :notifier_name, :notifier_url, :notifier_version,
|
9
9
|
:params_filters, :project_root, :port, :protocol, :proxy_host,
|
10
|
-
:proxy_pass, :proxy_port, :proxy_user, :secure, :framework].freeze
|
10
|
+
:proxy_pass, :proxy_port, :proxy_user, :secure, :framework, :js_notifier].freeze
|
11
11
|
|
12
12
|
# The API key for your project, found on the project edit form.
|
13
13
|
attr_accessor :api_key
|
@@ -62,6 +62,9 @@ module ErrornotNotifier
|
|
62
62
|
# +true+ if you want to check for production errors matching development errors, +false+ otherwise.
|
63
63
|
attr_accessor :development_lookup
|
64
64
|
|
65
|
+
# +true+ if you want to enable the JavaScript notifier in production environments
|
66
|
+
attr_accessor :js_notifier
|
67
|
+
|
65
68
|
# The name of the environment the application is running in
|
66
69
|
attr_accessor :environment_name
|
67
70
|
|
@@ -124,6 +127,7 @@ module ErrornotNotifier
|
|
124
127
|
@development_environments = %w(development test cucumber)
|
125
128
|
@development_lookup = true
|
126
129
|
@notifier_name = 'Errornot Notifier'
|
130
|
+
@js_notifier = false
|
127
131
|
@notifier_version = VERSION
|
128
132
|
@framework = 'Standalone'
|
129
133
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'builder'
|
2
|
+
|
1
3
|
module ErrornotNotifier
|
2
4
|
class Notice
|
3
5
|
|
@@ -78,9 +80,12 @@ module ErrornotNotifier
|
|
78
80
|
self.ignore_by_filters = args[:ignore_by_filters] || []
|
79
81
|
self.backtrace_filters = args[:backtrace_filters] || []
|
80
82
|
self.params_filters = args[:params_filters] || []
|
81
|
-
self.parameters = args[:parameters]
|
82
|
-
|
83
|
-
|
83
|
+
self.parameters = args[:parameters] ||
|
84
|
+
action_dispatch_params ||
|
85
|
+
rack_env(:params) ||
|
86
|
+
{}
|
87
|
+
self.component = args[:component] || args[:controller] || parameters['controller']
|
88
|
+
self.action = args[:action] || parameters['action']
|
84
89
|
|
85
90
|
self.environment_name = args[:environment_name]
|
86
91
|
self.cgi_data = args[:cgi_data] || args[:rack_env]
|
@@ -90,8 +95,10 @@ module ErrornotNotifier
|
|
90
95
|
"#{exception.class.name}: #{exception.message}"
|
91
96
|
end
|
92
97
|
|
98
|
+
also_use_rack_params_filters
|
93
99
|
find_session_data
|
94
100
|
clean_params
|
101
|
+
clean_rack_request_data
|
95
102
|
end
|
96
103
|
|
97
104
|
# Converts the given notice to XML
|
@@ -114,15 +121,15 @@ module ErrornotNotifier
|
|
114
121
|
'component' => controller,
|
115
122
|
'action' => action}
|
116
123
|
|
117
|
-
unless parameters.
|
124
|
+
unless parameters.nil? || parameters.empty?
|
118
125
|
error['request']['params'] = parameters
|
119
126
|
end
|
120
127
|
|
121
|
-
unless session_data.
|
128
|
+
unless session_data.nil? || session_data.empty?
|
122
129
|
error['session'] = session_data
|
123
130
|
end
|
124
131
|
|
125
|
-
unless cgi_data.
|
132
|
+
unless cgi_data.nil? || cgi_data.empty?
|
126
133
|
error['request']['cgi-data'] = cgi_data
|
127
134
|
end
|
128
135
|
error['environment'] = {'root' => project_root,
|
@@ -226,6 +233,13 @@ module ErrornotNotifier
|
|
226
233
|
end
|
227
234
|
if session_data
|
228
235
|
clean_unserializable_data_from(:session_data)
|
236
|
+
filter(session_data)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
def clean_rack_request_data
|
241
|
+
if cgi_data
|
242
|
+
cgi_data.delete("rack.request.form_vars")
|
229
243
|
end
|
230
244
|
end
|
231
245
|
|
@@ -243,12 +257,12 @@ module ErrornotNotifier
|
|
243
257
|
|
244
258
|
def filter_key?(key)
|
245
259
|
params_filters.any? do |filter|
|
246
|
-
key.to_s.include?(filter)
|
260
|
+
key.to_s.include?(filter.to_s)
|
247
261
|
end
|
248
262
|
end
|
249
263
|
|
250
264
|
def find_session_data
|
251
|
-
self.session_data = args[:session_data] || args[:session] || {}
|
265
|
+
self.session_data = args[:session_data] || args[:session] || rack_session || {}
|
252
266
|
self.session_data = session_data[:data] if session_data[:data]
|
253
267
|
end
|
254
268
|
|
@@ -283,5 +297,20 @@ module ErrornotNotifier
|
|
283
297
|
::Rack::Request.new(args[:rack_env])
|
284
298
|
end
|
285
299
|
end
|
300
|
+
|
301
|
+
def action_dispatch_params
|
302
|
+
args[:rack_env]['action_dispatch.request.parameters'] if args[:rack_env]
|
303
|
+
end
|
304
|
+
|
305
|
+
def rack_session
|
306
|
+
args[:rack_env]['rack.session'] if args[:rack_env]
|
307
|
+
end
|
308
|
+
|
309
|
+
def also_use_rack_params_filters
|
310
|
+
if args[:rack_env]
|
311
|
+
self.params_filters += rack_request.env["action_dispatch.parameter_filter"] || []
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
286
315
|
end
|
287
316
|
end
|
@@ -2,6 +2,7 @@ require 'errornot_notifier'
|
|
2
2
|
require 'errornot_notifier/rails/controller_methods'
|
3
3
|
require 'errornot_notifier/rails/action_controller_catcher'
|
4
4
|
require 'errornot_notifier/rails/error_lookup'
|
5
|
+
require 'errornot_notifier/rails/javascript_notifier'
|
5
6
|
|
6
7
|
module ErrornotNotifier
|
7
8
|
module Rails
|
@@ -10,6 +11,7 @@ module ErrornotNotifier
|
|
10
11
|
ActionController::Base.send(:include, ErrornotNotifier::Rails::ActionControllerCatcher)
|
11
12
|
ActionController::Base.send(:include, ErrornotNotifier::Rails::ErrorLookup)
|
12
13
|
ActionController::Base.send(:include, ErrornotNotifier::Rails::ControllerMethods)
|
14
|
+
ActionController::Base.send(:include, ErrornotNotifier::Rails::JavascriptNotifier)
|
13
15
|
end
|
14
16
|
|
15
17
|
rails_logger = if defined?(::Rails.logger)
|
@@ -20,7 +22,7 @@ module ErrornotNotifier
|
|
20
22
|
|
21
23
|
if defined?(::Rails.configuration) && ::Rails.configuration.respond_to?(:middleware)
|
22
24
|
::Rails.configuration.middleware.insert_after 'ActionController::Failsafe',
|
23
|
-
|
25
|
+
ErrornotNotifier::Rack
|
24
26
|
end
|
25
27
|
|
26
28
|
ErrornotNotifier.configure(true) do |config|
|
@@ -34,4 +36,3 @@ module ErrornotNotifier
|
|
34
36
|
end
|
35
37
|
|
36
38
|
ErrornotNotifier::Rails.initialize
|
37
|
-
|
@@ -19,7 +19,7 @@ module ErrornotNotifier
|
|
19
19
|
|
20
20
|
def errornot_request_data
|
21
21
|
{ :parameters => errornot_filter_if_filtering(params.to_hash),
|
22
|
-
:session_data => errornot_session_data,
|
22
|
+
:session_data => errornot_filter_if_filtering(errornot_session_data),
|
23
23
|
:controller => params[:controller],
|
24
24
|
:action => params[:action],
|
25
25
|
:url => errornot_request_url,
|
@@ -27,6 +27,8 @@ module ErrornotNotifier
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def errornot_filter_if_filtering(hash)
|
30
|
+
return hash if ! hash.is_a?(Hash)
|
31
|
+
|
30
32
|
if respond_to?(:filter_parameters)
|
31
33
|
filter_parameters(hash) rescue hash
|
32
34
|
else
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module ErrornotNotifier
|
2
|
+
module Rails
|
3
|
+
module JavascriptNotifier
|
4
|
+
def self.included(base) #:nodoc:
|
5
|
+
base.send(:after_filter, :insert_errornot_javascript_notifier)
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def insert_errornot_javascript_notifier
|
11
|
+
return unless ErrornotNotifier.configuration.public?
|
12
|
+
return unless ErrornotNotifier.configuration.js_notifier
|
13
|
+
|
14
|
+
path = File.join(File.dirname(__FILE__), '..', '..', 'templates', 'javascript_notifier.erb')
|
15
|
+
host = ErrornotNotifier.configuration.host.dup
|
16
|
+
port = ErrornotNotifier.configuration.port
|
17
|
+
host << ":#{port}" unless [80, 443].include?(port)
|
18
|
+
|
19
|
+
options = {
|
20
|
+
:file => path,
|
21
|
+
:layout => false,
|
22
|
+
:use_full_path => false,
|
23
|
+
:locals => {
|
24
|
+
:host => host,
|
25
|
+
:secure => ErrornotNotifier.configuration.secure,
|
26
|
+
:api_key => ErrornotNotifier.configuration.api_key,
|
27
|
+
:environment => ErrornotNotifier.configuration.environment_name
|
28
|
+
}
|
29
|
+
}
|
30
|
+
|
31
|
+
if @template
|
32
|
+
javascript = @template.render(options)
|
33
|
+
else
|
34
|
+
javascript = render_to_string(options)
|
35
|
+
end
|
36
|
+
|
37
|
+
if response.body.respond_to?(:gsub)
|
38
|
+
response.body = response.body.gsub(/<(head)>/i, "<\\1>\n" + javascript)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'errornot_notifier'
|
2
|
+
|
3
|
+
namespace :errornot do
|
4
|
+
desc "Notify Errornot of a new deploy."
|
5
|
+
task :deploy => :environment do
|
6
|
+
raise NotImplemented.new
|
7
|
+
end
|
8
|
+
|
9
|
+
desc "Verify your gem installation by sending a test exception to the errornot service"
|
10
|
+
task :test => [:environment] do
|
11
|
+
Rails.logger = Logger.new(STDOUT)
|
12
|
+
Rails.logger.level = Logger::DEBUG
|
13
|
+
ErrornotNotifier.configure(true) do |config|
|
14
|
+
config.logger = Rails.logger
|
15
|
+
end
|
16
|
+
|
17
|
+
require 'app/controllers/application_controller'
|
18
|
+
|
19
|
+
class ErrornotTestingException < RuntimeError; end
|
20
|
+
|
21
|
+
unless ErrornotNotifier.configuration.api_key
|
22
|
+
puts "Errornot needs an API key configured! Check the README to see how to add it."
|
23
|
+
exit
|
24
|
+
end
|
25
|
+
|
26
|
+
ErrornotNotifier.configuration.development_environments = []
|
27
|
+
|
28
|
+
puts "Configuration:"
|
29
|
+
ErrornotNotifier.configuration.to_hash.each do |key, value|
|
30
|
+
puts sprintf("%25s: %s", key.to_s, value.inspect.slice(0, 55))
|
31
|
+
end
|
32
|
+
|
33
|
+
unless defined?(ApplicationController)
|
34
|
+
puts "No ApplicationController found"
|
35
|
+
exit
|
36
|
+
end
|
37
|
+
|
38
|
+
puts 'Setting up the Controller.'
|
39
|
+
class ApplicationController
|
40
|
+
# This is to bypass any filters that may prevent access to the action.
|
41
|
+
prepend_before_filter :test_errornot
|
42
|
+
def test_errornot
|
43
|
+
puts "Raising '#{exception_class.name}' to simulate application failure."
|
44
|
+
raise exception_class.new, 'Testing errornot via "rake errornot:test". If you can see this, it works.'
|
45
|
+
end
|
46
|
+
|
47
|
+
# def rescue_action(exception)
|
48
|
+
# rescue_action_in_public exception
|
49
|
+
# end
|
50
|
+
|
51
|
+
# Ensure we actually have an action to go to.
|
52
|
+
def verify; end
|
53
|
+
|
54
|
+
# def consider_all_requests_local
|
55
|
+
# false
|
56
|
+
# end
|
57
|
+
|
58
|
+
# def local_request?
|
59
|
+
# false
|
60
|
+
# end
|
61
|
+
|
62
|
+
def exception_class
|
63
|
+
exception_name = ENV['EXCEPTION'] || "ErrornotTestingException"
|
64
|
+
Object.const_get(exception_name)
|
65
|
+
rescue
|
66
|
+
Object.const_set(exception_name, Class.new(Exception))
|
67
|
+
end
|
68
|
+
|
69
|
+
def logger
|
70
|
+
nil
|
71
|
+
end
|
72
|
+
end
|
73
|
+
class ErrornotVerificationController < ApplicationController; end
|
74
|
+
|
75
|
+
Rails.application.routes_reloader.execute_if_updated
|
76
|
+
Rails.application.routes.draw do
|
77
|
+
match 'verify' => 'application#verify', :as => 'verify'
|
78
|
+
end
|
79
|
+
|
80
|
+
puts 'Processing request.'
|
81
|
+
env = Rack::MockRequest.env_for("/verify")
|
82
|
+
|
83
|
+
Rails.application.call(env)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'errornot_notifier'
|
2
|
+
require 'rails'
|
3
|
+
|
4
|
+
module ErrornotNotifier
|
5
|
+
class Railtie < Rails::Railtie
|
6
|
+
rake_tasks do
|
7
|
+
require "errornot_notifier/rails3_tasks"
|
8
|
+
end
|
9
|
+
|
10
|
+
initializer "errornot.use_rack_middleware" do |app|
|
11
|
+
config.app_middleware.use "ErrornotNotifier::Rack"
|
12
|
+
end
|
13
|
+
|
14
|
+
config.after_initialize do
|
15
|
+
ErrornotNotifier.configure(true) do |config|
|
16
|
+
config.logger = Rails.logger
|
17
|
+
config.environment_name = Rails.env
|
18
|
+
config.project_root = Rails.root
|
19
|
+
config.framework = "Rails: #{::Rails::VERSION::STRING}"
|
20
|
+
end
|
21
|
+
|
22
|
+
if defined?(::ActionController::Base)
|
23
|
+
require 'errornot_notifier/rails/javascript_notifier'
|
24
|
+
|
25
|
+
::ActionController::Base.send(:include, ErrornotNotifier::Rails::JavascriptNotifier)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
data/lib/errornot_tasks.rb
CHANGED
@@ -31,7 +31,13 @@ module ErrornotTasks
|
|
31
31
|
opts.each {|k,v| params["deploy[#{k}]"] = v }
|
32
32
|
|
33
33
|
url = URI.parse("http://#{ErrornotNotifier.configuration.host}/deploys")
|
34
|
-
|
34
|
+
proxy = Net::HTTP.Proxy(ErrornotNotifier.configuration.proxy_host,
|
35
|
+
ErrornotNotifier.configuration.proxy_port,
|
36
|
+
ErrornotNotifier.configuration.proxy_user,
|
37
|
+
ErrornotNotifier.configuration.proxy_pass)
|
38
|
+
|
39
|
+
response = proxy.post_form(url, params)
|
40
|
+
|
35
41
|
puts response.body
|
36
42
|
return Net::HTTPSuccess === response
|
37
43
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
|
3
|
+
class ErrornotGenerator < Rails::Generators::Base
|
4
|
+
|
5
|
+
class_option :api_key, :aliases => "-k", :type => :string, :desc => "Your Errornot API key"
|
6
|
+
class_option :heroku, :type => :boolean, :desc => "Use the Heroku addon to provide your Hoptoad API key"
|
7
|
+
class_option :server, :type => :string, :desc => "Your host of Errornot"
|
8
|
+
|
9
|
+
def self.source_root
|
10
|
+
@_errornot_source_root ||= File.expand_path("../../../../../generators/errornot/templates", __FILE__)
|
11
|
+
end
|
12
|
+
|
13
|
+
def install
|
14
|
+
ensure_api_key_was_configured
|
15
|
+
generate_initializer unless api_key_configured?
|
16
|
+
test_errornot
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def ensure_api_key_was_configured
|
22
|
+
if !options[:api_key] && !options[:heroku] && !api_key_configured?
|
23
|
+
puts "Must pass --api-key or --heroku or create config/initializers/errornot.rb"
|
24
|
+
exit
|
25
|
+
end
|
26
|
+
if !options[:server] && !api_key_configured?
|
27
|
+
puts "Must pass --server or create config/initializers/errornot.rb"
|
28
|
+
exit
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def api_key_expression
|
33
|
+
s = if options[:api_key]
|
34
|
+
"'#{options[:api_key]}'"
|
35
|
+
elsif options[:heroku]
|
36
|
+
"ENV['HOPTOAD_API_KEY']"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def generate_initializer
|
41
|
+
template 'initializer.rb', 'config/initializers/errornot.rb'
|
42
|
+
end
|
43
|
+
|
44
|
+
def api_key_configured?
|
45
|
+
File.exists?('config/initializers/errornot.rb')
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_errornot
|
49
|
+
puts run("rake errornot:test --trace")
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,6 @@
|
|
1
|
+
<script type="text/javascript" src="http<%= 's' if secure %>://<%= host %>/javascripts/notifier.js"></script>
|
2
|
+
<script type="text/javascript">
|
3
|
+
Errornot.setKey('<%= api_key %>');
|
4
|
+
Errornot.setHost('<%= host %>');
|
5
|
+
Errornot.setEnvironment('<%= environment %>');
|
6
|
+
</script>
|
data/test/catcher_test.rb
CHANGED
@@ -214,10 +214,12 @@ class ActionControllerCatcherTest < Test::Unit::TestCase
|
|
214
214
|
assert_sent_request_info_for controller.request
|
215
215
|
end
|
216
216
|
|
217
|
-
should "use standard rails logging filters on params and env" do
|
217
|
+
should "use standard rails logging filters on params and session and env" do
|
218
218
|
filtered_params = { "abc" => "123",
|
219
219
|
"def" => "456",
|
220
220
|
"ghi" => "[FILTERED]" }
|
221
|
+
filtered_session = { "abc" => "123",
|
222
|
+
"ghi" => "[FILTERED]" }
|
221
223
|
ENV['ghi'] = 'abc'
|
222
224
|
filtered_env = { 'ghi' => '[FILTERED]' }
|
223
225
|
filtered_cgi = { 'REQUEST_METHOD' => '[FILTERED]' }
|
@@ -225,10 +227,12 @@ class ActionControllerCatcherTest < Test::Unit::TestCase
|
|
225
227
|
process_action_with_automatic_notification(:filters => [:ghi, :request_method],
|
226
228
|
:params => { "abc" => "123",
|
227
229
|
"def" => "456",
|
228
|
-
"ghi" => "789" }
|
229
|
-
|
230
|
-
|
231
|
-
|
230
|
+
"ghi" => "789" },
|
231
|
+
:session => { "abc" => "123",
|
232
|
+
"ghi" => "789" })
|
233
|
+
assert_sent_hash filtered_params, '/notice/request/params'
|
234
|
+
assert_sent_hash filtered_cgi, '/notice/request/cgi-data'
|
235
|
+
assert_sent_hash filtered_session, '/notice/request/session'
|
232
236
|
end
|
233
237
|
|
234
238
|
context "for a local error with development lookup enabled" do
|
data/test/configuration_test.rb
CHANGED
@@ -27,6 +27,7 @@ class ConfigurationTest < Test::Unit::TestCase
|
|
27
27
|
ErrornotNotifier::Configuration::IGNORE_DEFAULT
|
28
28
|
assert_config_default :development_lookup, true
|
29
29
|
assert_config_default :framework, 'Standalone'
|
30
|
+
assert_config_default :js_notifier, false
|
30
31
|
end
|
31
32
|
|
32
33
|
should "provide default values for secure connections" do
|
@@ -82,7 +83,7 @@ class ConfigurationTest < Test::Unit::TestCase
|
|
82
83
|
:http_read_timeout, :ignore, :ignore_by_filters, :ignore_user_agent,
|
83
84
|
:notifier_name, :notifier_url, :notifier_version, :params_filters,
|
84
85
|
:project_root, :port, :protocol, :proxy_host, :proxy_pass, :proxy_port,
|
85
|
-
:proxy_user, :secure, :development_lookup].each do |option|
|
86
|
+
:proxy_user, :secure, :development_lookup, :js_notifier].each do |option|
|
86
87
|
assert_equal config[option], hash[option], "Wrong value for #{option}"
|
87
88
|
end
|
88
89
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/helper'
|
2
|
+
require 'rubygems'
|
3
|
+
|
4
|
+
require File.dirname(__FILE__) + '/../lib/errornot_tasks'
|
5
|
+
require 'fakeweb'
|
6
|
+
|
7
|
+
FakeWeb.allow_net_connect = false
|
8
|
+
|
9
|
+
class ErrornotTasksTest < Test::Unit::TestCase
|
10
|
+
def successful_response(body = "")
|
11
|
+
response = Net::HTTPSuccess.new('1.2', '200', 'OK')
|
12
|
+
response.stubs(:body).returns(body)
|
13
|
+
return response
|
14
|
+
end
|
15
|
+
|
16
|
+
def unsuccessful_response(body = "")
|
17
|
+
response = Net::HTTPClientError.new('1.2', '200', 'OK')
|
18
|
+
response.stubs(:body).returns(body)
|
19
|
+
return response
|
20
|
+
end
|
21
|
+
|
22
|
+
context "being quiet" do
|
23
|
+
setup { ErrornotTasks.stubs(:puts) }
|
24
|
+
|
25
|
+
context "in a configured project" do
|
26
|
+
setup { ErrornotNotifier.configure { |config| config.api_key = "1234123412341234" } }
|
27
|
+
|
28
|
+
|
29
|
+
context "given an optional HTTP proxy and valid options" do
|
30
|
+
setup do
|
31
|
+
@response = stub("response", :body => "stub body")
|
32
|
+
@http_proxy = stub("proxy", :post_form => @response)
|
33
|
+
|
34
|
+
Net::HTTP.expects(:Proxy).
|
35
|
+
with(ErrornotNotifier.configuration.proxy_host,
|
36
|
+
ErrornotNotifier.configuration.proxy_port,
|
37
|
+
ErrornotNotifier.configuration.proxy_user,
|
38
|
+
ErrornotNotifier.configuration.proxy_pass).
|
39
|
+
returns(@http_proxy)
|
40
|
+
|
41
|
+
@options = { :rails_env => "staging" }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
data/test/notice_test.rb
CHANGED
@@ -164,6 +164,20 @@ class NoticeTest < Test::Unit::TestCase
|
|
164
164
|
assert_filters_hash(:cgi_data)
|
165
165
|
end
|
166
166
|
|
167
|
+
should "filter session" do
|
168
|
+
assert_filters_hash(:session_data)
|
169
|
+
end
|
170
|
+
|
171
|
+
should "remove rack.request.form_vars" do
|
172
|
+
original = {
|
173
|
+
"rack.request.form_vars" => "story%5Btitle%5D=The+TODO+label",
|
174
|
+
"abc" => "123"
|
175
|
+
}
|
176
|
+
|
177
|
+
notice = build_notice(:cgi_data => original)
|
178
|
+
assert_equal({"abc" => "123"}, notice.cgi_data)
|
179
|
+
end
|
180
|
+
|
167
181
|
context "a Notice turned into XML" do
|
168
182
|
setup do
|
169
183
|
ErrornotNotifier.configure do |config|
|
@@ -320,9 +334,6 @@ class NoticeTest < Test::Unit::TestCase
|
|
320
334
|
end
|
321
335
|
|
322
336
|
should "extract data from a rack environment hash" do
|
323
|
-
# TODO: extract session data
|
324
|
-
# TODO: extract controller
|
325
|
-
# TODO: extract action
|
326
337
|
url = "https://subdomain.happylane.com:100/test/file.rb?var=value&var2=value2"
|
327
338
|
parameters = { 'var' => 'value', 'var2' => 'value2' }
|
328
339
|
env = Rack::MockRequest.env_for(url)
|
@@ -334,6 +345,35 @@ class NoticeTest < Test::Unit::TestCase
|
|
334
345
|
assert_equal 'GET', notice.cgi_data['REQUEST_METHOD']
|
335
346
|
end
|
336
347
|
|
348
|
+
should "extract data from a rack environment hash with action_dispatch info" do
|
349
|
+
params = { 'controller' => 'users', 'action' => 'index', 'id' => '7' }
|
350
|
+
env = Rack::MockRequest.env_for('/', { 'action_dispatch.request.parameters' => params })
|
351
|
+
|
352
|
+
notice = build_notice(:rack_env => env)
|
353
|
+
|
354
|
+
assert_equal params, notice.parameters
|
355
|
+
assert_equal params['controller'], notice.component
|
356
|
+
assert_equal params['action'], notice.action
|
357
|
+
end
|
358
|
+
|
359
|
+
should "extract session data from a rack environment" do
|
360
|
+
session_data = { 'something' => 'some value' }
|
361
|
+
env = Rack::MockRequest.env_for('/', 'rack.session' => session_data)
|
362
|
+
|
363
|
+
notice = build_notice(:rack_env => env)
|
364
|
+
|
365
|
+
assert_equal session_data, notice.session_data
|
366
|
+
end
|
367
|
+
|
368
|
+
should "prefer passed session data to rack session data" do
|
369
|
+
session_data = { 'something' => 'some value' }
|
370
|
+
env = Rack::MockRequest.env_for('/')
|
371
|
+
|
372
|
+
notice = build_notice(:rack_env => env, :session_data => session_data)
|
373
|
+
|
374
|
+
assert_equal session_data, notice.session_data
|
375
|
+
end
|
376
|
+
|
337
377
|
def assert_accepts_exception_attribute(attribute, args = {}, &block)
|
338
378
|
exception = build_exception
|
339
379
|
block ||= lambda { exception.send(attribute) }
|
@@ -375,7 +415,7 @@ class NoticeTest < Test::Unit::TestCase
|
|
375
415
|
end
|
376
416
|
|
377
417
|
def assert_filters_hash(attribute)
|
378
|
-
filters =
|
418
|
+
filters = ["abc", :def]
|
379
419
|
original = { 'abc' => "123", 'def' => "456", 'ghi' => "789", 'nested' => { 'abc' => '100' } }
|
380
420
|
filtered = { 'abc' => "[FILTERED]",
|
381
421
|
'def' => "[FILTERED]",
|
data/test/sender_test.rb
CHANGED
@@ -39,7 +39,7 @@ class SenderTest < Test::Unit::TestCase
|
|
39
39
|
proxy = stub(:new => http)
|
40
40
|
Net::HTTP.stubs(:Proxy => proxy)
|
41
41
|
|
42
|
-
url = "http://
|
42
|
+
url = "http://shingara.fr:80#{ErrornotNotifier::Sender::NOTICES_URI}"
|
43
43
|
uri = URI.parse(url)
|
44
44
|
|
45
45
|
proxy_host = 'some.host'
|
@@ -61,7 +61,7 @@ class SenderTest < Test::Unit::TestCase
|
|
61
61
|
|
62
62
|
should "post to the right url for non-ssl" do
|
63
63
|
http = stub_http
|
64
|
-
url = "http://
|
64
|
+
url = "http://shingara.fr:80#{ErrornotNotifier::Sender::NOTICES_URI}"
|
65
65
|
uri = URI.parse(url)
|
66
66
|
send_exception(:secure => false)
|
67
67
|
assert_received(http, :post) {|expect| expect.with(uri.path, anything, ErrornotNotifier::HEADERS) }
|
metadata
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: errornot_notifier
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
+
hash: 19
|
4
5
|
prerelease: false
|
5
6
|
segments:
|
6
7
|
- 1
|
8
|
+
- 1
|
7
9
|
- 0
|
8
|
-
|
9
|
-
version: 1.0.2
|
10
|
+
version: 1.1.0
|
10
11
|
platform: ruby
|
11
12
|
authors:
|
12
13
|
- thoughtbot, inc, Cyril Mougel
|
@@ -14,16 +15,18 @@ autorequire:
|
|
14
15
|
bindir: bin
|
15
16
|
cert_chain: []
|
16
17
|
|
17
|
-
date: 2010-
|
18
|
+
date: 2010-09-07 00:00:00 +02:00
|
18
19
|
default_executable:
|
19
20
|
dependencies:
|
20
21
|
- !ruby/object:Gem::Dependency
|
21
22
|
name: activesupport
|
22
23
|
prerelease: false
|
23
24
|
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
24
26
|
requirements:
|
25
27
|
- - ">="
|
26
28
|
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
27
30
|
segments:
|
28
31
|
- 0
|
29
32
|
version: "0"
|
@@ -33,9 +36,11 @@ dependencies:
|
|
33
36
|
name: activerecord
|
34
37
|
prerelease: false
|
35
38
|
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
36
40
|
requirements:
|
37
41
|
- - ">="
|
38
42
|
- !ruby/object:Gem::Version
|
43
|
+
hash: 3
|
39
44
|
segments:
|
40
45
|
- 0
|
41
46
|
version: "0"
|
@@ -45,9 +50,11 @@ dependencies:
|
|
45
50
|
name: actionpack
|
46
51
|
prerelease: false
|
47
52
|
requirement: &id003 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
48
54
|
requirements:
|
49
55
|
- - ">="
|
50
56
|
- !ruby/object:Gem::Version
|
57
|
+
hash: 3
|
51
58
|
segments:
|
52
59
|
- 0
|
53
60
|
version: "0"
|
@@ -57,9 +64,11 @@ dependencies:
|
|
57
64
|
name: jferris-mocha
|
58
65
|
prerelease: false
|
59
66
|
requirement: &id004 !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
60
68
|
requirements:
|
61
69
|
- - ">="
|
62
70
|
- !ruby/object:Gem::Version
|
71
|
+
hash: 3
|
63
72
|
segments:
|
64
73
|
- 0
|
65
74
|
version: "0"
|
@@ -69,9 +78,11 @@ dependencies:
|
|
69
78
|
name: nokogiri
|
70
79
|
prerelease: false
|
71
80
|
requirement: &id005 !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
72
82
|
requirements:
|
73
83
|
- - ">="
|
74
84
|
- !ruby/object:Gem::Version
|
85
|
+
hash: 3
|
75
86
|
segments:
|
76
87
|
- 0
|
77
88
|
version: "0"
|
@@ -81,9 +92,11 @@ dependencies:
|
|
81
92
|
name: shoulda
|
82
93
|
prerelease: false
|
83
94
|
requirement: &id006 !ruby/object:Gem::Requirement
|
95
|
+
none: false
|
84
96
|
requirements:
|
85
97
|
- - ">="
|
86
98
|
- !ruby/object:Gem::Version
|
99
|
+
hash: 3
|
87
100
|
segments:
|
88
101
|
- 0
|
89
102
|
version: "0"
|
@@ -98,46 +111,52 @@ extensions: []
|
|
98
111
|
extra_rdoc_files:
|
99
112
|
- README.rdoc
|
100
113
|
files:
|
101
|
-
- SUPPORTED_RAILS_VERSIONS
|
102
|
-
- MIT-LICENSE
|
103
114
|
- CHANGELOG
|
115
|
+
- INSTALL
|
116
|
+
- MIT-LICENSE
|
104
117
|
- Rakefile
|
105
118
|
- README.rdoc
|
106
|
-
-
|
119
|
+
- SUPPORTED_RAILS_VERSIONS
|
107
120
|
- TESTING.rdoc
|
108
121
|
- generators/errornot/errornot_generator.rb
|
122
|
+
- generators/errornot/lib/insert_commands.rb
|
123
|
+
- generators/errornot/lib/rake_commands.rb
|
109
124
|
- generators/errornot/templates/capistrano_hook.rb
|
110
125
|
- generators/errornot/templates/errornot_notifier_tasks.rake
|
111
126
|
- generators/errornot/templates/initializer.rb
|
112
|
-
-
|
113
|
-
- generators/errornot/lib/insert_commands.rb
|
114
|
-
- lib/errornot_notifier.rb
|
127
|
+
- lib/errornot_notifier/backtrace.rb
|
115
128
|
- lib/errornot_notifier/capistrano.rb
|
129
|
+
- lib/errornot_notifier/configuration.rb
|
130
|
+
- lib/errornot_notifier/notice.rb
|
131
|
+
- lib/errornot_notifier/rack.rb
|
116
132
|
- lib/errornot_notifier/rails/action_controller_catcher.rb
|
117
133
|
- lib/errornot_notifier/rails/controller_methods.rb
|
118
134
|
- lib/errornot_notifier/rails/error_lookup.rb
|
119
|
-
- lib/errornot_notifier/
|
120
|
-
- lib/errornot_notifier/version.rb
|
121
|
-
- lib/errornot_notifier/sender.rb
|
122
|
-
- lib/errornot_notifier/rack.rb
|
135
|
+
- lib/errornot_notifier/rails/javascript_notifier.rb
|
123
136
|
- lib/errornot_notifier/rails.rb
|
137
|
+
- lib/errornot_notifier/rails3_tasks.rb
|
138
|
+
- lib/errornot_notifier/railtie.rb
|
139
|
+
- lib/errornot_notifier/sender.rb
|
124
140
|
- lib/errornot_notifier/tasks.rb
|
125
|
-
- lib/errornot_notifier/
|
126
|
-
- lib/errornot_notifier
|
141
|
+
- lib/errornot_notifier/version.rb
|
142
|
+
- lib/errornot_notifier.rb
|
127
143
|
- lib/errornot_tasks.rb
|
144
|
+
- lib/rails/generators/errornot/errornot_generator.rb
|
145
|
+
- test/backtrace_test.rb
|
128
146
|
- test/catcher_test.rb
|
129
147
|
- test/configuration_test.rb
|
130
|
-
- test/backtrace_test.rb
|
131
148
|
- test/erronot_tasks_test.rb
|
132
|
-
- test/
|
149
|
+
- test/errornot_tasks_test.rb
|
133
150
|
- test/helper.rb
|
151
|
+
- test/logger_test.rb
|
134
152
|
- test/notice_test.rb
|
135
153
|
- test/notifier_test.rb
|
136
154
|
- test/rack_test.rb
|
137
|
-
- test/
|
155
|
+
- test/rails_initializer_test.rb
|
138
156
|
- test/sender_test.rb
|
139
157
|
- rails/init.rb
|
140
158
|
- script/integration_test.rb
|
159
|
+
- lib/templates/javascript_notifier.erb
|
141
160
|
- lib/templates/rescue.erb
|
142
161
|
has_rdoc: true
|
143
162
|
homepage: http://github.com/shingara/errornot_notifier
|
@@ -151,34 +170,39 @@ rdoc_options:
|
|
151
170
|
require_paths:
|
152
171
|
- lib
|
153
172
|
required_ruby_version: !ruby/object:Gem::Requirement
|
173
|
+
none: false
|
154
174
|
requirements:
|
155
175
|
- - ">="
|
156
176
|
- !ruby/object:Gem::Version
|
177
|
+
hash: 3
|
157
178
|
segments:
|
158
179
|
- 0
|
159
180
|
version: "0"
|
160
181
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
182
|
+
none: false
|
161
183
|
requirements:
|
162
184
|
- - ">="
|
163
185
|
- !ruby/object:Gem::Version
|
186
|
+
hash: 3
|
164
187
|
segments:
|
165
188
|
- 0
|
166
189
|
version: "0"
|
167
190
|
requirements: []
|
168
191
|
|
169
192
|
rubyforge_project:
|
170
|
-
rubygems_version: 1.3.
|
193
|
+
rubygems_version: 1.3.7
|
171
194
|
signing_key:
|
172
195
|
specification_version: 3
|
173
196
|
summary: Send your application errors to a hosted service and reclaim your inbox.
|
174
197
|
test_files:
|
198
|
+
- test/backtrace_test.rb
|
175
199
|
- test/catcher_test.rb
|
176
200
|
- test/configuration_test.rb
|
177
|
-
- test/backtrace_test.rb
|
178
201
|
- test/erronot_tasks_test.rb
|
179
|
-
- test/
|
202
|
+
- test/errornot_tasks_test.rb
|
203
|
+
- test/logger_test.rb
|
180
204
|
- test/notice_test.rb
|
181
205
|
- test/notifier_test.rb
|
182
206
|
- test/rack_test.rb
|
183
|
-
- test/
|
207
|
+
- test/rails_initializer_test.rb
|
184
208
|
- test/sender_test.rb
|