Commit 6f59092607f8cc1355bf9172f9b7e16d0f5bcd20

Authored by Bob Lail
1 parent 4d5a1bf8
Exists in master and in 1 other branch production

refactored notice creation process: separate Errbit

concerns (e.g. fingerprint) from notice parsing
concerns.

This also makes it easier to support multiple
versions of the Airbrake API.
app/controllers/notices_controller.rb
1 1 class NoticesController < ApplicationController
2 2 respond_to :xml
3   -
  3 +
4 4 skip_before_filter :authenticate_user!, :only => :create
5   -
  5 +
6 6 def create
7 7 # params[:data] if the notice came from a GET request, raw_post if it came via POST
8   - @notice = Notice.from_xml(params[:data] || request.raw_post)
  8 + @notice = App.report_error!(params[:data] || request.raw_post)
9 9 respond_with @notice
10 10 end
11   -
  11 +
12 12 end
... ...
app/models/app.rb
... ... @@ -42,6 +42,47 @@ class App
42 42  
43 43  
44 44  
  45 + # Processes a new error report.
  46 + #
  47 + # Accepts either XML or a hash with the following attributes:
  48 + #
  49 + # * <tt>:klass</tt> - the class of error
  50 + # * <tt>:message</tt> - the error message
  51 + # * <tt>:backtrace</tt> - an array of stack trace lines
  52 + #
  53 + # * <tt>:request</tt> - a hash of values describing the request
  54 + # * <tt>:server_environment</tt> - a hash of values describing the server environment
  55 + #
  56 + # * <tt>:api_key</tt> - the API key with which the error was reported
  57 + # * <tt>:notifier</tt> - information to identify the source of the error report
  58 + #
  59 + def self.report_error!(*args)
  60 + report = ErrorReport.new(*args)
  61 + report.generate_notice!
  62 + end
  63 +
  64 +
  65 +
  66 + # Processes a new error report.
  67 + #
  68 + # Accepts a hash with the following attributes:
  69 + #
  70 + # * <tt>:klass</tt> - the class of error
  71 + # * <tt>:message</tt> - the error message
  72 + # * <tt>:backtrace</tt> - an array of stack trace lines
  73 + #
  74 + # * <tt>:request</tt> - a hash of values describing the request
  75 + # * <tt>:server_environment</tt> - a hash of values describing the server environment
  76 + #
  77 + # * <tt>:notifier</tt> - information to identify the source of the error report
  78 + #
  79 + def report_error!(hash)
  80 + report = ErrorReport.new(hash.merge(:api_key => api_key))
  81 + report.generate_notice!
  82 + end
  83 +
  84 +
  85 +
