git_workflow 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. data/README.markdown +77 -0
  2. data/Rakefile +33 -0
  3. data/bin/git-finish +4 -0
  4. data/bin/git-start +4 -0
  5. data/bin/git-workflow-setup +4 -0
  6. data/features/core_functionality/4049578_need_the_ability_to_start_a_new_branch.feature +15 -0
  7. data/features/core_functionality/4049611_need_the_ability_to_finish_a_specified_branch.feature +17 -0
  8. data/features/core_functionality/4056359_start_with_local_branch_already_present.feature +16 -0
  9. data/features/core_functionality/4056363_finish_the_current_branch.feature +17 -0
  10. data/features/core_functionality/4056389_changes_to_pt_story_name_cause_branch_issues.feature +25 -0
  11. data/features/core_functionality/4135658_control_output_verbosity_using_v_switch.feature +32 -0
  12. data/features/core_functionality/4468313_get_config_value_for_is_producing_an_error_log.feature +43 -0
  13. data/features/extensions/4058326_display_story_description_on_start.feature +14 -0
  14. data/features/git_branching/4135501_allow_branch_start_point_to_be_specified_from_command_line.feature +27 -0
  15. data/features/hooks/4069174_investigate_the_effort_required_to_add_pre_amp_post_hooks.feature +24 -0
  16. data/features/hooks/4199841_need_hooks_for_my_workflow.feature +51 -0
  17. data/features/hooks/4199845_need_hooks_for_sanger_workflow.feature +66 -0
  18. data/features/hooks/4205913_output_from_rake_tests_should_be_seen.feature +19 -0
  19. data/features/hooks/4424828_git_start_is_missing_pt_integration_for_my_hooks.feature +15 -0
  20. data/features/pivotal_tracker_api/4133291_chores_go_straight_to_accepted_not_to_finished.feature +15 -0
  21. data/features/release/4132264_fix_the_libxml_warnings_from_nokogiri.feature +20 -0
  22. data/features/step_definitions/aruba_extensions.rb +4 -0
  23. data/features/step_definitions/configuration_steps.rb +38 -0
  24. data/features/step_definitions/git_steps.rb +102 -0
  25. data/features/step_definitions/misc_steps.rb +3 -0
  26. data/features/step_definitions/pivotal_tracker_steps.rb +43 -0
  27. data/features/step_definitions/project_code_steps.rb +35 -0
  28. data/features/support/aruba.rb +1 -0
  29. data/features/support/hooks/configuration.rb +4 -0
  30. data/features/support/hooks/environment.rb +49 -0
  31. data/features/support/hooks/pivotal_tracker.rb +170 -0
  32. data/lib/git_workflow.rb +5 -0
  33. data/lib/git_workflow/callbacks.rb +16 -0
  34. data/lib/git_workflow/callbacks/pivotal_tracker_support.rb +64 -0
  35. data/lib/git_workflow/callbacks/remote_git_branch_support.rb +9 -0
  36. data/lib/git_workflow/callbacks/styles/debug.rb +58 -0
  37. data/lib/git_workflow/callbacks/styles/default.rb +43 -0
  38. data/lib/git_workflow/callbacks/styles/mine.rb +52 -0
  39. data/lib/git_workflow/callbacks/styles/sanger.rb +48 -0
  40. data/lib/git_workflow/callbacks/test_code_support.rb +29 -0
  41. data/lib/git_workflow/command_line.rb +46 -0
  42. data/lib/git_workflow/commands.rb +4 -0
  43. data/lib/git_workflow/commands/base.rb +31 -0
  44. data/lib/git_workflow/commands/finish.rb +36 -0
  45. data/lib/git_workflow/commands/setup.rb +157 -0
  46. data/lib/git_workflow/commands/start.rb +30 -0
  47. data/lib/git_workflow/configuration.rb +93 -0
  48. data/lib/git_workflow/core_ext.rb +106 -0
  49. data/lib/git_workflow/git.rb +143 -0
  50. data/lib/git_workflow/logger.yaml +27 -0
  51. data/lib/git_workflow/logging.rb +77 -0
  52. data/lib/git_workflow/story.rb +96 -0
  53. data/spec/core_functionality/4056539_support_http_proxy_spec.rb +68 -0
  54. data/spec/core_functionality/4058365_bad_request_on_story_update_spec.rb +13 -0
  55. data/spec/core_functionality/4058394_make_commands_more_verbose_spec.rb +41 -0
  56. data/spec/core_functionality/4058719_error_if_none_of_the_required_configuration_values_are_set_spec.rb +21 -0
  57. data/spec/core_functionality/4058861_git_config_returns_empty_strings_which_should_be_nil_spec.rb +59 -0
  58. data/spec/core_functionality/4172431_add_git_workflow_setup_command_to_make_setup_easier_spec.rb +148 -0
  59. data/spec/core_functionality/4199841_need_callbacks_for_my_workflow_spec.rb +97 -0
  60. data/spec/extensions/4199841_need_callbacks_for_my_workflow_spec.rb +167 -0
  61. data/spec/extensions/4205913_output_from_rake_tests_should_be_seen_spec.rb +34 -0
  62. data/spec/git_branching/4058723_branches_end_in_if_the_last_character_is_invalid_spec.rb +40 -0
  63. data/spec/git_branching/4059824_code_is_not_using_workflow_localbranchconvention_to_decode_branch_names_on_finish_spec.rb +115 -0
  64. data/spec/git_branching/4216087_support_pushing_branches_spec.rb +44 -0
  65. data/spec/hooks/4199845_need_hooks_for_sanger_workflow_spec.rb +27 -0
  66. data/spec/pivotal_tracker_api/4056381_pt_api_token_not_being_passed_in_headers_spec.rb +40 -0
  67. data/spec/pivotal_tracker_api/4056638_library_code_using_localhost_spec.rb +16 -0
  68. data/spec/pivotal_tracker_api/4056661_content_type_header_should_be_text_xml_on_updates_spec.rb +16 -0
  69. data/spec/pivotal_tracker_api/4058718_if_pt_username_is_not_set_use_user_name_spec.rb +26 -0
  70. data/spec/pivotal_tracker_api/4146016_decode_the_xml_encoded_text_of_description_and_name_spec.rb +63 -0
  71. data/spec/shared_examples/configuration.rb +10 -0
  72. data/spec/shared_examples/story.rb +17 -0
  73. data/spec/spec_helper.rb +18 -0
  74. metadata +220 -0
