diff --git a/Gemfile b/Gemfile index 4a90085..860d1cc 100644 --- a/Gemfile +++ b/Gemfile @@ -1,12 +1,16 @@ source 'http://rubygems.org' gem 'rails', '3.0.0.rc' -gem 'nokogiri' +gem 'libxml-ruby' gem 'bson_ext', :require => nil gem 'mongoid', '2.0.0.beta.15' gem 'haml' +group :development, :test do + gem 'rspec-rails', '>= 2.0.0.beta.19' +end + group :test do gem 'rspec', '>= 2.0.0.beta.19' - gem 'rspec-rails', '>= 2.0.0.beta.19' + gem 'database_cleaner', '0.5.2' end diff --git a/Gemfile.lock b/Gemfile.lock index 6940d8f..7c5f614 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -33,10 +33,13 @@ GEM bson (1.0.4) bson_ext (1.0.4) builder (2.1.2) + database_cleaner (0.5.2) diff-lcs (1.1.2) erubis (2.6.6) abstract (>= 1.0.0) + haml (3.0.15) i18n (0.4.1) + libxml-ruby (1.1.4) mail (2.2.5) activesupport (>= 2.3.6) mime-types @@ -97,8 +100,10 @@ PLATFORMS DEPENDENCIES bson_ext + database_cleaner (= 0.5.2) + haml + libxml-ruby mongoid (= 2.0.0.beta.15) - nokogiri rails (= 3.0.0.rc) rspec (>= 2.0.0.beta.19) rspec-rails (>= 2.0.0.beta.19) diff --git a/Rakefile b/Rakefile index fbcdf85..2b4c04d 100644 --- a/Rakefile +++ b/Rakefile @@ -3,5 +3,9 @@ require File.expand_path('../config/application', __FILE__) require 'rake' +require 'bundler' Hypnotoad::Application.load_tasks + +Rake::Task[:default].clear +task :default => ['spec'] \ No newline at end of file diff --git a/app/models/error.rb b/app/models/error.rb new file mode 100644 index 0000000..3c683fd --- /dev/null +++ b/app/models/error.rb @@ -0,0 +1,10 @@ +class Error + include Mongoid::Document + + embeds_many :notices + + def self.for(attrs) + self.where(attrs).first || create(attrs) + end + +end \ No newline at end of file diff --git a/app/models/notice.rb b/app/models/notice.rb new file mode 100644 index 0000000..8d4d2a8 --- /dev/null +++ b/app/models/notice.rb @@ -0,0 +1,32 @@ +require 'hoptoad' + +class Notice + include Mongoid::Document + + field :backtrace, :type => Array + field :server_environment, :type => Hash + field :request, :type => Hash + field :notifier, :type => Hash + + embedded_in :error, :inverse_of => :notices + + def self.from_xml(hoptoad_xml) + hoptoad_notice = Hoptoad::V2.parse_xml(hoptoad_xml) + + error = Error.for({ + :class_name => hoptoad_notice['error']['class'], + :message => hoptoad_notice['error']['message'], + :component => hoptoad_notice['request']['component'], + :action => hoptoad_notice['request']['action'], + :environment => hoptoad_notice['server-environment']['environment-name'] + }) + + error.notices.create({ + :backtrace => hoptoad_notice['error']['backtrace']['line'], + :server_environment => hoptoad_notice['server-environment'], + :request => hoptoad_notice['request'], + :notifier => hoptoad_notice['notifier'] + }) + end + +end \ No newline at end of file diff --git a/config/initializers/xml_backend.rb b/config/initializers/xml_backend.rb index 3bc16ac..fe1e988 100644 --- a/config/initializers/xml_backend.rb +++ b/config/initializers/xml_backend.rb @@ -1 +1 @@ -ActiveSupport::XmlMini.backend = 'Nokogiri' \ No newline at end of file +ActiveSupport::XmlMini.backend = 'LibXML' \ No newline at end of file diff --git a/lib/hoptoad.rb b/lib/hoptoad.rb new file mode 100644 index 0000000..70bb2a0 --- /dev/null +++ b/lib/hoptoad.rb @@ -0,0 +1,35 @@ +module Hoptoad + module V2 + + def self.parse_xml(xml) + parsed = ActiveSupport::XmlMini.backend.parse(xml)['notice'] + rekey(parsed) + end + + private + + def self.rekey(node) + if node.is_a?(Hash) && node.has_key?('var') && node.has_key?('key') + {node['key'] => rekey(node['var'])} + elsif node.is_a?(Hash) && node.has_key?('var') + rekey(node['var']) + elsif node.is_a?(Hash) && node.has_key?('__content__') && node.has_key?('key') + {node['key'] => node['__content__']} + elsif node.is_a?(Hash) && node.has_key?('__content__') + node['__content__'] + elsif node.is_a?(Hash) + node.inject({}) {|rekeyed, (key,val)| + rekeyed.merge(key => rekey(val)) + } + elsif node.is_a?(Array) && node.first.has_key?('key') + node.inject({}) {|rekeyed,keypair| + rekeyed.merge(rekey(keypair)) + } + elsif node.is_a?(Array) + node.map {|n| rekey(n)} + else + node + end + end + end +end \ No newline at end of file diff --git a/spec/fixtures/hoptoad_test_notice.xml b/spec/fixtures/hoptoad_test_notice.xml new file mode 100644 index 0000000..37dd941 --- /dev/null +++ b/spec/fixtures/hoptoad_test_notice.xml @@ -0,0 +1,147 @@ + + + ALLGLORYTOTHEHYPNOTOAD + + Hoptoad Notifier + 2.3.2 + http://hoptoadapp.com + + + HoptoadTestingException + HoptoadTestingException: Testing hoptoad via "rake hoptoad:test". If you can see this, it works. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + http://example.org/verify + application + verify + + verify + application + + + + text/html + + verify + application + + example.org + http + + 0 + #<StringIO:0x103d9dec0> + + + off + false + /verify + 994f235e3372684bc736dd11842b754d2ddcffc8c2958d33a29527c3217becd6655fa4653a318bc7c34131f9baf2acc0c424ed07e48e0e5e87c6cd34d711e985 + 11 + + + verify + application + + false + password + + + true + + 80 + GET + #<ApplicationController:0x103d2f560> + + false + true + / + + + + + #<StringIO:0x103d9dc90> + + + + + + + /path/to/sample/project + development + + \ No newline at end of file diff --git a/spec/models/error_spec.rb b/spec/models/error_spec.rb new file mode 100644 index 0000000..db64cda --- /dev/null +++ b/spec/models/error_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +describe Error do + + context '#for' do + before do + @conditions = { + :class_name => 'Whoops', + :message => 'Whoops: Oopsy Daisy', + :component => 'Foo', + :action => 'bar', + :environment => 'production' + } + end + + it 'returns the correct error if one already exists' do + existing = Error.create(@conditions) + Error.for(@conditions).should == existing + end + + it 'creates a new error if a matching one does not already exist' do + Error.where(@conditions).exists?.should == false + lambda { + Error.for(@conditions) + }.should change(Error,:count).by(1) + end + end + +end \ No newline at end of file diff --git a/spec/models/notice_spec.rb b/spec/models/notice_spec.rb new file mode 100644 index 0000000..3eab4ab --- /dev/null +++ b/spec/models/notice_spec.rb @@ -0,0 +1,55 @@ +require 'spec_helper' + +describe Notice do + + context '#from_xml' do + before do + @xml = Rails.root.join('spec','fixtures','hoptoad_test_notice.xml').read + end + + it 'finds the correct error for the notice' do + Error.should_receive(:for).with({ + :class_name => 'HoptoadTestingException', + :message => 'HoptoadTestingException: Testing hoptoad via "rake hoptoad:test". If you can see this, it works.', + :component => 'application', + :action => 'verify', + :environment => 'development' + }).and_return(err = Error.new) + err.notices.stub(:create) + @notice = Notice.from_xml(@xml) + end + + it 'should create a new notice' do + @notice = Notice.from_xml(@xml) + @notice.should be_persisted + end + + it 'assigns an error to the notice' do + @notice = Notice.from_xml(@xml) + @notice.error.should be_an(Error) + end + + it 'captures the backtrace' do + @notice = Notice.from_xml(@xml) + @notice.backtrace.size.should == 73 + @notice.backtrace.last['file'].should == '[GEM_ROOT]/bin/rake' + end + + it 'captures the server_environment' do + @notice = Notice.from_xml(@xml) + @notice.server_environment['environment-name'].should == 'development' + end + + it 'captures the request' do + @notice = Notice.from_xml(@xml) + @notice.request['url'].should == 'http://example.org/verify' + @notice.request['params']['controller'].should == 'application' + end + + it 'captures the notifier' do + @notice = Notice.from_xml(@xml) + @notice.notifier['name'].should == 'Hoptoad Notifier' + end + end + +end \ No newline at end of file diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index abf12b6..b135276 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -3,6 +3,7 @@ ENV["RAILS_ENV"] ||= 'test' require File.expand_path("../../config/environment", __FILE__) require 'rspec/rails' +require 'database_cleaner' # Requires supporting files with custom matchers and macros, etc, # in ./support/ and its subdirectories. @@ -10,5 +11,10 @@ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f} RSpec.configure do |config| config.mock_with :rspec - config.fixture_path = "#{::Rails.root}/spec/fixtures" + + config.before(:each) do + DatabaseCleaner.orm = "mongoid" + DatabaseCleaner.strategy = :truncation + DatabaseCleaner.clean + end end -- libgit2 0.21.2