45 86 def find_or_create_err!(attrs)
46 87 Err.where(attrs).first || problems.create!.errs.create!(attrs)
47 88 end
... ...
app/models/error_report.rb 0 → 100644
... ... @@ -0,0 +1,91 @@
  1 +require 'digest/md5'
  2 +
  3 +
  4 +class ErrorReport
  5 +
  6 +
  7 +
  8 + def initialize(xml_or_attributes)
  9 + @attributes = (xml_or_attributes.is_a?(String) ? Hoptoad.parse_xml!(xml_or_attributes) : xml_or_attributes).with_indifferent_access
  10 + end
  11 +
  12 +
  13 +
  14 + def klass
  15 + @attributes[:klass]
  16 + end
  17 +
  18 + def message
  19 + @attributes[:message]
  20 + end
  21 +
  22 + def backtrace
  23 + @attributes[:backtrace]
  24 + end
  25 +
  26 + def request
  27 + @attributes[:request]
  28 + end
  29 +
  30 + def server_environment
  31 + @attributes[:server_environment]
  32 + end
  33 +
  34 + def api_key
  35 + @attributes[:api_key]
  36 + end
  37 +
  38 + def notifier
  39 + @attributes[:notifier]
  40 + end
  41 +
  42 +
  43 +
  44 + def fingerprint
  45 + @fingerprint ||= ErrorReport.get_fingerprint(self)
  46 + end
  47 +
  48 + def self.get_fingerprint(report)
  49 + Digest::MD5.hexdigest(report.backtrace[0].to_s)
  50 + end
  51 +
  52 + def rails_env
  53 + server_environment['environment-name'] || 'development'
  54 + end
  55 +
  56 + def component
  57 + request['component'] || 'unknown'
  58 + end
  59 +
  60 + def action
  61 + request['action']
  62 + end
  63 +
  64 + def app
  65 + @app ||= App.find_by_api_key!(api_key)
  66 + end
  67 +
  68 +
  69 +
  70 + def generate_notice!
  71 + notice = Notice.new(
  72 + :message => message,
  73 + :backtrace => backtrace,
  74 + :request => request,
  75 + :server_environment => server_environment,
  76 + :notifier => notifier)
  77 +
  78 + err = app.find_or_create_err!(
  79 + :klass => klass,
  80 + :component => component,
  81 + :action => action,
  82 + :environment => rails_env,
  83 + :fingerprint => fingerprint)
  84 +
  85 + err.notices << notice
  86 + notice
  87 + end
  88 +
  89 +
  90 +
  91 +end
... ...
app/models/notice.rb
... ... @@ -16,8 +16,8 @@ class Notice
16 16 index :err_id
17 17 index :created_at
18 18  
  19 + after_create :increase_counter_cache, :cache_attributes_on_problem, :unresolve_problem
19 20 after_create :deliver_notification, :if => :should_notify?
20   - after_create :increase_counter_cache, :cache_attributes_on_problem
21 21 before_save :sanitize
22 22 before_destroy :decrease_counter_cache
23 23  
... ... @@ -30,34 +30,6 @@ class Notice
30 30  
31 31  
32 32  
33   - def self.from_xml(hoptoad_xml)
34   - hoptoad_notice = Hoptoad::V2.parse_xml(hoptoad_xml)
35   - app = App.find_by_api_key!(hoptoad_notice['api-key'])
36   -
37   - hoptoad_notice['request'] ||= {}
38   - hoptoad_notice['request']['component'] = 'unknown' if hoptoad_notice['request']['component'].blank?
39   - hoptoad_notice['request']['action'] = nil if hoptoad_notice['request']['action'].blank?
40   -
41   - err = app.find_or_create_err!({
42   - :klass => hoptoad_notice['error']['class'],
43   - :component => hoptoad_notice['request']['component'],
44   - :action => hoptoad_notice['request']['action'],
45   - :environment => hoptoad_notice['server-environment']['environment-name'],
46   - :fingerprint => hoptoad_notice['fingerprint']
47   - })
48   - err.problem.update_attributes(:resolved => false) if err.problem.resolved?
49   - err.notices.create!({
50   - :klass => hoptoad_notice['error']['class'],
51   - :message => hoptoad_notice['error']['message'],
52   - :backtrace => [hoptoad_notice['error']['backtrace']['line']].flatten,
53   - :server_environment => hoptoad_notice['server-environment'],
54   - :request => hoptoad_notice['request'],
55   - :notifier => hoptoad_notice['notifier']
56   - })
57   - end
58   -
59   -
60   -
61 33 def user_agent
62 34 agent_string = env_vars['HTTP_USER_AGENT']
63 35 agent_string.blank? ? nil : UserAgent.parse(agent_string)
... ... @@ -127,7 +99,7 @@ protected
127 99  
128 100  
129 101 def should_notify?
130   - app.notify_on_errs? && (Errbit::Config.per_app_email_at_notices && app.email_at_notices || Errbit::Config.email_at_notices).include?(err.notices.count) && app.watchers.any?
  102 + app.notify_on_errs? && (Errbit::Config.per_app_email_at_notices && app.email_at_notices || Errbit::Config.email_at_notices).include?(problem.notices_count) && app.watchers.any?
