rallycat 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -3,7 +3,6 @@
3
3
  .bundle
4
4
  .config
5
5
  .yardoc
6
- Gemfile.lock
7
6
  InstalledFiles
8
7
  _yardoc
9
8
  coverage
@@ -15,3 +14,4 @@ spec/reports
15
14
  test/tmp
16
15
  test/version_tmp
17
16
  tmp
17
+ vendor
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --format progress
3
+ --profile
data/Gemfile CHANGED
@@ -2,3 +2,9 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in rallycat.gemspec
4
4
  gemspec
5
+
6
+ group :development, :test do
7
+ gem 'artifice'
8
+ gem 'pry'
9
+ gem 'rspec'
10
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,44 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ rallycat (0.1.0)
5
+ builder
6
+ nokogiri
7
+ rally_rest_api
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ artifice (0.6)
13
+ rack-test
14
+ builder (3.0.0)
15
+ coderay (1.0.6)
16
+ diff-lcs (1.1.3)
17
+ method_source (0.7.1)
18
+ nokogiri (1.5.3)
19
+ pry (0.9.9.6)
20
+ coderay (~> 1.0.5)
21
+ method_source (~> 0.7.1)
22
+ slop (>= 2.4.4, < 3)
23
+ rack (1.4.1)
24
+ rack-test (0.6.1)
25
+ rack (>= 1.0)
26
+ rally_rest_api (1.0.3)
27
+ rspec (2.10.0)
28
+ rspec-core (~> 2.10.0)
29
+ rspec-expectations (~> 2.10.0)
30
+ rspec-mocks (~> 2.10.0)
31
+ rspec-core (2.10.1)
32
+ rspec-expectations (2.10.0)
33
+ diff-lcs (~> 1.1.3)
34
+ rspec-mocks (2.10.1)
35
+ slop (2.4.4)
36
+
37
+ PLATFORMS
38
+ ruby
39
+
40
+ DEPENDENCIES
41
+ artifice
42
+ pry
43
+ rallycat!
44
+ rspec
data/Rakefile CHANGED
@@ -1,2 +1,8 @@
1
1
  #!/usr/bin/env rake
