Commit de726f5b671455151e1a50ff6a2c72952c600370

Authored by Jared Pace
1 parent 16015844
Exists in master and in 1 other branch production

Parse Hoptoad XML to create errors/notices.

Gemfile
1 1 source 'http://rubygems.org'
2 2  
3 3 gem 'rails', '3.0.0.rc'
4   -gem 'nokogiri'
  4 +gem 'libxml-ruby'
5 5 gem 'bson_ext', :require => nil
6 6 gem 'mongoid', '2.0.0.beta.15'
7 7 gem 'haml'
8 8  
  9 +group :development, :test do
  10 + gem 'rspec-rails', '>= 2.0.0.beta.19'
  11 +end
  12 +
9 13 group :test do
10 14 gem 'rspec', '>= 2.0.0.beta.19'
11   - gem 'rspec-rails', '>= 2.0.0.beta.19'
  15 + gem 'database_cleaner', '0.5.2'
12 16 end
... ...
Gemfile.lock
... ... @@ -33,10 +33,13 @@ GEM
33 33 bson (1.0.4)
34 34 bson_ext (1.0.4)
35 35 builder (2.1.2)
  36 + database_cleaner (0.5.2)
36 37 diff-lcs (1.1.2)
37 38 erubis (2.6.6)
38 39 abstract (>= 1.0.0)
  40 + haml (3.0.15)
39 41 i18n (0.4.1)
  42 + libxml-ruby (1.1.4)
40 43 mail (2.2.5)
41 44 activesupport (>= 2.3.6)
42 45 mime-types
... ... @@ -97,8 +100,10 @@ PLATFORMS
97 100  
98 101 DEPENDENCIES
99 102 bson_ext
  103 + database_cleaner (= 0.5.2)
  104 + haml
  105 + libxml-ruby
100 106 mongoid (= 2.0.0.beta.15)
101   - nokogiri
102 107 rails (= 3.0.0.rc)
103 108 rspec (>= 2.0.0.beta.19)
104 109 rspec-rails (>= 2.0.0.beta.19)
... ...
Rakefile
... ... @@ -3,5 +3,9 @@
3 3  
4 4 require File.expand_path('../config/application', __FILE__)
5 5 require 'rake'
  6 +require 'bundler'
6 7  
7 8 Hypnotoad::Application.load_tasks
  9 +
  10 +Rake::Task[:default].clear
  11 +task :default => ['spec']
8 12 \ No newline at end of file
... ...
app/models/error.rb 0 → 100644
... ... @@ -0,0 +1,10 @@
  1 +class Error
  2 + include Mongoid::Document
  3 +
  4 + embeds_many :notices
  5 +
  6 + def self.for(attrs)
  7 + self.where(attrs).first || create(attrs)
  8 + end
  9 +
  10 +end
0 11 \ No newline at end of file
... ...
app/models/notice.rb 0 → 100644
... ... @@ -0,0 +1,32 @@
  1 +require 'hoptoad'
  2 +
  3 +class Notice
  4 + include Mongoid::Document
  5 +
  6 + field :backtrace, :type => Array
  7 + field :server_environment, :type => Hash
  8 + field :request, :type => Hash
  9 + field :notifier, :type => Hash
  10 +
  11 + embedded_in :error, :inverse_of => :notices
  12 +
  13 + def self.from_xml(hoptoad_xml)
  14 + hoptoad_notice = Hoptoad::V2.parse_xml(hoptoad_xml)
  15 +
  16 + error = Error.for({
  17 + :class_name => hoptoad_notice['error']['class'],
  18 + :message => hoptoad_notice['error']['message'],
  19 + :component => hoptoad_notice['request']['component'],
  20 + :action => hoptoad_notice['request']['action'],
  21 + :environment => hoptoad_notice['server-environment']['environment-name']
  22 + })
  23 +
  24 + error.notices.create({
  25 + :backtrace => hoptoad_notice['error']['backtrace']['line'],
  26 + :server_environment => hoptoad_notice['server-environment'],
  27 + :request => hoptoad_notice['request'],
  28 + :notifier => hoptoad_notice['notifier']
  29 + })
  30 + end
  31 +
  32 +end
