Commit ba17fba9c5abc4a3d73b2c0a5c4bdb5b45b36271

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

Merge branch 'feature/lighthouse'

Gemfile
... ... @@ -6,6 +6,7 @@ gem 'mongoid', '~> 2.0.0.rc.7'
6 6 gem 'haml'
7 7 gem 'will_paginate'
8 8 gem 'devise', '~> 1.1.8'
  9 +gem 'lighthouse-api'
9 10  
10 11 platform :ruby do
11 12 gem 'bson_ext', '~> 1.2'
... ... @@ -13,6 +14,7 @@ end
13 14  
14 15 group :development, :test do
15 16 gem 'rspec-rails', '~> 2.5'
  17 + gem 'webmock', :require => false
16 18 end
17 19  
18 20 group :test do
... ...
Gemfile.lock
... ... @@ -28,11 +28,13 @@ GEM
28 28 activemodel (= 3.0.5)
29 29 activesupport (= 3.0.5)
30 30 activesupport (3.0.5)
  31 + addressable (2.2.4)
31 32 arel (2.0.9)
32 33 bcrypt-ruby (2.1.4)
33 34 bson (1.2.4)
34 35 bson_ext (1.2.4)
35 36 builder (2.1.2)
  37 + crack (0.1.8)
36 38 database_cleaner (0.6.5)
37 39 devise (1.1.8)
38 40 bcrypt-ruby (~> 2.1.2)
... ... @@ -46,6 +48,9 @@ GEM
46 48 railties (>= 3.0.0)
47 49 haml (3.0.25)
48 50 i18n (0.5.0)
  51 + lighthouse-api (2.0)
  52 + activeresource (>= 3.0.0)
  53 + activesupport (>= 3.0.0)
49 54 mail (2.2.15)
50 55 activesupport (>= 2.3.6)
51 56 i18n (>= 0.4.0)
... ... @@ -99,6 +104,9 @@ GEM
99 104 tzinfo (0.3.25)
100 105 warden (1.0.3)
101 106 rack (>= 1.0.0)
  107 + webmock (1.6.2)
  108 + addressable (>= 2.2.2)
  109 + crack (>= 0.1.7)
102 110 will_paginate (3.0.pre2)
103 111  
104 112 PLATFORMS
... ... @@ -110,9 +118,11 @@ DEPENDENCIES
110 118 devise (~> 1.1.8)
111 119 factory_girl_rails
112 120 haml
  121 + lighthouse-api
113 122 mongoid (~> 2.0.0.rc.7)
114 123 nokogiri
115 124 rails (= 3.0.5)
116 125 rspec (~> 2.5)
117 126 rspec-rails (~> 2.5)
  127 + webmock
