guardrail_notifier 0.2.11

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source "http://rubygems.org"
2
+
3
+ group :development do
4
+ gem "actionpack", "3.2.8"
5
+ gem "json"
6
+ gem "fakeweb"
7
+ gem "shoulda"
8
+ gem "mocha"
9
+ gem "rake"
10
+ gem "jeweler"
11
+ end
12
+
data/Gemfile.lock ADDED
@@ -0,0 +1,67 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ actionpack (3.2.8)
5
+ activemodel (= 3.2.8)
6
+ activesupport (= 3.2.8)
7
+ builder (~> 3.0.0)
8
+ erubis (~> 2.7.0)
9
+ journey (~> 1.0.4)
10
+ rack (~> 1.4.0)
11
+ rack-cache (~> 1.2)
12
+ rack-test (~> 0.6.1)
13
+ sprockets (~> 2.1.3)
14
+ activemodel (3.2.8)
15
+ activesupport (= 3.2.8)
16
+ builder (~> 3.0.0)
17
+ activesupport (3.2.8)
18
+ i18n (~> 0.6)
19
+ multi_json (~> 1.0)
20
+ builder (3.0.3)
21
+ erubis (2.7.0)
22
+ fakeweb (1.3.0)
23
+ git (1.2.5)
24
+ hike (1.2.1)
25
+ i18n (0.6.1)
26
+ jeweler (1.8.4)
27
+ bundler (~> 1.0)
28
+ git (>= 1.2.5)
29
+ rake
30
+ rdoc
31
+ journey (1.0.4)
32
+ json (1.7.5)
33
+ metaclass (0.0.1)
34
+ mocha (0.12.7)
35
+ metaclass (~> 0.0.1)
36
+ multi_json (1.3.6)
37
+ rack (1.4.1)
38
+ rack-cache (1.2)
39
+ rack (>= 0.4)
40
+ rack-test (0.6.2)
41
+ rack (>= 1.0)
42
+ rake (0.9.2.2)
43
+ rdoc (3.12)
44
+ json (~> 1.4)
45
+ shoulda (3.3.0)
46
+ shoulda-context (~> 1.0)
47
+ shoulda-matchers (~> 1.4)
48
+ shoulda-context (1.0.0)
49
+ shoulda-matchers (1.4.0)
50
+ activesupport (>= 3.0.0)
51
+ sprockets (2.1.3)
52
+ hike (~> 1.2)
53
+ rack (~> 1.0)
54
+ tilt (~> 1.1, != 1.3.0)
55
+ tilt (1.3.3)
56
+
57
+ PLATFORMS
58
+ ruby
59
+
60
+ DEPENDENCIES
61
+ actionpack (= 3.2.8)
62
+ fakeweb
63
+ jeweler
64
+ json
65
+ mocha
66
+ rake
67
+ shoulda
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Terrible Labs, Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,98 @@
1
+ = GuardrailNotifier
2
+
3
+ {<img src="https://travis-ci.org/terriblelabs/guardrail_notifier.png" />}[https://travis-ci.org/terriblelabs/guardrail_notifier]
4
+
5
+ Stop hurting your users!
6
+
7
+ Guardrail captures validation errors from your Ruby on Rails application to help you identify and fix user experience issues. The GuardrailNotifier gem makes it easy to hook up your app to the Guardrail web service.
8
+
9
+ If you haven't already signed up for a Guardrail account, visit http://guardrailapp.com.
10
+
11
+
12
+ = Installation
13
+
14
+ === Rails 3
15
+
16
+ * Add the following line to Gemfile:
17
+
18
+ gem 'guardrail_notifier'
19
+
20
+ * Install the gem:
21
+
22
+ bundle install
23
+
24
+ === Rails 2.3
25
+
26
+ * Add the following line to config/environment.rb:
27
+
28
+ config.gem 'guardrail_notifier'
29
+
30
+ * Install the gem:
31
+
32
+ [sudo] rake gems:install GEM=guardrail_notifier
33
+
34
+ = Configuration
35
+
36
+ In your Rails app, create config/initializers/guardrail_notifier.rb and add the following line with the API key for your Guardrail project:
37
+
38
+ GuardrailNotifier.configure do |config|
39
+ config.api_key = "<YOUR API KEY>"
40
+ end
41
+
42
+ Or, if you prefer to keep API keys and the like out of your code, set the GUARDRAIL_API_KEY environment variable and GuardrailNotifier will find it automatically.
43
+
44
+ Validation errors will now be submitted to the Guardrail service on your create and update actions in production mode.
45
+
46
+ = Advanced Usage
47
+
48
+ === Tracking custom actions
49
+
50
+ GuardrailNotifier assumes your app is RESTful, so it only monitors the create and update actions by default. If you're creating or updating records in non-RESTful actions, you can use an after_filter to make sure any validation failures in those actions are caught as well:
51
+
52
+ after_filter :log_validation_failures_to_guardrail, :only => [:create, :update, :my_custom_action]
53
+
54
+ === Running in other environments
55
+
56
+ GuardrailNotifier is only enabled in the production environment by default. If you want to enable it in additional environments, such as staging, you'll need to add the following line to your Guardrail initializer:
57
+
58
+ GuardrailNotifier.configure do |config|
59
+ config.monitored_environments << 'staging'
60
+ end
61
+
62
+ === SSL
63
+
64
+ GuardrailNotifier uses a secure connection over SSL by default, but you can easily disable it if you prefer:
65
+
66
+ GuardrailNotifier.configure do |config|
67
+ config.secure = false
68
+ end
69
+
70
+ === Filtering params
71
+
72
+ GuardrailNotifier records the parameters submitted in actions that cause validation errors. Because your app may handle sensitive data, like passwords, credit card numbers, and nuclear launch codes, GuardrailNotifier respects the parameter filtering that you configure in Rails in config/application.rb (Rails 3) or ApplicationController (Rails 2.3). For example in Rails 3:
73
+
74
+ config.filter_parameters += [:password, :secret, :cvv]
75
+
76
+ Per Rails convention, those params will show up as "[FILTERED]" in the data submitted to Guardrail.
77
+
78
+
79
+ == Contact information
80
+
81
+ * Twitter: @guardrailapp
82
+ * Email: jeremy@guardrailapp.com, jeff@guardrailapp.com
83
+
84
+
85
+ == Note on Patches/Pull Requests
86
+
87
+ * Fork the project.
88
+ * Make your feature addition or bug fix.
89
+ * Add tests for it. This is important so I don't break it in a
90
+ future version unintentionally.
91
+ * Commit, do not mess with Rakefile, version, or history.
92
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
93
+ * Send me a pull request. Bonus points for topic branches.
94
+
95
+ == Copyright
96
+
97
+ Copyright (c) 2010 Terrible Labs, Inc. See LICENSE for details.
98
+
data/Rakefile ADDED
@@ -0,0 +1,40 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require File.dirname(__FILE__) + "/lib/guardrail_notifier/version.rb"
4
+
5
+ begin
6
+ require 'jeweler'
7
+ Jeweler::Tasks.new do |gem|
8
+ gem.version = GuardrailNotifier::VERSION
9
+ gem.name = "guardrail_notifier"
10
+ gem.summary = %Q{Guardrail (http://guardrailapp.com) captures validation errors from your Ruby on Rails application.}
11
+ gem.description = %Q{Guardrail captures validation errors from your Ruby on Rails application to help you identify and fix user experience issues. The GuardrailNotifier gem makes it easy to hook up your app to the Guardrail web service.}
12
+ gem.email = "support@guardrailapp.com"
13
+ gem.homepage = "http://github.com/terriblelabs/guardrail_notifier"
14
+ gem.authors = ["Jeffrey Chupp", "Jeremy Weiskotten"]
15
+ gem.add_development_dependency "shoulda", ">= 0"
16
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
17
+ end
18
+ Jeweler::GemcutterTasks.new
19
+ rescue LoadError
20
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
21
+ end
22
+
23
+ require 'rake/testtask'
24
+ Rake::TestTask.new(:test) do |test|
25
+ test.libs << 'lib' << 'test'
26
+ test.pattern = 'test/**/*_test.rb'
27
+ test.verbose = true
28
+ end
29
+
30
+ task :default => :test
31
+
32
+ require 'rdoc/task'
33
+ Rake::RDocTask.new do |rdoc|
34
+ version = GuardrailNotifier::VERSION
35
+
36
+ rdoc.rdoc_dir = 'rdoc'
37
+ rdoc.title = "guardrail_notifier #{version}"
38
+ rdoc.rdoc_files.include('README*')
39
+ rdoc.rdoc_files.include('lib/**/*.rb')
40
+ end
@@ -0,0 +1,74 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "guardrail_notifier"
8
+ s.version = "0.2.11"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Jeffrey Chupp", "Jeremy Weiskotten"]
12
+ s.date = "2012-10-12"
13
+ s.description = "Guardrail captures validation errors from your Ruby on Rails application to help you identify and fix user experience issues. The GuardrailNotifier gem makes it easy to hook up your app to the Guardrail web service."
14
+ s.email = "support@guardrailapp.com"
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ "Gemfile",
22
+ "Gemfile.lock",
23
+ "LICENSE",
24
+ "README.rdoc",
25
+ "Rakefile",
26
+ "guardrail_notifier.gemspec",
27
+ "lib/guardrail_notifier.rb",
28
+ "lib/guardrail_notifier/configuration.rb",
29
+ "lib/guardrail_notifier/rails/action_controller_monitor.rb",
30
+ "lib/guardrail_notifier/sender.rb",
31
+ "lib/guardrail_notifier/version.rb",
32
+ "test/guardrail_test.rb",
33
+ "test/helper.rb",
34
+ "test/sender_test.rb"
35
+ ]
36
+ s.homepage = "http://github.com/terriblelabs/guardrail_notifier"
37
+ s.require_paths = ["lib"]
38
+ s.rubygems_version = "1.8.23"
39
+ s.summary = "Guardrail (http://guardrailapp.com) captures validation errors from your Ruby on Rails application."
40
+
41
+ if s.respond_to? :specification_version then
42
+ s.specification_version = 3
43
+
44
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
45
+ s.add_development_dependency(%q<actionpack>, ["= 3.2.8"])
46
+ s.add_development_dependency(%q<json>, [">= 0"])
47
+ s.add_development_dependency(%q<fakeweb>, [">= 0"])
48
+ s.add_development_dependency(%q<shoulda>, [">= 0"])
49
+ s.add_development_dependency(%q<mocha>, [">= 0"])
50
+ s.add_development_dependency(%q<rake>, [">= 0"])
51
+ s.add_development_dependency(%q<jeweler>, [">= 0"])
52
+ s.add_development_dependency(%q<shoulda>, [">= 0"])
53
+ else
54
+ s.add_dependency(%q<actionpack>, ["= 3.2.8"])
55
+ s.add_dependency(%q<json>, [">= 0"])
56
+ s.add_dependency(%q<fakeweb>, [">= 0"])
57
+ s.add_dependency(%q<shoulda>, [">= 0"])
58
+ s.add_dependency(%q<mocha>, [">= 0"])
59
+ s.add_dependency(%q<rake>, [">= 0"])
60
+ s.add_dependency(%q<jeweler>, [">= 0"])
61
+ s.add_dependency(%q<shoulda>, [">= 0"])
62
+ end
63
+ else
64
+ s.add_dependency(%q<actionpack>, ["= 3.2.8"])
65
+ s.add_dependency(%q<json>, [">= 0"])
66
+ s.add_dependency(%q<fakeweb>, [">= 0"])
67
+ s.add_dependency(%q<shoulda>, [">= 0"])
68
+ s.add_dependency(%q<mocha>, [">= 0"])
69
+ s.add_dependency(%q<rake>, [">= 0"])
70
+ s.add_dependency(%q<jeweler>, [">= 0"])
71
+ s.add_dependency(%q<shoulda>, [">= 0"])
72
+ end
73
+ end
74
+
@@ -0,0 +1,37 @@
1
+ require 'net/http'
2
+ require 'net/https'
3
+ require 'uri'
4
+ require 'guardrail_notifier/version'
5
+ require 'guardrail_notifier/configuration'
6
+ require 'guardrail_notifier/sender'
7
+ require 'guardrail_notifier/rails/action_controller_monitor'
8
+
9
+ module GuardrailNotifier
10
+ API_VERSION = "alpha 1"
11
+
12
+ class << self
13
+ def configuration
14
+ @configuration ||= Configuration.new
15
+ end
16
+
17
+ def configure
18
+ yield(self.configuration)
19
+ end
20
+
21
+ def sender
22
+ @sender ||= Sender.new(self.configuration)
23
+ end
24
+
25
+ def notify(data)
26
+ self.sender.send_to_guardrail(data.merge(notifier_params))
27
+ end
28
+
29
+ def notifier_params
30
+ @notifier_params ||= {
31
+ :notifier_version => self.configuration.notifier_version,
32
+ :api_key => self.configuration.api_key || ENV['GUARDRAIL_API_KEY'],
33
+ :api_version => API_VERSION
34
+ }
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,33 @@
1
+ module GuardrailNotifier
2
+ class Configuration
3
+ attr_accessor :api_key
4
+
5
+ # The set of environments that should be monitored for validation errors (defaults to
6
+ # only the production environment).
7
+ attr_accessor :monitored_environments
8
+
9
+ # Number of seconds after which submission to Guardrail should timeout (defaults to 5).
10
+ attr_accessor :timeout_in_seconds
11
+
12
+ # +true+ for https connections, +false+ for http connections.
13
+ attr_accessor :secure
14
+ alias_method :secure?, :secure
15
+
16
+ # A +call+able object, such as a Proc, to be invoked if an exception occurs when
17
+ # logging to Guardrail (defaults to nil). For example, to notify Hoptoad:
18
+ #
19
+ # config.on_exception = proc { |e| notify_hoptoad(e) }
20
+ attr_accessor :on_exception
21
+
22
+ # The version of the notifier (defaults to the version of this gem).
23
+ attr_reader :notifier_version
24
+
25
+ def initialize
26
+ @notifier_version = VERSION
27
+ @timeout_in_seconds = 5
28
+ @monitored_environments = ['production']
29
+ @secure = true
30
+ @on_exception = nil
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,125 @@
1
+ module GuardrailNotifier
2
+ module Rails
3
+ # This module is mixed into ActionController::Base.
4
+ module ActionControllerMonitor
5
+
6
+ def self.included(base)
7
+ base.after_filter :log_validation_failures_to_guardrail, :only => [:create, :update]
8
+ end
9
+
10
+ protected
11
+
12
+ def log_validation_failures_to_guardrail
13
+ if should_log_failures_to_guardrail? && records_with_errors.present?
14
+ GuardrailNotifier.notify(guardrail_params)
15
+ end
16
+ rescue Exception => e
17
+ ::Rails.logger.error("Failed to log validation failure to Guardrail")
18
+
19
+ handler = GuardrailNotifier.configuration.on_exception
20
+ handler.call(e) if !handler.nil? && handler.respond_to?(:call)
21
+ end
22
+
23
+ def should_log_failures_to_guardrail?
24
+ GuardrailNotifier.configuration.monitored_environments.include?(::Rails.env.to_s)
25
+ end
26
+
27
+ def records_with_errors
28
+ @records_with_errors ||= begin
29
+ instance_variable_names.map do |var_name|
30
+ var = instance_variable_get(var_name)
31
+ if var.is_a?(Array)
32
+ var.select{|v| record_has_errors?(v)}
33
+ else
34
+ var if record_has_errors?(var)
35
+ end
36
+ end.flatten.compact
37
+ end
38
+ end
39
+
40
+ def record_has_errors?(record)
41
+ record.respond_to?(:errors) && record.errors.present?
42
+ end
43
+
44
+ def guardrail_params
45
+ {}.tap do |query|
46
+ query[:_controller] = params['controller']
47
+ query[:_action] = params['action']
48
+ query[:path] = request.path
49
+ query[:data] = request_data.to_json
50
+
51
+ query[:failures] = records_with_errors.map do |record|
52
+ error_hashes(record)
53
+ end.flatten.to_json
54
+ end
55
+ end
56
+
57
+ def error_hashes(record)
58
+ record.errors.map do |field, messages|
59
+ Array.wrap(messages).map do |message|
60
+ error_hash(record.class, field, message)
61
+ end
62
+ end
63
+ end
64
+
65
+ def error_hash(clazz, field, message)
66
+ {
67
+ :model => clazz.to_s,
68
+ :field => field,
69
+ :message => message
70
+ }
71
+ end
72
+
73
+ def request_data
74
+ {}.tap do |data|
75
+ # TODO: limit this in size - how big?
76
+ data[:params] = filtered_params # TODO: controller and action are redundant
77
+
78
+ if respond_to?(:current_user) && !current_user.nil?
79
+ data[:current_user] = current_user.id
80
+ end
81
+
82
+ data[:user_agent] = request.user_agent
83
+ data[:cookies] = stringify_values(request.cookies)
84
+ data[:session] = stringify_values(request.session)
85
+ end
86
+ end
87
+
88
+ def filtered_params
89
+ p = if respond_to?(:filter_parameters)
90
+ # pre-Rails 3
91
+ filter_parameters(params)
92
+ elsif request.respond_to?(:filtered_parameters)
93
+ # Rails 3
94
+ request.filtered_parameters
95
+ else
96
+ params
97
+ end
98
+ filter_files_from_params(p)
99
+ end
100
+
101
+ def filter_files_from_params(params)
102
+ params.each do |k,v|
103
+ if v.is_a?(Hash)
104
+ filter_files_from_params(v)
105
+ elsif v.is_a?(Tempfile)
106
+ params[k] = '[FILTERED]'
107
+ end
108
+ end
109
+ end
110
+
111
+ def stringify_values(values)
112
+ case values
113
+ when Array
114
+ values.map { |v| stringify_values(v) }
115
+ when Hash
116
+ Hash[values.map { |k,v| [k, stringify_values(v)] }]
117
+ else
118
+ values.to_s
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
124
+
125
+ ActionController::Base.send(:include, GuardrailNotifier::Rails::ActionControllerMonitor)
@@ -0,0 +1,43 @@
1
+ module GuardrailNotifier
2
+ class Sender
3
+ attr_reader :configuration
4
+
5
+ def initialize(configuration)
6
+ @configuration = configuration
7
+ end
8
+
9
+ def protocol
10
+ self.configuration.secure? ? 'https' : 'http'
11
+ end
12
+
13
+ def port
14
+ self.configuration.secure? ? 443 : 80
15
+ end
16
+
17
+ def host
18
+ 'api.guardrailapp.com'
19
+ end
20
+
21
+ def uri
22
+ URI.parse("#{protocol}://#{host}:#{port}").merge('/')
23
+ end
24
+
25
+ def send_to_guardrail(data)
26
+ uri = self.uri
27
+
28
+ http = Net::HTTP.new(uri.host, uri.port)
29
+ http.open_timeout = self.configuration.timeout_in_seconds
30
+ http.use_ssl = self.configuration.secure?
31
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
32
+
33
+ request = Net::HTTP::Post.new(uri.request_uri)
34
+ request.set_form_data(data)
35
+
36
+ begin
37
+ response = http.request(request)
38
+ rescue Exception => ex
39
+ warn "Could not submit guardrailapp notification: #{ex.class} - #{ex.message}"
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,3 @@
1
+ module GuardrailNotifier
2
+ VERSION = '0.2.11'
3
+ end
@@ -0,0 +1,156 @@
1
+ require 'helper'
2
+
3
+ class TestGuardrail < Test::Unit::TestCase
4
+ def fake_controller(klass)
5
+ fake = klass.new
6
+ fake.params = {'action' => "foo", 'controller' => "bar"}
7
+ fake.request = OpenStruct.new(
8
+ :user_agent => 'FooFox',
9
+ :cookies => {'one' => 'two', 'three' => 'four'},
10
+ :session => {'something' => 'ok', 'some_class' => String, 'some_number' => 42, :some_array => [Date, 24]}
11
+ )
12
+ fake
13
+ end
14
+
15
+ def setup
16
+ Rails.stubs(:env => 'production')
17
+
18
+ @foo_controller = fake_controller(FooController)
19
+
20
+ FakeWeb.register_uri(:post, 'http://api.guardrailapp.com', :body => "")
21
+
22
+ GuardrailNotifier.configure do |config|
23
+ config.api_key = "SOME API KEY"
24
+ config.monitored_environments = ['production']
25
+ end
26
+
27
+ @model_one = OpenStruct.new(:errors => {"bar" => "is blank"})
28
+ @model_two = OpenStruct.new(:errors => {"baz" => "is blank"})
29
+ @model_three = OpenStruct.new(:errors => {"f1" => "is blank","f2" => ["is too large", "is delicious"]})
30
+
31
+ @foo_controller.instance_variable_set("@lol", @model_one)
32
+ @foo_controller.instance_variable_set("@wtf", [@model_two, @model_three])
33
+
34
+ # we also set a non-errored model just to ensure that it is not matched
35
+ @foo_controller.instance_variable_set("@bbq", OpenStruct.new(:errors => []))
36
+ end
37
+
38
+ should "set an api key" do
39
+ GuardrailNotifier.configure { |c| c.api_key = "Foo" }
40
+ assert_equal "Foo", GuardrailNotifier.configuration.api_key
41
+ end
42
+
43
+ should "fallback to the ENV api key" do
44
+ GuardrailNotifier.configure { |c| c.api_key = nil }
45
+ ENV['GUARDRAIL_API_KEY'] = 'Cupcakes'
46
+ assert_equal "Cupcakes", GuardrailNotifier.notifier_params[:api_key]
47
+ end
48
+
49
+ should "set secure" do
50
+ GuardrailNotifier.configure { |c| c.secure = true }
51
+ assert GuardrailNotifier.configuration.secure?
52
+ end
53
+
54
+ should "set a timeout_in_seconds" do
55
+ assert_equal 5, GuardrailNotifier.configuration.timeout_in_seconds
56
+ GuardrailNotifier.configure { |c| c.timeout_in_seconds = 6 }
57
+ assert_equal 6, GuardrailNotifier.configuration.timeout_in_seconds
58
+ end
59
+
60
+ should "set monitored environments" do
61
+ assert_equal ["production"], GuardrailNotifier.configuration.monitored_environments
62
+ GuardrailNotifier.configure { |c| c.monitored_environments = ['stage', 'development'] }
63
+ assert_equal ['stage', 'development'], GuardrailNotifier.configuration.monitored_environments
64
+ end
65
+
66
+ should "respect monitored environments" do
67
+ assert @foo_controller.send(:should_log_failures_to_guardrail?)
68
+
69
+ GuardrailNotifier.configuration.monitored_environments = ['stage', 'development']
70
+ assert !@foo_controller.send(:should_log_failures_to_guardrail?)
71
+ end
72
+
73
+ should "identify no errors by default" do
74
+ assert_equal [], FooController.new.send(:records_with_errors)
75
+ end
76
+
77
+ should "identify records with errors" do
78
+ assert_same_elements [@model_one, @model_two, @model_three], @foo_controller.send(:records_with_errors)
79
+ end
80
+
81
+ should "create params via guardrail_params" do
82
+ expected = [
83
+ {"model"=>"OpenStruct", "field"=>"baz", "message"=>"is blank"},
84
+ {"model"=>"OpenStruct", "field"=>"f1", "message"=>"is blank"},
85
+ {"model"=>"OpenStruct", "field"=>"f2", "message"=>"is too large"},
86
+ {"model"=>"OpenStruct", "field"=>"f2", "message"=>"is delicious"},
87
+ {"model"=>"OpenStruct", "field"=>"bar", "message"=>"is blank"}
88
+ ]
89
+
90
+ params = @foo_controller.send(:guardrail_params)
91
+ failures = JSON.parse(params[:failures])
92
+ assert_same_elements expected, failures
93
+ end
94
+
95
+ # should "submit errors via log_validation_failures_to_guardrail" do
96
+ # Net::HTTP.expects(:post_form).with(URI.parse("http://api.guardrailapp.com:80"), @foo_controller.send(:guardrail_params))
97
+ # @foo_controller.send(:log_validation_failures_to_guardrail)
98
+ # end
99
+
100
+ should "log controller and action" do
101
+ assert_equal @foo_controller.params['action'], @foo_controller.send(:guardrail_params)[:_action]
102
+ assert_equal @foo_controller.params['controller'], @foo_controller.send(:guardrail_params)[:_controller]
103
+ end
104
+
105
+ should "log params" do
106
+ @foo_controller.params.merge!('lolcats' => "no longer funny")
107
+ assert_equal @foo_controller.params, JSON.parse(@foo_controller.send(:guardrail_params)[:data])['params']
108
+ end
109
+
110
+ should "filter tempfiles" do
111
+ Tempfile.open('foo') do |tempfile|
112
+ tempfile.write('test data')
113
+ tempfile.rewind
114
+
115
+ @foo_controller.params.merge!('value' => 'abc', 'photo' => tempfile, 'nested' => { 'nested_value' => 1, 'nested_photo' => tempfile })
116
+ params = JSON.parse(@foo_controller.send(:guardrail_params)[:data])['params']
117
+ assert_equal @foo_controller.params.merge('value' => 'abc', 'photo' => '[FILTERED]', 'nested' => { 'nested_value' => 1, 'nested_photo' => '[FILTERED]' }), params
118
+ end
119
+ end
120
+
121
+ should "log current user's id if the method is exposed" do
122
+ assert_equal nil, JSON.parse(@foo_controller.send(:guardrail_params)[:data])['current_user']
123
+ assert_equal 53077, JSON.parse(fake_controller(BarController).send(:guardrail_params)[:data])['current_user']
124
+ end
125
+
126
+ should "handle exceptions" do
127
+ logger = mock()
128
+ logger.expects(:error)
129
+ Rails.stubs(:logger => logger)
130
+
131
+ @foo_controller.stubs(:guardrail_params).raises(StandardError)
132
+ @foo_controller.send(:log_validation_failures_to_guardrail)
133
+
134
+ # with a custom exception handler
135
+ list = []
136
+ GuardrailNotifier.configure { |c| c.on_exception = proc { |e| list << e } }
137
+
138
+ logger.expects(:error)
139
+ @foo_controller.send(:log_validation_failures_to_guardrail)
140
+
141
+ assert_equal 1, list.size
142
+ assert_kind_of StandardError, list.first
143
+ end
144
+
145
+ [:cookies, :user_agent].each do |kind|
146
+ should "log #{kind}" do
147
+ assert @foo_controller.request.send(kind).present?
148
+ assert_equal @foo_controller.request.send(kind), JSON.parse(fake_controller(BarController).send(:guardrail_params)[:data])[kind.to_s]
149
+ end
150
+ end
151
+
152
+ should "log session" do
153
+ expected = {'something'=>'ok', 'some_class'=>'String', 'some_number'=>'42', 'some_array'=>['Date', '24']}
154
+ assert_equal expected, JSON.parse(fake_controller(BarController).send(:guardrail_params)[:data])['session']
155
+ end
156
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,35 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'ostruct'
4
+ require 'shoulda'
5
+ gem 'actionpack', '~>3.2'
6
+ require 'action_controller'
7
+ require 'fakeweb'
8
+ require 'mocha'
9
+ require 'tempfile'
10
+
11
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
12
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
13
+ require 'guardrail_notifier'
14
+
15
+ class Test::Unit::TestCase
16
+ end
17
+
18
+ class FooController < ActionController::Base
19
+ end
20
+
21
+ class User
22
+ attr_accessor :id
23
+ def initialize
24
+ @id = 53077
25
+ end
26
+ end
27
+
28
+ class BarController < ActionController::Base
29
+ def current_user
30
+ User.new
31
+ end
32
+ end
33
+
34
+ class Rails
35
+ end
@@ -0,0 +1,75 @@
1
+ require 'helper'
2
+
3
+ class SenderTest < Test::Unit::TestCase
4
+ def setup
5
+ GuardrailNotifier.configure do |c|
6
+ c.api_key = 'API_KEY'
7
+ end
8
+ @sender = GuardrailNotifier::Sender.new(GuardrailNotifier.configuration)
9
+ end
10
+
11
+ context "initialize" do
12
+ should "set configuration" do
13
+ assert_same GuardrailNotifier.configuration, @sender.configuration
14
+ assert_equal 'API_KEY', @sender.configuration.api_key
15
+ end
16
+ end
17
+
18
+ context "when not configured to be secure" do
19
+ setup do
20
+ GuardrailNotifier.configuration.secure = false
21
+ end
22
+
23
+ should "return 'http' for #protocol" do
24
+ assert_equal 'http', @sender.protocol
25
+ end
26
+
27
+ should "return 80 for #port" do
28
+ assert_equal 80, @sender.port
29
+ end
30
+
31
+ should "post data without ssl on insecure port" do
32
+ assert_sends_to_guardrail(false, 80)
33
+ end
34
+ end
35
+
36
+ context "when configured to be secure" do
37
+ setup do
38
+ GuardrailNotifier.configuration.secure = true
39
+ end
40
+
41
+ should "return 'https' for protocol" do
42
+ assert_equal 'https', @sender.protocol
43
+ end
44
+
45
+ should "return 443 for #port" do
46
+ assert_equal 443, @sender.port
47
+ end
48
+
49
+ should "post data with ssl on secure port" do
50
+ assert_sends_to_guardrail(true, 443)
51
+ end
52
+ end
53
+
54
+ should "return api.guardrailapp.com for #host" do
55
+ assert_equal 'api.guardrailapp.com', @sender.host
56
+ end
57
+
58
+
59
+ def assert_sends_to_guardrail(use_ssl, port)
60
+ data = {:key => :value}
61
+
62
+ mock_request = mock()
63
+ mock_request.expects(:set_form_data).with(data)
64
+ Net::HTTP::Post.stubs(:new => mock_request)
65
+
66
+ mock_http = mock()
67
+ mock_http.expects(:open_timeout=)
68
+ mock_http.expects(:use_ssl=).with(use_ssl)
69
+ mock_http.expects(:verify_mode=).with(OpenSSL::SSL::VERIFY_NONE)
70
+ mock_http.expects(:request).with(mock_request)
71
+ Net::HTTP.stubs(:new => mock_http)
72
+
73
+ @sender.send_to_guardrail(data)
74
+ end
75
+ end
metadata ADDED
@@ -0,0 +1,193 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: guardrail_notifier
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.11
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jeffrey Chupp
9
+ - Jeremy Weiskotten
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2012-10-12 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: actionpack
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - '='
21
+ - !ruby/object:Gem::Version
22
+ version: 3.2.8
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - '='
29
+ - !ruby/object:Gem::Version
30
+ version: 3.2.8
31
+ - !ruby/object:Gem::Dependency
32
+ name: json
33
+ requirement: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ! '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ type: :development
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: fakeweb
49
+ requirement: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ - !ruby/object:Gem::Dependency
64
+ name: shoulda
65
+ requirement: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ type: :development
72
+ prerelease: false
73
+ version_requirements: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ - !ruby/object:Gem::Dependency
80
+ name: mocha
81
+ requirement: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ! '>='
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ type: :development
88
+ prerelease: false
89
+ version_requirements: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ! '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ - !ruby/object:Gem::Dependency
96
+ name: rake
97
+ requirement: !ruby/object:Gem::Requirement
98
+ none: false
99
+ requirements:
100
+ - - ! '>='
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ! '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: jeweler
113
+ requirement: !ruby/object:Gem::Requirement
114
+ none: false
115
+ requirements:
116
+ - - ! '>='
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ none: false
123
+ requirements:
124
+ - - ! '>='
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ - !ruby/object:Gem::Dependency
128
+ name: shoulda
129
+ requirement: !ruby/object:Gem::Requirement
130
+ none: false
131
+ requirements:
132
+ - - ! '>='
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ type: :development
136
+ prerelease: false
137
+ version_requirements: !ruby/object:Gem::Requirement
138
+ none: false
139
+ requirements:
140
+ - - ! '>='
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ description: Guardrail captures validation errors from your Ruby on Rails application
144
+ to help you identify and fix user experience issues. The GuardrailNotifier gem makes
145
+ it easy to hook up your app to the Guardrail web service.
146
+ email: support@guardrailapp.com
147
+ executables: []
148
+ extensions: []
149
+ extra_rdoc_files:
150
+ - LICENSE
151
+ - README.rdoc
152
+ files:
153
+ - .document
154
+ - Gemfile
155
+ - Gemfile.lock
156
+ - LICENSE
157
+ - README.rdoc
158
+ - Rakefile
159
+ - guardrail_notifier.gemspec
160
+ - lib/guardrail_notifier.rb
161
+ - lib/guardrail_notifier/configuration.rb
162
+ - lib/guardrail_notifier/rails/action_controller_monitor.rb
163
+ - lib/guardrail_notifier/sender.rb
164
+ - lib/guardrail_notifier/version.rb
165
+ - test/guardrail_test.rb
166
+ - test/helper.rb
167
+ - test/sender_test.rb
168
+ homepage: http://github.com/terriblelabs/guardrail_notifier
169
+ licenses: []
170
+ post_install_message:
171
+ rdoc_options: []
172
+ require_paths:
173
+ - lib
174
+ required_ruby_version: !ruby/object:Gem::Requirement
175
+ none: false
176
+ requirements:
177
+ - - ! '>='
178
+ - !ruby/object:Gem::Version
179
+ version: '0'
180
+ required_rubygems_version: !ruby/object:Gem::Requirement
181
+ none: false
182
+ requirements:
183
+ - - ! '>='
184
+ - !ruby/object:Gem::Version
185
+ version: '0'
186
+ requirements: []
187
+ rubyforge_project:
188
+ rubygems_version: 1.8.23
189
+ signing_key:
190
+ specification_version: 3
191
+ summary: Guardrail (http://guardrailapp.com) captures validation errors from your
192
+ Ruby on Rails application.
193
+ test_files: []