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,7 +22,7 @@ class ProblemsController < ApplicationController
22 } 22 }
23 23
24 expose(:problem) { 24 expose(:problem) {
25 - app.problems.find(params[:id]) 25 + ProblemDecorator.new app.problems.find(params[:id])
26 } 26 }
27 27
28 expose(:all_errs) { 28 expose(:all_errs) {
@@ -50,7 +50,8 @@ class ProblemsController < ApplicationController @@ -50,7 +50,8 @@ class ProblemsController < ApplicationController
50 def index; end 50 def index; end
51 51
52 def show 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 @notice = @notices.first 55 @notice = @notices.first
55 @comment = Comment.new 56 @comment = Comment.new
56 end 57 end
app/decorators/backtrace_decorator.rb 0 → 100644
@@ -0,0 +1,5 @@ @@ -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 @@ @@ -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 @@ @@ -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 @@ @@ -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 module BacktraceLineHelper 1 module BacktraceLineHelper
2 - def link_to_source_file(line, &block) 2 + def link_to_source_file(line, app, &block)
3 text = capture_haml(&block) 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 end 5 end
6 6
7 private 7 private
8 - def link_to_in_app_source_file(line, text) 8 + def link_to_in_app_source_file(line, app, text)
9 return unless line.in_app? 9 return unless line.in_app?
10 if line.file_name =~ /\.js$/ 10 if line.file_name =~ /\.js$/
11 - link_to_hosted_javascript(line, text) 11 + link_to_hosted_javascript(line, app, text)
12 else 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 end 15 end
16 end 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 end 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 end 25 end
26 end 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 link_to(text || line.file_name, href, :target => '_blank') 31 link_to(text || line.file_name, href, :target => '_blank')
36 end 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 link_to(text || line.file_name, href, :target => '_blank') 37 link_to(text || line.file_name, href, :target => '_blank')
42 end 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 link_to(text || line.file_name, href, :target => '_blank') 43 link_to(text || line.file_name, href, :target => '_blank')
48 end 44 end
49 45
app/mailers/mailer.rb
@@ -13,11 +13,11 @@ class Mailer &lt; ActionMailer::Base @@ -13,11 +13,11 @@ class Mailer &lt; ActionMailer::Base
13 'Precedence' => 'bulk', 13 'Precedence' => 'bulk',
14 'Auto-Submitted' => 'auto-generated' 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 count = count > 1 ? "(#{count}) " : "" 21 count = count > 1 ? "(#{count}) " : ""
22 22
23 errbit_headers 'App' => @app.name, 23 errbit_headers 'App' => @app.name,
@@ -30,7 +30,7 @@ class Mailer &lt; ActionMailer::Base @@ -30,7 +30,7 @@ class Mailer &lt; ActionMailer::Base
30 30
31 def deploy_notification(deploy) 31 def deploy_notification(deploy)
32 @deploy = deploy 32 @deploy = deploy
33 - @app = deploy.app 33 + @app = AppDecorator.new deploy.app
34 34
35 errbit_headers 'App' => @app.name, 35 errbit_headers 'App' => @app.name,
36 'Environment' => @deploy.environment, 36 'Environment' => @deploy.environment,
@@ -44,7 +44,7 @@ class Mailer &lt; ActionMailer::Base @@ -44,7 +44,7 @@ class Mailer &lt; ActionMailer::Base
44 def comment_notification(comment) 44 def comment_notification(comment)
45 @comment = comment 45 @comment = comment
46 @user = comment.user 46 @user = comment.user
47 - @problem = comment.err 47 + @problem = ProblemDecorator.new comment.err
48 @notice = @problem.notices.first 48 @notice = @problem.notices.first
49 @app = @problem.app 49 @app = @problem.app
50 50
app/models/backtrace.rb
@@ -2,31 +2,23 @@ class Backtrace @@ -2,31 +2,23 @@ class Backtrace
2 include Mongoid::Document 2 include Mongoid::Document
3 include Mongoid::Timestamps 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 def self.find_or_create(lines) 12 def self.find_or_create(lines)
18 fingerprint = generate_fingerprint(lines) 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 end 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 end 22 end
31 23
32 private 24 private
app/models/backtrace_line.rb
@@ -1,42 +0,0 @@ @@ -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,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,23 +79,20 @@ class ErrorReport
79 # Update problem cache with information about this notice 79 # Update problem cache with information about this notice
80 def cache_attributes_on_problem 80 def cache_attributes_on_problem
81 @problem = Problem.cache_notice(@error.problem_id, @notice) 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 end 82 end
86 83
87 # Send email notification if needed 84 # Send email notification if needed
88 def email_notification 85 def email_notification
89 return false unless app.emailable? 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 rescue => e 89 rescue => e
93 HoptoadNotifier.notify(e) 90 HoptoadNotifier.notify(e)
94 end 91 end
95 92
96 def should_notify? 93 def should_notify?
97 app.notification_service.notify_at_notices.include?(0) || 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 end 96 end
100 97
101 # Launch all notification define on the app associate to this notice 98 # Launch all notification define on the app associate to this notice
app/models/notice.rb
@@ -103,10 +103,6 @@ class Notice @@ -103,10 +103,6 @@ class Notice
103 request['session'] || {} 103 request['session'] || {}
104 end 104 end
105 105
106 - def in_app_backtrace_lines  
107 - backtrace_lines.in_app  
108 - end  
109 -  
110 ## 106 ##
111 # TODO: Move on decorator maybe 107 # TODO: Move on decorator maybe
112 # 108 #
app/models/problem.rb
@@ -76,7 +76,7 @@ class Problem @@ -76,7 +76,7 @@ class Problem
76 host_digest = Digest::MD5.hexdigest(notice.host) 76 host_digest = Digest::MD5.hexdigest(notice.host)
77 user_agent_digest = Digest::MD5.hexdigest(notice.user_agent_string) 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 '$set' => { 80 '$set' => {
81 'environment' => notice.environment_name, 81 'environment' => notice.environment_name,
82 'error_class' => notice.error_class, 82 'error_class' => notice.error_class,
@@ -95,7 +95,7 @@ class Problem @@ -95,7 +95,7 @@ class Problem
95 "hosts.#{host_digest}.count" => 1, 95 "hosts.#{host_digest}.count" => 1,
96 "user_agents.#{user_agent_digest}.count" => 1, 96 "user_agents.#{user_agent_digest}.count" => 1,
97 } 97 }
98 - ) 98 + }, return_document: :after)
99 end 99 end
100 100
101 def uncache_notice(notice) 101 def uncache_notice(notice)
app/views/issue_trackers/issue.md.erb
@@ -27,7 +27,7 @@ @@ -27,7 +27,7 @@
27 27
28 ## Backtrace ## 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 <% end %> 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,6 +16,6 @@ Env: &lt;%= pretty_hash notice.env_vars %&gt;
16 16
17 Backtrace 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 <% end %> 20 <% end %>
21 <% end %> 21 <% end %>
app/views/mailer/comment_notification.text.erb
@@ -21,8 +21,8 @@ WHERE: @@ -21,8 +21,8 @@ WHERE:
21 21
22 <%= @notice.where %> 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 <% end %> 26 <% end %>
27 27
28 28
app/views/mailer/err_notification.html.haml
@@ -27,9 +27,10 @@ @@ -27,9 +27,10 @@
27 %p.heading WHERE: 27 %p.heading WHERE:
28 %p.monospace 28 %p.monospace
29 = @notice.where 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 %p.backtrace 32 %p.backtrace
32 - = link_to_source_file(line) do 33 + = line.link_to_source_file(@app) do
33 = line.to_s 34 = line.to_s
34 %br 35 %br
35 - if @notice.app_version.present? 36 - if @notice.app_version.present?
@@ -59,11 +60,11 @@ @@ -59,11 +60,11 @@
59 %td(style="text-align: right; padding-right: 10px; color: #6a6a6a;")= key.to_s.titleize + ":" 60 %td(style="text-align: right; padding-right: 10px; color: #6a6a6a;")= key.to_s.titleize + ":"
60 %td= auto_link(value.to_s) 61 %td= auto_link(value.to_s)
61 %br 62 %br
62 - - if @notice.backtrace_lines.any? 63 + - if @notice.backtrace.lines.any?
63 %br 64 %br
64 %p.heading FULL BACKTRACE: 65 %p.heading FULL BACKTRACE:
65 - - @notice.backtrace_lines.each do |line| 66 + - @notice.backtrace.lines.each do |line|
66 %p.backtrace 67 %p.backtrace
67 - = link_to_source_file(line) do 68 + = link_to_source_file(line, @app) do
68 = line.to_s 69 = line.to_s
69 %br 70 %br
app/views/mailer/err_notification.text.erb
@@ -14,7 +14,8 @@ WHERE: @@ -14,7 +14,8 @@ WHERE:
14 14
15 <%= @notice.where %> 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 <%= line %> 19 <%= line %>
19 <% end %> 20 <% end %>
20 21
@@ -51,7 +52,7 @@ USER: @@ -51,7 +52,7 @@ USER:
51 52
52 BACKTRACE: 53 BACKTRACE:
53 54
54 -<% @notice.backtrace_lines.each do |line| %> 55 +<% @notice.backtrace.lines.each do |line| %>
55 <%= line %> 56 <%= line %>
56 <% end %> 57 <% end %>
57 58
app/views/notices/_backtrace_line.html.haml
1 %tr{:class => defined?(row_class) && row_class} 1 %tr{:class => defined?(row_class) && row_class}
2 %td.line{:class => line.in_app? && 'in-app' } 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 %span.path>= raw line.decorated_path 4 %span.path>= raw line.decorated_path
5 %span.file>= line.file_name 5 %span.file>= line.file_name
6 - if line.number.present? 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,8 +52,8 @@ feature &quot;Create an application&quot; do
52 fill_in 'app_name', :with => 'My new app' 52 fill_in 'app_name', :with => 'My new app'
53 click_on I18n.t('apps.new.add_app') 53 click_on I18n.t('apps.new.add_app')
54 page.has_content?(I18n.t('controllers.apps.flash.create.success')) 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 click_on I18n.t('shared.navigation.apps') 59 click_on I18n.t('shared.navigation.apps')
@@ -62,8 +62,8 @@ feature &quot;Create an application&quot; do @@ -62,8 +62,8 @@ feature &quot;Create an application&quot; do
62 fill_in 'app_name', :with => 'My new app 2' 62 fill_in 'app_name', :with => 'My new app 2'
63 click_on I18n.t('apps.edit.update') 63 click_on I18n.t('apps.edit.update')
64 page.has_content?(I18n.t('controllers.apps.flash.update.success')) 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 end 68 end
69 69
spec/controllers/problems_controller_spec.rb
@@ -145,8 +145,6 @@ describe ProblemsController, type: &#39;controller&#39; do @@ -145,8 +145,6 @@ describe ProblemsController, type: &#39;controller&#39; do
145 end 145 end
146 146
147 describe "GET /apps/:app_id/problems/:id" do 147 describe "GET /apps/:app_id/problems/:id" do
148 - #render_views  
149 -  
150 context 'when logged in as an admin' do 148 context 'when logged in as an admin' do
151 before do 149 before do
152 sign_in admin 150 sign_in admin
@@ -250,8 +248,8 @@ describe ProblemsController, type: &#39;controller&#39; do @@ -250,8 +248,8 @@ describe ProblemsController, type: &#39;controller&#39; do
250 before { sign_in admin } 248 before { sign_in admin }
251 249
252 context "when app has a issue tracker" do 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 let(:issue_tracker) do 253 let(:issue_tracker) do
256 Fabricate(:issue_tracker).tap do |t| 254 Fabricate(:issue_tracker).tap do |t|
257 t.instance_variable_set(:@tracker, ErrbitPlugin::MockIssueTracker.new(t.options)) 255 t.instance_variable_set(:@tracker, ErrbitPlugin::MockIssueTracker.new(t.options))
spec/fabricators/err_fabricator.rb
@@ -18,12 +18,11 @@ Fabricator :notice do @@ -18,12 +18,11 @@ Fabricator :notice do
18 end 18 end
19 19
20 Fabricator :backtrace do 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 end 28 end
29 -  
spec/fabricators/problem_fabricator.rb
@@ -23,8 +23,7 @@ end @@ -23,8 +23,7 @@ end
23 23
24 Fabricator(:problem_resolved, :from => :problem) do 24 Fabricator(:problem_resolved, :from => :problem) do
25 after_create do |pr| 25 after_create do |pr|
26 - Fabricate(:notice,  
27 - :err => Fabricate(:err, :problem => pr)) 26 + Fabricate(:notice, :err => Fabricate(:err, :problem => pr))
28 pr.resolve! 27 pr.resolve!
29 end 28 end
30 end 29 end
spec/mailers/mailer_spec.rb
1 shared_examples "a notification email" do 1 shared_examples "a notification email" do
2 it "should have X-Mailer header" do 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 end 4 end
5 5
6 it "should have X-Errbit-Host header" do 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 end 8 end
9 9
10 it "should have Precedence header" do 10 it "should have Precedence header" do
11 - expect(@email).to have_header('Precedence', 'bulk') 11 + expect(email).to have_header('Precedence', 'bulk')
12 end 12 end
13 13
14 it "should have Auto-Submitted header" do 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 end 16 end
17 17
18 it "should have X-Auto-Response-Suppress header" do 18 it "should have X-Auto-Response-Suppress header" do
19 # http://msdn.microsoft.com/en-us/library/ee219609(v=EXCHG.80).aspx 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 end 21 end
22 22
23 it "should send the email" do 23 it "should send the email" do
  24 + email
