Commit ad298154e42a9d18f785a152a24029dd6f52f8b5

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

Create a Project class with an api_key that Errs will belong to

app/models/err.rb 0 → 100644
@@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
  1 +class Err
  2 + include Mongoid::Document
  3 + include Mongoid::Timestamps
  4 +
  5 + field :klass
  6 + field :message
  7 + field :component
  8 + field :action
  9 + field :environment
  10 + field :resolved, :type => Boolean
  11 +
  12 + referenced_in :project
  13 + embeds_many :notices
  14 +
  15 + validates_presence_of :klass, :environment
  16 +
  17 + def self.for(attrs)
  18 + project = attrs.delete(:project)
  19 + project.errs.where(attrs).first || project.errs.create(attrs)
  20 + end
  21 +
  22 + def resolve!
  23 + self.update_attributes(:resolved => true)
  24 + end
  25 +
  26 + def unresolved?
  27 + !resolved?
  28 + end
  29 +
  30 + def last_notice_at
  31 + notices.last.try(:created_at)
  32 + end
  33 +
  34 +end
0 \ No newline at end of file 35 \ No newline at end of file
app/models/error.rb
@@ -1,32 +0,0 @@ @@ -1,32 +0,0 @@
1 -class Error  
2 - include Mongoid::Document  
3 - include Mongoid::Timestamps  
4 -  
5 - field :klass  
6 - field :message  
7 - field :component  
8 - field :action  
9 - field :environment  
10 - field :resolved, :type => Boolean  
11 -  
12 - embeds_many :notices  
13 -  
14 - validates_presence_of :klass, :environment  
15 -  
16 - def self.for(attrs)  
17 - self.where(attrs).first || create(attrs)  
18 - end  
19 -  
20 - def resolve!  
21 - self.update_attributes(:resolved => true)  
22 - end  
23 -  
24 - def unresolved?  
25 - !resolved?  
26 - end  
27 -  
28 - def last_notice_at  
29 - notices.last.try(:created_at)  
30 - end  
31 -  
32 -end  
33 \ No newline at end of file 0 \ No newline at end of file
app/models/notice.rb
@@ -9,14 +9,16 @@ class Notice @@ -9,14 +9,16 @@ class Notice
9 field :request, :type => Hash 9 field :request, :type => Hash
10 field :notifier, :type => Hash 10 field :notifier, :type => Hash
11 11
12 - embedded_in :error, :inverse_of => :notices 12 + embedded_in :err, :inverse_of => :notices
13 13
14 validates_presence_of :backtrace, :server_environment, :notifier 14 validates_presence_of :backtrace, :server_environment, :notifier
15 15
16 def self.from_xml(hoptoad_xml) 16 def self.from_xml(hoptoad_xml)
17 hoptoad_notice = Hoptoad::V2.parse_xml(hoptoad_xml) 17 hoptoad_notice = Hoptoad::V2.parse_xml(hoptoad_xml)
  18 + project = Project.find_by_api_key!(hoptoad_notice['api-key'])
