Commit ba17fba9c5abc4a3d73b2c0a5c4bdb5b45b36271

Authored by Nick Recobra
2 parents f707fd46 3960fb5d
Exists in master and in 1 other branch production

Merge branch 'feature/lighthouse'

@@ -6,6 +6,7 @@ gem 'mongoid', '~> 2.0.0.rc.7' @@ -6,6 +6,7 @@ gem 'mongoid', '~> 2.0.0.rc.7'
6 gem 'haml' 6 gem 'haml'
7 gem 'will_paginate' 7 gem 'will_paginate'
8 gem 'devise', '~> 1.1.8' 8 gem 'devise', '~> 1.1.8'
  9 +gem 'lighthouse-api'
9 10
10 platform :ruby do 11 platform :ruby do
11 gem 'bson_ext', '~> 1.2' 12 gem 'bson_ext', '~> 1.2'
@@ -13,6 +14,7 @@ end @@ -13,6 +14,7 @@ end
13 14
14 group :development, :test do 15 group :development, :test do
15 gem 'rspec-rails', '~> 2.5' 16 gem 'rspec-rails', '~> 2.5'
  17 + gem 'webmock', :require => false
16 end 18 end
17 19
18 group :test do 20 group :test do
@@ -28,11 +28,13 @@ GEM @@ -28,11 +28,13 @@ GEM
28 activemodel (= 3.0.5) 28 activemodel (= 3.0.5)
29 activesupport (= 3.0.5) 29 activesupport (= 3.0.5)
30 activesupport (3.0.5) 30 activesupport (3.0.5)
  31 + addressable (2.2.4)
31 arel (2.0.9) 32 arel (2.0.9)
32 bcrypt-ruby (2.1.4) 33 bcrypt-ruby (2.1.4)
33 bson (1.2.4) 34 bson (1.2.4)
34 bson_ext (1.2.4) 35 bson_ext (1.2.4)
35 builder (2.1.2) 36 builder (2.1.2)
  37 + crack (0.1.8)
36 database_cleaner (0.6.5) 38 database_cleaner (0.6.5)
37 devise (1.1.8) 39 devise (1.1.8)
38 bcrypt-ruby (~> 2.1.2) 40 bcrypt-ruby (~> 2.1.2)
@@ -46,6 +48,9 @@ GEM @@ -46,6 +48,9 @@ GEM
46 railties (>= 3.0.0) 48 railties (>= 3.0.0)
47 haml (3.0.25) 49 haml (3.0.25)
48 i18n (0.5.0) 50 i18n (0.5.0)
  51 + lighthouse-api (2.0)
  52 + activeresource (>= 3.0.0)
  53 + activesupport (>= 3.0.0)
49 mail (2.2.15) 54 mail (2.2.15)
50 activesupport (>= 2.3.6) 55 activesupport (>= 2.3.6)
51 i18n (>= 0.4.0) 56 i18n (>= 0.4.0)
@@ -99,6 +104,9 @@ GEM @@ -99,6 +104,9 @@ GEM
99 tzinfo (0.3.25) 104 tzinfo (0.3.25)
100 warden (1.0.3) 105 warden (1.0.3)
101 rack (>= 1.0.0) 106 rack (>= 1.0.0)
  107 + webmock (1.6.2)
  108 + addressable (>= 2.2.2)
  109 + crack (>= 0.1.7)
102 will_paginate (3.0.pre2) 110 will_paginate (3.0.pre2)
103 111
104 PLATFORMS 112 PLATFORMS
@@ -110,9 +118,11 @@ DEPENDENCIES @@ -110,9 +118,11 @@ DEPENDENCIES
110 devise (~> 1.1.8) 118 devise (~> 1.1.8)
111 factory_girl_rails 119 factory_girl_rails
112 haml 120 haml
  121 + lighthouse-api
113 mongoid (~> 2.0.0.rc.7) 122 mongoid (~> 2.0.0.rc.7)
114 nokogiri 123 nokogiri
115 rails (= 3.0.5) 124 rails (= 3.0.5)
116 rspec (~> 2.5) 125 rspec (~> 2.5)
117 rspec-rails (~> 2.5) 126 rspec-rails (~> 2.5)
  127 + webmock