2
- require "bundler/gem_tasks"
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task :default => :spec
data/TODO ADDED
@@ -0,0 +1,50 @@
1
+ # Rallycat TODO
2
+
3
+ * Slow as balls? Make nice wait messages
4
+ * Reconcile USER vs username (cli arg vs config attribute)
5
+ * Add help
6
+ * Add man page
7
+ * Integration specs
8
+
9
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
10
+
11
+ ## Ideas
12
+
13
+ US666 - My Story
14
+ =================
15
+
16
+ ````
17
+ rallycat update --complete --blocked [-cdpsb] -o "Adam Tanner" TA1932`
18
+
19
+ rallycat update -p -o "Emilio Cavazos" TA9832
20
+ rallycat update -b TA1823 -m "Fix specs."
21
+ ````
22
+
23
+ ### Notes
24
+
25
+ * Unblock by setting a new state
26
+ * Don't allow setting a new state and blocking, -b blocks current state
27
+
28
+
29
+ ## Stats
30
+ * Stories in progress
31
+ * Stories accepted
32
+ * Stories not accepted
33
+ * Total To-Do Hours
34
+
35
+
36
+
37
+ rally.vim -> Making User Stories from Vim
38
+
39
+ ## Rallycat Futures
40
+
41
+ cat tasks | rallycat boilerplate
42
+
43
+ [DESCRIPTION] [ESTIMATE] [TO-DO]
44
+
45
+ ### Task Example
46
+
47
+ Show Andrew on frontpage ## 2 ## 2
48
+ Do some shit with Redis ## 3 ## 3
49
+ Redo everything ever ## 140 ## 140
50
+
data/bin/rallycat CHANGED
@@ -1,10 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "rallycat"
3
+ require 'rallycat'
4
4
 
5
- case ARGV[0]
6
- when "cat"
7
- exec "echo '#{Rallycat::Cat.new.story(ARGV[1])}' | less"
8
- else
9
- puts "YOU DONE FUCKED UP. SEE HELP WHEN AVAILABLE"
10
- end
5
+ Rallycat::CLI.new(ARGV).run
data/lib/rallycat/cat.rb CHANGED
@@ -1,26 +1,69 @@
1
+ require 'nokogiri'
2
+
1
3
  module Rallycat
2
4
  class Cat
3
5
 
6
+ def initialize(rally_api)
7
+ @rally_api = rally_api
8
+ end
9
+
4
10
  def story(story_number)
5
- <<-STORY
11
+ story_type = story_number.start_with?('US') ? :hierarchical_requirement : :defect
12
+
13
+ results = @rally_api.find(story_type, fetch: true) do
14
+ equal :formatted_id, story_number
15
+ end
16
+
17
+ return "Story (#{ story_number }) does not exist." if results.total_result_count == 0
18
+
19
+ consolidate_newlines parse_story(results.first)
20
+ end
21
+
22
+ private
6
23
 
24
+ # NOTE: story.elements.keys => all properties exposed by rally
25
+ def parse_story(story)
26
+ <<-TEXT
7
27
 
8
- ------------------------------------------------
9
- ## [#{story_number}] Rally Title
10
- ------------------------------------------------
28
+ # [#{story.formatted_i_d}] - #{story.name}
11
29
 
12
- As a user
13
- ISBAT be able to update a record
30
+ Plan Estimate: #{story.plan_estimate}
31
+ State: #{story.schedule_state}
32
+ Task Actual: #{story.task_actual_total}
33
+ Task Estimate: #{story.task_estimate_total}
34
+ Task Remaining: #{story.task_remaining_total}
35
+ Owner: #{story.owner}
14
36
 
15
- * [TA1234] I should be able to go to a page
16
- * [TA1234] I should be able to go to a page
17
- * [TA1234] I should be able to go to a page
37
+ ## DESCRIPTION
18
38
 
39
+ #{HtmlToTextConverter.new(story.description).parse}
19
40
 
20
- ------------------------------------------------
41
+ ## TASKS
21
42
 
22
- STORY
43
+ #{parse_tasks(story)}
44
+
45
+ TEXT
23
46
  end
24
47
 
48
+ def parse_tasks(story)
49
+ return '' unless story.tasks
50
+
51
+ tasks = story.tasks
52
+ sorted_tasks = tasks.sort_by{ |t| t.task_index }
53
+
54
+ # This is an example of a task formatted in plain text:
55
+ #
56
+ # [TA12345] [C] The name of the task.
57
+ #
58
+ sorted_tasks.map do |task|
59
+ state = task.state == 'In-Progress' ? 'P' : task.state[0]
60
+ "[#{task.formatted_i_d}] [#{state}] #{task.name}"
61
+ end.join("\n")
62
+ end
63
+
64
+ def consolidate_newlines(story)
65
+ story.gsub(/\n{3,}/, "\n\n")
66
+ end
25
67
  end
26
- end
68
+ end
69
+
@@ -0,0 +1,35 @@
1
+ require 'optparse'
2
+
3
+ module Rallycat
4
+ class CLI
5
+ def initialize(argv, stdout=STDOUT)
6
+ @argv = argv
7
+ @stdout = stdout
8
+ end
9
+
10
+ def run
11
+ options = {}
12
+ option_parser = OptionParser.new do |opts|
13
+ opts.on('-u USER') do |user|
14
+ options[:user] = user
15
+ end
16
+
17
+ opts.on('-p PASSWORD') do |password|
18
+ options[:password] = password
19
+ end
20
+ end
21
+
22
+ option_parser.parse! @argv
23
+
24
+ case @argv.shift
25
+ when 'cat'
26
+ api = Rallycat::Connection.new(options[:user], options[:password]).api
27
+
28
+ @stdout.puts Rallycat::Cat.new(api).story(@argv.shift)
29
+ else
30
+ @stdout.puts 'only support for `cat` exists at the moment.'
31
+ end
32
+ end
33
+ end
34
+ end
35
+
@@ -0,0 +1,44 @@
1
+ require 'yaml'
2
+ require 'rally_rest_api'
3
+
4
+ module Rallycat
5
+ class InvalidConfigError < StandardError; end
6
+ class InvalidCredentialsError < StandardError; end
7
+
8
+ class Connection
9
+ attr_reader :api
10
+
11
+ def initialize(username=nil, password=nil)
12
+ @username = username
13
+ @password = password
14
+
15
+ config = parse_config
16
+
17
+ begin
18
+ @api = RallyRestAPI.new \
19
+ base_url: 'https://rally1.rallydev.com/slm',
20
+ username: config.fetch('username'),
21
+ password: config.fetch('password')
22
+ rescue Rally::NotAuthenticatedError
23
+ raise InvalidCredentialsError.new('Your Rally credentials are invalid.')
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def parse_config
30
+ rc_file_path = File.expand_path '~/.rallycatrc'
31
+
32
+ if @username || @password
33
+ { 'username' => @username, 'password' => @password }
34
+ else
35
+ begin
36
+ YAML.load_file(rc_file_path)
37
+ rescue StandardError
38
+ message = "Your rallycat config file is missing or invalid. Please RTFM."
39
+ raise InvalidConfigError.new message
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,56 @@
1
+ require 'nokogiri'
2
+
3
+ class HtmlToTextConverter
4
+
5
+ def initialize(html_string)
6
+ @html_string = html_string
7
+ end
8
+
9
+ def parse
10
+ return '' unless @html_string
11
+
12
+ html = pre_parse
13
+
14
+ @fragment = Nokogiri::HTML.fragment(html)
15
+
16
+ add_newline_after_block_elements
17
+ parse_whitespace
18
+ parse_lists
19
+
20
+ @fragment.content
21
+ end
22
+
23
+ private
24
+
25
+ def pre_parse
26
+ html = @html_string.gsub(/>\s+</, '><') # remove whitespace between tags
27
+ html.gsub('&nbsp;', ' ') # replace nbsp with regular space
28
+ end
29
+
30
+ def add_newline_after_block_elements
31
+ @fragment.css('br, p, div, ul, ol').each { |node| node.after("\n") }
32
+ end
33
+
34
+ def parse_whitespace
35
+ @fragment.xpath('.//text()').each do |node|
36
+ if node.content =~ /\S/ # has non-whitespace characters
37
+ node.content = node.content.squeeze(' ') # consolidate whitespace
38
+ node.content = node.content.lstrip
39
+ else
40
+ # remove nodes that are only whitespace
41
+ node.remove unless node.content.include?("\n")
42
+ end
43
+ end
44
+ end
45
+
46
+ def parse_lists
47
+ # Make lists pretty:
48
+ # * list item 1
49
+ # * list item 2
50
+ # * list item 3
51
+ @fragment.css('li').each do |node|
52
+ node.content = " * #{node.content}\n"
53
+ end
54
+ end
55
+
56
+ end
@@ -1,3 +1,3 @@
1
1
  module Rallycat
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/rallycat.rb CHANGED
@@ -1,5 +1,8 @@
1
- require "rallycat/version"
2
- require "rallycat/cat"
1
+ require 'rallycat/cat'
2
+ require 'rallycat/cli'
3
+ require 'rallycat/connection'
4
+ require 'rallycat/html_to_text_converter'
5
+ require 'rallycat/version'
3
6
 
4
7
  module Rallycat
5
8
  # Your code goes here...
data/rallycat.gemspec CHANGED
@@ -14,4 +14,10 @@ Gem::Specification.new do |gem|
14
14
  gem.name = "rallycat"
15
15
  gem.require_paths = ["lib"]
16
16
  gem.version = Rallycat::VERSION
17
+
18
+ # rally depends on this but does not declare it as a dependency
19
+ gem.add_dependency 'builder'
20
+ gem.add_dependency 'rally_rest_api'
21
+ gem.add_dependency 'nokogiri'
22
+
17
23
  end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Rallycat' do
4
+
5
+ context 'cat' do
6
+
7
+ it 'fetches, parses and outputs the rally story requested by the user' do
8
+ auth_responder = lambda do |env|
9
+ # 'https://rally1.rallydev.com/slm/webservice/current/user'
10
+ [200, {}, ['<foo>bar</foo>']]
11
+ end
12
+
13
+ sout = StringIO.new
14
+ cli = nil
15
+
16
+ Artifice.activate_with auth_responder do
17
+ cli = Rallycat::CLI.new %w{ cat US4567 -u=foo.bar@rallycat.com -p=password'}, sout
18
+ end
19
+
20
+ story_responder = RallyStoryResponder.new
21
+
22
+ Artifice.activate_with story_responder.endpoint do
23
+ cli.run
24
+ end
25
+
26
+ sout.rewind
27
+ sout.read.should include('# [US4567] - [Rework] Change link to button')
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,133 @@
1
+ require 'spec_helper'
2
+ require 'cgi'
3
+
4
+ describe Rallycat::Cat, '#story' do
5
+
6
+ before do
7
+ responder = lambda do |env|
8
+ # 'https://rally1.rallydev.com/slm/webservice/current/user'
9
+ [200, {}, ['<foo>bar</foo>']]
10
+ end
11
+
12
+ Artifice.activate_with responder do
13
+ @api = Rallycat::Connection.new('foo.bar@rallycat.com', 'password').api
14
+ end
15
+ end
16
+
17
+ it 'outputs the rally story' do
18
+
19
+ expected = <<-STORY
20
+
21
+ # [US4567] - [Rework] Change link to button
22
+
23
+ Plan Estimate: 1.0
24
+ State: In-Progress
25
+ Task Actual: 0.0
26
+ Task Estimate: 6.5
27
+ Task Remaining: 0.5
28
+ Owner: scootin@fruity.com
29
+
30
+ ## DESCRIPTION
31
+
32
+ This is the story
33
+
34
+ * Remember to do this.
35
+ * And this too.
36
+
37
+ ## TASKS
38
+
39
+ [TA1234] [C] Change link to button
40
+ [TA1235] [P] Add confirmation
41
+ [TA1236] [D] Code Review
42
+ [TA1237] [D] QA Test
43
+
44
+ STORY
45
+
46
+ responder = RallyStoryResponder.new
47
+
48
+ Artifice.activate_with responder.endpoint do
49
+ story_num = 'US7176'
50
+ cat = Rallycat::Cat.new(@api)
51
+ cat.story(story_num).should == expected
52
+ end
53
+ end
54
+
55
+ it 'parses the defect' do
56
+
57
+ expected = <<-STORY
58
+
59
+ # [DE1234] - [Rework] Change link to button
60
+
61
+ Plan Estimate: 1.0
62
+ State: In-Progress
63
+ Task Actual: 0.0
64
+ Task Estimate: 6.5
65
+ Task Remaining: 0.5
66
+ Owner: scootin@fruity.com
67
+
68
+ ## DESCRIPTION
69
+
70
+ This is a defect.
71
+
72
+ ## TASKS
73
+
74
+ STORY
75
+
76
+ responder = RallyDefectResponder.new
77
+
78
+ Artifice.activate_with responder.endpoint do
79
+ story_num = 'DE1234'
80
+ cat = Rallycat::Cat.new(@api)
81
+ cat.story(story_num).should == expected
82
+ end
83
+ end
84
+
85
+ it 'displays nothing under the tasks section when there are no tasks' do
86
+
87
+
88
+ responder = lambda do |env|
89
+ [200, {}, [
90
+ <<-XML
91
+ <QueryResult>
92
+ <Results>
93
+ <Object>
94
+ <FormattedID>US4567</FormattedID>
95
+ <Name>Sky Touch</Name>
96
+ <Description>#{ CGI::escapeHTML('<div>As a user<br /> ISBAT touch the sky.</div>') }</Description>
97
+ </Object>
98
+ </Results>
99
+ <TotalResultCount>1</TotalResultCount>
100
+ </QueryResult>
101
+ XML
102
+ ]]
103
+ end
104
+
105
+ expected = <<-STORY
106
+
107
+ # [US4567] - Sky Touch
108
+
109
+ Plan Estimate:
110
+ State:
111
+ Task Actual:
112
+ Task Estimate:
113
+ Task Remaining:
114
+ Owner:
115
+
116
+ ## DESCRIPTION
117
+
118
+ As a user
119
+ ISBAT touch the sky.
120
+
121
+ ## TASKS
122
+
123
+ STORY
124
+
125
+ Artifice.activate_with responder do
126
+ story_num = 'US4567'
127
+ cat = Rallycat::Cat.new(@api)
128
+ cat.story(story_num).should == expected
129
+ end
130
+ end
131
+
132
+ end
133
+
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rallycat::CLI do
4
+ it 'should default to STDOUT' do
5
+ STDOUT.should_receive(:puts).with 'only support for `cat` exists at the moment.'
6
+ cli = Rallycat::CLI.new []
7
+ cli.run
8
+ end
9
+ end
10
+
11
+ describe Rallycat::CLI, '#run' do
12
+
13
+ it 'should execute command' do
14
+ sout = StringIO.new
15
+ cli = Rallycat::CLI.new ['help'], sout
16
+
17
+ cli.run
18
+
19
+ sout.rewind
20
+ sout.read.should == "only support for `cat` exists at the moment.\n"
21
+ end
22
+ end
@@ -0,0 +1,91 @@
1
+ require "spec_helper"
2
+ require "yaml"
3
+
4
+ describe Rallycat::Connection do
5
+
6
+ before do
7
+ @responder = lambda do |env|
8
+ # 'https://rally1.rallydev.com/slm/webservice/current/user'
9
+ [200, {}, ['<foo>bar</foo>']]
10
+ end
11
+
12
+ @rc_file_path = File.expand_path("~/.rallycatrc")
13
+ end
14
+
15
+ describe '#initialize' do
16
+
17
+ it "raises when no config file is present" do
18
+ # Force file to not exist.
19
+ YAML.stub(:load_file).with(@rc_file_path).and_raise(Errno::ENOENT)
20
+
21
+ lambda {
22
+ Artifice.activate_with @responder do
23
+ @connection = Rallycat::Connection.new
24
+ end
25
+ }.should raise_error Rallycat::InvalidConfigError,
26
+ 'Your rallycat config file is missing or invalid. Please RTFM.'
27
+ end
28
+
29
+ it "raises when the credentials are invalid" do
30
+ responder = lambda do |env|
31
+ [401, {}, "Invalid, homes."]
32
+ end
33
+
34
+ YAML.stub(:load_file).with(@rc_file_path).and_return({
35
+ 'username' => "",
36
+ 'password' => ""
37
+ })
38
+
39
+ lambda {
40
+ Artifice.activate_with responder do
41
+ @connection = Rallycat::Connection.new
42
+ end
43
+ }.should raise_error Rallycat::InvalidCredentialsError,
44
+ "Your Rally credentials are invalid."
45
+ end
46
+ end
47
+
48
+ describe "#api" do
49
+
50
+ before do
51
+ responder = lambda do |env|
52
+ # 'https://rally1.rallydev.com/slm/webservice/current/user'
53
+ [200, {}, ['<foo>bar</foo>']]
54
+ end
55
+
56
+ YAML.stub(:load_file).with(@rc_file_path).and_return({
57
+ 'username' => "bitches@rallydev.com",
58
+ 'password' => "wesuckatsoftware"
59
+ })
60
+
61
+
62
+ end
63
+
64
+ it "creates a rally connection using user's rallycatrc config file" do
65
+ Artifice.activate_with @responder do
66
+ @connection = Rallycat::Connection.new
67
+ end
68
+
69
+ rally_connection = @connection.api
70
+
71
+ rally_connection.should be_kind_of(RallyRestAPI)
72
+
73
+ rally_connection.username.should eq("bitches@rallydev.com")
74
+ rally_connection.password.should eq("wesuckatsoftware")
75
+ end
76
+
77
+ it "creates a rally connection given the username and password provided" do
78
+ Artifice.activate_with @responder do
79
+ @connection = Rallycat::Connection.new("hoes@rallydev.com", "wesuckatpasswords")
80
+ end
81
+
82
+ rally_connection = @connection.api
83
+
84
+ rally_connection.should be_kind_of(RallyRestAPI)
85
+
86
+ rally_connection.username.should eq("hoes@rallydev.com")
87
+ rally_connection.password.should eq("wesuckatpasswords")
88
+ end
89
+
90
+ end
91
+ end
@@ -0,0 +1,78 @@
1
+ require 'spec_helper'
2
+
3
+ describe HtmlToTextConverter do
4
+
5
+ it 'strips away <p> tags and adds a newline to the end of the paragraph' do
6
+ html = '<p>This is a one sentence paragraph.</p>'
7
+ text = HtmlToTextConverter.new(html).parse
8
+ text.should == "This is a one sentence paragraph.\n"
9
+ end
10
+
11
+ it 'returns an empty string when given nil' do
12
+ text = HtmlToTextConverter.new(nil).parse
13
+ text.should == ''
14
+ end
15
+
16
+ it 'strips away <p> tags and adds a newline to the end of each paragraph' do
17
+ html = '<p>Paragraph 1</p><p>Paragraph 2</p>'
18
+ text = HtmlToTextConverter.new(html).parse
19
+ text.should == "Paragraph 1\nParagraph 2\n"
20
+ end
21
+
22
+ it 'strips away spaces in between tags' do
23
+ html = '<p>Paragraph 1</p> <p>Paragraph 2</p>'
24
+ text = HtmlToTextConverter.new(html).parse
25
+ text.should == "Paragraph 1\nParagraph 2\n"
26
+ end
27
+
28
+ it 'consolidates whitespace' do
29
+ html = '<p>I like <b>hot sauce</b>.</p>'
30
+ text = HtmlToTextConverter.new(html).parse
31
+ text.should == "I like hot sauce.\n"
32
+ end
33
+
34
+ it 'converts nbsp to a regular space' do
35
+ html = ' hello&nbsp;world '
36
+ text = HtmlToTextConverter.new(html).parse
37
+ text.should == " hello world "
38
+ end
39
+
40
+ it 'turns <br /> into \n' do
41
+ html = '<div>foo<br /> bar</div>'
42
+ text = HtmlToTextConverter.new(html).parse
43
+ text.should == "foo\nbar\n"
44
+ end
45
+
46
+ it 'indents and stars items in a list' do
47
+ html = '<ol><li>Item 1</li><li>Item 2</li><li>Item 3</li></ol>'
48
+ text = HtmlToTextConverter.new(html).parse
49
+ text.should == " * Item 1\n * Item 2\n * Item 3\n\n"
50
+ end
51
+
52
+ it 'converts complex HTML to clean plain text' do
53
+ html = "<div>This is a story:</div> <div><br /></div> <div> <ul> <li>list item 1</li> <li>list item 2</li> </ul> </div> <div>We need to do something important.</div> <div><br /></div> <div>NOTE:</div> <div>- a note with the word <b><font color=\"#ff0000\">red</font></b>:</div> <div>Some more text.</div> <div><br /></div> <div><b>QA:</b></div> <div>Instructions for QA.</div>"
54
+
55
+ expected = <<-TEXT
56
+ This is a story:
57
+
58
+
59
+ * list item 1
60
+ * list item 2
61
+
62
+
63
+ We need to do something important.
64
+
65
+
66
+ NOTE:
67
+ - a note with the word red:
68
+ Some more text.
69
+
70
+
71
+ QA:
72
+ Instructions for QA.
73
+ TEXT
74
+
75
+ text = HtmlToTextConverter.new(html).parse
76
+ text.should == expected
77
+ end
78
+ end
@@ -0,0 +1,20 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper.rb"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+
8
+ require 'rallycat'
9
+ require 'pry'
10
+ require 'rally_rest_api'
11
+ require 'artifice'
12
+
13
+ root = File.expand_path('../..', __FILE__)
14
+ Dir["#{root}/spec/support/**/*.rb"].each { |f| require f }
15
+
16
+ RSpec.configure do |config|
17
+ config.treat_symbols_as_metadata_keys_with_true_values = true
18
+ config.run_all_when_everything_filtered = true
19
+ config.filter_run :focus
20
+ end
@@ -0,0 +1,42 @@
1
+ class RallyDefectResponder
2
+
3
+ attr_reader :requests
4
+
5
+ def initialize
6
+ @requests = []
7
+ end
8
+
9
+ def last_request
10
+ @requests.last
11
+ end
12
+
13
+ def endpoint
14
+ lambda do |env|
15
+ @requests << request = Rack::Request.new(env)
16
+
17
+ case request.url
18
+ when "https://rally1.rallydev.com/slm/webservice/current/Defect?query=%28FormattedId+%3D+DE1234%29&fetch=true"
19
+ [200, {}, [
20
+ <<-XML
21
+ <QueryResult>
22
+ <Results>
23
+ <Object>
24
+ <FormattedID>DE1234</FormattedID>
25
+ <Name>[Rework] Change link to button</Name>
26
+ <PlanEstimate>1.0</PlanEstimate>
27
+ <ScheduleState>In-Progress</ScheduleState>
28
+ <TaskActualTotal>0.0</TaskActualTotal>
29
+ <TaskEstimateTotal>6.5</TaskEstimateTotal>
30
+ <TaskRemainingTotal>0.5</TaskRemainingTotal>
31
+ <Owner>scootin@fruity.com</Owner>
32
+ <Description>#{ CGI::escapeHTML('<div><p>This is a defect.</p></div>') }</Description>
33
+ </Object>
34
+ </Results>
35
+ <TotalResultCount>1</TotalResultCount>
36
+ </QueryResult>
37
+ XML
38
+ ]]
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,91 @@
1
+ require 'cgi'
2
+
3
+ class RallyStoryResponder
4
+
5
+ attr_reader :requests
6
+
7
+ def initialize
8
+ @requests = []
9
+ end
10
+
11
+ def last_request
12
+ @requests.last
13
+ end
14
+
15
+ def endpoint
16
+ lambda do |env|
17
+ @requests << request = Rack::Request.new(env)
18
+
19
+ case request.url
20
+ when 'https://rally1.rallydev.com/slm/webservice/1.17/task/1'
21
+ [200, {}, [
22
+ <<-XML
23
+ <Task refObjectName="Change link to button">
24
+ <FormattedID>TA1234</FormattedID>
25
+ <State>Complete</State>
26
+ <TaskIndex>1</TaskIndex>
27
+ </Task>
28
+ XML
29
+ ]]
30
+ when 'https://rally1.rallydev.com/slm/webservice/1.17/task/2'
31
+ [200, {}, [
32
+ <<-XML
33
+ <Task refObjectName="Add confirmation">
34
+ <FormattedID>TA1235</FormattedID>
35
+ <State>In-Progress</State>
36
+ <TaskIndex>2</TaskIndex>
37
+ </Task>
38
+ XML
39
+ ]]
40
+ when 'https://rally1.rallydev.com/slm/webservice/1.17/task/3'
41
+ [200, {}, [
42
+ <<-XML
43
+ <Task refObjectName="Code Review">
44
+ <FormattedID>TA1236</FormattedID>
45
+ <State>Defined</State>
46
+ <TaskIndex>3</TaskIndex>
47
+ </Task>
48
+ XML
49
+ ]]
50
+ when 'https://rally1.rallydev.com/slm/webservice/1.17/task/4'
51
+ [200, {}, [
52
+ <<-XML
53
+ <Task refObjectName="QA Test">
54
+ <FormattedID>TA1237</FormattedID>
55
+ <State>Defined</State>
56
+ <TaskIndex>4</TaskIndex>
57
+ </Task>
58
+ XML
59
+ ]]
60
+ else
61
+ # https://rally1.rallydev.com/slm/webservice/current/HierarchicalRequirement?query=%28FormattedId+%3D+US7176%29&fetch=true
62
+ [200, {}, [
63
+ <<-XML
64
+ <QueryResult>
65
+ <Results>
66
+ <Object>
67
+ <FormattedID>US4567</FormattedID>
68
+ <Name>[Rework] Change link to button</Name>
69
+ <PlanEstimate>1.0</PlanEstimate>
70
+ <ScheduleState>In-Progress</ScheduleState>
71
+ <TaskActualTotal>0.0</TaskActualTotal>
72
+ <TaskEstimateTotal>6.5</TaskEstimateTotal>
73
+ <TaskRemainingTotal>0.5</TaskRemainingTotal>
74
+ <Owner>scootin@fruity.com</Owner>
75
+ <Description>#{ CGI::escapeHTML('<div><p>This is the story</p></div><ul><li>Remember to do this.</li><li>And this too.</li></ul>') }</Description>
76
+ <Tasks>
77
+ <Task ref="https://rally1.rallydev.com/slm/webservice/1.17/task/1" />
78
+ <Task ref="https://rally1.rallydev.com/slm/webservice/1.17/task/2" />
79
+ <Task ref="https://rally1.rallydev.com/slm/webservice/1.17/task/3" />
80
+ <Task ref="https://rally1.rallydev.com/slm/webservice/1.17/task/4" />
81
+ </Tasks>
82
+ </Object>
83
+ </Results>
84
+ <TotalResultCount>1</TotalResultCount>
85
+ </QueryResult>
86
+ XML
87
+ ]]
88
+ end
89
+ end
90
+ end
91
+ 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.0.1
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,8 +10,41 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2012-06-11 00:00:00.000000000 Z
14
- dependencies: []
13
+ date: 2012-07-17 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: builder
17
+ requirement: &70363599472260 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: *70363599472260
26
+ - !ruby/object:Gem::Dependency
27
+ name: rally_rest_api
28
+ requirement: &70363599471700 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: *70363599471700
37
+ - !ruby/object:Gem::Dependency
38
+ name: nokogiri
39
+ requirement: &70363599471120 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ type: :runtime
46
+ prerelease: false
47
+ version_requirements: *70363599471120
15
48
  description: The Rally website sucks. CLI is better.
16
49
  email:
17
50
  - adam@adamtanner.org
@@ -22,15 +55,29 @@ extensions: []
22
55
  extra_rdoc_files: []
23
56
  files:
24
57
  - .gitignore
58
+ - .rspec
25
59
  - Gemfile
60
+ - Gemfile.lock
26
61
  - LICENSE
27
62
  - README.md
28
63
  - Rakefile
64
+ - TODO
29
65
  - bin/rallycat
30
66
  - lib/rallycat.rb
31
67
  - lib/rallycat/cat.rb
68
+ - lib/rallycat/cli.rb
69
+ - lib/rallycat/connection.rb
70
+ - lib/rallycat/html_to_text_converter.rb
32
71
  - lib/rallycat/version.rb
33
72
  - rallycat.gemspec
73
+ - spec/integration_spec.rb
74
+ - spec/lib/rallycat/cat_spec.rb
75
+ - spec/lib/rallycat/cli_spec.rb
76
+ - spec/lib/rallycat/connection_spec.rb
77
+ - spec/lib/rallycat/html_to_text_converter_spec.rb
78
+ - spec/spec_helper.rb
79
+ - spec/support/rally_defect_responder.rb
80
+ - spec/support/rally_story_responder.rb
34
81
  homepage: https://github.com/adamtanner/rallycat
35
82
  licenses: []
36
83
  post_install_message:
@@ -45,7 +92,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
45
92
  version: '0'
46
93
  segments:
47
94
  - 0
48
- hash: -2187327888201420853
95
+ hash: -2940249692508219253
49
96
  required_rubygems_version: !ruby/object:Gem::Requirement
50
97
  none: false
51
98
  requirements:
@@ -54,11 +101,19 @@ required_rubygems_version: !ruby/object:Gem::Requirement
54
101
  version: '0'
55
102
  segments:
56
103
  - 0
57
- hash: -2187327888201420853
104
+ hash: -2940249692508219253
58
105
  requirements: []
59
106
  rubyforge_project:
60
107
  rubygems_version: 1.8.7
61
108
  signing_key:
62
109
  specification_version: 3
63
110
  summary: The Rally website sucks. CLI is better.
64
- test_files: []
111
+ test_files:
112
+ - spec/integration_spec.rb
113
+ - spec/lib/rallycat/cat_spec.rb
114
+ - spec/lib/rallycat/cli_spec.rb
115
+ - spec/lib/rallycat/connection_spec.rb
116
+ - spec/lib/rallycat/html_to_text_converter_spec.rb
117
+ - spec/spec_helper.rb
118
+ - spec/support/rally_defect_responder.rb
119
+ - spec/support/rally_story_responder.rb