131 103 end
132 104  
133 105  
... ... @@ -141,6 +113,11 @@ protected
141 113 end
142 114  
143 115  
  116 + def unresolve_problem
  117 + problem.update_attribute(:resolved, false) if problem.resolved?
  118 + end
  119 +
  120 +
144 121 def cache_attributes_on_problem
145 122 problem.cache_notice_attributes(self) if problem.notices_count == 1
146 123 end
... ...
config/initializers/requirements.rb 0 → 100644
... ... @@ -0,0 +1 @@
  1 +require 'core_ext/hash'
... ...
lib/core_ext/hash.rb 0 → 100644
... ... @@ -0,0 +1,61 @@
  1 +module CoreExt
  2 + module Hash
  3 +
  4 +
  5 +
  6 + def pick(*picks)
  7 + picks = picks.flatten
  8 + picks.inject({}) {|result, key| self.key?(key) ? result.merge(key => self[key]) : result}
  9 + end
  10 +
  11 +
  12 +
  13 + def pick!(*picks)
  14 + picks = picks.flatten
  15 + keys.each {|key| self.delete(key) unless picks.member?(key) }
  16 + end
  17 +
  18 +
  19 +
  20 + def except(*picks)
  21 + result = self.dup
  22 + result.except!(*picks)
  23 + result
  24 + end
  25 +
  26 +
  27 +
  28 + def except!(*picks)
  29 + picks = picks.flatten
  30 + keys.each {|key| self.delete(key) if picks.member?(key) }
  31 + end
  32 +
  33 +
  34 +
  35 + def inspect!(depth=0)
  36 + s = ""
  37 + self.each do |k,v|
  38 + s << (" " * depth)
  39 + s << k
  40 + s << ": "
  41 + if v.is_a?(Hash)
  42 + s << "{\n"
  43 + s << v.inspect!(depth + 2)
  44 + s << (" " * depth)
  45 + s << "}"
  46 + elsif v.is_a?(Array)
  47 + s << v.inspect
  48 + else
  49 + s << v.to_s
  50 + end
  51 + s << "\n"
  52 + end
  53 + s
  54 + end
  55 +
  56 +
  57 +
  58 + end
  59 +end
  60 +
  61 +Hash.send :include, CoreExt::Hash
0 62 \ No newline at end of file
... ...
lib/hoptoad.rb
1   -module Hoptoad
2   - module V2
3   - require 'digest/md5'
  1 +require 'hoptoad/v2'
4 2  
5   - class ApiVersionError < StandardError
6   - def initialize(version)
7   - super "Wrong API Version: Expecting v2.0, got version: #{version}"
8   - end
9   - end
10 3  
11   - def self.parse_xml(xml)
12   - xml = xml.unpack('C*').pack('U*') # Repack string into Unicode to fix invalid UTF-8 chars
13   - parsed = ActiveSupport::XmlMini.backend.parse(xml)['notice']
14   - raise ApiVersionError.new(parsed['version']) unless parsed && parsed['version'].to_s == '2.0'
15   - rekeyed = rekey(parsed)
16   - rekeyed['fingerprint'] = Digest::MD5.hexdigest(rekeyed['error']['backtrace'].to_s)
17   - rekeyed
  4 +module Hoptoad
  5 +
  6 +
  7 + def self.parse_xml!(xml)
  8 + xml = xml.unpack('C*').pack('U*') # Repack string into Unicode to fix invalid UTF-8 chars
  9 + parsed = ActiveSupport::XmlMini.backend.parse(xml)['notice'] || raise(ApiVersionError)
  10 + processor = get_version_processor(parsed['version'])
  11 + processor.process_notice(parsed)
  12 + end
  13 +
  14 +
  15 +private
  16 +
  17 +
  18 + def self.get_version_processor(version)
  19 + case version
  20 + when '2.0'; Hoptoad::V2
  21 + else; raise ApiVersionError
  22 + end
  23 + end
  24 +
  25 +
  26 +public
  27 +
  28 +
  29 + class ApiVersionError < StandardError
  30 + def initialize
  31 + super "Wrong API Version: Expecting v2.0"
