diff --git a/app/models/err.rb b/app/models/err.rb new file mode 100644 index 0000000..943e2be --- /dev/null +++ b/app/models/err.rb @@ -0,0 +1,34 @@ +class Err + include Mongoid::Document + include Mongoid::Timestamps + + field :klass + field :message + field :component + field :action + field :environment + field :resolved, :type => Boolean + + referenced_in :project + embeds_many :notices + + validates_presence_of :klass, :environment + + def self.for(attrs) + project = attrs.delete(:project) + project.errs.where(attrs).first || project.errs.create(attrs) + end + + def resolve! + self.update_attributes(:resolved => true) + end + + def unresolved? + !resolved? + end + + def last_notice_at + notices.last.try(:created_at) + end + +end \ No newline at end of file diff --git a/app/models/error.rb b/app/models/error.rb deleted file mode 100644 index 671580e..0000000 --- a/app/models/error.rb +++ /dev/null @@ -1,32 +0,0 @@ -class Error - include Mongoid::Document - include Mongoid::Timestamps - - field :klass - field :message - field :component - field :action - field :environment - field :resolved, :type => Boolean - - embeds_many :notices - - validates_presence_of :klass, :environment - - def self.for(attrs) - self.where(attrs).first || create(attrs) - end - - def resolve! - self.update_attributes(:resolved => true) - end - - def unresolved? - !resolved? - end - - def last_notice_at - notices.last.try(:created_at) - end - -end \ No newline at end of file diff --git a/app/models/notice.rb b/app/models/notice.rb index 0539175..f75cd56 100644 --- a/app/models/notice.rb +++ b/app/models/notice.rb @@ -9,14 +9,16 @@ class Notice field :request, :type => Hash field :notifier, :type => Hash - embedded_in :error, :inverse_of => :notices + embedded_in :err, :inverse_of => :notices validates_presence_of :backtrace, :server_environment, :notifier def self.from_xml(hoptoad_xml) hoptoad_notice = Hoptoad::V2.parse_xml(hoptoad_xml) + project = Project.find_by_api_key!(hoptoad_notice['api-key']) - error = Error.for({ + error = Err.for({ + :project => project, :klass => hoptoad_notice['error']['class'], :message => hoptoad_notice['error']['message'], :component => hoptoad_notice['request']['component'], diff --git a/app/models/project.rb b/app/models/project.rb new file mode 100644 index 0000000..7c279f3 --- /dev/null +++ b/app/models/project.rb @@ -0,0 +1,25 @@ +class Project + include Mongoid::Document + include Mongoid::Timestamps + + field :name, :type => String + field :api_key + + references_many :errs + + before_validation :generate_api_key, :on => :create + + validates_presence_of :name, :api_key + validates_uniqueness_of :name, :api_key, :allow_blank => true + + def self.find_by_api_key!(key) + where(:api_key => key).first || raise(Mongoid::Errors::DocumentNotFound.new(self,key)) + end + + protected + + def generate_api_key + self.api_key ||= ActiveSupport::SecureRandom.hex + end + +end diff --git a/spec/controllers/notices_controller_spec.rb b/spec/controllers/notices_controller_spec.rb index f93669d..9edb9bb 100644 --- a/spec/controllers/notices_controller_spec.rb +++ b/spec/controllers/notices_controller_spec.rb @@ -5,6 +5,7 @@ describe NoticesController do context 'POST[XML] notices#create' do before do @xml = Rails.root.join('spec','fixtures','hoptoad_test_notice.xml').read + Project.stub(:find_by_api_key!).and_return(Factory.build(:project)) @notice = Notice.from_xml(@xml) request.env['Content-type'] = 'text/xml' diff --git a/spec/factories/err_factories.rb b/spec/factories/err_factories.rb new file mode 100644 index 0000000..4f16fdc --- /dev/null +++ b/spec/factories/err_factories.rb @@ -0,0 +1,25 @@ +Factory.define :err do |e| + e.project {|p| p.association :project} + e.klass 'FooError' + e.message 'FooError: Too Much Bar' + e.component 'foo' + e.action 'bar' + e.environment 'production' +end + +Factory.define :notice do |n| + n.err {|e| e.association :err} + n.backtrace { random_backtrace } + n.server_environment 'server-environment' => 'production' + n.notifier 'name' => 'Notifier', 'version' => '1', 'url' => 'http://toad.com' +end + +def random_backtrace + backtrace = [] + 99.times {|t| backtrace << { + 'number' => rand(999), + 'file' => "/path/to/file/#{ActiveSupport::SecureRandom.hex(4)}.rb", + 'method' => ActiveSupport.methods.shuffle.first + }} + backtrace +end \ No newline at end of file diff --git a/spec/factories/error_factories.rb b/spec/factories/error_factories.rb deleted file mode 100644 index 3b0c55a..0000000 --- a/spec/factories/error_factories.rb +++ /dev/null @@ -1,24 +0,0 @@ -Factory.define :error do |e| - e.klass 'FooError' - e.message 'FooError: Too Much Bar' - e.component 'foo' - e.action 'bar' - e.environment 'production' -end - -Factory.define :notice do |n| - n.error {|e| e.association :error} - n.backtrace { random_backtrace } - n.server_environment 'server-environment' => 'production' - n.notifier 'name' => 'Notifier', 'version' => '1', 'url' => 'http://toad.com' -end - -def random_backtrace - backtrace = [] - 99.times {|t| backtrace << { - 'number' => rand(999), - 'file' => "/path/to/file/#{ActiveSupport::SecureRandom.hex(4)}.rb", - 'method' => ActiveSupport.methods.shuffle.first - }} - backtrace -end \ No newline at end of file diff --git a/spec/factories/project_factories.rb b/spec/factories/project_factories.rb new file mode 100644 index 0000000..d20321b --- /dev/null +++ b/spec/factories/project_factories.rb @@ -0,0 +1,5 @@ +Factory.sequence(:project_name) {|n| "Project ##{n}"} + +Factory.define(:project) do |p| + p.name { Factory.next :project_name } +end \ No newline at end of file diff --git a/spec/models/err_spec.rb b/spec/models/err_spec.rb new file mode 100644 index 0000000..40321cf --- /dev/null +++ b/spec/models/err_spec.rb @@ -0,0 +1,77 @@ +require 'spec_helper' + +describe Err do + + context 'validations' do + it 'requires a klass' do + error = Factory.build(:err, :klass => nil) + error.should_not be_valid + error.errors[:klass].should include("can't be blank") + end + + it 'requires an environment' do + error = Factory.build(:err, :environment => nil) + error.should_not be_valid + error.errors[:environment].should include("can't be blank") + end + end + + context '#for' do + before do + @project = Factory(:project) + @conditions = { + :project => @project, + :klass => 'Whoops', + :message => 'Whoops: Oopsy Daisy', + :component => 'Foo', + :action => 'bar', + :environment => 'production' + } + end + + it 'returns the correct error if one already exists' do + existing = Err.create(@conditions) + Err.for(@conditions).should == existing + end + + it 'assigns the returned error to the given project' do + Err.for(@conditions).project.should == @project + end + + it 'creates a new error if a matching one does not already exist' do + Err.where(@conditions.except(:project)).exists?.should == false + lambda { + Err.for(@conditions) + }.should change(Err,:count).by(1) + end + end + + context '#last_notice_at' do + it "returns the created_at timestamp of the latest notice" do + error = Factory(:err) + error.last_notice_at.should be_nil + + notice1 = Factory(:notice, :err => error) + error.last_notice_at.should == notice1.created_at + + notice2 = Factory(:notice, :err => error) + error.last_notice_at.should == notice2.created_at + end + end + + context "#resolved?" do + it "should start out as unresolved" do + error = Err.new + error.should_not be_resolved + error.should be_unresolved + end + + it "should be able to be resolved" do + error = Factory(:err) + error.should_not be_resolved + error.resolve! + error.reload.should be_resolved + end + end + +end \ No newline at end of file diff --git a/spec/models/error_spec.rb b/spec/models/error_spec.rb deleted file mode 100644 index b9f0a53..0000000 --- a/spec/models/error_spec.rb +++ /dev/null @@ -1,71 +0,0 @@ -require 'spec_helper' - -describe Error do - - context 'validations' do - it 'requires a klass' do - error = Factory.build(:error, :klass => nil) - error.should_not be_valid - error.errors[:klass].should include("can't be blank") - end - - it 'requires an environment' do - error = Factory.build(:error, :environment => nil) - error.should_not be_valid - error.errors[:environment].should include("can't be blank") - end - end - - context '#for' do - before do - @conditions = { - :klass => '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 - - context '#last_notice_at' do - it "returns the created_at timestamp of the latest notice" do - error = Factory(:error) - error.last_notice_at.should be_nil - - notice1 = Factory(:notice, :error => error) - error.last_notice_at.should == notice1.created_at - - notice2 = Factory(:notice, :error => error) - error.last_notice_at.should == notice2.created_at - end - end - - context "#resolved?" do - it "should start out as unresolved" do - error = Error.new - error.should_not be_resolved - error.should be_unresolved - end - - it "should be able to be resolved" do - error = Factory(:error) - error.should_not be_resolved - error.resolve! - error.reload.should be_resolved - end - end - -end \ No newline at end of file diff --git a/spec/models/notice_spec.rb b/spec/models/notice_spec.rb index a1732d6..f29e5f2 100644 --- a/spec/models/notice_spec.rb +++ b/spec/models/notice_spec.rb @@ -25,16 +25,23 @@ describe Notice do context '#from_xml' do before do @xml = Rails.root.join('spec','fixtures','hoptoad_test_notice.xml').read + @project = Factory(:project, :api_key => 'ALLGLORYTOTHEHYPNOTOAD') + end + + it 'finds the correct project' do + @notice = Notice.from_xml(@xml) + @notice.err.project.should == @project end it 'finds the correct error for the notice' do - Error.should_receive(:for).with({ + Err.should_receive(:for).with({ + :project => @project, :klass => '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) + }).and_return(err = Err.new) err.notices.stub(:create) @notice = Notice.from_xml(@xml) end @@ -46,7 +53,7 @@ describe Notice do it 'assigns an error to the notice' do @notice = Notice.from_xml(@xml) - @notice.error.should be_an(Error) + @notice.err.should be_a(Err) end it 'captures the backtrace' do diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb new file mode 100644 index 0000000..ac5ac05 --- /dev/null +++ b/spec/models/project_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +describe Project do + + context 'validations' do + it 'requires a name' do + project = Factory.build(:project, :name => nil) + project.should_not be_valid + project.errors[:name].should include("can't be blank") + end + + it 'requires unique names' do + Factory(:project, :name => 'Hypnotoad') + project = Factory.build(:project, :name => 'Hypnotoad') + project.should_not be_valid + project.errors[:name].should include('is already taken') + end + + it 'requires unique api_keys' do + Factory(:project, :api_key => 'APIKEY') + project = Factory.build(:project, :api_key => 'APIKEY') + project.should_not be_valid + project.errors[:api_key].should include('is already taken') + end + end + + context 'being created' do + it 'generates a new api-key' do + project = Factory.build(:project) + project.api_key.should be_nil + project.save + project.api_key.should_not be_nil + end + + it 'generates a correct api-key' do + project = Factory(:project) + project.api_key.should match(/^[a-f0-9]{32}$/) + end + end + +end -- libgit2 0.21.2