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 @@
  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 35 \ No newline at end of file
... ...
app/models/error.rb
... ... @@ -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 0 \ No newline at end of file
app/models/notice.rb
... ... @@ -9,14 +9,16 @@ class Notice
9 9 field :request, :type => Hash
10 10 field :notifier, :type => Hash
11 11  
12   - embedded_in :error, :inverse_of => :notices
  12 + embedded_in :err, :inverse_of => :notices
13 13  
14 14 validates_presence_of :backtrace, :server_environment, :notifier
15 15  
16 16 def self.from_xml(hoptoad_xml)
17 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 22 :klass => hoptoad_notice['error']['class'],
21 23 :message => hoptoad_notice['error']['message'],
22 24 :component => hoptoad_notice['request']['component'],
... ...
app/models/project.rb 0 → 100644
... ... @@ -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 5 context 'POST[XML] notices#create' do
6 6 before do
7 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 9 @notice = Notice.from_xml(@xml)
9 10  
10 11 request.env['Content-type'] = 'text/xml'
... ...
spec/factories/err_factories.rb 0 → 100644
... ... @@ -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 26 \ No newline at end of file
... ...
spec/factories/error_factories.rb
... ... @@ -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 0 \ No newline at end of file
spec/factories/project_factories.rb 0 → 100644
... ... @@ -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 6 \ No newline at end of file
... ...
spec/models/err_spec.rb 0 → 100644
... ... @@ -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 78 \ No newline at end of file
... ...
spec/models/error_spec.rb
... ... @@ -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 0 \ No newline at end of file
spec/models/notice_spec.rb
... ... @@ -25,16 +25,23 @@ describe Notice do
25 25 context '#from_xml' do
26 26 before do
27 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 34 end
29 35  
30 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 39 :klass => 'HoptoadTestingException',
33 40 :message => 'HoptoadTestingException: Testing hoptoad via "rake hoptoad:test". If you can see this, it works.',
34 41 :component => 'application',
35 42 :action => 'verify',
36 43 :environment => 'development'
37   - }).and_return(err = Error.new)
  44 + }).and_return(err = Err.new)
38 45 err.notices.stub(:create)
39 46 @notice = Notice.from_xml(@xml)
40 47 end
... ... @@ -46,7 +53,7 @@ describe Notice do
46 53  
47 54 it 'assigns an error to the notice' do
48 55 @notice = Notice.from_xml(@xml)
49   - @notice.error.should be_an(Error)
  56 + @notice.err.should be_a(Err)
50 57 end
51 58  
52 59 it 'captures the backtrace' do
... ...
spec/models/project_spec.rb 0 → 100644
... ... @@ -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
... ...