0 33 \ No newline at end of file
... ...
config/initializers/xml_backend.rb
1   -ActiveSupport::XmlMini.backend = 'Nokogiri'
2 1 \ No newline at end of file
  2 +ActiveSupport::XmlMini.backend = 'LibXML'
3 3 \ No newline at end of file
... ...
lib/hoptoad.rb 0 → 100644
... ... @@ -0,0 +1,35 @@
  1 +module Hoptoad
  2 + module V2
  3 +
  4 + def self.parse_xml(xml)
  5 + parsed = ActiveSupport::XmlMini.backend.parse(xml)['notice']
  6 + rekey(parsed)
  7 + end
  8 +
  9 + private
  10 +
  11 + def self.rekey(node)
  12 + if node.is_a?(Hash) && node.has_key?('var') && node.has_key?('key')
  13 + {node['key'] => rekey(node['var'])}
  14 + elsif node.is_a?(Hash) && node.has_key?('var')
  15 + rekey(node['var'])
  16 + elsif node.is_a?(Hash) && node.has_key?('__content__') && node.has_key?('key')
  17 + {node['key'] => node['__content__']}
  18 + elsif node.is_a?(Hash) && node.has_key?('__content__')
  19 + node['__content__']
  20 + elsif node.is_a?(Hash)
  21 + node.inject({}) {|rekeyed, (key,val)|
  22 + rekeyed.merge(key => rekey(val))
  23 + }
  24 + elsif node.is_a?(Array) && node.first.has_key?('key')
  25 + node.inject({}) {|rekeyed,keypair|
  26 + rekeyed.merge(rekey(keypair))
  27 + }
  28 + elsif node.is_a?(Array)
  29 + node.map {|n| rekey(n)}
  30 + else
  31 + node
  32 + end
  33 + end
  34 + end
  35 +end