@@ -0,0 +1,19 @@
1
+ # rake features FEATURE=features/hooks/4205913_output_from_rake_tests_should_be_seen.feature
2
+ @hooks @needs_service
3
+ Feature: Output from rake tests should be seen
4
+ Scenario: The output should be appearing
5
+ Given my Pivotal Tracker configuration is setup as normal
6
+ And I have "mine" callbacks enabled
7
+
8
+ Given the story 4205913 exists
9
+ And the name of story 4205913 is "Output from rake tests should be seen"
10
+
11
+ Given the local branch "4205913_output_from_rake_tests_should_be_seen" exists
12
+ And the local branch "4205913_output_from_rake_tests_should_be_seen" is active
13
+ And the rake task "spec" will fail
14
+ And the rake task "features" will fail
15
+
16
+ When I execute "git finish"
17
+
18
+ Then the stdout should contain "Running spec"
19
+ And the stderr should contain "Failing spec"
@@ -0,0 +1,15 @@
1
+ # rake features FEATURE=4424828_git_start_is_missing_pt_integration_for_my_hooks
2
+ @hooks @needs_service @my_workflow
3
+ Feature: 'git start' should integrate with PT for my workflow
4
+ Scenario: Starting a new branch with my workflow
5
+ Given my Pivotal Tracker configuration is setup as normal
6
+ And I have "mine" callbacks enabled
7
+
8
+ Given the story 4424828 exists
9
+ And the name of story 4424828 is "'git start' is missing PT integration for my hooks"
10
+
11
+ When I successfully execute "git start 4424828"
12
+
13
+ Then the branch "4424828_git_start_is_missing_pt_integration_for_my_hooks" should be active
14
+ And the owner of story 4424828 should be "Matthew Denner"
15
+ And story 4424828 should be started
@@ -0,0 +1,15 @@
1
+ # rake features FEATURE=features/pivotal_tracker_api/4133291_chores_go_straight_to_accepted_not_to_finished.feature
2
+ @pivotal_tracker_api @needs_service
3
+ Feature: Finishing a chore means accepting it
4
+ Scenario: Finishing a chore
5
+ Given my Pivotal Tracker configuration is setup as normal
6
+
7
+ Given the story 4133291 exists
8
+ And story 4133291 is a chore
9
+ And the name of story 4133291 is "Chores go straight to 'accepted' not to 'finished'"
10
+
11
+ Given the local branch "4133291_chores_go_straight_to_accepted_not_to_finished" exists
12
+
13
+ When I successfully execute "git finish 4133291"
14
+
15
+ Then story 4133291 should be accepted
@@ -0,0 +1,20 @@
1
+ # rake features FEATURE=features/release/4132264_fix_the_libxml_warnings_from_nokogiri.feature
2
+ @release @needs_service
3
+ Feature: Fix the LibXML warnings from Nokogiri
4
+ Background:
5
+ Given my Pivotal Tracker configuration is setup as normal
6
+
7
+ Given the story 4132264 exists
8
+ And the name of story 4132264 is "Fix the LibXML warnings from Nokogiri"
9
+
10
+ Scenario: Starting a branch
11
+ When I successfully execute "git start 4132264"
12
+
13
+ Then the output should not contain "I_KNOW_I_AM_USING_AN_OLD_AND_BUGGY_VERSION_OF_LIBXML2"
14
+
15
+ Scenario: Finishing a branch
16
+ Given the local branch "4132264_fix_the_libxml_warnings_from_nokogiri" exists
17
+
18
+ When I successfully execute "git finish 4132264"
19
+
20
+ Then the output should not contain "I_KNOW_I_AM_USING_AN_OLD_AND_BUGGY_VERSION_OF_LIBXML2"
@@ -0,0 +1,4 @@
1
+ # NOTE: only here because installed Aruba doesn't have this for some reason
2
+ Given /^I cd to the root$/ do
3
+ @dirs = [ @dirs.first ]
4
+ end
@@ -0,0 +1,38 @@
1
+ Given /^my Pivotal Tracker configuration is setup as normal$/ do
2
+ Given %Q{my Pivotal Tracker username is "Matthew Denner"}
3
+ Given %Q{my Pivotal Tracker project ID is 93630}
4
+ Given %Q{my Pivotal Tracker token is 1234567890}
5
+ Given %Q{my local branch naming convention is "${number}_${name}"}
6
+ Given %Q{my remote branch naming convention is "${number}_${name}"}
7
+ end
8
+
9
+ Given /^my git username is "([^\"]+)"$/ do |username|
10
+ git_configure('user.name', username)
11
+ end
12
+
13
+ Given /^my Pivotal Tracker username is "([^\"]+)"$/ do |username|
14
+ git_configure('pt.username', username)
15
+ end
16
+
17
+ Given /^my Pivotal Tracker project ID is (\d+)$/ do |id|
18
+ git_configure('pt.projectid', id.to_i)
19
+ mock_service.project_id = id.to_i
20
+ end
21
+
22
+ Given /^my Pivotal Tracker token is ([a-zA-Z0-9]+)$/ do |token|
23
+ git_configure('pt.token', token)
24
+ end
25
+
26
+ Given /^my (local|remote) branch naming convention is "((?:(?:(?:\$\{[a-z][a-z_]+\})|[a-zA-Z0-9_]+)+))"$/ do |branch,convention|
27
+ git_configure("workflow.#{ branch }branchconvention", convention)
28
+ end
29
+
30
+ Given /^I have "([^\"]+)" callbacks enabled$/ do |callbacks|
31
+ git_configure("workflow.callbacks", callbacks)
32
+ end
33
+
34
+ def git_configure(key, value)
35
+ in_current_dir do
36
+ %x{git config '#{ key }' '#{ value }'}
37
+ end
38
+ end
@@ -0,0 +1,102 @@
1
+ def execute_silently(command)
2
+ %x{#{ command } > /dev/null 2>&1}
3
+ end
4
+
5
+ Given /^the local branch "([^\"]+)" exists$/ do |branch|
6
+ in_current_dir do
7
+ execute_silently(%Q{git checkout -b #{ branch } master})
8
+ execute_silently(%Q{git checkout master})
9
+ end
10
+ end
11
+
12
+ Given /^the local branch "([^\"]+)" is active$/ do |branch|
13
+ in_current_dir do
14
+ execute_silently(%Q{git checkout #{ branch }})
15
+ end
16
+ end
17
+
18
+ Then /^the branch "([^\"]*)" should be active$/ do |name|
19
+ in_current_dir do
20
+ %x{git branch}.split("\n").map(&:strip).should include("* #{ name }")
21
+ end
22
+ end
23
+
24
+ Then /^the branch "([^\"]*)" should be merged into master$/ do |name|
25
+ in_current_dir do
26
+ execute_silently(%Q{git checkout master})
27
+ %x{git branch --no-merge}.split("\n").map(&:strip).should_not include(name)
28
+ end
29
+ end
30
+
31
+ Then /^the branch "([^\"]+)" should not be merged into master$/ do |name|
32
+ in_current_dir do
33
+ execute_silently(%Q{git checkout master})
34
+ %x{git branch --no-merge}.split("\n").map(&:strip).should include(name)
35
+ end
36
+ end
37
+
38
+ When /^I( successfully)? execute "git (start|finish)([^\"]*)"$/ do |success,command,arguments|
39
+ root_path = File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
40
+ real_command = File.join(root_path, 'bin', "git-#{ command }")
41
+ lib_path = File.join(root_path, 'lib')
42
+ When %Q{I#{ success } run "IGNORE_GIT_GLOBAL=true ruby -I#{ lib_path } -rrubygems #{ real_command } #{ arguments }"}
43
+ end
44
+
45
+ Given /^I commit "([^\"]+)"$/ do |filename|
46
+ in_current_dir do
47
+ execute_silently(%Q{git add '#{ filename }'})
48
+ execute_silently(%Q{git commit -m 'Committing "#{ filename }"' '#{ filename }'})
49
+ end
50
+ end
51
+
52
+ Then /^the parent of branch "([^\"]+)" should be "([^\"]+)"$/ do |child,parent|
53
+ in_current_dir do
54
+ # This is probably the most hacky piece of shit I've written so far!
55
+ execute_silently("git checkout #{ child }")
56
+ output = %x{git show-branch --topo-order --current}
57
+ lines = output.split("\n")
58
+
59
+ # Determine the branches that are matching and their offset in the output lines.
60
+ matchers = []
61
+ until lines.empty?
62
+ line = lines.shift
63
+ break if line =~ /^---.*$/
64
+
65
+ if line =~ /^(\s*)\*\s+\[#{ child }\]/
66
+ matchers[ $1.length ] = '\*'
67
+ elsif line =~ /^(\s*)!\s\[#{ parent }\]/
68
+ matchers[ $1.length ] = '\+'
69
+ end
70
+ end
71
+ raise StandardError, "Cannot find branches in list:\n#{ output }" unless [ '\*', '\+' ].all? { |m| matchers.include?(m) }
72
+
73
+ # Find a line that matches the appropriate commit. Basically when the two branches
74
+ # share a commit. This doesn't work if the branches are intermerging.
75
+ regexp = Regexp.new("^#{ matchers.map { |m| m || ' ' }.join }.+")
76
+
77
+ matched_line = nil
78
+ until lines.empty?
79
+ line = lines.shift
80
+ next unless line =~ regexp
81
+ matched_line = line
82
+ break
83
+ end
84
+ raise StandardError, "#{ child } appears not to be related to #{ parent }" if matched_line.nil?
85
+ end
86
+ end
87
+
88
+ Then /^the local and remote "([^\"]+)" branches should agree$/ do |branch|
89
+ Then %Q{the local branch "#{ branch }" and remote branch "#{ branch }" should agree}
90
+ end
91
+
92
+ Then /^the local branch "([^\"]+)" and remote branch "([^\"]+)" should agree$/ do |local,remote|
93
+ in_current_dir do
94
+ ref_to_sha = %x{git show-ref}.split("\n").inject({}) do |ref_to_sha, line|
95
+ match = line.match(%r{^([a-f0-9]+)\s+refs/(.+)$}) or raise StandardError, "Cannot parse ref line '#{ line }'"
96
+ ref_to_sha[ match[ 2 ] ] = match[ 1 ]
97
+ ref_to_sha
98
+ end
99
+
100
+ ref_to_sha[ "heads/#{ local }" ].should == ref_to_sha[ "remotes/origin/#{ remote }" ]
101
+ end
102
+ end
@@ -0,0 +1,3 @@
1
+ Then /^fail now$/ do
2
+ raise StandardError, 'Failing as requested'
3
+ end
@@ -0,0 +1,43 @@
1
+ Transform /^story (\d+)$/ do |id|
2
+ id.to_i
3
+ end
4
+
5
+ Given /^the story (\d+) exists$/ do |id|
6
+ create_story(id)
7
+ end
8
+
9
+ Given /^story (\d+) is a (feature|bug|chore)$/ do |id, type|
10
+ for_story(id) do |story|
11
+ story.story_type = type
12
+ end
13
+ end
14
+
15
+ Given /^the name of story (\d+) is "([^\"]+)"$/ do |id,name|
16
+ for_story(id) do |story|
17
+ story.name = name
18
+ end
19
+ end
20
+
21
+ Given /^the description of story (\d+) is "([^\"]+)"$/ do |id,description|
22
+ for_story(id) do |story|
23
+ story.description = description
24
+ end
25
+ end
26
+
27
+ Then /^story (\d+) should be (started|finished|delivered|rejected|accepted)$/ do |id,state|
28
+ for_story(id) do |story|
29
+ story.current_state.should == state
30
+ end
31
+ end
32
+
33
+ Then /^story (\d+) should have a comment of "([^\"]+)"$/ do |id,comment|
34
+ for_story(id) do |story|
35
+ story.comments.should include(comment)
36
+ end
37
+ end
38
+
39
+ Then /^the owner of story (\d+) should be "([^\"]+)"$/ do |id,owner|
40
+ for_story(id) do |story|
41
+ story.owned_by.should == owner
42
+ end
43
+ end
@@ -0,0 +1,35 @@
1
+ Given /^the rake task "([^\"]+)" will fail$/ do |task|
2
+ in_current_dir do
3
+ File.open('Rakefile', 'a+') do |file|
4
+ file << <<-END_OF_RAKE_TASK
5
+ task(:#{ task }) do
6
+ $stdout.puts "Running #{ task }"
7
+ $stderr.puts "Failing #{ task }"
8
+
9
+ raise 'Task "#{ task }" is setup to fail'
10
+ end
11
+ END_OF_RAKE_TASK
12
+ end
13
+
14
+ # Add and commit the file
15
+ %x{git add Rakefile}
16
+ %x{git commit -m 'Upated #{ task } to fail' Rakefile}
17
+ end
18
+ end
19
+
20
+ Given /^the rake task "([^\"]+)" will succeed$/ do |task|
21
+ in_current_dir do
22
+ File.open('Rakefile', 'a+') do |file|
23
+ file << <<-END_OF_RAKE_TASK
24
+ task(:#{ task }) do
25
+ $stdout.puts "Running #{ task }"
26
+ $stderr.puts "Succeeding #{ task }"
27
+ end
28
+ END_OF_RAKE_TASK
29
+ end
30
+
31
+ # Add and commit the file
32
+ %x{git add Rakefile}
33
+ %x{git commit -m 'Upated #{ task } to succeed' Rakefile}
34
+ end
35
+ end
@@ -0,0 +1 @@
1
+ require 'aruba'
@@ -0,0 +1,4 @@
1
+ # Ensures that before every scenario the configuration is emptied
2
+ Before do
3
+ @configuration = {}
4
+ end
@@ -0,0 +1,49 @@
1
+ # Ensures that the environment is setup appropriately for all scenarios
2
+ Before do
3
+ # Modify the PATH so that the bin directory is available
4
+ @path_before = ENV['PATH']
5
+ ENV['PATH'] = "#{ File.expand_path(File.join(File.dirname(__FILE__), %w{.. .. .. bin})) }:#{ ENV['PATH'] }"
6
+
7
+ # We always need a local repository
8
+ Given %Q{a directory named "local_repository"}
9
+ end
10
+
11
+ Before('@needs_remote_repository') do
12
+ # Sets up the remote repository
13
+ Given %Q{a directory named "remote_repository"}
14
+ Given %Q{I cd to "remote_repository"}
15
+ Given %Q{I successfully run "git init --bare ."}
16
+
17
+ # Creates the local one by cloning the remote repository
18
+ Given %Q{I cd to the root}
19
+ Given %Q{I successfully run "git clone remote_repository local_repository"}
20
+
21
+ # This then sets up the master branch, both locally and remotely
22
+ Given %Q{I cd to "local_repository"}
23
+ Given %Q{an empty file named "basic-file"}
24
+ Given %Q{I successfully run "git add basic-file"}
25
+ Given %Q{I successfully run "git commit -a -m 'Initial project'"}
26
+ Given %Q{I successfully run "git push origin master"}
27
+ end
28
+
29
+ Before('~@needs_remote_repository') do
30
+ Given %Q{I cd to "local_repository"}
31
+ Given %Q{I successfully run "git init ."}
32
+ Given %Q{an empty file named "basic-file"}
33
+ Given %Q{I successfully run "git add basic-file"}
34
+ Given %Q{I successfully run "git commit -a -m 'Initial project'"}
35
+ end
36
+
37
+ Before do
38
+ Given %Q{I cd to the root}
39
+ Given %Q{I cd to "local_repository"}
40
+ end
41
+
42
+ # Tidy up the environmental setup
43
+ After do
44
+ ENV['PATH'] = @path_before
45
+ end
46
+
47
+ def project_root
48
+ File.join(File.dirname(__FILE__), %w{.. .. ..})
49
+ end
@@ -0,0 +1,170 @@
1
+ require 'logger'
2
+ require 'net/http'
3
+ require 'sinatra'
4
+ require 'ostruct'
5
+ require 'builder'
6
+
7
+ # Nokogiri doesn't like buggy LibXML2
8
+ I_KNOW_I_AM_USING_AN_OLD_AND_BUGGY_VERSION_OF_LIBXML2 = true
9
+ require 'nokogiri'
10
+
11
+ # This is a fix-up for sinatra so that we can silence the output. It allows us to
12
+ # pass a Hash of options for webrick in the options passed to the #run! method.
13
+ class Sinatra::Base
14
+ class << self
15
+ def run!(options={})
16
+ set options
17
+ handler = detect_rack_handler
18
+ handler_name = handler.name.gsub(/.*::/, '')
19
+ handler.run(self, { :Host => bind, :Port => port }.merge(options.fetch(:webrick, {}))) do |server|
20
+ trap(:INT) do
21
+ ## Use thins' hard #stop! if available, otherwise just #stop
22
+ server.respond_to?(:stop!) ? server.stop! : server.stop
23
+ end
24
+ set :running, true
25
+ end
26
+ rescue Errno::EADDRINUSE => e
27
+ puts "== Someone is already performing on port #{port}!"
28
+ end
29
+ end
30
+ end
31
+
32
+ class PivotalTrackerService
33
+ class MockService < Sinatra::Base
34
+ get '/services/v3/projects/:project_id/stories/:story_id' do |_, story_id|
35
+ handle_story(story_id) do |story|
36
+ xml = Builder::XmlMarkup.new(:indent => 2)
37
+ xml.instruct!
38
+ xml.story {
39
+ xml.id(story.story_id, :type => 'integer')
40
+ [ :story_type, :current_state, :name, :owned_by, :description ].each do |element|
41
+ xml.tag!(element, story.send(element))
42
+ end
43
+ }
44
+ xml.to_s
45
+ end
46
+ end
47
+
48
+ put '/services/v3/projects/:project_id/stories/:story_id' do |_, story_id|
49
+ handle_story(story_id) do |story|
50
+ Nokogiri::XML(request.body.read).xpath('/story/*').each do |element|
51
+ story.send(:"#{ element.name }=", element.content)
52
+ end
53
+ end
54
+ end
55
+
56
+ post '/services/v3/projects/:project_id/stories/:story_id/notes' do |_, story_id|
57
+ handle_story(story_id) do |story|
58
+ comment = Nokogiri::XML(request.body.read).xpath('/note/text').first or raise StandardError, 'No comment body found!'
59
+ story.comments.push(comment.content)
60
+ end
61
+ end
62
+
63
+ def handle_story(id, &block)
64
+ self.class.handle_story(id, &block)
65
+ end
66
+
67
+ class << self
68
+ def stories
69
+ @stories ||= {}
70
+ end
71
+
72
+ def handle_story(id, &block)
73
+ story = stories[ id ] or raise StandardError, "Story #{ id } does not appear to exist"
74
+ yield(story)
75
+ end
76
+
77
+ def reset_stories!
78
+ @stories = {}
79
+ end
80
+
81
+ def create_story(id)
82
+ raise StandardError, "Story #{ id } already exists" unless stories[ id ].nil?
83
+ stories[ id ] = story = OpenStruct.new(
84
+ :story_id => id,
85
+ :story_type => 'feature',
86
+ :current_state => 'not yet started',
87
+ :owned_by => '',
88
+ :name => '',
89
+ :comments => []
90
+ )
91
+ end
92
+
93
+ def for_story(id, &block)
94
+ story = stories[ id ] or raise StandardError, "Story #{ id } is undefined"
95
+ yield(story)
96
+ end
97
+ end
98
+ end
99
+
100
+ attr_writer :project_id
101
+
102
+ def initialize(host, port)
103
+ @host, @port = host, port.to_i
104
+ end
105
+
106
+ def run!
107
+ kill_previous_instance
108
+ start_sinatra
109
+ wait_for_sinatra_to_startup!
110
+ end
111
+
112
+ private
113
+
114
+ def kill_previous_instance
115
+ Thread.list.first.kill if Thread.list.size > 2
116
+ end
117
+
118
+ def start_sinatra
119
+ Thread.new do
120
+ # The effort you have to go through to silence Sinatra/WEBrick!
121
+ logger = Logger.new(STDERR)
122
+ logger.level = Logger::FATAL
123
+
124
+ MockService.run!(:host => @host, :port => @port, :webrick => { :Logger => logger, :AccessLog => [] })
125
+ end
126
+ end
127
+
128
+ # We have to pause execution until Sinatra has come up. This makes a number of attempts to
129
+ # retrieve the root document. If it runs out of attempts it raises an exception
130
+ def wait_for_sinatra_to_startup!
131
+ (1..10).each do |_|
132
+ begin
133
+ Net::HTTP.get(URI.parse('http://localhost:7000/'))
134
+ return
135
+ rescue Errno::ECONNREFUSED => exception
136
+ sleep(1)
137
+ end
138
+ end
139
+
140
+ raise StandardError, "Our dummy webservice did not start up in time!"
141
+ end
142
+ end
143
+
144
+ mock_service = PivotalTrackerService.new('localhost', 7000)
145
+ mock_service.run!
146
+ self.class.instance_eval do
147
+ define_method(:mock_service) { mock_service }
148
+ end
149
+ eval <<-END_OF_WORLD_HOOKS
150
+ def create_story(id)
151
+ PivotalTrackerService::MockService.create_story(id)
152
+ end
153
+
154
+ def for_story(id, &block)
155
+ PivotalTrackerService::MockService.for_story(id, &block)
156
+ end
157
+ END_OF_WORLD_HOOKS
158
+
159
+ # This ensures that we have a behaviour similar to that of Pivotal Tracker.
160
+ Before('@needs_service') do
161
+ @_http_proxy_before = ENV['http_proxy']
162
+ ENV['http_proxy'] = 'http://localhost:7000/'
163
+ end
164
+
165
+ After('@needs_service') do
166
+ ENV['http_proxy'] = @_http_proxy_before
167
+
168
+ PivotalTrackerService::MockService.reset_stories!
169
+ end
170
+