18 32 end
19   -
20   - private
21   -
22   - def self.rekey(node)
23   - if node.is_a?(Hash) && node.has_key?('var') && node.has_key?('key')
24   - {node['key'] => rekey(node['var'])}
25   - elsif node.is_a?(Hash) && node.has_key?('var')
26   - rekey(node['var'])
27   - elsif node.is_a?(Hash) && node.has_key?('__content__') && node.has_key?('key')
28   - {node['key'] => node['__content__']}
29   - elsif node.is_a?(Hash) && node.has_key?('__content__')
30   - node['__content__']
31   - elsif node.is_a?(Hash)
32   - node.inject({}) {|rekeyed, (key,val)|
33   - rekeyed.merge(key => rekey(val))
34   - }
35   - elsif node.is_a?(Array) && node.first.has_key?('key')
36   - node.inject({}) {|rekeyed,keypair|
37   - rekeyed.merge(rekey(keypair))
38   - }
39   - elsif node.is_a?(Array)
40   - node.map {|n| rekey(n)}
41   - else
42   - node
43   - end
44   - end
45 33 end
  34 +
  35 +
46 36 end
47   -
... ...
lib/hoptoad/v2.rb 0 → 100644
... ... @@ -0,0 +1,78 @@
  1 +module Hoptoad
  2 + module V2
  3 +
  4 +
  5 + def self.process_notice(parsed)
  6 + for_errbit_api(
  7 + normalize(
  8 + rekey(parsed)))
  9 + end
  10 +
  11 +
  12 + private
  13 +
  14 +
  15 + def self.rekey(node)
  16 + case node
  17 + when Hash
  18 + if node.has_key?('var') && node.has_key?('key')
  19 + {normalize_key(node['key']) => rekey(node['var'])}
  20 + elsif node.has_key?('var')
  21 + rekey(node['var'])
  22 + elsif node.has_key?('__content__') && node.has_key?('key')
  23 + {normalize_key(node['key']) => rekey(node['__content__'])}
  24 + elsif node.has_key?('__content__')
  25 + rekey(node['__content__'])
  26 + else
  27 + node.inject({}) {|rekeyed, (key, val)| rekeyed.merge(normalize_key(key) => rekey(val))}
  28 + end
  29 +
  30 + when Array
  31 + if node.first.has_key?('key')
  32 + node.inject({}) {|rekeyed, keypair| rekeyed.merge(rekey(keypair))}
  33 + else
  34 + node.map {|n| rekey(n)}
  35 + end
  36 +
  37 + else
  38 + node
  39 +
  40 + end
  41 + end
  42 +
  43 +
  44 + def self.normalize_key(key)
  45 + key.gsub('.', '_')
  46 + end
  47 +
  48 +
  49 + def self.normalize(notice)
  50 + error = notice['error']
  51 + backtrace = error['backtrace']
  52 + backtrace['line'] = [backtrace['line']] unless backtrace['line'].is_a?(Array)
  53 +
  54 + notice['request'] ||= {}
  55 + notice['request']['component'] = 'unknown' if notice['request']['component'].blank?
  56 + notice['request']['action'] = nil if notice['request']['action'].blank?
  57 +
  58 + notice
  59 + end
  60 +
  61 +
  62 + def self.for_errbit_api(notice)
  63 + {
  64 + :klass => notice['error']['class'],
  65 + :message => notice['error']['message'],
  66 + :backtrace => notice['error']['backtrace']['line'],
  67 +
  68 + :request => notice['request'],
  69 + :server_environment => notice['server-environment'],
  70 +
  71 + :api_key => notice['api-key'],
  72 + :notifier => notice['notifier']
  73 + }
  74 + end
  75 +
  76 +
  77 + end
  78 +end
