git_workflow 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+