rallycat 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/.rspec CHANGED
@@ -1,3 +1,3 @@
1
1
  --color
2
- --format progress
2
+ --format documentation
3
3
  --profile
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rallycat (0.2.0)
4
+ rallycat (0.3.0)
5
5
  builder
6
6
  nokogiri
7
7
  rally_rest_api
data/TODO CHANGED
@@ -1,14 +1,8 @@
1
1
  # Rallycat TODO
2
2
 
3
- * add update behavior for stories
4
- * add update behavior for defects
5
- * add sub-command support (so we can use -p for in-progress)
6
- * give user friendly error messages
7
- * when you don't pass in a task
8
- * when task does not exist
9
- * when owner cannot be found
10
- * update help
11
- * make "help" default output
3
+ * update help (use option_parser help or custom help)
4
+ * add update behavior for stories (maybe)
5
+ * add update behavior for defects (maybe)
12
6
  * Slow as balls? Make nice wait messages
13
7
  * Add man page
14
8
 
data/lib/rallycat/cat.rb CHANGED
@@ -2,6 +2,7 @@ require 'nokogiri'
2
2
 
3
3
  module Rallycat
4
4
  class Cat
5
+ class StoryNotFound < StandardError; end
5
6
 
6
7
  def initialize(rally_api)
7
8
  @rally_api = rally_api
@@ -14,7 +15,9 @@ module Rallycat
14
15
  equal :formatted_id, story_number
15
16
  end
16
17
 
17
- return "Story (#{ story_number }) does not exist." if results.total_result_count == 0
18
+ if results.total_result_count == 0
19
+ raise StoryNotFound, "Story (#{ story_number }) does not exist."
20
+ end
18
21
 
19
22
  consolidate_newlines parse_story(results.first)
20
23
  end
data/lib/rallycat/cli.rb CHANGED
@@ -9,7 +9,8 @@ module Rallycat
9
9
 
10
10
  def run
11
11
  options = {}
12
- option_parser = OptionParser.new do |opts|
12
+
13
+ global = OptionParser.new do |opts|
13
14
  opts.on('-u USERNAME', '--username') do |user|
14
15
  options[:user] = user
15
16
  end
@@ -18,40 +19,67 @@ module Rallycat
18
19
  options[:password] = password
19
20
  end
20
21
 
21
- opts.on('-b', '--blocked') do |blocked|
22
- options[:blocked] = true
22
+ opts.on('-h', '--help') do
23
+ @stdout.puts Rallycat::Help.new
24
+ exit
23
25
  end
26
+ end
24
27
 
25
- opts.on('-i', '--in-progress') do |in_progress|
26
- options[:in_progress] = true
27
- end
28
+ commands = {
29
+ 'cat' => OptionParser.new,
28
30
 
29
- opts.on('-c', '--completed') do |completed|
30
- options[:completed] = true
31
- end
31
+ 'update' => OptionParser.new do |opts|
32
+ opts.banner = 'Usage: rallycat update <story number> [options]'
32
33
 
33
- opts.on('-d', '--defined') do |defined|
34
- options[:defined] = true
35
- end
34
+ opts.on('-b', '--blocked') do |blocked|
35
+ options[:blocked] = true
36
+ end
36
37
 
37
- opts.on('-o OWNER', '--owner') do |owner|
38
- options[:owner] = owner
39
- end
40
- end
38
+ opts.on('-p', '--in-progress') do |in_progress|
39
+ options[:in_progress] = true
40
+ end
41
+
42
+ opts.on('-c', '--completed') do |completed|
43
+ options[:completed] = true
44
+ end
45
+
46
+ opts.on('-d', '--defined') do |defined|
47
+ options[:defined] = true
48
+ end
41
49
 
42
- option_parser.parse! @argv
50
+ opts.on('-o OWNER', '--owner') do |owner|
51
+ options[:owner] = owner
52
+ end
53
+ end,
43
54
 
44
- case @argv.shift
55
+ 'help' => OptionParser.new
56
+ }
57
+
58
+ global.order! @argv
59
+
60
+ command = @argv.shift
61
+ commands[command].order! @argv if commands.has_key? command
62
+
63
+ case command
45
64
  when 'cat'
46
65
  api = Rallycat::Connection.new(options[:user], options[:password]).api
47
66
 