118 128 will_paginate
... ...
README.md
... ... @@ -89,6 +89,13 @@ for you. Checkout [Hoptoad](http://hoptoadapp.com) from the guys over at
89 89  
90 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 99 TODO
93 100 ----
94 101  
... ...
app/controllers/apps_controller.rb
... ... @@ -22,10 +22,12 @@ class AppsController < ApplicationController
22 22 def new
23 23 @app = App.new
24 24 @app.watchers.build
  25 + @app.issue_tracker = IssueTracker.new
25 26 end
26 27  
27 28 def edit
28 29 @app.watchers.build if @app.watchers.none?
  30 + @app.issue_tracker = IssueTracker.new if @app.issue_tracker.nil?
29 31 end
30 32  
31 33 def create
... ...
app/controllers/errs_controller.rb
1 1 class ErrsController < ApplicationController
2 2  
3 3 before_filter :find_app, :except => [:index, :all]
  4 + before_filter :find_err, :except => [:index, :all]
4 5  
5 6 def index
6 7 app_scope = current_user.admin? ? App.all : current_user.apps
... ... @@ -20,16 +21,32 @@ class ErrsController &lt; ApplicationController
20 21 end
21 22  
22 23 def show
23   - @err = @app.errs.find(params[:id])
24 24 page = (params[:notice] || @err.notices.count)
25 25 page = 1 if page.to_i.zero?
26 26 @notices = @err.notices.ordered.paginate(:page => page, :per_page => 1)
27 27 @notice = @notices.first
28 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 49 def resolve
31   - @err = @app.errs.find(params[:id])
32   -
33 50 # Deal with bug in mogoid where find is returning an Enumberable obj
34 51 @err = @err.first if @err.respond_to?(:first)
35 52  
... ... @@ -51,5 +68,15 @@ class ErrsController &lt; ApplicationController
51 68 # apparently finding by 'watchers.email' and 'id' is broken
52 69 raise(Mongoid::Errors::DocumentNotFound.new(App,@app.id)) unless current_user.admin? || current_user.watching?(@app)
53 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 82 end
... ...
app/helpers/form_helper.rb
... ... @@ -6,7 +6,7 @@ module FormHelper
6 6 content_tag(:div, :class => 'error-messages') do
7 7 body = content_tag(:h2, 'Dang. The following errors are keeping this from being a success.')
8 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 10 end
11 11 end
12 12 end
... ...
app/models/app.rb
... ... @@ -16,6 +16,7 @@ class App
16 16  
17 17 embeds_many :watchers
18 18 embeds_many :deploys
  19 + embeds_one :issue_tracker
19 20 references_many :errs, :dependent => :destroy
20 21  
21 22 before_validation :generate_api_key, :on => :create
... ... @@ -24,9 +25,12 @@ class App
24 25 validates_uniqueness_of :name, :allow_blank => true
25 26 validates_uniqueness_of :api_key, :allow_blank => true
26 27 validates_associated :watchers
  28 + validate :check_issue_tracker
27 29  
28 30 accepts_nested_attributes_for :watchers, :allow_destroy => true,
29 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 35 # Mongoid Bug: find(id) on association proxies returns an Enumerator
32 36 def self.find_by_id!(app_id)
... ... @@ -46,5 +50,13 @@ class App
46 50 def generate_api_key
47 51 self.api_key ||= ActiveSupport::SecureRandom.hex
48 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 62 end
... ...
app/models/err.rb
... ... @@ -9,6 +9,7 @@ class Err
9 9 field :fingerprint
10 10 field :last_notice_at, :type => DateTime
11 11 field :resolved, :type => Boolean, :default => false
  12 + field :issue_link, :type => String
12 13  
13 14 index :last_notice_at
14 15  
... ...
app/models/issue_tracker.rb 0 → 100644
... ... @@ -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 21 = w.select :user_id, User.all.map{|u| [u.name,u.id.to_s]}, :include_blank => '-- Select a User --'
22 22 %div.email{:class => w.object.email.present? ? 'choosen' : nil}
23 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 10 %strong Last Notice:
11 11 = last_notice_at(@err).to_s(:micro)
12 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 22 %h4= @notice.try(:message)
16 23  
... ...
config/environments/development.rb
... ... @@ -16,6 +16,7 @@ Errbit::Application.configure do
16 16  
17 17 # Don't care if the mailer can't send
18 18 config.action_mailer.raise_delivery_errors = false
  19 + config.action_mailer.default_url_options = { :host => 'localhost:3000' }
19 20  
20 21 # Print deprecation notices to the Rails logger
21 22 config.active_support.deprecation = :log
... ...
config/environments/test.rb
... ... @@ -24,6 +24,7 @@ Errbit::Application.configure do
24 24 # The :test delivery method accumulates sent emails in the
25 25 # ActionMailer::Base.deliveries array.
26 26 config.action_mailer.delivery_method = :test
  27 + config.action_mailer.default_url_options = { :host => 'test.host' }
27 28  
28 29 # Use SQL instead of Active Record's schema dumper when creating the test database.
29 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 20 resources :notices
21 21 member do
22 22 put :resolve
  23 + post :create_issue
  24 + delete :clear_issue
23 25 end
24 26 end
25 27  
... ...
spec/controllers/apps_controller_spec.rb
... ... @@ -147,7 +147,6 @@ describe AppsController do
147 147 describe "PUT /apps/:id" do
148 148 before do
149 149 @app = Factory(:app)
150   - App.stub(:find).with(@app.id).and_return(@app)
151 150 end
152 151  
153 152 context "when the update is successful" do
... ... @@ -172,11 +171,60 @@ describe AppsController do
172 171  
173 172 context "when the update is unsuccessful" do
174 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 175 response.should render_template(:edit)
178 176 end
179 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 228 end
181 229  
182 230 describe "DELETE /apps/:id" do
... ...
spec/controllers/errs_controller_spec.rb
... ... @@ -123,6 +123,33 @@ describe ErrsController do
123 123 get :show, :app_id => app.id, :id => err.id
124 124 response.should be_success
125 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 153 end
127 154  
128 155 context 'when logged in as a user' do
... ... @@ -186,5 +213,119 @@ describe ErrsController do
186 213 response.should redirect_to(errs_path)
187 214 end
188 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 331 end
... ...
spec/factories.rb
1 1 Factory.sequence(:name) {|n| "John #{n} Doe"}
  2 +Factory.sequence(:word) {|n| "word#{n}"}
2 3 Factory.sequence(:app_name) {|n| "App ##{n}"}
3 4 Factory.sequence(:email) {|n| "email#{n}@example.com"}
4 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 25 d.repository 'git@github.com/jdpace/errbit.git'
26 26 d.environment 'production'
27 27 d.revision ActiveSupport::SecureRandom.hex(10)
28   -end
29 28 \ No newline at end of file
  29 +end
... ...
spec/factories/issue_tracker_factories.rb 0 → 100644
... ... @@ -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 8 \ No newline at end of file
... ...
spec/models/issue_tracker_spec.rb 0 → 100644
... ... @@ -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 4 require File.expand_path("../../config/environment", __FILE__)
5 5 require 'rspec/rails'
6 6 require 'database_cleaner'
  7 +require 'webmock/rspec'
7 8  
8 9 # Requires supporting files with custom matchers and macros, etc,
9 10 # in ./support/ and its subdirectories.
... ... @@ -21,4 +22,5 @@ RSpec.configure do |config|
21 22 DatabaseCleaner[:mongoid].strategy = :truncation
22 23 DatabaseCleaner.clean
23 24 end
  25 + config.include WebMock::API
24 26 end
25 27 \ No newline at end of file
... ...