0 79 \ No newline at end of file
... ...
spec/controllers/notices_controller_spec.rb
1 1 require 'spec_helper'
2 2  
3 3 describe NoticesController do
4   -
  4 +
5 5 context 'notices API' do
6 6 before do
7 7 @xml = Rails.root.join('spec','fixtures','hoptoad_test_notice.xml').read
8 8 @app = Factory(:app_with_watcher)
9 9 App.stub(:find_by_api_key!).and_return(@app)
10   - @notice = Notice.from_xml(@xml)
11   -
  10 + @notice = App.report_error!(@xml)
  11 +
12 12 request.env['Content-type'] = 'text/xml'
13 13 request.env['Accept'] = 'text/xml, application/xml'
14 14 end
15   -
  15 +
16 16 it "generates a notice from xml [POST]" do
17   - Notice.should_receive(:from_xml).with(@xml).and_return(@notice)
  17 + App.should_receive(:report_error!).with(@xml).and_return(@notice)
18 18 request.should_receive(:raw_post).and_return(@xml)
19 19 post :create
20 20 end
21   -
  21 +
22 22 it "generates a notice from xml [GET]" do
23   - Notice.should_receive(:from_xml).with(@xml).and_return(@notice)
  23 + App.should_receive(:report_error!).with(@xml).and_return(@notice)
24 24 get :create, {:data => @xml}
25 25 end
26   -
  26 +
27 27 it "sends a notification email" do
28 28 request.should_receive(:raw_post).and_return(@xml)
29 29 post :create
... ... @@ -34,5 +34,5 @@ describe NoticesController do
34 34 email.subject.should include("[#{@notice.environment_name}]")
35 35 end
36 36 end
37   -
  37 +
38 38 end
... ...
spec/fixtures/hoptoad_test_notice_with_one_line_of_backtrace.xml 0 → 100644
... ... @@ -0,0 +1,75 @@
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<notice version="2.0">
  3 + <api-key>APIKEY</api-key>
  4 + <notifier>
  5 + <name>Hoptoad Notifier</name>
  6 + <version>2.3.2</version>
  7 + <url>http://hoptoadapp.com</url>
  8 + </notifier>
  9 + <error>
  10 + <class>HoptoadTestingException</class>
  11 + <message>HoptoadTestingException: Testing hoptoad via "rake hoptoad:test". If you can see this, it works.</message>
  12 + <backtrace>
  13 + <line number="425" file="[GEM_ROOT]/gems/activesupport-3.0.0.rc/lib/active_support/callbacks.rb" method="_run__2115867319__process_action__262109504__callbacks"/>
  14 + </backtrace>
  15 + </error>
  16 + <request>
  17 + <url>http://example.org/verify</url>
  18 + <component>application</component>
  19 + <action>verify</action>
  20 + <params>
  21 + <var key="action">verify</var>
  22 + <var key="controller">application</var>
  23 + </params>
  24 + <cgi-data>
  25 + <var key="rack.session"/>
  26 + <var key="action_dispatch.request.formats">text/html</var>
  27 + <var key="action_dispatch.request.parameters">
  28 + <var key="action">verify</var>
  29 + <var key="controller">application</var>
  30 + </var>
  31 + <var key="SERVER_NAME">example.org</var>
  32 + <var key="rack.url_scheme">http</var>
  33 + <var key="action_dispatch.remote_ip"/>
  34 + <var key="CONTENT_LENGTH">0</var>
  35 + <var key="rack.errors">#&lt;StringIO:0x103d9dec0&gt;</var>
  36 + <var key="action_dispatch.request.unsigned_session_cookie"/>
  37 + <var key="action_dispatch.request.query_parameters"/>
  38 + <var key="HTTPS">off</var>
  39 + <var key="rack.run_once">false</var>
  40 + <var key="PATH_INFO">/verify</var>
  41 + <var key="action_dispatch.secret_token">994f235e3372684bc736dd11842b754d2ddcffc8c2958d33a29527c3217becd6655fa4653a318bc7c34131f9baf2acc0c424ed07e48e0e5e87c6cd34d711e985</var>
  42 + <var key="rack.version">11</var>
  43 + <var key="SCRIPT_NAME"/>
  44 + <var key="action_dispatch.request.path_parameters">
  45 + <var key="action">verify</var>
  46 + <var key="controller">application</var>
  47 + </var>
  48 + <var key="rack.multithread">false</var>
  49 + <var key="action_dispatch.parameter_filter">password</var>
  50 + <var key="action_dispatch.cookies"/>
  51 + <var key="action_dispatch.request.request_parameters"/>
  52 + <var key="rack.multiprocess">true</var>
  53 + <var key="rack.request.query_hash"/>
  54 + <var key="SERVER_PORT">80</var>
  55 + <var key="REQUEST_METHOD">GET</var>
  56 + <var key="action_controller.instance">#&lt;ApplicationController:0x103d2f560&gt;</var>
  57 + <var key="rack.session.options">
  58 + <var key="secure">false</var>
  59 + <var key="httponly">true</var>
  60 + <var key="path">/</var>
  61 + <var key="expire_after"/>
  62 + <var key="domain"/>
  63 + <var key="id"/>
  64 + </var>
  65 + <var key="rack.input">#&lt;StringIO:0x103d9dc90&gt;</var>
  66 + <var key="action_dispatch.request.content_type"/>
  67 + <var key="rack.request.query_string"/>
  68 + <var key="QUERY_STRING"/>
  69 + </cgi-data>
  70 + </request>
  71 + <server-environment>
  72 + <project-root>/path/to/sample/project</project-root>
  73 + <environment-name>development</environment-name>
  74 + </server-environment>
  75 +</notice>