0 36 \ No newline at end of file
... ...
spec/fixtures/hoptoad_test_notice.xml 0 → 100644
... ... @@ -0,0 +1,147 @@
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<notice version="2.0">
  3 + <api-key>ALLGLORYTOTHEHYPNOTOAD</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 + <line number="404" file="[GEM_ROOT]/gems/activesupport-3.0.0.rc/lib/active_support/callbacks.rb" method="send"/>
  15 + <line number="404" file="[GEM_ROOT]/gems/activesupport-3.0.0.rc/lib/active_support/callbacks.rb" method="_run_process_action_callbacks"/>
  16 + <line number="93" file="[GEM_ROOT]/gems/activesupport-3.0.0.rc/lib/active_support/callbacks.rb" method="send"/>
  17 + <line number="93" file="[GEM_ROOT]/gems/activesupport-3.0.0.rc/lib/active_support/callbacks.rb" method="run_callbacks"/>
  18 + <line number="17" file="[GEM_ROOT]/gems/actionpack-3.0.0.rc/lib/abstract_controller/callbacks.rb" method="process_action"/>
  19 + <line number="30" file="[GEM_ROOT]/gems/actionpack-3.0.0.rc/lib/action_controller/metal/instrumentation.rb" method="process_action"/>
  20 + <line number="52" file="[GEM_ROOT]/gems/activesupport-3.0.0.rc/lib/active_support/notifications.rb" method="instrument"/>
  21 + <line number="21" file="[GEM_ROOT]/gems/activesupport-3.0.0.rc/lib/active_support/notifications/instrumenter.rb" method="instrument"/>
  22 + <line number="52" file="[GEM_ROOT]/gems/activesupport-3.0.0.rc/lib/active_support/notifications.rb" method="instrument"/>
  23 + <line number="29" file="[GEM_ROOT]/gems/actionpack-3.0.0.rc/lib/action_controller/metal/instrumentation.rb" method="process_action"/>
  24 + <line number="17" file="[GEM_ROOT]/gems/actionpack-3.0.0.rc/lib/action_controller/metal/rescue.rb" method="process_action"/>
  25 + <line number="105" file="[GEM_ROOT]/gems/actionpack-3.0.0.rc/lib/abstract_controller/base.rb" method="process"/>
  26 + <line number="40" file="[GEM_ROOT]/gems/actionpack-3.0.0.rc/lib/abstract_controller/rendering.rb" method="process"/>
  27 + <line number="133" file="[GEM_ROOT]/gems/actionpack-3.0.0.rc/lib/action_controller/metal.rb" method="dispatch"/>
  28 + <line number="14" file="[GEM_ROOT]/gems/actionpack-3.0.0.rc/lib/action_controller/metal/rack_delegation.rb" method="dispatch"/>
  29 + <line number="173" file="[GEM_ROOT]/gems/actionpack-3.0.0.rc/lib/action_controller/metal.rb" method="action"/>
  30 + <line number="62" file="[GEM_ROOT]/gems/actionpack-3.0.0.rc/lib/action_dispatch/routing/route_set.rb" method="call"/>
  31 + <line number="62" file="[GEM_ROOT]/gems/actionpack-3.0.0.rc/lib/action_dispatch/routing/route_set.rb" method="dispatch"/>
  32 + <line number="27" file="[GEM_ROOT]/gems/actionpack-3.0.0.rc/lib/action_dispatch/routing/route_set.rb" method="call"/>
  33 + <line number="148" file="[GEM_ROOT]/gems/rack-mount-0.6.9/lib/rack/mount/route_set.rb" method="call"/>
  34 + <line number="89" file="[GEM_ROOT]/gems/rack-mount-0.6.9/lib/rack/mount/code_generation.rb" method="recognize"/>
  35 + <line number="66" file="[GEM_ROOT]/gems/rack-mount-0.6.9/lib/rack/mount/code_generation.rb" method="optimized_each"/>
  36 + <line number="88" file="[GEM_ROOT]/gems/rack-mount-0.6.9/lib/rack/mount/code_generation.rb" method="recognize"/>
  37 + <line number="139" file="[GEM_ROOT]/gems/rack-mount-0.6.9/lib/rack/mount/route_set.rb" method="call"/>
  38 + <line number="489" file="[GEM_ROOT]/gems/actionpack-3.0.0.rc/lib/action_dispatch/routing/route_set.rb" method="call"/>
  39 + <line number="41" file="[GEM_ROOT]/gems/haml-3.0.15/lib/sass/plugin/rack.rb" method="call"/>
  40 + <line number="14" file="[GEM_ROOT]/gems/actionpack-3.0.0.rc/lib/action_dispatch/middleware/head.rb" method="call"/>
  41 + <line number="24" file="[GEM_ROOT]/gems/rack-1.2.1/lib/rack/methodoverride.rb" method="call"/>
  42 + <line number="21" file="[GEM_ROOT]/gems/actionpack-3.0.0.rc/lib/action_dispatch/middleware/params_parser.rb" method="call"/>
  43 + <line number="177" file="[GEM_ROOT]/gems/actionpack-3.0.0.rc/lib/action_dispatch/middleware/flash.rb" method="call"/>
  44 + <line number="149" file="[GEM_ROOT]/gems/actionpack-3.0.0.rc/lib/action_dispatch/middleware/session/abstract_store.rb" method="call"/>
  45 + <line number="268" file="[GEM_ROOT]/gems/actionpack-3.0.0.rc/lib/action_dispatch/middleware/cookies.rb" method="call"/>
  46 + <line number="32" file="[GEM_ROOT]/gems/activerecord-3.0.0.rc/lib/active_record/query_cache.rb" method="call"/>
  47 + <line number="28" file="[GEM_ROOT]/gems/activerecord-3.0.0.rc/lib/active_record/connection_adapters/abstract/query_cache.rb" method="cache"/>
  48 + <line number="12" file="[GEM_ROOT]/gems/activerecord-3.0.0.rc/lib/active_record/query_cache.rb" method="cache"/>
  49 + <line number="31" file="[GEM_ROOT]/gems/activerecord-3.0.0.rc/lib/active_record/query_cache.rb" method="call"/>
  50 + <line number="46" file="[GEM_ROOT]/gems/actionpack-3.0.0.rc/lib/action_dispatch/middleware/callbacks.rb" method="call"/>
  51 + <line number="410" file="[GEM_ROOT]/gems/activesupport-3.0.0.rc/lib/active_support/callbacks.rb" method="_run_call_callbacks"/>
  52 + <line number="44" file="[GEM_ROOT]/gems/actionpack-3.0.0.rc/lib/action_dispatch/middleware/callbacks.rb" method="call"/>
  53 + <line number="107" file="[GEM_ROOT]/gems/rack-1.2.1/lib/rack/sendfile.rb" method="call"/>
  54 + <line number="48" file="[GEM_ROOT]/gems/actionpack-3.0.0.rc/lib/action_dispatch/middleware/remote_ip.rb" method="call"/>
  55 + <line number="48" file="[GEM_ROOT]/gems/actionpack-3.0.0.rc/lib/action_dispatch/middleware/show_exceptions.rb" method="call"/>
  56 + <line number="13" file="[GEM_ROOT]/gems/railties-3.0.0.rc/lib/rails/rack/logger.rb" method="call"/>
  57 + <line number="17" file="[GEM_ROOT]/gems/rack-1.2.1/lib/rack/runtime.rb" method="call"/>
  58 + <line number="72" file="[GEM_ROOT]/gems/activesupport-3.0.0.rc/lib/active_support/cache/strategy/local_cache.rb" method="call"/>
  59 + <line number="11" file="[GEM_ROOT]/gems/rack-1.2.1/lib/rack/lock.rb" method="call"/>
  60 + <line number="11" file="[GEM_ROOT]/gems/rack-1.2.1/lib/rack/lock.rb" method="synchronize"/>
  61 + <line number="11" file="[GEM_ROOT]/gems/rack-1.2.1/lib/rack/lock.rb" method="call"/>
  62 + <line number="30" file="[GEM_ROOT]/gems/actionpack-3.0.0.rc/lib/action_dispatch/middleware/static.rb" method="call"/>
  63 + <line number="168" file="[GEM_ROOT]/gems/railties-3.0.0.rc/lib/rails/application.rb" method="call"/>
  64 + <line number="77" file="[GEM_ROOT]/gems/railties-3.0.0.rc/lib/rails/application.rb" method="send"/>
  65 + <line number="77" file="[GEM_ROOT]/gems/railties-3.0.0.rc/lib/rails/application.rb" method="method_missing"/>
  66 + <line number="636" file="[GEM_ROOT]/gems/rake-0.8.7/lib/rake.rb" method="call"/>
  67 + <line number="636" file="[GEM_ROOT]/gems/rake-0.8.7/lib/rake.rb" method="execute"/>
  68 + <line number="631" file="[GEM_ROOT]/gems/rake-0.8.7/lib/rake.rb" method="each"/>
  69 + <line number="631" file="[GEM_ROOT]/gems/rake-0.8.7/lib/rake.rb" method="execute"/>
  70 + <line number="597" file="[GEM_ROOT]/gems/rake-0.8.7/lib/rake.rb" method="invoke_with_call_chain"/>
  71 + <line number="242" file="/Users/jdpace/.rvm/rubies/ree-1.8.7-2010.02/lib/ruby/1.8/monitor.rb" method="synchronize"/>
  72 + <line number="590" file="[GEM_ROOT]/gems/rake-0.8.7/lib/rake.rb" method="invoke_with_call_chain"/>
  73 + <line number="583" file="[GEM_ROOT]/gems/rake-0.8.7/lib/rake.rb" method="invoke"/>
  74 + <line number="2051" file="[GEM_ROOT]/gems/rake-0.8.7/lib/rake.rb" method="invoke_task"/>
  75 + <line number="2029" file="[GEM_ROOT]/gems/rake-0.8.7/lib/rake.rb" method="top_level"/>
  76 + <line number="2029" file="[GEM_ROOT]/gems/rake-0.8.7/lib/rake.rb" method="each"/>
  77 + <line number="2029" file="[GEM_ROOT]/gems/rake-0.8.7/lib/rake.rb" method="top_level"/>
  78 + <line number="2068" file="[GEM_ROOT]/gems/rake-0.8.7/lib/rake.rb" method="standard_exception_handling"/>
  79 + <line number="2023" file="[GEM_ROOT]/gems/rake-0.8.7/lib/rake.rb" method="top_level"/>
  80 + <line number="2001" file="[GEM_ROOT]/gems/rake-0.8.7/lib/rake.rb" method="run"/>
  81 + <line number="2068" file="[GEM_ROOT]/gems/rake-0.8.7/lib/rake.rb" method="standard_exception_handling"/>
  82 + <line number="1998" file="[GEM_ROOT]/gems/rake-0.8.7/lib/rake.rb" method="run"/>
  83 + <line number="31" file="[GEM_ROOT]/gems/rake-0.8.7/bin/rake" method=""/>
  84 + <line number="19" file="[GEM_ROOT]/bin/rake" method="load"/>
  85 + <line number="19" file="[GEM_ROOT]/bin/rake" method=""/>
  86 + </backtrace>
  87 + </error>
  88 + <request>
  89 + <url>http://example.org/verify</url>
  90 + <component>application</component>
  91 + <action>verify</action>
  92 + <params>
  93 + <var key="action">verify</var>
  94 + <var key="controller">application</var>
  95 + </params>
  96 + <cgi-data>
  97 + <var key="rack.session"/>
  98 + <var key="action_dispatch.request.formats">text/html</var>
  99 + <var key="action_dispatch.request.parameters">
  100 + <var key="action">verify</var>
  101 + <var key="controller">application</var>
  102 + </var>
  103 + <var key="SERVER_NAME">example.org</var>
  104 + <var key="rack.url_scheme">http</var>
  105 + <var key="action_dispatch.remote_ip"/>
  106 + <var key="CONTENT_LENGTH">0</var>
  107 + <var key="rack.errors">#&lt;StringIO:0x103d9dec0&gt;</var>
  108 + <var key="action_dispatch.request.unsigned_session_cookie"/>
  109 + <var key="action_dispatch.request.query_parameters"/>
  110 + <var key="HTTPS">off</var>
  111 + <var key="rack.run_once">false</var>
  112 + <var key="PATH_INFO">/verify</var>
  113 + <var key="action_dispatch.secret_token">994f235e3372684bc736dd11842b754d2ddcffc8c2958d33a29527c3217becd6655fa4653a318bc7c34131f9baf2acc0c424ed07e48e0e5e87c6cd34d711e985</var>
  114 + <var key="rack.version">11</var>
  115 + <var key="SCRIPT_NAME"/>
  116 + <var key="action_dispatch.request.path_parameters">
  117 + <var key="action">verify</var>
  118 + <var key="controller">application</var>
  119 + </var>
  120 + <var key="rack.multithread">false</var>
  121 + <var key="action_dispatch.parameter_filter">password</var>
  122 + <var key="action_dispatch.cookies"/>
  123 + <var key="action_dispatch.request.request_parameters"/>
  124 + <var key="rack.multiprocess">true</var>
  125 + <var key="rack.request.query_hash"/>
  126 + <var key="SERVER_PORT">80</var>
  127 + <var key="REQUEST_METHOD">GET</var>
  128 + <var key="action_controller.instance">#&lt;ApplicationController:0x103d2f560&gt;</var>
  129 + <var key="rack.session.options">
  130 + <var key="secure">false</var>
  131 + <var key="httponly">true</var>
  132 + <var key="path">/</var>
  133 + <var key="expire_after"/>
  134 + <var key="domain"/>
  135 + <var key="id"/>
  136 + </var>
  137 + <var key="rack.input">#&lt;StringIO:0x103d9dc90&gt;</var>
  138 + <var key="action_dispatch.request.content_type"/>
  139 + <var key="rack.request.query_string"/>
  140 + <var key="QUERY_STRING"/>
  141 + </cgi-data>
  142 + </request>
  143 + <server-environment>
  144 + <project-root>/path/to/sample/project</project-root>
  145 + <environment-name>development</environment-name>
  146 + </server-environment>
  147 +</notice>