24 expect(ActionMailer::Base.deliveries.size).to eq 1 25 expect(ActionMailer::Base.deliveries.size).to eq 1
25 end 26 end
26 end 27 end
@@ -30,44 +31,63 @@ describe Mailer do @@ -30,44 +31,63 @@ describe Mailer do
30 include EmailSpec::Helpers 31 include EmailSpec::Helpers
31 include EmailSpec::Matchers 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 :asset_host => "http://example.com", 44 :asset_host => "http://example.com",
41 :notify_all_users => true 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 end 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 it "should html-escape the notice's message for the html part" do 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 end 73 end
54 74
55 it "should have inline css" do 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 end 77 end
58 78
59 it "should have links to source files" do 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 end 81 end
62 82
63 it "should have the error count in the subject" do 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 end 85 end
66 86
67 context 'with a very long message' do 87 context 'with a very long message' do
68 let(:notice) { Fabricate(:notice, :message => 6.times.collect{|a| "0123456789" }.join('')) } 88 let(:notice) { Fabricate(:notice, :message => 6.times.collect{|a| "0123456789" }.join('')) }
69 it "should truncate the long message" do 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 end 91 end
72 end 92 end
73 end 93 end
spec/models/backtrace_line_spec.rb
@@ -1,10 +0,0 @@ @@ -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 describe Backtrace, type: 'model' do 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 end 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 end 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 end 23 end
48 end 24 end
49 end 25 end
spec/models/error_report_spec.rb
@@ -21,9 +21,7 @@ describe ErrorReport do @@ -21,9 +21,7 @@ describe ErrorReport do
21 Rails.root.join('spec','fixtures','hoptoad_test_notice.xml').read 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 let!(:app) { 26 let!(:app) {
29 Fabricate( 27 Fabricate(
@@ -210,14 +208,13 @@ describe ErrorReport do @@ -210,14 +208,13 @@ describe ErrorReport do
210 end 208 end
211 209
212 it 'find the correct err for the notice' do 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 expect { 214 expect {
218 - error_report.generate_notice! 215 + ErrorReport.new(xml).generate_notice!
219 }.to change { 216 }.to change {
220 - error_report.error.resolved? 217 + error_report.problem.reload.resolved?
221 }.from(true).to(false) 218 }.from(true).to(false)
222 end 219 end
223 220
@@ -227,6 +224,7 @@ describe ErrorReport do @@ -227,6 +224,7 @@ describe ErrorReport do
227 app.watchers.build(:email => 'foo@example.com') 224 app.watchers.build(:email => 'foo@example.com')
228 app.save 225 app.save
229 end 226 end
  227 +
230 it 'send email' do 228 it 'send email' do
231 notice = error_report.generate_notice! 229 notice = error_report.generate_notice!
232 email = ActionMailer::Base.deliveries.last 230 email = ActionMailer::Base.deliveries.last
spec/models/fingerprint/md5_spec.rb
1 describe Fingerprint::MD5, type: 'model' do 1 describe Fingerprint::MD5, type: 'model' do
2 context 'being created' do 2 context 'being created' do
3 let(:backtrace) do 3 let(:backtrace) do
4 - Backtrace.create(:raw => [ 4 + Backtrace.find_or_create([
5 { 5 {
6 "number"=>"17", 6 "number"=>"17",
7 "file"=>"[GEM_ROOT]/gems/activesupport/lib/active_support/callbacks.rb", 7 "file"=>"[GEM_ROOT]/gems/activesupport/lib/active_support/callbacks.rb",
@@ -14,10 +14,9 @@ describe Fingerprint::MD5, type: &#39;model&#39; do @@ -14,10 +14,9 @@ describe Fingerprint::MD5, type: &#39;model&#39; do
14 14
15 context "with same backtrace" do 15 context "with same backtrace" do
16 let(:backtrace_2) do 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 end 20 end
22 21
23 it "normalizes the fingerprint of generated methods" do 22 it "normalizes the fingerprint of generated methods" do
@@ -27,10 +26,9 @@ describe Fingerprint::MD5, type: &#39;model&#39; do @@ -27,10 +26,9 @@ describe Fingerprint::MD5, type: &#39;model&#39; do
27 26
28 context "with same backtrace where FRAGMENT has not been extracted" do 27 context "with same backtrace where FRAGMENT has not been extracted" do
29 let(:backtrace_2) do 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 end 32 end
35 33
36 it "normalizes the fingerprint of generated methods" do 34 it "normalizes the fingerprint of generated methods" do
spec/models/fingerprint/sha1_spec.rb
1 describe Fingerprint::Sha1, type: 'model' do 1 describe Fingerprint::Sha1, type: 'model' do
2 context '#generate' do 2 context '#generate' do
3 let(:backtrace) { 3 let(:backtrace) {
4 - Backtrace.create(:raw => [ 4 + Backtrace.find_or_create([
5 {"number"=>"425", "file"=>"[GEM_ROOT]/gems/activesupport-3.0.0.rc/lib/active_support/callbacks.rb", "method"=>"_run__2115867319__process_action__262109504__callbacks"}, 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 {"number"=>"404", "file"=>"[GEM_ROOT]/gems/activesupport-3.0.0.rc/lib/active_support/callbacks.rb", "method"=>"send"}, 6 {"number"=>"404", "file"=>"[GEM_ROOT]/gems/activesupport-3.0.0.rc/lib/active_support/callbacks.rb", "method"=>"send"},
7 {"number"=>"404", "file"=>"[GEM_ROOT]/gems/activesupport-3.0.0.rc/lib/active_support/callbacks.rb", "method"=>"_run_process_action_callbacks"} 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,11 +19,9 @@ describe Fingerprint::Sha1, type: &#39;model&#39; do
19 19
20 context "with different backtrace with only last line change" do 20 context "with different backtrace with only last line change" do
21 let(:backtrace_2) { 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 it 'should not same fingerprint' do 26 it 'should not same fingerprint' do
29 expect( 27 expect(
spec/spec_helper.rb
1 # This file is copied to ~/spec when you run 'ruby script/generate rspec' 1 # This file is copied to ~/spec when you run 'ruby script/generate rspec'
2 # from the project root directory. 2 # from the project root directory.
3 ENV["RAILS_ENV"] = 'test' 3 ENV["RAILS_ENV"] = 'test'
4 -ENV["ERRBIT_LOG_LEVEL"] = 'error' 4 +ENV["ERRBIT_LOG_LEVEL"] = 'info'
5 5
6 if ENV['COVERAGE'] 6 if ENV['COVERAGE']
7 require 'coveralls' 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,7 +6,7 @@ describe &quot;issue_trackers/issue.md.erb&quot;, type: &#39;view&#39; do
6 } 6 }
7 7
8 before do 8 before do
9 - allow(view).to receive(:problem).and_return(problem) 9 + allow(view).to receive(:problem).and_return(ProblemDecorator.new(problem))
10 end 10 end
11 11
12 it "has the problem url" do 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,7 +6,8 @@ describe &quot;issue_trackers/issue.txt.erb&quot;, type: &#39;view&#39; do
6 } 6 }
7 7
8 before do 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 end 11 end
11 12
12 it "has the problem url" do 13 it "has the problem url" do