Commit 8c60eb54e744ccc0170231436f56d1f0fef7a99e

Authored by Stephen Crosby
1 parent 8ef1dc2b
Exists in master and in 1 other branch production

fix specs related to mongoid5 upgrade

app/controllers/problems_controller.rb
... ... @@ -22,7 +22,7 @@ class ProblemsController < ApplicationController
22 22 }
23 23  
24 24 expose(:problem) {
25   - app.problems.find(params[:id])
  25 + ProblemDecorator.new app.problems.find(params[:id])
26 26 }
27 27  
28 28 expose(:all_errs) {
... ... @@ -50,7 +50,8 @@ class ProblemsController < ApplicationController
50 50 def index; end
51 51  
52 52 def show
53   - @notices = problem.notices.reverse_ordered.page(params[:notice]).per(1)
  53 + @notices = problem.object.notices.reverse_ordered
  54 + .page(params[:notice]).per(1)
54 55 @notice = @notices.first
55 56 @comment = Comment.new
56 57 end
... ...
app/decorators/backtrace_decorator.rb 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +class BacktraceDecorator < Draper::Decorator
  2 + def lines
  3 + @lines ||= object.lines.map { |line| BacktraceLineDecorator.new line }
  4 + end
  5 +end
... ...
app/decorators/backtrace_line_decorator.rb 0 → 100644
... ... @@ -0,0 +1,76 @@
  1 +class BacktraceLineDecorator < Draper::Decorator
  2 + EMPTY_STRING = ''.freeze
  3 +
  4 + def in_app?
  5 + object[:file].match Backtrace::IN_APP_PATH
  6 + end
  7 +
  8 + def number
  9 + object[:number]
  10 + end
  11 +
  12 + def file
  13 + object[:file]
  14 + end
  15 +
  16 + def method
  17 + object[:method]
  18 + end
  19 +
  20 + def file_relative
  21 + file.to_s.sub(Backtrace::IN_APP_PATH, EMPTY_STRING)
  22 + end
  23 +
  24 + def file_name
  25 + File.basename file
  26 + end
  27 +
  28 + def to_s
  29 + column = object.try(:[], :column)
  30 + "#{file_relative}:#{number}#{column.present? ? ":#{column}" : ''}"
  31 + end
  32 +
  33 + def link_to_source_file(app, &block)
  34 + text = h.capture_haml(&block)
  35 + link_to_in_app_source_file(app, text) || text
  36 + end
  37 +
  38 + private
  39 + def link_to_in_app_source_file(app, text)
  40 + return unless in_app?
  41 + if file_name =~ /\.js$/
  42 + link_to_hosted_javascript(app, text)
  43 + else
  44 + link_to_repo_source_file(app, text) ||
  45 + link_to_issue_tracker_file(app, text)
  46 + end
  47 + end
  48 +
  49 + def link_to_repo_source_file(app, text)
  50 + link_to_github(app, text) || link_to_bitbucket(app, text)
  51 + end
  52 +
  53 + def link_to_hosted_javascript(app, text)
  54 + if app.asset_host?
  55 + h.link_to(text, "#{app.asset_host}/#{file_relative}", :target => '_blank')
  56 + end
  57 + end
  58 +
  59 + def link_to_github(app, text = nil)
  60 + return unless app.github_repo?
  61 + href = "%s#L%s" % [app.github_url_to_file(decorated_path + file_name), number]
  62 + h.link_to(text || file_name, href, :target => '_blank')
  63 + end
  64 +
  65 + def link_to_bitbucket(app, text = nil)
  66 + return unless app.bitbucket_repo?
  67 + href = "%s#cl-%s" % [app.bitbucket_url_to_file(decorated_path + file_name), number]
  68 + h.link_to(text || file_name, href, :target => '_blank')
  69 + end
  70 +
  71 + def link_to_issue_tracker_file(app, text = nil)
  72 + return unless app.issue_tracker && app.issue_tracker.respond_to?(:url_to_file)
  73 + href = app.issue_tracker.url_to_file(file_relative, number)
  74 + h.link_to(text || file_name, href, :target => '_blank')
  75 + end
  76 +end
... ...
app/decorators/notice_decorator.rb 0 → 100644
... ... @@ -0,0 +1,4 @@
  1 +class NoticeDecorator < Draper::Decorator
  2 + decorates_association :backtrace
  3 + delegate_all
  4 +end
... ...
app/decorators/problem_decorator.rb 0 → 100644
... ... @@ -0,0 +1,4 @@
  1 +class ProblemDecorator < Draper::Decorator
  2 + decorates_association :notices
  3 + delegate_all
  4 +end
... ...
app/helpers/backtrace_line_helper.rb
1 1 module BacktraceLineHelper
2   - def link_to_source_file(line, &block)
  2 + def link_to_source_file(line, app, &block)
3 3 text = capture_haml(&block)
4   - link_to_in_app_source_file(line, text) || link_to_external_source_file(text)
  4 + link_to_in_app_source_file(line, app, text) || text
5 5 end
6 6  
7 7 private
8   - def link_to_in_app_source_file(line, text)
  8 + def link_to_in_app_source_file(line, app, text)
9 9 return unless line.in_app?
10 10 if line.file_name =~ /\.js$/
11   - link_to_hosted_javascript(line, text)
  11 + link_to_hosted_javascript(line, app, text)
12 12 else
13   - link_to_repo_source_file(line, text) ||
14   - link_to_issue_tracker_file(line, text)
  13 + link_to_repo_source_file(line, app, text) ||
  14 + link_to_issue_tracker_file(line, app, text)
15 15 end
16 16 end
17 17  
18   - def link_to_repo_source_file(line, text)
19   - link_to_github(line, text) || link_to_bitbucket(line, text)
  18 + def link_to_repo_source_file(line, app, text)
  19 + link_to_github(line, app, text) || link_to_bitbucket(line, app, text)
20 20 end
21 21  
22   - def link_to_hosted_javascript(line, text)
23   - if line.app.asset_host?
24   - link_to(text, "#{line.app.asset_host}/#{line.file_relative}", :target => '_blank')
  22 + def link_to_hosted_javascript(line, app, text)
  23 + if app.asset_host?
  24 + link_to(text, "#{app.asset_host}/#{line.file_relative}", :target => '_blank')
25 25 end
26 26 end
27 27  
28   - def link_to_external_source_file(text)
29   - text
30   - end
31   -
32   - def link_to_github(line, text = nil)
33   - return unless line.app.github_repo?
34   - href = "%s#L%s" % [line.app.github_url_to_file(line.decorated_path + line.file_name), line.number]
  28 + def link_to_github(line, app, text = nil)
  29 + return unless app.github_repo?
  30 + href = "%s#L%s" % [app.github_url_to_file(line.decorated_path + line.file_name), line.number]
35 31 link_to(text || line.file_name, href, :target => '_blank')
36 32 end
37 33  
38   - def link_to_bitbucket(line, text = nil)
39   - return unless line.app.bitbucket_repo?
40   - href = "%s#cl-%s" % [line.app.bitbucket_url_to_file(line.decorated_path + line.file_name), line.number]
  34 + def link_to_bitbucket(line, app, text = nil)
  35 + return unless app.bitbucket_repo?
  36 + href = "%s#cl-%s" % [app.bitbucket_url_to_file(line.decorated_path + line.file_name), line.number]
41 37 link_to(text || line.file_name, href, :target => '_blank')
42 38 end
43 39  
44   - def link_to_issue_tracker_file(line, text = nil)
45   - return unless line.app.issue_tracker && line.app.issue_tracker.respond_to?(:url_to_file)
46   - href = line.app.issue_tracker.url_to_file(line.file_relative, line.number)
  40 + def link_to_issue_tracker_file(line, app, text = nil)
  41 + return unless app.issue_tracker && app.issue_tracker.respond_to?(:url_to_file)
  42 + href = app.issue_tracker.url_to_file(line.file_relative, line.number)
47 43 link_to(text || line.file_name, href, :target => '_blank')
48 44 end
49 45  
... ...
app/mailers/mailer.rb
... ... @@ -13,11 +13,11 @@ class Mailer &lt; ActionMailer::Base
13 13 'Precedence' => 'bulk',
14 14 'Auto-Submitted' => 'auto-generated'
15 15  
16   - def err_notification(notice)
17   - @notice = notice
18   - @app = notice.app
  16 + def err_notification(error_report)
  17 + @notice = NoticeDecorator.new error_report.notice
  18 + @app = AppDecorator.new error_report.app
19 19  
20   - count = @notice.similar_count
  20 + count = error_report.problem.notices_count
21 21 count = count > 1 ? "(#{count}) " : ""
22 22  
23 23 errbit_headers 'App' => @app.name,
... ... @@ -30,7 +30,7 @@ class Mailer &lt; ActionMailer::Base
30 30  
31 31 def deploy_notification(deploy)
32 32 @deploy = deploy
33   - @app = deploy.app
  33 + @app = AppDecorator.new deploy.app
34 34  
35 35 errbit_headers 'App' => @app.name,
36 36 'Environment' => @deploy.environment,
... ... @@ -44,7 +44,7 @@ class Mailer &lt; ActionMailer::Base
44 44 def comment_notification(comment)
45 45 @comment = comment
46 46 @user = comment.user
47   - @problem = comment.err
  47 + @problem = ProblemDecorator.new comment.err
48 48 @notice = @problem.notices.first
49 49 @app = @problem.app
50 50  
... ...
app/models/backtrace.rb
... ... @@ -2,31 +2,23 @@ class Backtrace
2 2 include Mongoid::Document
3 3 include Mongoid::Timestamps
4 4  
5   - field :fingerprint
6   - index :fingerprint => 1
7   -
8   - has_many :notices
9   - has_one :notice
  5 + IN_APP_PATH = %r{^\[PROJECT_ROOT\](?!(\/vendor))/?}
10 6  
11   - embeds_many :lines, :class_name => "BacktraceLine"
12   -
13   - after_initialize :generate_fingerprint
  7 + field :fingerprint
  8 + field :lines
14 9  
15   - delegate :app, :to => :notice
  10 + index :fingerprint => 1
16 11  
17 12 def self.find_or_create(lines)
18 13 fingerprint = generate_fingerprint(lines)
19 14  
20   - where(fingerprint: generate_fingerprint(lines)).
21   - find_one_and_update(
22   - { '$setOnInsert' => { fingerprint: fingerprint, lines: lines } },
23   - { return_document: :after, upsert: true })
  15 + where(fingerprint: fingerprint).find_one_and_update(
  16 + { '$setOnInsert' => { fingerprint: fingerprint, lines: lines } },
  17 + { return_document: :after, upsert: true })
24 18 end
25 19  
26   - def raw=(raw)
27   - raw.compact.each do |raw_line|
28   - lines << BacktraceLine.new(BacktraceLineNormalizer.new(raw_line).call)
29   - end
  20 + def self.generate_fingerprint(lines)
  21 + Digest::SHA1.hexdigest(lines.map(&:to_s).join)
30 22 end
31 23  
32 24 private
... ...
app/models/backtrace_line.rb
... ... @@ -1,42 +0,0 @@
1   -class BacktraceLine
2   - include Mongoid::Document
3   - IN_APP_PATH = %r{^\[PROJECT_ROOT\](?!(\/vendor))/?}
4   - GEMS_PATH = %r{\[GEM_ROOT\]\/gems\/([^\/]+)}
5   -
6   - field :number, :type => Integer
7   - field :column, :type => Integer
8   - field :file
9   - field :method
10   -
11   - embedded_in :backtrace
12   -
13   - scope :in_app, ->{ where(:file => IN_APP_PATH) }
14   -
15   - delegate :app, :to => :backtrace
16   -
17   - def to_s
18   - "#{file_relative}:#{number}" << (column.present? ? ":#{column}" : "")
19   - end
20   -
21   - def in_app?
22   - !!(file =~ IN_APP_PATH)
23   - end
24   -
25   - def path
26   - File.dirname(file).gsub(/^\.$/, '') + "/"
27   - end
28   -
29   - def file_relative
30   - file.to_s.sub(IN_APP_PATH, '')
31   - end
32   -
33   - def file_name
34   - File.basename file
35   - end
36   -
37   - def decorated_path
38   - path.sub(BacktraceLine::IN_APP_PATH, '').
39   - sub(BacktraceLine::GEMS_PATH, "<strong>\\1</strong>")
40   - end
41   -
42   -end
app/models/backtrace_line_normalizer.rb
... ... @@ -1,32 +0,0 @@
1   -class BacktraceLineNormalizer
2   - def initialize(raw_line)
3   - @raw_line = raw_line || {}
4   - end
5   -
6   - def call
7   - @raw_line.merge 'file' => normalized_file, 'method' => normalized_method
8   - end
9   -
10   - private
11   - def normalized_file
12   - if @raw_line['file'].blank?
13   - "[unknown source]"
14   - else
15   - file = @raw_line['file'].to_s
16   - # Detect lines from gem
17   - file.gsub!(/\[PROJECT_ROOT\]\/.*\/ruby\/[0-9.]+\/gems/, '[GEM_ROOT]/gems')
18   - # Strip any query strings
19   - file.gsub!(/\?[^\?]*$/, '')
20   - @raw_line['file'] = file
21   - end
22   - end
23   -
24   - def normalized_method
25   - if @raw_line['method'].blank?
26   - "[unknown method]"
27   - else
28   - @raw_line['method'].to_s.gsub(/[0-9_]{10,}+/, "__FRAGMENT__")
29   - end
30   - end
31   -
32   -end
app/models/error_report.rb
... ... @@ -79,23 +79,20 @@ class ErrorReport
79 79 # Update problem cache with information about this notice
80 80 def cache_attributes_on_problem
81 81 @problem = Problem.cache_notice(@error.problem_id, @notice)
82   -
83   - # cache_notice returns the old problem, so the count is one higher
84   - @similar_count = @problem.notices_count + 1
85 82 end
86 83  
87 84 # Send email notification if needed
88 85 def email_notification
89 86 return false unless app.emailable?
90   - return false unless app.email_at_notices.include?(@similar_count)
91   - Mailer.err_notification(@notice).deliver
  87 + return false unless app.email_at_notices.include?(@problem.notices_count)
  88 + Mailer.err_notification(self).deliver
92 89 rescue => e
93 90 HoptoadNotifier.notify(e)
94 91 end
95 92  
96 93 def should_notify?
97 94 app.notification_service.notify_at_notices.include?(0) ||
98   - app.notification_service.notify_at_notices.include?(@similar_count)
  95 + app.notification_service.notify_at_notices.include?(@problem.notices_count)
99 96 end
100 97  
101 98 # Launch all notification define on the app associate to this notice
... ...
app/models/notice.rb
... ... @@ -103,10 +103,6 @@ class Notice
103 103 request['session'] || {}
104 104 end
105 105  
106   - def in_app_backtrace_lines
107   - backtrace_lines.in_app
108   - end
109   -
110 106 ##
111 107 # TODO: Move on decorator maybe
112 108 #
... ...
app/models/problem.rb
... ... @@ -76,7 +76,7 @@ class Problem
76 76 host_digest = Digest::MD5.hexdigest(notice.host)
77 77 user_agent_digest = Digest::MD5.hexdigest(notice.user_agent_string)
78 78  
79   - Problem.where('_id' => id).find_one_and_update(
  79 + Problem.where('_id' => id).find_one_and_update({
80 80 '$set' => {
81 81 'environment' => notice.environment_name,
82 82 'error_class' => notice.error_class,
... ... @@ -95,7 +95,7 @@ class Problem
95 95 "hosts.#{host_digest}.count" => 1,
96 96 "user_agents.#{user_agent_digest}.count" => 1,
97 97 }
98   - )
  98 + }, return_document: :after)
99 99 end
100 100  
101 101 def uncache_notice(notice)
... ...
app/views/issue_trackers/issue.md.erb
... ... @@ -27,7 +27,7 @@
27 27  
28 28 ## Backtrace ##
29 29 ~~~
30   -<% notice.backtrace_lines.each do |line| %><%= line.number %>: <%= line.file_relative %> -> **<%= line.method %>**
  30 +<% notice.backtrace.lines.each do |line| %><%= line.number %>: <%= line.file_relative %> -> **<%= line.method %>**
31 31 <% end %>
32 32 ~~~
33 33  
... ...
app/views/issue_trackers/issue.txt.erb
... ... @@ -16,6 +16,6 @@ Env: &lt;%= pretty_hash notice.env_vars %&gt;
16 16  
17 17 Backtrace
18 18 ---------
19   -<% notice.backtrace_lines.each do |line| %><%= sprintf('%5d: %s **%s', line.number, line.file_relative, line.method) %>
  19 +<% notice.backtrace.lines.each do |line| %><%= sprintf('%5d: %s **%s', line.number, line.file_relative, line.method) %>
20 20 <% end %>
21 21 <% end %>
... ...
app/views/mailer/comment_notification.text.erb
... ... @@ -21,8 +21,8 @@ WHERE:
21 21  
22 22 <%= @notice.where %>
23 23  
24   -<% @notice.in_app_backtrace_lines.each do |line| %>
25   - <%= line %>
  24 +<% @notice.backtrace.lines.each do |line| %>
  25 + <% next unless line.in_app? %><%= line %>
26 26 <% end %>
27 27  
28 28  
... ...
app/views/mailer/err_notification.html.haml
... ... @@ -27,9 +27,10 @@
27 27 %p.heading WHERE:
28 28 %p.monospace
29 29 = @notice.where
30   - - @notice.in_app_backtrace_lines.each do |line|
  30 + - @notice.backtrace.lines.each do |line|
  31 + - next unless line.in_app?
31 32 %p.backtrace
32   - = link_to_source_file(line) do
  33 + = line.link_to_source_file(@app) do
33 34 = line.to_s
34 35 %br
35 36 - if @notice.app_version.present?
... ... @@ -59,11 +60,11 @@
59 60 %td(style="text-align: right; padding-right: 10px; color: #6a6a6a;")= key.to_s.titleize + ":"
60 61 %td= auto_link(value.to_s)
61 62 %br
62   - - if @notice.backtrace_lines.any?
  63 + - if @notice.backtrace.lines.any?
63 64 %br
64 65 %p.heading FULL BACKTRACE:
65   - - @notice.backtrace_lines.each do |line|
  66 + - @notice.backtrace.lines.each do |line|
66 67 %p.backtrace
67   - = link_to_source_file(line) do
  68 + = link_to_source_file(line, @app) do
68 69 = line.to_s
69 70 %br
... ...
app/views/mailer/err_notification.text.erb
... ... @@ -14,7 +14,8 @@ WHERE:
14 14  
15 15 <%= @notice.where %>
16 16  
17   -<% @notice.in_app_backtrace_lines.each do |line| %>
  17 +<% @notice.backtrace.lines.each do |line| %>
  18 + <% next unless line.in_app? %>
18 19 <%= line %>
19 20 <% end %>
20 21  
... ... @@ -51,7 +52,7 @@ USER:
51 52  
52 53 BACKTRACE:
53 54  
54   -<% @notice.backtrace_lines.each do |line| %>
  55 +<% @notice.backtrace.lines.each do |line| %>
55 56 <%= line %>
56 57 <% end %>
57 58  
... ...
app/views/notices/_backtrace_line.html.haml
1 1 %tr{:class => defined?(row_class) && row_class}
2 2 %td.line{:class => line.in_app? && 'in-app' }
3   - = link_to_source_file(line) do
  3 + = line.link_to_source_file(app) do
4 4 %span.path>= raw line.decorated_path
5 5 %span.file>= line.file_name
6 6 - if line.number.present?
... ...
spec/acceptance/app_regenerate_api_key_spec.rb
... ... @@ -52,8 +52,8 @@ feature &quot;Create an application&quot; do
52 52 fill_in 'app_name', :with => 'My new app'
53 53 click_on I18n.t('apps.new.add_app')
54 54 page.has_content?(I18n.t('controllers.apps.flash.create.success'))
55   - expect(App.where(:name => 'My new app').count).to eql 1
56   - expect(App.where(:name => 'My new app 2').count).to eql 0
  55 + expect(App.where(:name => 'My new app').count).to eq 1
  56 + expect(App.where(:name => 'My new app 2').count).to eq 0
57 57  
58 58  
59 59 click_on I18n.t('shared.navigation.apps')
... ... @@ -62,8 +62,8 @@ feature &quot;Create an application&quot; do
62 62 fill_in 'app_name', :with => 'My new app 2'
63 63 click_on I18n.t('apps.edit.update')
64 64 page.has_content?(I18n.t('controllers.apps.flash.update.success'))
65   - expect(App.where(:name => 'My new app').count).to eql 0
66   - expect(App.where(:name => 'My new app 2').count).to eql 1
  65 + expect(App.where(:name => 'My new app').count).to eq 0
  66 + expect(App.where(:name => 'My new app 2').count).to eq 1
67 67  
68 68 end
69 69  
... ...
spec/controllers/problems_controller_spec.rb
... ... @@ -145,8 +145,6 @@ describe ProblemsController, type: &#39;controller&#39; do
145 145 end
146 146  
147 147 describe "GET /apps/:app_id/problems/:id" do
148   - #render_views
149   -
150 148 context 'when logged in as an admin' do
151 149 before do
152 150 sign_in admin
... ... @@ -250,8 +248,8 @@ describe ProblemsController, type: &#39;controller&#39; do
250 248 before { sign_in admin }
251 249  
252 250 context "when app has a issue tracker" do
253   - let(:notice) { Fabricate :notice }
254   - let(:problem) { notice.problem }
  251 + let(:notice) { NoticeDecorator.new(Fabricate :notice) }
  252 + let(:problem) { ProblemDecorator.new(notice.problem) }
255 253 let(:issue_tracker) do
256 254 Fabricate(:issue_tracker).tap do |t|
257 255 t.instance_variable_set(:@tracker, ErrbitPlugin::MockIssueTracker.new(t.options))
... ...
spec/fabricators/err_fabricator.rb
... ... @@ -18,12 +18,11 @@ Fabricator :notice do
18 18 end
19 19  
20 20 Fabricator :backtrace do
21   - lines(:count => 99) { Fabricate.build(:backtrace_line) }
22   -end
23   -
24   -Fabricator :backtrace_line do
25   - number { rand(999) }
26   - file { "/path/to/file/#{SecureRandom.hex(4)}.rb" }
27   - method(:method) { ActiveSupport.methods.shuffle.first }
  21 + lines(:count => 99) do
  22 + {
  23 + number: rand(999),
  24 + file: "/path/to/file/#{SecureRandom.hex(4)}.rb",
  25 + method: ActiveSupport.methods.shuffle.first
  26 + }
  27 + end
28 28 end
29   -
... ...
spec/fabricators/problem_fabricator.rb
... ... @@ -23,8 +23,7 @@ end
23 23  
24 24 Fabricator(:problem_resolved, :from => :problem) do
25 25 after_create do |pr|
26   - Fabricate(:notice,
27   - :err => Fabricate(:err, :problem => pr))
  26 + Fabricate(:notice, :err => Fabricate(:err, :problem => pr))
28 27 pr.resolve!
29 28 end
30 29 end
... ...
spec/mailers/mailer_spec.rb
1 1 shared_examples "a notification email" do
2 2 it "should have X-Mailer header" do
3   - expect(@email).to have_header('X-Mailer', 'Errbit')
  3 + expect(email).to have_header('X-Mailer', 'Errbit')
4 4 end
5 5  
6 6 it "should have X-Errbit-Host header" do
7   - expect(@email).to have_header('X-Errbit-Host', Errbit::Config.host)
  7 + expect(email).to have_header('X-Errbit-Host', Errbit::Config.host)
8 8 end
9 9  
10 10 it "should have Precedence header" do
11   - expect(@email).to have_header('Precedence', 'bulk')
  11 + expect(email).to have_header('Precedence', 'bulk')
12 12 end
13 13  
14 14 it "should have Auto-Submitted header" do
15   - expect(@email).to have_header('Auto-Submitted', 'auto-generated')
  15 + expect(email).to have_header('Auto-Submitted', 'auto-generated')
16 16 end
17 17  
18 18 it "should have X-Auto-Response-Suppress header" do
19 19 # http://msdn.microsoft.com/en-us/library/ee219609(v=EXCHG.80).aspx
20   - expect(@email).to have_header('X-Auto-Response-Suppress', 'OOF, AutoReply')
  20 + expect(email).to have_header('X-Auto-Response-Suppress', 'OOF, AutoReply')
21 21 end
22 22  
23 23 it "should send the email" do
  24 + email
24 25 expect(ActionMailer::Base.deliveries.size).to eq 1
25 26 end
26 27 end
... ... @@ -30,44 +31,63 @@ describe Mailer do
30 31 include EmailSpec::Helpers
31 32 include EmailSpec::Matchers
32 33  
33   - let(:notice) { Fabricate(:notice, :message => "class < ActionController::Base") }
34   - let!(:user) { Fabricate(:admin) }
  34 + let(:notice) do
  35 + n = Fabricate(:notice, message: "class < ActionController::Base")
  36 + n.backtrace.lines.last[:file] = '[PROJECT_ROOT]/path/to/file.js'
  37 + # notice.backtrace.update_attributes(lines: lines)
  38 + n
  39 + end
35 40  
36   - before do
37   - ActionMailer::Base.deliveries = []
38   - notice.backtrace.lines.last.update_attributes(:file => "[PROJECT_ROOT]/path/to/file.js")
39   - notice.app.update_attributes(
  41 + let(:app) do
  42 + a = notice.app
  43 + a.update_attributes(
40 44 :asset_host => "http://example.com",
41 45 :notify_all_users => true
42 46 )
43   - notice.problem.update_attributes :notices_count => 3
44   -
45   - @email = Mailer.err_notification(notice).deliver
  47 + a
  48 + end
  49 + let(:problem) do
  50 + p = notice.problem
  51 + p.notices_count = 3
  52 + p
  53 + end
  54 + let!(:user) { Fabricate(:admin) }
  55 + let(:error_report) do
  56 + instance_double(
  57 + 'ErrorReport',
  58 + notice: notice,
  59 + app: app,
  60 + problem: problem
  61 + )
  62 + end
  63 + let(:email) do
  64 + Mailer.err_notification(error_report).deliver
46 65 end
47 66  
48   - it_should_behave_like "a notification email"
  67 + before { email }
49 68  
  69 + it_should_behave_like "a notification email"
50 70  
51 71 it "should html-escape the notice's message for the html part" do
52   - expect(@email).to have_body_text("class &lt; ActionController::Base")
  72 + expect(email).to have_body_text("class &lt; ActionController::Base")
53 73 end
54 74  
55 75 it "should have inline css" do
56   - expect(@email).to have_body_text('<p class="backtrace" style="')
  76 + expect(email).to have_body_text('<p class="backtrace" style="')
57 77 end
58 78  
59 79 it "should have links to source files" do
60   - expect(@email).to have_body_text('<a href="http://example.com/path/to/file.js" target="_blank">path/to/file.js')
  80 + expect(email).to have_body_text('<a href="http://example.com/path/to/file.js" target="_blank">path/to/file.js')
61 81 end
62 82  
63 83 it "should have the error count in the subject" do
64   - expect(@email.subject).to match( /^\(3\) / )
  84 + expect(email.subject).to match( /^\(3\) / )
65 85 end
66 86  
67 87 context 'with a very long message' do
68 88 let(:notice) { Fabricate(:notice, :message => 6.times.collect{|a| "0123456789" }.join('')) }
69 89 it "should truncate the long message" do
70   - expect(@email.subject).to match( / \d{47}\.{3}$/ )
  90 + expect(email.subject).to match( / \d{47}\.{3}$/ )
71 91 end
72 92 end
73 93 end
... ...
spec/models/backtrace_line_spec.rb
... ... @@ -1,10 +0,0 @@
1   -describe BacktraceLine, type: 'model' do
2   - subject { described_class.new(raw_line) }
3   -
4   - describe "root at the start of decorated filename" do
5   - let(:raw_line) { { 'number' => rand(999), 'file' => '[PROJECT_ROOT]/app/controllers/pages_controller.rb', 'method' => ActiveSupport.methods.shuffle.first.to_s } }
6   - it "should leave leading root symbol in filepath" do
7   - expect(subject.decorated_path).to eq 'app/controllers/'
8   - end
9   - end
10   -end
spec/models/backtrace_spec.rb
1 1 describe Backtrace, type: 'model' do
2   - subject { described_class.new }
3   -
4   - its(:fingerprint) { should be_present }
5   -
6   - describe "#similar" do
7   - context "no similar backtrace" do
8   - its(:similar) { should be_nil }
  2 + describe '.find_or_create' do
  3 + let(:lines) do
  4 + [
  5 + { 'number' => '123', 'file' => '/some/path/to.rb', 'method' => 'abc' },
  6 + { 'number' => '345', 'file' => '/path/to.rb', 'method' => 'dowhat' }
  7 + ]
9 8 end
  9 + let(:fingerprint) { Backtrace.generate_fingerprint(lines) }
10 10  
11   - context "similar backtrace exist" do
12   - let!(:similar_backtrace) {
13   - b = Fabricate(:backtrace)
14   - b.fingerprint = fingerprint
15   - b.save!
16   - b
17   - }
18   - let(:fingerprint) { "fingerprint" }
19   -
20   - before { allow(subject).to receive(:fingerprint).and_return(fingerprint) }
21   -
22   - its(:similar) { should == similar_backtrace }
23   - end
24   - end
25   -
26   - describe "find_or_create" do
27   - subject { described_class.find_or_create(attributes) }
28   - let(:attributes) { double :attributes }
29   - let(:backtrace) { double :backtrace }
30   -
31   - before { allow(described_class).to receive(:new).and_return(backtrace) }
32   -
33   - context "no similar backtrace" do
34   - before { allow(backtrace).to receive(:similar).and_return(nil) }
35   - it "create new backtrace" do
36   - expect(described_class).to receive(:create).with(attributes)
  11 + it 'create new backtrace' do
  12 + backtrace = described_class.find_or_create(lines)
37 13  
38   - described_class.find_or_create(attributes)
39   - end
  14 + expect(backtrace.lines).to eq(lines)
  15 + expect(backtrace.fingerprint).to eq(fingerprint)
40 16 end
41 17  
42   - context "similar backtrace exist" do
43   - let(:similar_backtrace) { double :similar_backtrace }
44   - before { allow(backtrace).to receive(:similar).and_return(similar_backtrace) }
  18 + it 'creates one backtrace for two identical ones' do
  19 + described_class.find_or_create(lines)
  20 + described_class.find_or_create(lines)
45 21  
46   - it { should == similar_backtrace }
  22 + expect(Backtrace.where(fingerprint: fingerprint).count).to eq(1)
47 23 end
48 24 end
49 25 end
... ...
spec/models/error_report_spec.rb
... ... @@ -21,9 +21,7 @@ describe ErrorReport do
21 21 Rails.root.join('spec','fixtures','hoptoad_test_notice.xml').read
22 22 }
23 23  
24   - let(:error_report) {
25   - ErrorReport.new(xml)
26   - }
  24 + let(:error_report) { ErrorReport.new(xml) }
27 25  
28 26 let!(:app) {
29 27 Fabricate(
... ... @@ -210,14 +208,13 @@ describe ErrorReport do
210 208 end
211 209  
212 210 it 'find the correct err for the notice' do
213   - err = Fabricate(:err, :problem => Fabricate(:problem, :resolved => true))
214   -
215   - allow(error_report).to receive(:fingerprint).and_return(err.fingerprint)
  211 + error_report.generate_notice!
  212 + error_report.problem.resolve!
216 213  
217 214 expect {
218   - error_report.generate_notice!
  215 + ErrorReport.new(xml).generate_notice!
219 216 }.to change {
220   - error_report.error.resolved?
  217 + error_report.problem.reload.resolved?
221 218 }.from(true).to(false)
222 219 end
223 220  
... ... @@ -227,6 +224,7 @@ describe ErrorReport do
227 224 app.watchers.build(:email => 'foo@example.com')
228 225 app.save
229 226 end
  227 +
230 228 it 'send email' do
231 229 notice = error_report.generate_notice!
232 230 email = ActionMailer::Base.deliveries.last
... ...
spec/models/fingerprint/md5_spec.rb
1 1 describe Fingerprint::MD5, type: 'model' do
2 2 context 'being created' do
3 3 let(:backtrace) do
4   - Backtrace.create(:raw => [
  4 + Backtrace.find_or_create([
5 5 {
6 6 "number"=>"17",
7 7 "file"=>"[GEM_ROOT]/gems/activesupport/lib/active_support/callbacks.rb",
... ... @@ -14,10 +14,9 @@ describe Fingerprint::MD5, type: &#39;model&#39; do
14 14  
15 15 context "with same backtrace" do
16 16 let(:backtrace_2) do
17   - backtrace
18   - backtrace.lines.last.method = '_run__FRAGMENT__process_action__FRAGMENT__callbacks'
19   - backtrace.save
20   - backtrace
  17 + new_lines = backtrace.lines.dup
  18 + new_lines.last[:method] = '_run__FRAGMENT__process_action__FRAGMENT__callbacks'
  19 + Backtrace.find_or_create(new_lines)
21 20 end
22 21  
23 22 it "normalizes the fingerprint of generated methods" do
... ... @@ -27,10 +26,9 @@ describe Fingerprint::MD5, type: &#39;model&#39; do
27 26  
28 27 context "with same backtrace where FRAGMENT has not been extracted" do
29 28 let(:backtrace_2) do
30   - backtrace
31   - backtrace.lines.last.method = '_run__998857585768765__process_action__1231231312321313__callbacks'
32   - backtrace.save
33   - backtrace
  29 + new_lines = backtrace.lines.dup
  30 + new_lines.last[:method] = '_run__998857585768765__process_action__1231231312321313__callbacks'
  31 + Backtrace.find_or_create(new_lines)
34 32 end
35 33  
36 34 it "normalizes the fingerprint of generated methods" do
... ...
spec/models/fingerprint/sha1_spec.rb
1 1 describe Fingerprint::Sha1, type: 'model' do
2 2 context '#generate' do
3 3 let(:backtrace) {
4   - Backtrace.create(:raw => [
  4 + Backtrace.find_or_create([
5 5 {"number"=>"425", "file"=>"[GEM_ROOT]/gems/activesupport-3.0.0.rc/lib/active_support/callbacks.rb", "method"=>"_run__2115867319__process_action__262109504__callbacks"},
6 6 {"number"=>"404", "file"=>"[GEM_ROOT]/gems/activesupport-3.0.0.rc/lib/active_support/callbacks.rb", "method"=>"send"},
7 7 {"number"=>"404", "file"=>"[GEM_ROOT]/gems/activesupport-3.0.0.rc/lib/active_support/callbacks.rb", "method"=>"_run_process_action_callbacks"}
... ... @@ -19,11 +19,9 @@ describe Fingerprint::Sha1, type: &#39;model&#39; do
19 19  
20 20 context "with different backtrace with only last line change" do
21 21 let(:backtrace_2) {
22   - backtrace
23   - backtrace.lines.last.number = 401
24   - backtrace.send(:generate_fingerprint)
25   - backtrace.save
26   - backtrace
  22 + new_lines = backtrace.lines.dup
  23 + new_lines.last[:number] = 401
  24 + Backtrace.find_or_create backtrace.lines
27 25 }
28 26 it 'should not same fingerprint' do
29 27 expect(
... ...
spec/spec_helper.rb
1 1 # This file is copied to ~/spec when you run 'ruby script/generate rspec'
2 2 # from the project root directory.
3 3 ENV["RAILS_ENV"] = 'test'
4   -ENV["ERRBIT_LOG_LEVEL"] = 'error'
  4 +ENV["ERRBIT_LOG_LEVEL"] = 'info'
5 5  
6 6 if ENV['COVERAGE']
7 7 require 'coveralls'
... ...
spec/views/issue_trackers/issue.md.erb_spec.rb
... ... @@ -6,7 +6,7 @@ describe &quot;issue_trackers/issue.md.erb&quot;, type: &#39;view&#39; do
6 6 }
7 7  
8 8 before do
9   - allow(view).to receive(:problem).and_return(problem)
  9 + allow(view).to receive(:problem).and_return(ProblemDecorator.new(problem))
10 10 end
11 11  
12 12 it "has the problem url" do
... ...
spec/views/issue_trackers/issue.txt.erb_spec.rb
... ... @@ -6,7 +6,8 @@ describe &quot;issue_trackers/issue.txt.erb&quot;, type: &#39;view&#39; do
6 6 }
7 7  
8 8 before do
9   - allow(view).to receive(:problem).and_return(problem)
  9 + allow(view).to receive(:problem).and_return(
  10 + ProblemDecorator.new(problem))
10 11 end
11 12  
12 13 it "has the problem url" do
... ...