0 148 \ No newline at end of file
... ...
spec/models/error_spec.rb 0 → 100644
... ... @@ -0,0 +1,29 @@
  1 +require 'spec_helper'
  2 +
  3 +describe Error do
  4 +
  5 + context '#for' do
  6 + before do
  7 + @conditions = {
  8 + :class_name => 'Whoops',
  9 + :message => 'Whoops: Oopsy Daisy',
  10 + :component => 'Foo',
  11 + :action => 'bar',
  12 + :environment => 'production'
  13 + }
  14 + end
  15 +
  16 + it 'returns the correct error if one already exists' do
  17 + existing = Error.create(@conditions)
  18 + Error.for(@conditions).should == existing
  19 + end
  20 +
  21 + it 'creates a new error if a matching one does not already exist' do
  22 + Error.where(@conditions).exists?.should == false
  23 + lambda {
  24 + Error.for(@conditions)
  25 + }.should change(Error,:count).by(1)
  26 + end
  27 + end
  28 +
  29 +end
0 30 \ No newline at end of file
... ...
spec/models/notice_spec.rb 0 → 100644
... ... @@ -0,0 +1,55 @@
  1 +require 'spec_helper'
  2 +
  3 +describe Notice do
  4 +
  5 + context '#from_xml' do
  6 + before do
  7 + @xml = Rails.root.join('spec','fixtures','hoptoad_test_notice.xml').read
  8 + end
  9 +
  10 + it 'finds the correct error for the notice' do
  11 + Error.should_receive(:for).with({
  12 + :class_name => 'HoptoadTestingException',
  13 + :message => 'HoptoadTestingException: Testing hoptoad via "rake hoptoad:test". If you can see this, it works.',
  14 + :component => 'application',
  15 + :action => 'verify',
  16 + :environment => 'development'
  17 + }).and_return(err = Error.new)
  18 + err.notices.stub(:create)
  19 + @notice = Notice.from_xml(@xml)
  20 + end
  21 +
  22 + it 'should create a new notice' do
  23 + @notice = Notice.from_xml(@xml)
  24 + @notice.should be_persisted
  25 + end
  26 +
  27 + it 'assigns an error to the notice' do
  28 + @notice = Notice.from_xml(@xml)
  29 + @notice.error.should be_an(Error)
  30 + end
  31 +
  32 + it 'captures the backtrace' do
  33 + @notice = Notice.from_xml(@xml)
  34 + @notice.backtrace.size.should == 73
  35 + @notice.backtrace.last['file'].should == '[GEM_ROOT]/bin/rake'
  36 + end
  37 +
  38 + it 'captures the server_environment' do
  39 + @notice = Notice.from_xml(@xml)
  40 + @notice.server_environment['environment-name'].should == 'development'
  41 + end
  42 +
  43 + it 'captures the request' do
  44 + @notice = Notice.from_xml(@xml)
  45 + @notice.request['url'].should == 'http://example.org/verify'
  46 + @notice.request['params']['controller'].should == 'application'
  47 + end
  48 +
  49 + it 'captures the notifier' do
  50 + @notice = Notice.from_xml(@xml)
  51 + @notice.notifier['name'].should == 'Hoptoad Notifier'
  52 + end
  53 + end
  54 +
  55 +end
0 56 \ No newline at end of file
... ...
spec/spec_helper.rb
... ... @@ -3,6 +3,7 @@
3 3 ENV["RAILS_ENV"] ||= 'test'
4 4 require File.expand_path("../../config/environment", __FILE__)
5 5 require 'rspec/rails'
  6 +require 'database_cleaner'
6 7  
7 8 # Requires supporting files with custom matchers and macros, etc,
8 9 # in ./support/ and its subdirectories.
... ... @@ -10,5 +11,10 @@ Dir[&quot;#{File.dirname(__FILE__)}/support/**/*.rb&quot;].each {|f| require f}
10 11  
11 12 RSpec.configure do |config|
12 13 config.mock_with :rspec
13   - config.fixture_path = "#{::Rails.root}/spec/fixtures"
  14 +
  15 + config.before(:each) do
  16 + DatabaseCleaner.orm = "mongoid"
  17 + DatabaseCleaner.strategy = :truncation
  18 + DatabaseCleaner.clean
  19 + end
14 20 end
... ...