118 will_paginate 128 will_paginate
@@ -89,6 +89,13 @@ for you. Checkout [Hoptoad](http://hoptoadapp.com) from the guys over at @@ -89,6 +89,13 @@ for you. Checkout [Hoptoad](http://hoptoadapp.com) from the guys over at
89 89
90 4. Enjoy! 90 4. Enjoy!
91 91
  92 +Lighthouseapp integration
  93 +-------------------------
  94 +
  95 +* Account is the name of your subdomain, i.e. **litcafe** for project at http://litcafe.lighthouseapp.com/projects/73466-face/overview
  96 +* Errbit uses token-based authentication. Get your API Token or visit [http://help.lighthouseapp.com/kb/api/how-do-i-get-an-api-token](http://help.lighthouseapp.com/kb/api/how-do-i-get-an-api-token) to learn how to get it.
  97 +* Project id is number identifier of your project, i.e. **73466** for project at http://litcafe.lighthouseapp.com/projects/73466-face/overview
  98 +
92 TODO 99 TODO
93 ---- 100 ----
94 101
app/controllers/apps_controller.rb
@@ -22,10 +22,12 @@ class AppsController < ApplicationController @@ -22,10 +22,12 @@ class AppsController < ApplicationController
22 def new 22 def new
23 @app = App.new 23 @app = App.new
24 @app.watchers.build 24 @app.watchers.build
  25 + @app.issue_tracker = IssueTracker.new
25 end 26 end
26 27
27 def edit 28 def edit
28 @app.watchers.build if @app.watchers.none? 29 @app.watchers.build if @app.watchers.none?
  30 + @app.issue_tracker = IssueTracker.new if @app.issue_tracker.nil?
29 end 31 end
30 32
31 def create 33 def create
app/controllers/errs_controller.rb
1 class ErrsController < ApplicationController 1 class ErrsController < ApplicationController
2 2
3 before_filter :find_app, :except => [:index, :all] 3 before_filter :find_app, :except => [:index, :all]
  4 + before_filter :find_err, :except => [:index, :all]
4 5
5 def index 6 def index
6 app_scope = current_user.admin? ? App.all : current_user.apps 7 app_scope = current_user.admin? ? App.all : current_user.apps
@@ -20,16 +21,32 @@ class ErrsController &lt; ApplicationController @@ -20,16 +21,32 @@ class ErrsController &lt; ApplicationController
20 end 21 end
21 22
22 def show 23 def show
23 - @err = @app.errs.find(params[:id])  
24 page = (params[:notice] || @err.notices.count) 24 page = (params[:notice] || @err.notices.count)
25 page = 1 if page.to_i.zero? 25 page = 1 if page.to_i.zero?
26 @notices = @err.notices.ordered.paginate(:page => page, :per_page => 1) 26 @notices = @err.notices.ordered.paginate(:page => page, :per_page => 1)
27 @notice = @notices.first 27 @notice = @notices.first
28 end 28 end
  29 +
  30 + def create_issue
  31 + set_tracker_params
  32 +
  33 + if @app.issue_tracker
  34 + @app.issue_tracker.create_issue @err
  35 + else
  36 + flash[:error] = "This up has no issue tracker setup."
  37 + end
  38 + redirect_to app_err_path(@app, @err)
  39 + rescue ActiveResource::ConnectionError
  40 + flash[:error] = "There was an error during issue creation. Check your tracker settings or try again later."
  41 + redirect_to app_err_path(@app, @err)
  42 + end
  43 +
  44 + def clear_issue
  45 + @err.update_attribute :issue_link, nil
  46 + redirect_to app_err_path(@app, @err)
  47 + end
29 48
30 def resolve 49 def resolve
31 - @err = @app.errs.find(params[:id])  
32 -  
33 # Deal with bug in mogoid where find is returning an Enumberable obj 50 # Deal with bug in mogoid where find is returning an Enumberable obj
34 @err = @err.first if @err.respond_to?(:first) 51 @err = @err.first if @err.respond_to?(:first)
35 52
@@ -51,5 +68,15 @@ class ErrsController &lt; ApplicationController @@ -51,5 +68,15 @@ class ErrsController &lt; ApplicationController
51 # apparently finding by 'watchers.email' and 'id' is broken 68 # apparently finding by 'watchers.email' and 'id' is broken
52 raise(Mongoid::Errors::DocumentNotFound.new(App,@app.id)) unless current_user.admin? || current_user.watching?(@app) 69 raise(Mongoid::Errors::DocumentNotFound.new(App,@app.id)) unless current_user.admin? || current_user.watching?(@app)
53 end 70 end
  71 +
  72 + def find_err
  73 + @err = @app.errs.find(params[:id])
  74 + end
  75 +
  76 + def set_tracker_params
  77 + IssueTracker.default_url_options[:host] = request.host
  78 + IssueTracker.default_url_options[:port] = request.port
  79 + IssueTracker.default_url_options[:protocol] = request.scheme
  80 + end
54 81
55 end 82 end
app/helpers/form_helper.rb
@@ -6,7 +6,7 @@ module FormHelper @@ -6,7 +6,7 @@ module FormHelper
6 content_tag(:div, :class => 'error-messages') do 6 content_tag(:div, :class => 'error-messages') do
7 body = content_tag(:h2, 'Dang. The following errors are keeping this from being a success.') 7 body = content_tag(:h2, 'Dang. The following errors are keeping this from being a success.')
8 body += content_tag(:ul) do 8 body += content_tag(:ul) do
9 - document.errors.full_messages.inject('') {|errs, msg| errs += content_tag(:li, msg) } 9 + document.errors.full_messages.inject('') {|errs, msg| errs += content_tag(:li, h(msg)) }.html_safe
10 end 10 end
11 end 11 end
12 end 12 end
app/models/app.rb
@@ -16,6 +16,7 @@ class App @@ -16,6 +16,7 @@ class App
16 16
17 embeds_many :watchers 17 embeds_many :watchers
18 embeds_many :deploys 18 embeds_many :deploys
  19 + embeds_one :issue_tracker
19 references_many :errs, :dependent => :destroy 20 references_many :errs, :dependent => :destroy
20 21
21 before_validation :generate_api_key, :on => :create 22 before_validation :generate_api_key, :on => :create
@@ -24,9 +25,12 @@ class App @@ -24,9 +25,12 @@ class App
24 validates_uniqueness_of :name, :allow_blank => true 25 validates_uniqueness_of :name, :allow_blank => true
25 validates_uniqueness_of :api_key, :allow_blank => true 26 validates_uniqueness_of :api_key, :allow_blank => true
26 validates_associated :watchers 27 validates_associated :watchers
  28 + validate :check_issue_tracker
27 29
28 accepts_nested_attributes_for :watchers, :allow_destroy => true, 30 accepts_nested_attributes_for :watchers, :allow_destroy => true,
29 :reject_if => proc { |attrs| attrs[:user_id].blank? && attrs[:email].blank? } 31 :reject_if => proc { |attrs| attrs[:user_id].blank? && attrs[:email].blank? }
  32 + accepts_nested_attributes_for :issue_tracker, :allow_destroy => true,
  33 + :reject_if => proc { |attrs| !%w( lighthouseapp ).include?(attrs[:issue_tracker_type]) }
30 34
31 # Mongoid Bug: find(id) on association proxies returns an Enumerator 35 # Mongoid Bug: find(id) on association proxies returns an Enumerator
32 def self.find_by_id!(app_id) 36 def self.find_by_id!(app_id)
@@ -46,5 +50,13 @@ class App @@ -46,5 +50,13 @@ class App
46 def generate_api_key 50 def generate_api_key
47 self.api_key ||= ActiveSupport::SecureRandom.hex 51 self.api_key ||= ActiveSupport::SecureRandom.hex
48 end 52 end
49 - 53 +
  54 + def check_issue_tracker
  55 + if issue_tracker.present?
  56 + issue_tracker.valid?
  57 + issue_tracker.errors.full_messages.each do |error|
  58 + errors[:base] << error
  59 + end if issue_tracker.errors
  60 + end
  61 + end
50 end 62 end
app/models/err.rb
@@ -9,6 +9,7 @@ class Err @@ -9,6 +9,7 @@ class Err
9 field :fingerprint 9 field :fingerprint
10 field :last_notice_at, :type => DateTime 10 field :last_notice_at, :type => DateTime
11 field :resolved, :type => Boolean, :default => false 11 field :resolved, :type => Boolean, :default => false
  12 + field :issue_link, :type => String
12 13
13 index :last_notice_at 14 index :last_notice_at
14 15
app/models/issue_tracker.rb 0 → 100644
@@ -0,0 +1,96 @@ @@ -0,0 +1,96 @@
  1 +class IssueTracker
  2 + include Mongoid::Document
  3 + include Mongoid::Timestamps
  4 + include HashHelper
  5 + include Rails.application.routes.url_helpers
  6 + default_url_options[:host] = Errbit::Application.config.action_mailer.default_url_options[:host]
  7 +
  8 + validate :check_lighthouseapp_params
  9 +
  10 + embedded_in :app, :inverse_of => :issue_tracker
  11 +
  12 + field :account, :type => String
  13 + field :api_token, :type => String
  14 + field :project_id, :type => String
  15 + field :issue_tracker_type, :type => String, :default => 'lighthouseapp'
  16 +
  17 + def create_issue err
  18 + Lighthouse.account = account
  19 + Lighthouse.token = api_token
  20 +
  21 + # updating lighthouse account
  22 + Lighthouse::Ticket.site
  23 +
  24 + ticket = Lighthouse::Ticket.new(:project_id => project_id)
  25 + ticket.title = "[#{ err.environment }][#{ err.where }] #{err.message.to_s.truncate(100)}"
  26 +
  27 + ticket.body = ""
  28 + ticket.body += "[See this exception on Errbit](#{ app_err_url err.app, err } \"See this exception on Errbit\")"
  29 + ticket.body += "\n"
  30 + if notice = err.notices.first
  31 + ticket.body += "# #{notice.message} #"
  32 + ticket.body += "\n"
  33 + ticket.body += "## Summary ##"
  34 + ticket.body += "\n"
  35 + if notice.request['url'].present?
  36 + ticket.body += "### URL ###"
  37 + ticket.body += "\n"
  38 + ticket.body += "[#{notice.request['url']}](#{notice.request['url']})"
  39 + ticket.body += "\n"
  40 + end
  41 + ticket.body += "### Where ###"
  42 + ticket.body += "\n"
  43 + ticket.body += notice.err.where
  44 + ticket.body += "\n"
  45 +
  46 + ticket.body += "### Occured ###"
  47 + ticket.body += "\n"
  48 + ticket.body += notice.created_at.to_s(:micro)
  49 + ticket.body += "\n"
  50 +
  51 + ticket.body += "### Similar ###"
  52 + ticket.body += "\n"
  53 + ticket.body += (notice.err.notices.count - 1).to_s
  54 + ticket.body += "\n"
  55 +
  56 + ticket.body += "## Params ##"
  57 + ticket.body += "\n"
  58 + ticket.body += "<code>#{pretty_hash(notice.params)}</code>"
  59 + ticket.body += "\n"
  60 +
  61 + ticket.body += "## Session ##"
  62 + ticket.body += "\n"
  63 + ticket.body += "<code>#{pretty_hash(notice.session)}</code>"
  64 + ticket.body += "\n"
  65 +
  66 + ticket.body += "## Backtrace ##"
  67 + ticket.body += "\n"
  68 + ticket.body += "<code>"
  69 + for line in notice.backtrace
  70 + ticket.body += "#{line['number']}: #{line['file'].sub(/^\[PROJECT_ROOT\]/, '')} -> **#{line['method']}**"
  71 + ticket.body += "\n"
  72 + end
  73 + ticket.body += "</code>"
  74 + ticket.body += "\n"
  75 +
  76 + ticket.body += "## Environment ##"
  77 + ticket.body += "\n"
  78 + for key, val in notice.env_vars
  79 + ticket.body += "#{key}: #{val}"
  80 + end
  81 + ticket.body += "\n"
  82 + end
  83 +
  84 + ticket.tags << "errbit"
  85 + ticket.save!
  86 + err.update_attribute :issue_link, "#{Lighthouse::Ticket.site.to_s.sub(/#{Lighthouse::Ticket.site.path}$/, '')}#{Lighthouse::Ticket.element_path(ticket.id, :project_id => project_id)}".sub(/\.xml$/, '')
  87 + end
  88 +
  89 + protected
  90 + def check_lighthouseapp_params
  91 + blank_flags = %w( api_token project_id account ).map {|m| self[m].blank? }
  92 + if blank_flags.any? && !blank_flags.all?
  93 + errors.add(:base, "You must specify your Lighthouseapp account, token and project id")
  94 + end
  95 + end
  96 +end
app/views/apps/_fields.html.haml
@@ -21,3 +21,20 @@ @@ -21,3 +21,20 @@
21 = w.select :user_id, User.all.map{|u| [u.name,u.id.to_s]}, :include_blank => '-- Select a User --' 21 = w.select :user_id, User.all.map{|u| [u.name,u.id.to_s]}, :include_blank => '-- Select a User --'
22 %div.email{:class => w.object.email.present? ? 'choosen' : nil} 22 %div.email{:class => w.object.email.present? ? 'choosen' : nil}
23 = w.text_field :email 23 = w.text_field :email
  24 +
  25 +%fieldset
  26 + %legend Issue tracker
  27 + = f.fields_for :issue_tracker do |w|
  28 + %div.watcher.nested
  29 + %div.choose
  30 + = w.radio_button :issue_tracker_type, :lighthouseapp
  31 + = label_tag :issue_tracker_type_lighthouseapp, 'Lighthouse', :for => label_for_attr(w, 'issue_tracker_type_lighthouseapp')
  32 + %div.lighthouseapp{:class => 'choosen'}
  33 + = w.label :account, "Account"
  34 + = w.text_field :account
  35 + = w.label :api_token, "API token"
  36 + = w.text_field :api_token
  37 + = w.label :project_id, "Project ID"
  38 + = w.text_field :project_id
  39 +
  40 +
app/views/errs/show.html.haml
@@ -10,7 +10,14 @@ @@ -10,7 +10,14 @@
10 %strong Last Notice: 10 %strong Last Notice:
11 = last_notice_at(@err).to_s(:micro) 11 = last_notice_at(@err).to_s(:micro)
12 - content_for :action_bar do 12 - content_for :action_bar do
13 - %span= link_to 'resolve', resolve_app_err_path(@app, @err), :method => :put, :confirm => err_confirm, :class => 'resolve' if @err.unresolved? 13 + - if @err.app.issue_tracker
  14 + - if @err.issue_link.blank?
  15 + %span= link_to 'create issue', create_issue_app_err_path(@app, @err), :method => :post, :class => 'create-issue'
  16 + - else
  17 + %span= link_to 'go to issue', @err.issue_link, :class => 'goto-issue'
  18 + = link_to 'clear issue', clear_issue_app_err_path(@app, @err), :method => :delete, :confirm => "Clear err issues?", :class => 'clear-issue'
  19 + - if @err.unresolved?
  20 + %span= link_to 'resolve', resolve_app_err_path(@app, @err), :method => :put, :confirm => err_confirm, :class => 'resolve'
14 21
15 %h4= @notice.try(:message) 22 %h4= @notice.try(:message)
16 23
config/environments/development.rb
@@ -16,6 +16,7 @@ Errbit::Application.configure do @@ -16,6 +16,7 @@ Errbit::Application.configure do
16 16
17 # Don't care if the mailer can't send 17 # Don't care if the mailer can't send
18 config.action_mailer.raise_delivery_errors = false 18 config.action_mailer.raise_delivery_errors = false
  19 + config.action_mailer.default_url_options = { :host => 'localhost:3000' }
19 20
20 # Print deprecation notices to the Rails logger 21 # Print deprecation notices to the Rails logger
21 config.active_support.deprecation = :log 22 config.active_support.deprecation = :log
config/environments/test.rb
@@ -24,6 +24,7 @@ Errbit::Application.configure do @@ -24,6 +24,7 @@ Errbit::Application.configure do
24 # The :test delivery method accumulates sent emails in the 24 # The :test delivery method accumulates sent emails in the
25 # ActionMailer::Base.deliveries array. 25 # ActionMailer::Base.deliveries array.
26 config.action_mailer.delivery_method = :test 26 config.action_mailer.delivery_method = :test
  27 + config.action_mailer.default_url_options = { :host => 'test.host' }
27 28
28 # Use SQL instead of Active Record's schema dumper when creating the test database. 29 # Use SQL instead of Active Record's schema dumper when creating the test database.
29 # This is necessary if your schema can't be completely dumped by the schema dumper, 30 # This is necessary if your schema can't be completely dumped by the schema dumper,
config/routes.rb
@@ -20,6 +20,8 @@ Errbit::Application.routes.draw do @@ -20,6 +20,8 @@ Errbit::Application.routes.draw do
20 resources :notices 20 resources :notices
21 member do 21 member do
22 put :resolve 22 put :resolve
  23 + post :create_issue
  24 + delete :clear_issue
23 end 25 end
24 end 26 end
25 27
spec/controllers/apps_controller_spec.rb
@@ -147,7 +147,6 @@ describe AppsController do @@ -147,7 +147,6 @@ describe AppsController do
147 describe "PUT /apps/:id" do 147 describe "PUT /apps/:id" do
148 before do 148 before do
149 @app = Factory(:app) 149 @app = Factory(:app)
150 - App.stub(:find).with(@app.id).and_return(@app)  
151 end 150 end
152 151
153 context "when the update is successful" do 152 context "when the update is successful" do
@@ -172,11 +171,60 @@ describe AppsController do @@ -172,11 +171,60 @@ describe AppsController do
172 171
173 context "when the update is unsuccessful" do 172 context "when the update is unsuccessful" do
174 it "should render the edit page" do 173 it "should render the edit page" do
175 - @app.should_receive(:update_attributes).and_return(false)  
176 - put :update, :id => @app.id, :app => {} 174 + put :update, :id => @app.id, :app => { :name => '' }
177 response.should render_template(:edit) 175 response.should render_template(:edit)
178 end 176 end
179 end 177 end
  178 +
  179 + context "setting up issue tracker", :cur => true do
  180 + context "unknown tracker type" do
  181 + before(:each) do
  182 + put :update, :id => @app.id, :app => { :issue_tracker_attributes => {
  183 + :issue_tracker_type => 'unknown', :project_id => '1234', :api_token => '123123', :account => 'myapp'
  184 + } }
  185 + @app.reload
  186 + end
  187 +
  188 + it "should not create issue tracker" do
  189 + @app.issue_tracker.should be_nil
  190 + end
  191 + end
  192 +
  193 + context "lighthouseapp" do
  194 + it "should save tracker params" do
  195 + put :update, :id => @app.id, :app => { :issue_tracker_attributes => {
  196 + :issue_tracker_type => 'lighthouseapp', :project_id => '1234', :api_token => '123123', :account => 'myapp'
  197 + } }
  198 + @app.reload
  199 +
  200 + tracker = @app.issue_tracker
  201 + tracker.issue_tracker_type.should == 'lighthouseapp'
  202 + tracker.project_id.should == '1234'
  203 + tracker.api_token.should == '123123'
  204 + tracker.account.should == 'myapp'
  205 + end
  206 +
  207 + it "should show validation notice when sufficient params are not present" do
  208 + put :update, :id => @app.id, :app => { :issue_tracker_attributes => {
  209 + :issue_tracker_type => 'lighthouseapp', :project_id => '1234', :api_token => '123123'
  210 + } }
  211 + @app.reload
  212 +
  213 + @app.issue_tracker.should be_nil
  214 + response.body.should match(/You must specify your Lighthouseapp account, token and project id/)
  215 + end
  216 +
  217 + it "should show validation notice when sufficient params are not present" do
  218 + put :update, :id => @app.id, :app => { :issue_tracker_attributes => {
  219 + :issue_tracker_type => 'lighthouseapp', :project_id => '1234', :api_token => '123123'
  220 + } }
  221 + @app.reload
  222 +
  223 + @app.issue_tracker.should be_nil
  224 + response.body.should match(/You must specify your Lighthouseapp account, token and project id/)
  225 + end
  226 + end
  227 + end
180 end 228 end
181 229
182 describe "DELETE /apps/:id" do 230 describe "DELETE /apps/:id" do
spec/controllers/errs_controller_spec.rb
@@ -123,6 +123,33 @@ describe ErrsController do @@ -123,6 +123,33 @@ describe ErrsController do
123 get :show, :app_id => app.id, :id => err.id 123 get :show, :app_id => app.id, :id => err.id
124 response.should be_success 124 response.should be_success
125 end 125 end
  126 +
  127 + context "create issue button" do
  128 + let(:button_matcher) { match(/create issue/) }
  129 +
  130 + it "should not exist for err's app without issue tracker" do
  131 + err = Factory :err
  132 + get :show, :app_id => err.app.id, :id => err.id
  133 +
  134 + response.body.should_not button_matcher
  135 + end
  136 +
  137 + it "should exist for err's app with issue tracker" do
  138 + tracker = Factory(:lighthouseapp_tracker)
  139 + err = Factory(:err, :app => tracker.app)
  140 + get :show, :app_id => err.app.id, :id => err.id
  141 +
  142 + response.body.should button_matcher
  143 + end
  144 +
  145 + it "should not exist for err with issue_link" do
  146 + tracker = Factory(:lighthouseapp_tracker)
  147 + err = Factory(:err, :app => tracker.app, :issue_link => "http://some.host")
  148 + get :show, :app_id => err.app.id, :id => err.id
  149 +
  150 + response.body.should_not button_matcher
  151 + end
  152 + end
126 end 153 end
127 154
128 context 'when logged in as a user' do 155 context 'when logged in as a user' do
@@ -186,5 +213,119 @@ describe ErrsController do @@ -186,5 +213,119 @@ describe ErrsController do
186 response.should redirect_to(errs_path) 213 response.should redirect_to(errs_path)
187 end 214 end
188 end 215 end
189 - 216 +
  217 + describe "POST /apps/:app_id/errs/:id/create_issue" do
  218 + render_views
  219 +
  220 + before(:each) do
  221 + sign_in Factory(:admin)
  222 + end
  223 +
  224 + context "successful issue creation" do
  225 + context "lighthouseapp tracker" do
  226 + let(:notice) { Factory :notice }
  227 + let(:tracker) { Factory :lighthouseapp_tracker, :app => notice.err.app }
  228 + let(:err) { notice.err }
  229 +
  230 + before(:each) do
  231 + number = 5
  232 + @issue_link = "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets/#{number}.xml"
  233 + body = "<ticket><number type=\"integer\">#{number}</number></ticket>"
  234 + stub_request(:post, "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets.xml").to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body )
  235 +
  236 + post :create_issue, :app_id => err.app.id, :id => err.id
  237 + err.reload
  238 + end
  239 +
  240 + it "should make request to Lighthouseapp with err params" do
  241 + requested = have_requested(:post, "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets.xml")
  242 + WebMock.should requested.with(:headers => {'X-Lighthousetoken' => tracker.api_token})
  243 + WebMock.should requested.with(:body => /<tag>errbit<\/tag>/)
  244 + WebMock.should requested.with(:body => /<title>\[#{ err.environment }\]\[#{err.where}\] #{err.message.to_s.truncate(100)}<\/title>/)
  245 + WebMock.should requested.with(:body => /<body>.+<\/body>/m)
  246 + end
  247 +
  248 + it "should redirect to err page" do
  249 + response.should redirect_to( app_err_path(err.app, err) )
  250 + end
  251 +
  252 + it "should create issue link for err" do
  253 + err.issue_link.should == @issue_link.sub(/\.xml$/, '')
  254 + end
  255 + end
  256 + end
  257 +
  258 + context "absent issue tracker" do
  259 + let(:err) { Factory :err }
  260 +
  261 + before(:each) do
  262 + post :create_issue, :app_id => err.app.id, :id => err.id
  263 + end
  264 +
  265 + it "should redirect to err page" do
  266 + response.should redirect_to( app_err_path(err.app, err) )
  267 + end
  268 +
  269 + it "should set flash error message telling issue tracker of the app doesn't exist" do
  270 + flash[:error].should == "This up has no issue tracker setup."
  271 + end
  272 + end
  273 +
  274 + context "error during request to a tracker" do
  275 + context "lighthouseapp tracker" do
  276 + let(:tracker) { Factory :lighthouseapp_tracker }
  277 + let(:err) { Factory :err, :app => tracker.app }
  278 +
  279 + before(:each) do
  280 + stub_request(:post, "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets.xml").to_return(:status => 500)
  281 +
  282 + post :create_issue, :app_id => err.app.id, :id => err.id
  283 + end
  284 +
  285 + it "should redirect to err page" do
  286 + response.should redirect_to( app_err_path(err.app, err) )
  287 + end
  288 +
  289 + it "should notify of connection error" do
  290 + flash[:error].should == "There was an error during issue creation. Check your tracker settings or try again later."
  291 + end
  292 + end
  293 + end
  294 + end
  295 +
  296 + describe "DELETE /apps/:app_id/errs/:id/clear_issue" do
  297 + before(:each) do
  298 + sign_in Factory(:admin)
  299 + end
  300 +
  301 + context "err with issue" do
  302 + let(:err) { Factory :err, :issue_link => "http://some.host" }
  303 +
  304 + before(:each) do
  305 + delete :clear_issue, :app_id => err.app.id, :id => err.id
  306 + err.reload
  307 + end
  308 +
  309 + it "should redirect to err page" do
  310 + response.should redirect_to( app_err_path(err.app, err) )
  311 + end
  312 +
  313 + it "should clear issue link" do
  314 + err.issue_link.should be_nil
  315 + end
  316 + end
  317 +
  318 + context "err without issue" do
  319 + let(:err) { Factory :err }
  320 +
  321 + before(:each) do
  322 + delete :clear_issue, :app_id => err.app.id, :id => err.id
  323 + err.reload
  324 + end
  325 +
  326 + it "should redirect to err page" do
  327 + response.should redirect_to( app_err_path(err.app, err) )
  328 + end
  329 + end
  330 + end
190 end 331 end
spec/factories.rb
1 Factory.sequence(:name) {|n| "John #{n} Doe"} 1 Factory.sequence(:name) {|n| "John #{n} Doe"}
  2 +Factory.sequence(:word) {|n| "word#{n}"}
2 Factory.sequence(:app_name) {|n| "App ##{n}"} 3 Factory.sequence(:app_name) {|n| "App ##{n}"}
3 Factory.sequence(:email) {|n| "email#{n}@example.com"} 4 Factory.sequence(:email) {|n| "email#{n}@example.com"}
4 Factory.sequence(:user_email) {|n| "user.#{n}@example.com"} 5 Factory.sequence(:user_email) {|n| "user.#{n}@example.com"}
spec/factories/app_factories.rb
@@ -25,4 +25,4 @@ Factory.define(:deploy) do |d| @@ -25,4 +25,4 @@ Factory.define(:deploy) do |d|
25 d.repository 'git@github.com/jdpace/errbit.git' 25 d.repository 'git@github.com/jdpace/errbit.git'
26 d.environment 'production' 26 d.environment 'production'
27 d.revision ActiveSupport::SecureRandom.hex(10) 27 d.revision ActiveSupport::SecureRandom.hex(10)
28 -end  
29 \ No newline at end of file 28 \ No newline at end of file
  29 +end
spec/factories/issue_tracker_factories.rb 0 → 100644
@@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
  1 +Factory.define :lighthouseapp_tracker, :class => IssueTracker do |e|
  2 + e.issue_tracker_type 'lighthouseapp'
  3 + e.account { Factory.next :word }
  4 + e.api_token { Factory.next :word }
  5 + e.project_id { Factory.next :word }
  6 + e.association :app, :factory => :app
  7 +end
0 \ No newline at end of file 8 \ No newline at end of file
spec/models/issue_tracker_spec.rb 0 → 100644
@@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
  1 +# encoding: utf-8
  2 +require 'spec_helper'
  3 +
  4 +describe IssueTracker do
  5 +end
spec/spec_helper.rb
@@ -4,6 +4,7 @@ ENV[&quot;RAILS_ENV&quot;] ||= &#39;test&#39; @@ -4,6 +4,7 @@ ENV[&quot;RAILS_ENV&quot;] ||= &#39;test&#39;
4 require File.expand_path("../../config/environment", __FILE__) 4 require File.expand_path("../../config/environment", __FILE__)
5 require 'rspec/rails' 5 require 'rspec/rails'
6 require 'database_cleaner' 6 require 'database_cleaner'
  7 +require 'webmock/rspec'
7 8
8 # Requires supporting files with custom matchers and macros, etc, 9 # Requires supporting files with custom matchers and macros, etc,
9 # in ./support/ and its subdirectories. 10 # in ./support/ and its subdirectories.
@@ -21,4 +22,5 @@ RSpec.configure do |config| @@ -21,4 +22,5 @@ RSpec.configure do |config|
21 DatabaseCleaner[:mongoid].strategy = :truncation 22 DatabaseCleaner[:mongoid].strategy = :truncation
22 DatabaseCleaner.clean 23 DatabaseCleaner.clean
23 end 24 end
  25 + config.include WebMock::API
24 end 26 end
25 \ No newline at end of file 27 \ No newline at end of file