18 19
19 - error = Error.for({ 20 + error = Err.for({
  21 + :project => project,
20 :klass => hoptoad_notice['error']['class'], 22 :klass => hoptoad_notice['error']['class'],
21 :message => hoptoad_notice['error']['message'], 23 :message => hoptoad_notice['error']['message'],
22 :component => hoptoad_notice['request']['component'], 24 :component => hoptoad_notice['request']['component'],
app/models/project.rb 0 → 100644
@@ -0,0 +1,25 @@ @@ -0,0 +1,25 @@
  1 +class Project
  2 + include Mongoid::Document
  3 + include Mongoid::Timestamps
  4 +
  5 + field :name, :type => String
  6 + field :api_key
  7 +
  8 + references_many :errs
  9 +
  10 + before_validation :generate_api_key, :on => :create
  11 +
  12 + validates_presence_of :name, :api_key
  13 + validates_uniqueness_of :name, :api_key, :allow_blank => true
  14 +
  15 + def self.find_by_api_key!(key)
  16 + where(:api_key => key).first || raise(Mongoid::Errors::DocumentNotFound.new(self,key))
  17 + end
  18 +
  19 + protected
  20 +
  21 + def generate_api_key
  22 + self.api_key ||= ActiveSupport::SecureRandom.hex
  23 + end
  24 +
  25 +end
spec/controllers/notices_controller_spec.rb
@@ -5,6 +5,7 @@ describe NoticesController do @@ -5,6 +5,7 @@ describe NoticesController do
5 context 'POST[XML] notices#create' do 5 context 'POST[XML] notices#create' do
6 before do 6 before do
7 @xml = Rails.root.join('spec','fixtures','hoptoad_test_notice.xml').read 7 @xml = Rails.root.join('spec','fixtures','hoptoad_test_notice.xml').read
  8 + Project.stub(:find_by_api_key!).and_return(Factory.build(:project))
8 @notice = Notice.from_xml(@xml) 9 @notice = Notice.from_xml(@xml)
9 10
10 request.env['Content-type'] = 'text/xml' 11 request.env['Content-type'] = 'text/xml'
spec/factories/err_factories.rb 0 → 100644
@@ -0,0 +1,25 @@ @@ -0,0 +1,25 @@
  1 +Factory.define :err do |e|
  2 + e.project {|p| p.association :project}
  3 + e.klass 'FooError'
  4 + e.message 'FooError: Too Much Bar'
  5 + e.component 'foo'
  6 + e.action 'bar'
  7 + e.environment 'production'
  8 +end
  9 +
  10 +Factory.define :notice do |n|
  11 + n.err {|e| e.association :err}
  12 + n.backtrace { random_backtrace }
  13 + n.server_environment 'server-environment' => 'production'
  14 + n.notifier 'name' => 'Notifier', 'version' => '1', 'url' => 'http://toad.com'
  15 +end
  16 +
  17 +def random_backtrace
  18 + backtrace = []
  19 + 99.times {|t| backtrace << {
  20 + 'number' => rand(999),
  21 + 'file' => "/path/to/file/#{ActiveSupport::SecureRandom.hex(4)}.rb",
  22 + 'method' => ActiveSupport.methods.shuffle.first
  23 + }}
  24 + backtrace
  25 +end
0 \ No newline at end of file 26 \ No newline at end of file
spec/factories/error_factories.rb
@@ -1,24 +0,0 @@ @@ -1,24 +0,0 @@
1 -Factory.define :error do |e|  
2 - e.klass 'FooError'  
3 - e.message 'FooError: Too Much Bar'  
4 - e.component 'foo'  
5 - e.action 'bar'  
6 - e.environment 'production'  
7 -end  
8 -  
9 -Factory.define :notice do |n|  
10 - n.error {|e| e.association :error}  
11 - n.backtrace { random_backtrace }  
12 - n.server_environment 'server-environment' => 'production'  
13 - n.notifier 'name' => 'Notifier', 'version' => '1', 'url' => 'http://toad.com'  
14 -end  
15 -  
16 -def random_backtrace  
17 - backtrace = []  
18 - 99.times {|t| backtrace << {  
19 - 'number' => rand(999),  
20 - 'file' => "/path/to/file/#{ActiveSupport::SecureRandom.hex(4)}.rb",  
21 - 'method' => ActiveSupport.methods.shuffle.first  
22 - }}  
23 - backtrace  
24 -end  
25 \ No newline at end of file 0 \ No newline at end of file
spec/factories/project_factories.rb 0 → 100644
@@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
  1 +Factory.sequence(:project_name) {|n| "Project ##{n}"}
  2 +
  3 +Factory.define(:project) do |p|
  4 + p.name { Factory.next :project_name }
  5 +end
0 \ No newline at end of file 6 \ No newline at end of file
spec/models/err_spec.rb 0 → 100644
@@ -0,0 +1,77 @@ @@ -0,0 +1,77 @@
  1 +require 'spec_helper'
  2 +
  3 +describe Err do
  4 +
  5 + context 'validations' do
  6 + it 'requires a klass' do
  7 + error = Factory.build(:err, :klass => nil)
  8 + error.should_not be_valid
  9 + error.errors[:klass].should include("can't be blank")
  10 + end
  11 +
  12 + it 'requires an environment' do
  13 + error = Factory.build(:err, :environment => nil)
  14 + error.should_not be_valid
  15 + error.errors[:environment].should include("can't be blank")
  16 + end
  17 + end
  18 +
  19 + context '#for' do
  20 + before do
  21 + @project = Factory(:project)
  22 + @conditions = {
  23 + :project => @project,
  24 + :klass => 'Whoops',
  25 + :message => 'Whoops: Oopsy Daisy',
  26 + :component => 'Foo',
  27 + :action => 'bar',
  28 + :environment => 'production'
  29 + }
  30 + end
  31 +
  32 + it 'returns the correct error if one already exists' do
  33 + existing = Err.create(@conditions)
  34 + Err.for(@conditions).should == existing
  35 + end
  36 +
  37 + it 'assigns the returned error to the given project' do
  38 + Err.for(@conditions).project.should == @project
  39 + end
  40 +
  41 + it 'creates a new error if a matching one does not already exist' do
  42 + Err.where(@conditions.except(:project)).exists?.should == false
  43 + lambda {
  44 + Err.for(@conditions)
  45 + }.should change(Err,:count).by(1)
  46 + end
  47 + end
  48 +
  49 + context '#last_notice_at' do
  50 + it "returns the created_at timestamp of the latest notice" do
  51 + error = Factory(:err)
  52 + error.last_notice_at.should be_nil
  53 +
  54 + notice1 = Factory(:notice, :err => error)
  55 + error.last_notice_at.should == notice1.created_at
  56 +
  57 + notice2 = Factory(:notice, :err => error)
  58 + error.last_notice_at.should == notice2.created_at
  59 + end
  60 + end
  61 +
  62 + context "#resolved?" do
  63 + it "should start out as unresolved" do
  64 + error = Err.new
  65 + error.should_not be_resolved
  66 + error.should be_unresolved
  67 + end
  68 +
  69 + it "should be able to be resolved" do
  70 + error = Factory(:err)
  71 + error.should_not be_resolved
  72 + error.resolve!
  73 + error.reload.should be_resolved
  74 + end
  75 + end
  76 +
  77 +end
0 \ No newline at end of file 78 \ No newline at end of file
spec/models/error_spec.rb
@@ -1,71 +0,0 @@ @@ -1,71 +0,0 @@
1 -require 'spec_helper'  
2 -  
3 -describe Error do  
4 -  
5 - context 'validations' do  
6 - it 'requires a klass' do  
7 - error = Factory.build(:error, :klass => nil)  
8 - error.should_not be_valid  
9 - error.errors[:klass].should include("can't be blank")  
10 - end  
11 -  
12 - it 'requires an environment' do  
13 - error = Factory.build(:error, :environment => nil)  
14 - error.should_not be_valid  
15 - error.errors[:environment].should include("can't be blank")  
16 - end  
17 - end  
18 -  
19 - context '#for' do  
20 - before do  
21 - @conditions = {  
22 - :klass => 'Whoops',  
23 - :message => 'Whoops: Oopsy Daisy',  
24 - :component => 'Foo',  
25 - :action => 'bar',  
26 - :environment => 'production'  
27 - }  
28 - end  
29 -  
30 - it 'returns the correct error if one already exists' do  
31 - existing = Error.create(@conditions)  
32 - Error.for(@conditions).should == existing  
33 - end  
34 -  
35 - it 'creates a new error if a matching one does not already exist' do  
36 - Error.where(@conditions).exists?.should == false  
37 - lambda {  
38 - Error.for(@conditions)  
39 - }.should change(Error,:count).by(1)  
40 - end  
41 - end  
42 -  
43 - context '#last_notice_at' do  
44 - it "returns the created_at timestamp of the latest notice" do  
45 - error = Factory(:error)  
46 - error.last_notice_at.should be_nil  
47 -  
48 - notice1 = Factory(:notice, :error => error)  
49 - error.last_notice_at.should == notice1.created_at  
50 -  
51 - notice2 = Factory(:notice, :error => error)  
52 - error.last_notice_at.should == notice2.created_at  
53 - end  
54 - end  
55 -  
56 - context "#resolved?" do  
57 - it "should start out as unresolved" do  
58 - error = Error.new  
59 - error.should_not be_resolved  
60 - error.should be_unresolved  
61 - end  
62 -  
63 - it "should be able to be resolved" do  
64 - error = Factory(:error)  
65 - error.should_not be_resolved  
66 - error.resolve!  
67 - error.reload.should be_resolved  
68 - end  
69 - end  
70 -  
71 -end  
72 \ No newline at end of file 0 \ No newline at end of file
spec/models/notice_spec.rb
@@ -25,16 +25,23 @@ describe Notice do @@ -25,16 +25,23 @@ describe Notice do
25 context '#from_xml' do 25 context '#from_xml' do
26 before do 26 before do
27 @xml = Rails.root.join('spec','fixtures','hoptoad_test_notice.xml').read 27 @xml = Rails.root.join('spec','fixtures','hoptoad_test_notice.xml').read
  28 + @project = Factory(:project, :api_key => 'ALLGLORYTOTHEHYPNOTOAD')
  29 + end
  30 +
  31 + it 'finds the correct project' do
  32 + @notice = Notice.from_xml(@xml)
  33 + @notice.err.project.should == @project
28 end 34 end
29 35
30 it 'finds the correct error for the notice' do 36 it 'finds the correct error for the notice' do
31 - Error.should_receive(:for).with({ 37 + Err.should_receive(:for).with({
  38 + :project => @project,
32 :klass => 'HoptoadTestingException', 39 :klass => 'HoptoadTestingException',
33 :message => 'HoptoadTestingException: Testing hoptoad via "rake hoptoad:test". If you can see this, it works.', 40 :message => 'HoptoadTestingException: Testing hoptoad via "rake hoptoad:test". If you can see this, it works.',
34 :component => 'application', 41 :component => 'application',
35 :action => 'verify', 42 :action => 'verify',
36 :environment => 'development' 43 :environment => 'development'
37 - }).and_return(err = Error.new) 44 + }).and_return(err = Err.new)
38 err.notices.stub(:create) 45 err.notices.stub(:create)
39 @notice = Notice.from_xml(@xml) 46 @notice = Notice.from_xml(@xml)
40 end 47 end
@@ -46,7 +53,7 @@ describe Notice do @@ -46,7 +53,7 @@ describe Notice do
46 53
47 it 'assigns an error to the notice' do 54 it 'assigns an error to the notice' do
48 @notice = Notice.from_xml(@xml) 55 @notice = Notice.from_xml(@xml)
49 - @notice.error.should be_an(Error) 56 + @notice.err.should be_a(Err)
50 end 57 end
51 58
52 it 'captures the backtrace' do 59 it 'captures the backtrace' do
spec/models/project_spec.rb 0 → 100644
@@ -0,0 +1,41 @@ @@ -0,0 +1,41 @@
  1 +require 'spec_helper'
  2 +
  3 +describe Project do
  4 +
  5 + context 'validations' do
  6 + it 'requires a name' do
  7 + project = Factory.build(:project, :name => nil)
  8 + project.should_not be_valid
  9 + project.errors[:name].should include("can't be blank")
  10 + end
  11 +
  12 + it 'requires unique names' do
  13 + Factory(:project, :name => 'Hypnotoad')
  14 + project = Factory.build(:project, :name => 'Hypnotoad')
  15 + project.should_not be_valid
  16 + project.errors[:name].should include('is already taken')
  17 + end
  18 +
  19 + it 'requires unique api_keys' do
  20 + Factory(:project, :api_key => 'APIKEY')
  21 + project = Factory.build(:project, :api_key => 'APIKEY')
  22 + project.should_not be_valid
  23 + project.errors[:api_key].should include('is already taken')
  24 + end
  25 + end
  26 +
  27 + context 'being created' do
  28 + it 'generates a new api-key' do
  29 + project = Factory.build(:project)
  30 + project.api_key.should be_nil
  31 + project.save
  32 + project.api_key.should_not be_nil
  33 + end
  34 +
  35 + it 'generates a correct api-key' do
  36 + project = Factory(:project)
  37 + project.api_key.should match(/^[a-f0-9]{32}$/)
  38 + end
  39 + end
  40 +
  41 +end