Commit 07720a8f0ef44457bdbb8030894211f69e0201af

Authored by Nathan Broadbent
1 parent 4e7477d1
Exists in master and in 1 other branch production

* Major reorganization of issue tracker specs. Migrated specs out of the errs_co…

…ntroller into their respective models.
* Wrote specs for Fogbugz. Turns out that the Fogbugz API used the 'Crack' gem which happened to break mongoid by playing with the String class.
  See here for details: [ https://github.com/mongoid/mongoid/issues/618 ].
* This is why I had to upgrade mongoid to version 2.1.1, which included the 'Crack' fix.
  Upgrading Mongoid to 2.1.1 required the following changes:
** Fixing a counter_cache test on Err model
** Namespacing all of the subclassed IssueTracker models based on their parent directory
** Setting `config.mongoid.preload_models = true` in config/application.rb
Gemfile
... ... @@ -2,7 +2,7 @@ source 'http://rubygems.org'
2 2  
3 3 gem 'rails', '3.0.5'
4 4 gem 'nokogiri'
5   -gem 'mongoid', '2.0.2'
  5 +gem 'mongoid', '2.1.1'
6 6 gem 'haml'
7 7 gem 'will_paginate'
8 8 gem 'devise', '~> 1.4.0'
... ...
Gemfile.lock
... ... @@ -94,7 +94,7 @@ GEM
94 94 mime-types (1.16)
95 95 mongo (1.3.1)
96 96 bson (>= 1.3.1)
97   - mongoid (2.0.2)
  97 + mongoid (2.1.1)
98 98 activemodel (~> 3.0)
99 99 mongo (~> 1.3)
100 100 tzinfo (~> 0.3.22)
... ... @@ -208,7 +208,7 @@ DEPENDENCIES
208 208 hoptoad_notifier (~> 2.3)
209 209 inherited_resources
210 210 lighthouse-api
211   - mongoid (= 2.0.2)
  211 + mongoid (= 2.1.1)
212 212 mongoid_rails_migrations
213 213 nokogiri
214 214 octokit
... ...
app/models/issue_trackers/fogbugz_tracker.rb
1   -class FogbugzTracker < IssueTracker
  1 +class IssueTrackers::FogbugzTracker < IssueTracker
2 2 Label = "fogbugz"
3 3 RequiredFields = %w(project_id account username password)
4 4  
... ...
app/models/issue_trackers/github_tracker.rb
1   -class GithubTracker < IssueTracker
  1 +class IssueTrackers::GithubTracker < IssueTracker
2 2 Label = "github"
3 3 RequiredFields = %w(project_id username api_token)
4 4  
... ...
app/models/issue_trackers/lighthouse_tracker.rb
1   -class LighthouseTracker < IssueTracker
  1 +class IssueTrackers::LighthouseTracker < IssueTracker
2 2 Label = "lighthouseapp"
3 3 RequiredFields = %w(account api_token project_id)
4 4  
... ...
app/models/issue_trackers/mingle_tracker.rb
1   -class MingleTracker < IssueTracker
  1 +class IssueTrackers::MingleTracker < IssueTracker
2 2 Label = "mingle"
3 3 RequiredFields = %w(account project_id username password)
4 4  
... ...
app/models/issue_trackers/pivotal_labs_tracker.rb
1   -class PivotalLabsTracker < IssueTracker
  1 +class IssueTrackers::PivotalLabsTracker < IssueTracker
2 2 Label = "pivotal"
3 3 RequiredFields = %w(api_token project_id)
4 4  
... ...
app/models/issue_trackers/redmine_tracker.rb
1   -class RedmineTracker < IssueTracker
  1 +class IssueTrackers::RedmineTracker < IssueTracker
2 2 Label = "redmine"
3 3 RequiredFields = %w(account api_token project_id)
4 4  
... ...
config/application.rb
... ... @@ -46,6 +46,9 @@ module Errbit
46 46 g.test_framework :rspec, :fixture => false
47 47 end
48 48  
  49 + # IssueTracker subclasses use inheritance, so preloading models provides querying consistency in dev mode.
  50 + config.mongoid.preload_models = true
  51 +
49 52 # Configure the default encoding used in templates for Ruby 1.9.
50 53 config.encoding = "utf-8"
51 54  
... ...
config/initializers/issue_tracker_apis.rb
... ... @@ -1,3 +0,0 @@
1   -# Require all issue tracker apis in lib/issue_tracker_apis
2   -Dir.glob(Rails.root.join('lib/issue_tracker_apis/*.rb')).each {|t| require t }
3   -
config/initializers/issue_trackers.rb 0 → 100644
... ... @@ -0,0 +1,6 @@
  1 +# Include nested issue tracker models
  2 +include IssueTrackers
  3 +
  4 +# Require all issue tracker apis in lib/issue_tracker_apis
  5 +Dir.glob(Rails.root.join('lib/issue_tracker_apis/*.rb')).each {|t| require t }
  6 +
... ...
spec/controllers/errs_controller_spec.rb
... ... @@ -269,178 +269,16 @@ describe ErrsController do
269 269 number = 5
270 270 @issue_link = "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets/#{number}.xml"
271 271 body = "<ticket><number type=\"integer\">#{number}</number></ticket>"
272   - stub_request(:post, "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets.xml").to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body )
273   -
274   - post :create_issue, :app_id => err.app.id, :id => err.id
275   - err.reload
276   - end
277   -
278   - it "should make request to Lighthouseapp with err params" do
279   - requested = have_requested(:post, "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets.xml")
280   - WebMock.should requested.with(:headers => {'X-Lighthousetoken' => tracker.api_token})
281   - WebMock.should requested.with(:body => /<tag>errbit<\/tag>/)
282   - WebMock.should requested.with(:body => /<title>\[#{ err.environment }\]\[#{err.where}\] #{err.message.to_s.truncate(100)}<\/title>/)
283   - WebMock.should requested.with(:body => /<body>.+<\/body>/m)
284   - end
285   -
286   - it "should redirect to err page" do
287   - response.should redirect_to( app_err_path(err.app, err) )
288   - end
289   -
290   - it "should create issue link for err" do
291   - err.issue_link.should == @issue_link.sub(/\.xml$/, '')
292   - end
293   - end
294   -
295   - context "redmine tracker" do
296   - let(:notice) { Factory :notice }
297   - let(:tracker) { Factory :redmine_tracker, :app => notice.err.app }
298   - let(:err) { notice.err }
299   -
300   - before(:each) do
301   - number = 5
302   - @issue_link = "#{tracker.account}/issues/#{number}.xml?project_id=#{tracker.project_id}"
303   - body = "<issue><subject>my subject</subject><id>#{number}</id></issue>"
304   - stub_request(:post, "#{tracker.account}/issues.xml").to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body )
305   -
306   - post :create_issue, :app_id => err.app.id, :id => err.id
307   - err.reload
308   - end
309   -
310   - it "should make request to Redmine with err params" do
311   - requested = have_requested(:post, "#{tracker.account}/issues.xml")
312   - WebMock.should requested.with(:headers => {'X-Redmine-API-Key' => tracker.api_token})
313   - WebMock.should requested.with(:body => /<project-id>#{tracker.project_id}<\/project-id>/)
314   - WebMock.should requested.with(:body => /<subject>\[#{ err.environment }\]\[#{err.where}\] #{err.message.to_s.truncate(100)}<\/subject>/)
315   - WebMock.should requested.with(:body => /<description>.+<\/description>/m)
316   - end
317   -
318   - it "should redirect to err page" do
319   - response.should redirect_to( app_err_path(err.app, err) )
320   - end
321   -
322   - it "should create issue link for err" do
323   - err.issue_link.should == @issue_link.sub(/\.xml/, '')
324   - end
325   - end
326   -
327   - context "pivotal tracker" do
328   - let(:notice) { Factory :notice }
329   - let(:tracker) { Factory :pivotal_labs_tracker, :app => notice.err.app, :project_id => 10 }
330   - let(:err) { notice.err }
331   -
332   - before(:each) do
333   - story_id = 5
334   - @issue_link = "https://www.pivotaltracker.com/story/show/#{story_id}"
335   -
336   - project_body = "<project><id>#{tracker.project_id}</id><name>TestProject</name></project>"
337   - stub_request(:get, "https://www.pivotaltracker.com/services/v3/projects/#{tracker.project_id}").
338   - to_return(:status => 200, :headers => {'Location' => @issue_link}, :body => project_body )
339   -
340   - story_body = "<story><name>Test Story</name><id>#{story_id}</id></story>"
341   - stub_request(:post, "https://www.pivotaltracker.com/services/v3/projects/#{tracker.project_id}/stories").
342   - to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => story_body )
343   -
344   - post :create_issue, :app_id => err.app.id, :id => err.id
345   - err.reload
346   - end
347   -
348   - it "should make request to Pivotal Tracker with err params" do
349   - requested = have_requested(:post, "https://www.pivotaltracker.com/services/v3/projects/#{tracker.project_id}/stories")
350   - WebMock.should requested.with(:headers => {'X-Trackertoken' => tracker.api_token})
351   - WebMock.should requested.with(:body => /See this exception on Errbit/)
352   - WebMock.should requested.with(:body => /<name>\[#{ err.environment }\]\[#{err.where}\] #{err.message.to_s.truncate(100)}<\/name>/)
353   - WebMock.should requested.with(:body => /<description>.+<\/description>/m)
354   - end
355   -
356   - it "should redirect to err page" do
357   - response.should redirect_to( app_err_path(err.app, err) )
358   - end
359   -
360   - it "should create issue link for err" do
361   - err.issue_link.should == @issue_link
362   - end
363   - end
364   -
365   - context "mingle tracker" do
366   - let(:notice) { Factory :notice }
367   - let(:tracker) { Factory :mingle_tracker, :app => notice.err.app }
368   - let(:err) { notice.err }
369   -
370   - before(:each) do
371   - number = 5
372   - @issue_link = "#{tracker.account}/projects/#{tracker.project_id}/cards/#{number}.xml"
373   - @basic_auth = tracker.account.gsub("://", "://#{tracker.username}:#{tracker.password}@")
374   - body = "<card><id type=\"integer\">#{number}</id></card>"
375   - stub_request(:post, "#{@basic_auth}/api/v1/projects/#{tracker.project_id}/cards.xml").
  272 + stub_request(:post, "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets.xml").
376 273 to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body )
377 274  
378 275 post :create_issue, :app_id => err.app.id, :id => err.id
379 276 err.reload
380 277 end
381 278  
382   - it "should make request to Mingle with err params" do
383   - requested = have_requested(:post, "#{@basic_auth}/api/v1/projects/#{tracker.project_id}/cards.xml")
384   - WebMock.should requested.with(:headers => {'Content-Type' => 'application/xml'})
385   - WebMock.should requested.with(:body => /FooError: Too Much Bar/)
386   - WebMock.should requested.with(:body => /See this exception on Errbit/)
387   - WebMock.should requested.with(:body => /<card-type-name>Defect<\/card-type-name>/)
388   - end
389   -
390   - it "should redirect to err page" do
391   - response.should redirect_to( app_err_path(err.app, err) )
392   - end
393   -
394   - it "should create issue link for err" do
395   - err.issue_link.should == @issue_link.sub(/\.xml$/, '')
396   - end
397   - end
398   -
399   - context "github issues tracker" do
400   - let(:notice) { Factory :notice }
401   - let(:tracker) { Factory :github_tracker, :app => notice.err.app }
402   - let(:err) { notice.err }
403   -
404   - before(:each) do
405   - number = 5
406   - @issue_link = "https://github.com/#{tracker.project_id}/issues/#{number}"
407   - body = <<EOF
408   -{
409   - "issue": {
410   - "position": 1.0,
411   - "number": #{number},
412   - "votes": 0,
413   - "created_at": "2010/01/21 13:45:59 -0800",
414   - "comments": 0,
415   - "body": "Test Body",
416   - "title": "Test Issue",
417   - "user": "test_user",
418   - "state": "open",
419   - "html_url": "#{@issue_link}"
420   - }
421   -}
422   -EOF
423   - stub_request(:post, "https://#{tracker.username}%2Ftoken:#{tracker.api_token}@github.com/api/v2/json/issues/open/#{tracker.project_id}").
424   - to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body )
425   -
426   - post :create_issue, :app_id => err.app.id, :id => err.id
427   - err.reload
428   - end
429   -
430   - it "should make request to Github with err params" do
431   - requested = have_requested(:post, "https://#{tracker.username}%2Ftoken:#{tracker.api_token}@github.com/api/v2/json/issues/open/#{tracker.project_id}")
432   - WebMock.should requested.with(:headers => {'Content-Type' => 'application/x-www-form-urlencoded'})
433   - WebMock.should requested.with(:body => /title=%5Bproduction%5D%5Bfoo%23bar%5D%20FooError%3A%20Too%20Much%20Bar/)
434   - WebMock.should requested.with(:body => /See%20this%20exception%20on%20Errbit/)
435   - end
436   -
437 279 it "should redirect to err page" do
438 280 response.should redirect_to( app_err_path(err.app, err) )
439 281 end
440   -
441   - it "should create issue link for err" do
442   - err.issue_link.should == @issue_link
443   - end
444 282 end
445 283 end
446 284  
... ...
spec/factories/err_factories.rb
1 1 Factory.define :err do |e|
2   - e.app {|p| p.association :app}
  2 + e.app {|p| p.association :app }
3 3 e.klass 'FooError'
4 4 e.component 'foo'
5 5 e.action 'bar'
... ...
spec/factories/issue_tracker_factories.rb
... ... @@ -23,6 +23,5 @@ end
23 23 Factory.define :github_tracker, :parent => :issue_tracker, :class => :github_tracker do |e|
24 24 e.project_id 'test_account/test_project'
25 25 e.username 'test_username'
26   - e.api_token '12497asfa987'
27 26 end
28 27  
... ...
spec/models/err_spec.rb
... ... @@ -162,9 +162,11 @@ describe Err do
162 162 notice1 = Factory(:notice, :err => @err, :message => 'ERR 1')
163 163 lambda {
164 164 @err.notices.first.destroy
  165 + @err.reload
165 166 }.should change(@err, :notices_count).from(1).to(0)
166 167 end
167 168 end
168 169  
169 170  
170 171 end
  172 +
... ...
spec/models/issue_tracker_spec.rb
... ... @@ -1,5 +0,0 @@
1   -# encoding: utf-8
2   -require 'spec_helper'
3   -
4   -describe IssueTracker do
5   -end
spec/models/issue_trackers/fogbugz_tracker_spec.rb
1   -# encoding: utf-8
2 1 require 'spec_helper'
3 2  
4 3 describe FogbugzTracker do
5   - let(:notice) { Factory :notice }
6   - let(:tracker) { Factory :fogbugz_tracker, :password => "password", :app => notice.err.app }
7   - let(:err) { notice.err }
  4 + it "should create an issue on Fogbugz with err params, and set issue link for err" do
  5 + notice = Factory :notice
  6 + tracker = Factory :fogbugz_tracker, :app => notice.err.app
  7 + err = notice.err
8 8  
9   - before do
10 9 number = 123
11 10 @issue_link = "https://#{tracker.account}.fogbugz.com/default.asp?#{number}"
12   - auth_response = "<response><token>12345</token></response>"
13   - command_response = "<response><case><ixBug>123</ixBug></case></response>"
  11 + response = "<response><token>12345</token><case><ixBug>123</ixBug></case></response>"
14 12 http_mock = mock()
15 13 http_mock.should_receive(:new).and_return(http_mock)
16   - http_mock.should_receive(:request).with(:logon, {:params=>{:email=>"test@example.com", :password=>"password"}}).
17   - and_return(auth_response)
18   - http_mock.should_receive(:request).
19   - and_return(command_response)
  14 + http_mock.should_receive(:request).twice.and_return(response)
20 15 Fogbugz.adapter[:http] = http_mock
21   - end
22 16  
23   - it "should create an issue on Fogbugz with err params, and set issue link for err" do
24 17 err.app.issue_tracker.create_issue(err)
25 18 err.reload
26 19  
... ...
spec/models/issue_trackers/github_tracker_spec.rb 0 → 100644
... ... @@ -0,0 +1,41 @@
  1 +require 'spec_helper'
  2 +
  3 +describe GithubTracker do
  4 + it "should create an issue on Github Issues with err params, and set issue link for err" do
  5 + notice = Factory :notice
  6 + tracker = Factory :github_tracker, :app => notice.err.app
  7 + err = notice.err
  8 +
  9 + number = 5
  10 + @issue_link = "https://github.com/#{tracker.project_id}/issues/#{number}"
  11 + body = <<EOF
  12 +{
  13 + "issue": {
  14 + "position": 1.0,
  15 + "number": #{number},
  16 + "votes": 0,
  17 + "created_at": "2010/01/21 13:45:59 -0800",
  18 + "comments": 0,
  19 + "body": "Test Body",
  20 + "title": "Test Issue",
  21 + "user": "test_user",
  22 + "state": "open",
  23 + "html_url": "#{@issue_link}"
  24 + }
  25 +}
  26 +EOF
  27 + stub_request(:post, "https://#{tracker.username}%2Ftoken:#{tracker.api_token}@github.com/api/v2/json/issues/open/#{tracker.project_id}").
  28 + to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body )
  29 +
  30 + err.app.issue_tracker.create_issue(err)
  31 + err.reload
  32 +
  33 + requested = have_requested(:post, "https://#{tracker.username}%2Ftoken:#{tracker.api_token}@github.com/api/v2/json/issues/open/#{tracker.project_id}")
  34 + WebMock.should requested.with(:headers => {'Content-Type' => 'application/x-www-form-urlencoded'})
  35 + WebMock.should requested.with(:body => /title=%5Bproduction%5D%5Bfoo%23bar%5D%20FooError%3A%20Too%20Much%20Bar/)
  36 + WebMock.should requested.with(:body => /See%20this%20exception%20on%20Errbit/)
  37 +
  38 + err.issue_link.should == @issue_link
  39 + end
  40 +end
  41 +
... ...
spec/models/issue_trackers/lighthouse_tracker_spec.rb 0 → 100644
... ... @@ -0,0 +1,27 @@
  1 +require 'spec_helper'
  2 +
  3 +describe LighthouseTracker do
  4 + it "should create an issue on Lighthouse with err params, and set issue link for err" do
  5 + notice = Factory :notice
  6 + tracker = Factory :lighthouse_tracker, :app => notice.err.app
  7 + err = notice.err
  8 +
  9 + number = 5
  10 + @issue_link = "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets/#{number}.xml"
  11 + body = "<ticket><number type=\"integer\">#{number}</number></ticket>"
  12 + stub_request(:post, "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets.xml").
  13 + to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body )
  14 +
  15 + err.app.issue_tracker.create_issue(err)
  16 + err.reload
  17 +
  18 + requested = have_requested(:post, "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets.xml")
  19 + WebMock.should requested.with(:headers => {'X-Lighthousetoken' => tracker.api_token})
  20 + WebMock.should requested.with(:body => /<tag>errbit<\/tag>/)
  21 + WebMock.should requested.with(:body => /<title>\[#{ err.environment }\]\[#{err.where}\] #{err.message.to_s.truncate(100)}<\/title>/)
  22 + WebMock.should requested.with(:body => /<body>.+<\/body>/m)
  23 +
  24 + err.issue_link.should == @issue_link.sub(/\.xml$/, '')
  25 + end
  26 +end
  27 +
... ...
spec/models/issue_trackers/mingle_tracker_spec.rb 0 → 100644
... ... @@ -0,0 +1,28 @@
  1 +require 'spec_helper'
  2 +
  3 +describe MingleTracker do
  4 + it "should create an issue on Mingle with err params, and set issue link for err" do
  5 + notice = Factory :notice
  6 + tracker = Factory :mingle_tracker, :app => notice.err.app
  7 + err = notice.err
  8 +
  9 + number = 5
  10 + @issue_link = "#{tracker.account}/projects/#{tracker.project_id}/cards/#{number}.xml"
  11 + @basic_auth = tracker.account.gsub("://", "://#{tracker.username}:#{tracker.password}@")
  12 + body = "<card><id type=\"integer\">#{number}</id></card>"
  13 + stub_request(:post, "#{@basic_auth}/api/v1/projects/#{tracker.project_id}/cards.xml").
  14 + to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body )
  15 +
  16 + err.app.issue_tracker.create_issue(err)
  17 + err.reload
  18 +
  19 + requested = have_requested(:post, "#{@basic_auth}/api/v1/projects/#{tracker.project_id}/cards.xml")
  20 + WebMock.should requested.with(:headers => {'Content-Type' => 'application/xml'})
  21 + WebMock.should requested.with(:body => /FooError: Too Much Bar/)
  22 + WebMock.should requested.with(:body => /See this exception on Errbit/)
  23 + WebMock.should requested.with(:body => /<card-type-name>Defect<\/card-type-name>/)
  24 +
  25 + err.issue_link.should == @issue_link.sub(/\.xml$/, '')
  26 + end
  27 +end
  28 +
... ...
spec/models/issue_trackers/pivotal_labs_tracker_spec.rb 0 → 100644
... ... @@ -0,0 +1,30 @@
  1 +require 'spec_helper'
  2 +
  3 +describe PivotalLabsTracker do
  4 + it "should create an issue on Pivotal Tracker with err params, and set issue link for err" do
  5 + notice = Factory :notice
  6 + tracker = Factory :pivotal_labs_tracker, :app => notice.err.app, :project_id => 10
  7 + err = notice.err
  8 +
  9 + story_id = 5
  10 + @issue_link = "https://www.pivotaltracker.com/story/show/#{story_id}"
  11 + project_body = "<project><id>#{tracker.project_id}</id><name>TestProject</name></project>"
  12 + stub_request(:get, "https://www.pivotaltracker.com/services/v3/projects/#{tracker.project_id}").
  13 + to_return(:status => 200, :headers => {'Location' => @issue_link}, :body => project_body )
  14 + story_body = "<story><name>Test Story</name><id>#{story_id}</id></story>"
  15 + stub_request(:post, "https://www.pivotaltracker.com/services/v3/projects/#{tracker.project_id}/stories").
  16 + to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => story_body )
  17 +
  18 + err.app.issue_tracker.create_issue(err)
  19 + err.reload
  20 +
  21 + requested = have_requested(:post, "https://www.pivotaltracker.com/services/v3/projects/#{tracker.project_id}/stories")
  22 + WebMock.should requested.with(:headers => {'X-Trackertoken' => tracker.api_token})
  23 + WebMock.should requested.with(:body => /See this exception on Errbit/)
  24 + WebMock.should requested.with(:body => /<name>\[#{ err.environment }\]\[#{err.where}\] #{err.message.to_s.truncate(100)}<\/name>/)
  25 + WebMock.should requested.with(:body => /<description>.+<\/description>/m)
  26 +
  27 + err.issue_link.should == @issue_link
  28 + end
  29 +end
  30 +
... ...
spec/models/issue_trackers/redmine_tracker_spec.rb 0 → 100644
... ... @@ -0,0 +1,26 @@
  1 +require 'spec_helper'
  2 +
  3 +describe RedmineTracker do
  4 + it "should create an issue on Redmine with err params, and set issue link for err" do
  5 + notice = Factory :notice
  6 + tracker = Factory :redmine_tracker, :app => notice.err.app, :project_id => 10
  7 + err = notice.err
  8 + number = 5
  9 + @issue_link = "#{tracker.account}/issues/#{number}.xml?project_id=#{tracker.project_id}"
  10 + body = "<issue><subject>my subject</subject><id>#{number}</id></issue>"
  11 + stub_request(:post, "#{tracker.account}/issues.xml").
  12 + to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body )
  13 +
  14 + err.app.issue_tracker.create_issue(err)
  15 + err.reload
  16 +
  17 + requested = have_requested(:post, "#{tracker.account}/issues.xml")
  18 + WebMock.should requested.with(:headers => {'X-Redmine-API-Key' => tracker.api_token})
  19 + WebMock.should requested.with(:body => /<project-id>#{tracker.project_id}<\/project-id>/)
  20 + WebMock.should requested.with(:body => /<subject>\[#{ err.environment }\]\[#{err.where}\] #{err.message.to_s.truncate(100)}<\/subject>/)
  21 + WebMock.should requested.with(:body => /<description>.+<\/description>/m)
  22 +
  23 + err.issue_link.should == @issue_link.sub(/\.xml/, '')
  24 + end
  25 +end
  26 +
... ...