48
- @stdout.puts Rallycat::Cat.new(api).story(@argv.shift)
67
+ story_number = @argv.shift
68
+
69
+ abort 'The "cat" command requires a story or defect number.' unless story_number
70
+
71
+ begin
72
+ @stdout.puts Rallycat::Cat.new(api).story(story_number)
73
+ rescue Rallycat::Cat::StoryNotFound => e
74
+ abort e.message
75
+ end
49
76
  when 'update'
50
77
  api = Rallycat::Connection.new(options[:user], options[:password]).api
51
78
 
52
-
53
79
  task_number = @argv.shift
54
80
 
81
+ abort 'The "update" command requires a task number.' unless task_number
82
+
55
83
  opts = {}
56
84
  opts[:blocked] = true if options[:blocked]
57
85
  opts[:state] = "In-Progress" if options[:in_progress]
@@ -59,12 +87,16 @@ module Rallycat
59
87
  opts[:state] = "Defined" if options[:defined]
60
88
  opts[:owner] = options[:owner] if options[:owner]
61
89
 
62
- @stdout.puts Rallycat::Update.new(api).task(task_number, opts)
90
+ begin
91
+ @stdout.puts Rallycat::Update.new(api).task(task_number, opts)
92
+ rescue Rallycat::Update::UserNotFound, Rallycat::Update::TaskNotFound => e
93
+ abort e.message
94
+ end
63
95
  when 'help'
64
96
  # `puts` calls `to_s`
65
97
  @stdout.puts Rallycat::Help.new
66
98
  else
67
- @stdout.puts 'only support for `cat` exists at the moment.'
99
+ @stdout.puts "'#{command}' is not a supported command. See 'rallycat help'."
68
100
  end
69
101
  end
70
102
  end
data/lib/rallycat/help.rb CHANGED
@@ -13,13 +13,17 @@ Configuration:
13
13
  at runtime and will take higher precedence than the configuration file.
14
14
 
15
15
  Global Options:
16
- -u [USERNAME] # The Rally user
17
- -p [PASSWORD] # The password for the Rally user
18
- -h, --help # Displays this help text
16
+ -u [USERNAME] # The Rally user
17
+ -p [PASSWORD] # The password for the Rally user
18
+ -h, --help # Displays this help text
19
19
 
20
20
  Commands:
21
- rallycat cat [STORY NUMBER] # Displays the user story
22
- rallycat help # Displays this help text
21
+ rallycat cat <story number> # Displays the user story or defect
22
+ rallycat update <task number> # Displays the user story
23
+ [--blocked | -b] [--in-progress | -i]
24
+ [--completed | -c] [--defined | -d]
25
+ [--owner | -o <fullname>]
26
+ rallycat help # Displays this help text
23
27
 
24
28
 
25
29
  HELP
@@ -1,38 +1,88 @@
1
1
  module Rallycat
2
2
  class Update
3
+ class UserNotFound < StandardError; end
4
+ class TaskNotFound < StandardError; end
5
+
3
6
  def initialize(api)
4
7
  @api = api
5
8
  end
6
9
 
7
10
  def task(task_number, attributes)
8
- results = @api.find(:task) do
9
- equal :formatted_id, task_number
10
- end
11
-
12
- task = results.first
11
+ task = find_task(task_number)
13
12
 
13
+ # When we set the state of the task and we don't explicitly set it to
14
+ # blocked we want to remove the block. In our workflow, when you change
15
+ # the state of the task it is also unblocked.
14
16
  if attributes[:state] && !attributes[:blocked]
15
17
  attributes[:blocked] = false
16
18
  end
17
19
 
18
- user_name = attributes[:owner]
19
-
20
- if user_name
21
- user_results = @api.find(:user) do
22
- equal :display_name, user_name
23
- end
24
- attributes[:owner] = user_results.first.login_name
20
+ # The value in attributes[:owner] should be equal to the desired owner's
21
+ # display name in Rally. We need to fetch the user in order to get their
22
+ # login_name. The login_name is needed in order to set the owner
23
+ # attribute of a task.
24
+ #
25
+ # We decided it would be easier for the user to enter 'John Smith'
26
+ # instead of 'john.smith@foobar.com'.
27
+ if display_name = attributes[:owner]
28
+ login_name = find_user(display_name)
29
+ attributes[:owner] = login_name
25
30
  end
26
31
 
27
32
  task.update(attributes)
28
33
 
29
34
  messages = []
30
35
 