0 76 \ No newline at end of file
... ...
spec/models/app_spec.rb
... ... @@ -145,4 +145,94 @@ describe App do
145 145 end
146 146  
147 147  
  148 + context '#report_error!' do
  149 + before do
  150 + @xml = Rails.root.join('spec','fixtures','hoptoad_test_notice.xml').read
  151 + @app = Factory(:app, :api_key => 'APIKEY')
  152 + ErrorReport.stub(:get_fingerprint).and_return('fingerprintdigest')
  153 + end
  154 +
  155 + it 'finds the correct app' do
  156 + @notice = App.report_error!(@xml)
  157 + @notice.err.app.should == @app
  158 + end
  159 +
  160 + it 'finds the correct err for the notice' do
  161 + App.should_receive(:find_by_api_key!).and_return(@app)
  162 + @app.should_receive(:find_or_create_err!).with({
  163 + :klass => 'HoptoadTestingException',
  164 + :component => 'application',
  165 + :action => 'verify',
  166 + :environment => 'development',
  167 + :fingerprint => 'fingerprintdigest'
  168 + }).and_return(err = Factory(:err))
  169 + err.notices.stub(:create!)
  170 + @notice = App.report_error!(@xml)
  171 + end
  172 +
  173 + it 'marks the err as unresolved if it was previously resolved' do
  174 + App.should_receive(:find_by_api_key!).and_return(@app)
  175 + @app.should_receive(:find_or_create_err!).with({
  176 + :klass => 'HoptoadTestingException',
  177 + :component => 'application',
  178 + :action => 'verify',
  179 + :environment => 'development',
  180 + :fingerprint => 'fingerprintdigest'
  181 + }).and_return(err = Factory(:err, :problem => Factory(:problem, :resolved => true)))
  182 + err.should be_resolved
  183 + @notice = App.report_error!(@xml)
  184 + @notice.err.should == err
  185 + @notice.err.should_not be_resolved
  186 + end
  187 +
  188 + it 'should create a new notice' do
  189 + @notice = App.report_error!(@xml)
  190 + @notice.should be_persisted
  191 + end
  192 +
  193 + it 'assigns an err to the notice' do
  194 + @notice = App.report_error!(@xml)
  195 + @notice.err.should be_a(Err)
  196 + end
  197 +
  198 + it 'captures the err message' do
  199 + @notice = App.report_error!(@xml)
  200 + @notice.message.should == 'HoptoadTestingException: Testing hoptoad via "rake hoptoad:test". If you can see this, it works.'
  201 + end
  202 +
  203 + it 'captures the backtrace' do
  204 + @notice = App.report_error!(@xml)
  205 + @notice.backtrace.size.should == 73
  206 + @notice.backtrace.last['file'].should == '[GEM_ROOT]/bin/rake'
  207 + end
  208 +
  209 + it 'captures the server_environment' do
  210 + @notice = App.report_error!(@xml)
  211 + @notice.server_environment['environment-name'].should == 'development'
  212 + end
  213 +
  214 + it 'captures the request' do
  215 + @notice = App.report_error!(@xml)
  216 + @notice.request['url'].should == 'http://example.org/verify'
  217 + @notice.request['params']['controller'].should == 'application'
  218 + end
  219 +
  220 + it 'captures the notifier' do
  221 + @notice = App.report_error!(@xml)
  222 + @notice.notifier['name'].should == 'Hoptoad Notifier'
  223 + end
  224 +
  225 + it "should handle params without 'request' section" do
  226 + xml = Rails.root.join('spec','fixtures','hoptoad_test_notice_without_request_section.xml').read
  227 + lambda { App.report_error!(xml) }.should_not raise_error
  228 + end
  229 +
  230 + it "should handle params with only a single line of backtrace" do
  231 + xml = Rails.root.join('spec','fixtures','hoptoad_test_notice_with_one_line_of_backtrace.xml').read
  232 + lambda { @notice = App.report_error!(xml) }.should_not raise_error
  233 + @notice.backtrace.length.should == 1
  234 + end
  235 + end
  236 +
  237 +
148 238 end
... ...
spec/models/notice_spec.rb
... ... @@ -39,111 +39,21 @@ describe Notice do
39 39 'method' => ActiveSupport.methods.shuffle.first
40 40 }]
41 41 end
42   -
  42 +
43 43 it "should be false for line not starting with PROJECT_ROOT" do
44 44 Notice.in_app_backtrace_line?(backtrace[0]).should == false
45 45 end
46   -
  46 +
47 47 it "should be false for file in vendor dir" do
48 48 Notice.in_app_backtrace_line?(backtrace[1]).should == false
49 49 end
50   -
  50 +
51 51 it "should be true for application file" do
52 52 Notice.in_app_backtrace_line?(backtrace[2]).should == true
53 53 end
54 54 end
55 55  
56 56  
57   - context '#from_xml' do
58   - before do
59   - @xml = Rails.root.join('spec','fixtures','hoptoad_test_notice.xml').read
60   - @app = Factory(:app, :api_key => 'APIKEY')
61   - Digest::MD5.stub(:hexdigest).and_return('fingerprintdigest')
62   - end
63   -
64   - it 'finds the correct app' do
65   - @notice = Notice.from_xml(@xml)
66   - @notice.err.app.should == @app
67   - end
68   -
69   - it 'finds the correct err for the notice' do
70   - App.should_receive(:find_by_api_key!).and_return(@app)
71   - @app.should_receive(:find_or_create_err!).with({
72   - :klass => 'HoptoadTestingException',
73   - :component => 'application',
74   - :action => 'verify',
75   - :environment => 'development',
76   - :fingerprint => 'fingerprintdigest'
77   - }).and_return(err = Factory(:err))
78   - err.notices.stub(:create!)
79   - @notice = Notice.from_xml(@xml)
80   - end
81   -
82   -
83   - it 'marks the err as unresolved if it was previously resolved' do
84   - App.should_receive(:find_by_api_key!).and_return(@app)
85   - @app.should_receive(:find_or_create_err!).with({
86   - :klass => 'HoptoadTestingException',
87   - :component => 'application',
88   - :action => 'verify',
89   - :environment => 'development',
90   - :fingerprint => 'fingerprintdigest'
91   - }).and_return(err = Factory(:err, :problem => Factory(:problem, :resolved => true)))
92   - err.should be_resolved
93   - @notice = Notice.from_xml(@xml)
94   - @notice.err.should == err
95   - @notice.err.should_not be_resolved
96   - end
97   -
98   - it 'should create a new notice' do
99   - @notice = Notice.from_xml(@xml)
100   - @notice.should be_persisted
101   - end
102   -
103   - it 'assigns an err to the notice' do
104   - @notice = Notice.from_xml(@xml)
105   - @notice.err.should be_a(Err)
106   - end
107   -
108   - it 'captures the err message' do
109   - @notice = Notice.from_xml(@xml)
110   - @notice.message.should == 'HoptoadTestingException: Testing hoptoad via "rake hoptoad:test". If you can see this, it works.'
111   - end
112   -
113   - it 'captures the backtrace' do
114   - @notice = Notice.from_xml(@xml)
115   - @notice.backtrace.size.should == 73
116   - @notice.backtrace.last['file'].should == '[GEM_ROOT]/bin/rake'
117   - end
118   -
119   - it 'captures the server_environment' do
120   - @notice = Notice.from_xml(@xml)
121   - @notice.server_environment['environment-name'].should == 'development'
122   - end
123   -
124   - it 'captures the request' do
125   - @notice = Notice.from_xml(@xml)
126   - @notice.request['url'].should == 'http://example.org/verify'
127   - @notice.request['params']['controller'].should == 'application'
128   - end
129   -
130   - it 'captures the notifier' do
131   - @notice = Notice.from_xml(@xml)
132   - @notice.notifier['name'].should == 'Hoptoad Notifier'
133   - end
134   -
135   - it "should handle params withour 'request' section" do
136   - @xml = Rails.root.join('spec','fixtures','hoptoad_test_notice_without_request_section.xml').read
137   - lambda { Notice.from_xml(@xml) }.should_not raise_error
138   - end
139   -
140   - it "should raise ApiVersionError" do
141   - @xml = Rails.root.join('spec', 'fixtures', 'hoptoad_test_notice_with_wrong_version.xml').read
142   - expect { Notice.from_xml(@xml) }.to raise_error(Hoptoad::V2::ApiVersionError)
143   - end
144   - end
145   -
146   -
147 57 describe "key sanitization" do
148 58 before do
149 59 @hash = { "some.key" => { "$nested.key" => {"$Path" => "/", "some$key" => "key"}}}
... ... @@ -179,7 +89,7 @@ describe Notice do
179 89 before do
180 90 Errbit::Config.per_app_email_at_notices = true
181 91 @app = Factory(:app_with_watcher, :email_at_notices => custom_thresholds)
182   - @problem = Factory(:err, :problem => @app.problems.create!)
  92 + @err = Factory(:err, :problem => Factory(:problem, :app => @app))
183 93 end
184 94  
185 95 after do
... ... @@ -188,10 +98,10 @@ describe Notice do
188 98  
189 99 custom_thresholds.each do |threshold|
190 100 it "sends an email notification after #{threshold} notice(s)" do
191   - @problem.notices.stub(:count).and_return(threshold)
  101 + @err.problem.stub(:notices_count).and_return(threshold)
192 102 Mailer.should_receive(:err_notification).
193 103 and_return(mock('email', :deliver => true))
194   - Factory(:notice, :err => @problem)
  104 + Factory(:notice, :err => @err)
195 105 end
196 106 end
197 107 end
... ...