31
- messages << %{Task (#{task_number}) was set to "#{attributes[:state]}".} if attributes[:state]
32
- messages << "Task (#{task_number}) was blocked." if attributes[:blocked]
33
- messages << %{Task (#{task_number}) was assigned to "#{user_name}".} if attributes[:owner]
36
+ if attributes[:state]
37
+ messages << state_message(task_number, attributes[:state])
38
+ end
39
+
40
+ if attributes[:blocked]
41
+ messages << blocked_message(task_number)
42
+ end
43
+
44
+ if attributes[:owner]
45
+ messages << owner_message(task_number, display_name)
46
+ end
34
47
 
35
48
  messages.join("\n")
36
49
  end
50
+
51
+ private
52
+ def find_task(task_number)
53
+ results = @api.find(:task) do
54
+ equal :formatted_id, task_number
55
+ end
56
+
57
+ if results.total_result_count == 0
58
+ raise TaskNotFound, "Task (#{task_number}) does not exist."
59
+ end
60
+
61
+ results.first
62
+ end
63
+
64
+ def find_user(display_name)
65
+ user_results = @api.find(:user) do
66
+ equal :display_name, display_name
67
+ end
68
+
69
+ if user_results.total_result_count == 0
70
+ raise UserNotFound, "User (#{display_name}) does not exist."
71
+ end
72
+
73
+ user_results.first.login_name
74
+ end
75
+
76
+ def state_message(task_number, state)
77
+ %{Task (#{task_number}) was set to "#{state}".}
78
+ end
79
+
80
+ def blocked_message(task_number)
81
+ "Task (#{task_number}) was blocked."
82
+ end
83
+
84
+ def owner_message(task_number, owner)
85
+ %{Task (#{task_number}) was assigned to "#{owner}".}
86
+ end
37
87
  end
38
88
  end
@@ -1,3 +1,3 @@
1
1
  module Rallycat
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -18,9 +18,34 @@ describe 'Rallycat' do
18
18
  sout.rewind
19
19
  sout.read.should include('# [US4567] - [Rework] Change link to button')
20
20
  end
21
+
22
+ it 'aborts when a story number is not given' do
23
+ sout = StringIO.new
24
+
25
+ cli = Rallycat::CLI.new %w{ -u foo.bar@rallycat.com -p password cat }, sout
26
+
27
+ lambda {
28
+ Artifice.activate_with RallyStoryResponder.new do
29
+ cli.run
30
+ end
31
+ }.should raise_error(SystemExit, 'The "cat" command requires a story or defect number.')
32
+ end
33
+
34
+ it 'aborts when the story does not exist' do
35
+ sout = StringIO.new
36
+
37
+ cli = Rallycat::CLI.new %w{ -u foo.bar@rallycat.com -p password cat US9999 }, sout
38
+
39
+ lambda {
40
+ Artifice.activate_with RallyNoResultsResponder.new do
41
+ cli.run
42
+ end
43
+ }.should raise_error(SystemExit, 'Story (US9999) does not exist.')
44
+ end
21
45
  end
22
46
 
23
47
  context 'help' do
48
+
24
49
  it 'displays a help screen to the user' do
25
50
  string_io = StringIO.new
26
51
 
@@ -31,7 +56,7 @@ describe 'Rallycat' do
31
56
  string_io.rewind
32
57
  expected = string_io.read
33
58
 
34
- expected.should include("rallycat cat [STORY NUMBER]")
59
+ expected.should include("rallycat cat <story number>")
35
60
  expected.should include("Displays the user story")
36
61
  end
37
62
  end
@@ -43,7 +68,7 @@ describe 'Rallycat' do
43
68
  it 'sets the state to in-progress' do
44
69
  sout = StringIO.new
45
70
 
46
- cli = Rallycat::CLI.new %w{ update -i TA6666 -u foo.bar@rallycat.com -p password }, sout
71
+ cli = Rallycat::CLI.new %w{ update -p TA6666 -u foo.bar@rallycat.com -p password }, sout
47
72
 
48
73
  task_responder = RallyTaskUpdateResponder.new
49
74
 
@@ -114,6 +139,48 @@ describe 'Rallycat' do
114
139
  sout.rewind
115
140
  sout.read.should include('Task (TA6666) was assigned to "Freddy Fender".')
116
141
  end
142
+
143
+ it 'aborts when the owner does not exist' do
144
+ sout = StringIO.new
145
+
146
+ cli = Rallycat::CLI.new %w{ update -o Norman\ Notreal TA6666 -u foo.bar@rallycat.com -p password }, sout
147
+
148
+ task_responder = RallyTaskUpdateResponder.new
149
+
150
+ lambda {
151
+ Artifice.activate_with task_responder do
152
+ cli.run
153
+ end
154
+ }.should raise_error(SystemExit, 'User (Norman Notreal) does not exist.')
155
+ end
156
+
157
+ it 'aborts when a task number is not given' do
158
+ sout = StringIO.new
159
+
160
+ cli = Rallycat::CLI.new %w{ -u foo.bar@rallycat.com -p password update }, sout
161
+
162
+ task_responder = RallyTaskUpdateResponder.new
163
+
164
+ lambda {
165
+ Artifice.activate_with task_responder do
166
+ cli.run
167
+ end
168
+ }.should raise_error(SystemExit, 'The "update" command requires a task number.')
169
+ end
170
+
171
+ it 'aborts when a task does not exist' do
172
+ sout = StringIO.new
173
+
174
+ cli = Rallycat::CLI.new %w{ update -i TA9999 -u foo.bar@rallycat.com -p password }, sout
175
+
176
+ task_responder = RallyNoResultsResponder.new
177
+
178
+ lambda {
179
+ Artifice.activate_with task_responder do
180
+ cli.run
181
+ end
182
+ }.should raise_error(SystemExit, 'Task (TA9999) does not exist.')
183
+ end
117
184
  end
118
185
  end
119
186
  end
@@ -129,5 +129,30 @@ STORY
129
129
  end
130
130
  end
131
131
 
132
+ it 'raises when the story does not exist' do
133
+ responder = lambda do |env|
134
+ [200, {}, [
135
+ <<-XML
136
+ <QueryResult rallyAPIMajor="1" rallyAPIMinor="17">
137
+ <Errors/>
138
+ <Warnings/>
139
+ <TotalResultCount>0</TotalResultCount>
140
+ <StartIndex>1</StartIndex>
141
+ <PageSize>20</PageSize>
142
+ <Results/>
143
+ </QueryResult>
144
+ XML
145
+ ]]
146
+ end
147
+
148
+ Artifice.activate_with responder do
149
+ story_num = "US9999"
150
+ cat = Rallycat::Cat.new(@api)
151
+
152
+ lambda {
153
+ cat.story(story_num)
154
+ }.should raise_error(Rallycat::Cat::StoryNotFound, 'Story (US9999) does not exist.')
155
+ end
156
+ end
132
157
  end
133
158
 
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe Rallycat::CLI do
4
4
  it 'should default to STDOUT' do
5
- STDOUT.should_receive(:puts).with 'only support for `cat` exists at the moment.'
5
+ STDOUT.should_receive(:puts).with "'' is not a supported command. See 'rallycat help'."
6
6
  cli = Rallycat::CLI.new []
7
7
  cli.run
8
8
  end
@@ -17,6 +17,6 @@ describe Rallycat::CLI, '#run' do
17
17
  cli.run
18
18
 
19
19
  sout.rewind
20
- sout.read.should == "only support for `cat` exists at the moment.\n"
20
+ sout.read.should == "'foo' is not a supported command. See 'rallycat help'.\n"
21
21
  end
22
22
  end
@@ -82,7 +82,6 @@ describe Rallycat::Update do
82
82
  message.should include('Task (TA6666) was assigned to "Freddy Fender"')
83
83
  end
84
84
 
85
- # require "pry"; binding.pry
86
85
  post_request = responder.requests[4] # this is the request that actually updates the task
87
86
  post_request.should be_post
88
87
  post_request.url.should == 'https://rally1.rallydev.com/slm/webservice/1.17/task/12345'
@@ -92,4 +91,31 @@ describe Rallycat::Update do
92
91
  body.should include('<Owner>fred.fender@testing.com</Owner>')
93
92
  end
94
93
  end
94
+
95
+ it 'raises when the user could not be found' do
96
+ responder = RallyTaskUpdateResponder.new
97
+
98
+ Artifice.activate_with responder do
99
+ task_num = "TA6666"
100
+ update = Rallycat::Update.new(@api)
101
+
102
+ lambda {
103
+ update.task(task_num, state: 'Completed', owner: 'Norman Notreal')
104
+ }.should raise_error(Rallycat::Update::UserNotFound, 'User (Norman Notreal) does not exist.')
105
+ end
106
+ end
107
+
108
+ it 'raises when the task could not be found' do
109
+ responder = RallyNoResultsResponder.new
110
+
111
+ Artifice.activate_with responder do
112
+ task_num = "TA6666"
113
+ update = Rallycat::Update.new(@api)
114
+
115
+ lambda {
116
+ update.task(task_num, state: 'Completed')
117
+ }.should raise_error(Rallycat::Update::TaskNotFound, 'Task (TA6666) does not exist.')
118
+ end
119
+
120
+ end
95
121
  end
@@ -0,0 +1,28 @@
1
+ class RallyNoResultsResponder
2
+ attr_reader :requests
3
+
4
+ def initialize
5
+ @requests = []
6
+ end
7
+
8
+ def last_request
9
+ @requests.last
10
+ end
11
+
12
+ def call(env)
13
+ @requests << request = Rack::Request.new(env)
14
+
15
+ [200, {}, [
16
+ <<-XML
17
+ <QueryResult rallyAPIMajor="1" rallyAPIMinor="17">
18
+ <Errors/>
19
+ <Warnings/>
20
+ <TotalResultCount>0</TotalResultCount>
21
+ <StartIndex>1</StartIndex>
22
+ <PageSize>20</PageSize>
23
+ <Results/>
24
+ </QueryResult>
25
+ XML
26
+ ]]
27
+ end
28
+ end
@@ -82,7 +82,7 @@ class RallyTaskUpdateResponder
82
82
  XML
83
83
  ]]
84
84
  else
85
- [200, {}, ['<else />']]
85
+ RallyNoResultsResponder.new.call(env)
86
86
  end
87
87
  end
88
88
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rallycat
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -14,7 +14,7 @@ date: 2012-07-17 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: builder
17
- requirement: &70176499099720 !ruby/object:Gem::Requirement
17
+ requirement: &70303674111620 !ruby/object:Gem::Requirement
18
18
  none: false
19
19
  requirements:
20
20
  - - ! '>='
@@ -22,10 +22,10 @@ dependencies:
22
22
  version: '0'
23
23
  type: :runtime
24
24
  prerelease: false
25
- version_requirements: *70176499099720
25
+ version_requirements: *70303674111620
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: rally_rest_api
28
- requirement: &70176499099160 !ruby/object:Gem::Requirement
28
+ requirement: &70303674111020 !ruby/object:Gem::Requirement
29
29
  none: false
30
30
  requirements:
31
31
  - - ! '>='
@@ -33,10 +33,10 @@ dependencies:
33
33
  version: '0'
34
34
  type: :runtime
35
35
  prerelease: false
36
- version_requirements: *70176499099160
36
+ version_requirements: *70303674111020
37
37
  - !ruby/object:Gem::Dependency
38
38
  name: nokogiri
39
- requirement: &70176499098740 !ruby/object:Gem::Requirement
39
+ requirement: &70303674110340 !ruby/object:Gem::Requirement
40
40
  none: false
41
41
  requirements:
42
42
  - - ! '>='
@@ -44,7 +44,7 @@ dependencies:
44
44
  version: '0'
45
45
  type: :runtime
46
46
  prerelease: false
47
- version_requirements: *70176499098740
47
+ version_requirements: *70303674110340
48
48
  description: The Rally website sucks. CLI is better.
49
49
  email:
50
50
  - adam@adamtanner.org
@@ -80,6 +80,7 @@ files:
80
80
  - spec/lib/rallycat/update_spec.rb
81
81
  - spec/spec_helper.rb
82
82
  - spec/support/rally_defect_responder.rb
83
+ - spec/support/rally_no_results_responder.rb
83
84
  - spec/support/rally_story_responder.rb
84
85
  - spec/support/rally_task_update_responder.rb
85
86
  homepage: https://github.com/adamtanner/rallycat
@@ -96,7 +97,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
96
97
  version: '0'
97
98
  segments:
98
99
  - 0
99
- hash: 333992134023580695
100
+ hash: 4474919884476792167
100
101
  required_rubygems_version: !ruby/object:Gem::Requirement
101
102
  none: false
102
103
  requirements:
@@ -105,7 +106,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
105
106
  version: '0'
106
107
  segments:
107
108
  - 0
108
- hash: 333992134023580695
109
+ hash: 4474919884476792167
109
110
  requirements: []
110
111
  rubyforge_project:
111
112
  rubygems_version: 1.8.7
@@ -121,5 +122,6 @@ test_files:
121
122
  - spec/lib/rallycat/update_spec.rb
122
123
  - spec/spec_helper.rb
123
124
  - spec/support/rally_defect_responder.rb
125
+ - spec/support/rally_no_results_responder.rb
124
126
  - spec/support/rally_story_responder.rb
125
127
  - spec/support/rally_task_update_responder.rb