napcs-pivotalrecord 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc ADDED
@@ -0,0 +1,31 @@
1
+ =PivotalRecord
2
+
3
+ Provides a simple interface to PivotalTracker using syntax similar to ActiveRecord.
4
+
5
+ @projects = Pivotal::Project.find(246)
6
+ @stories = @projects.first.stories
7
+
8
+ == Configuring
9
+
10
+ Require the library in your project and then configure it with your API key.
11
+
12
+ require 'pivotal'
13
+ Pivotal::Configuration.options[:api_key] = "asdfagjasd"
14
+
15
+
16
+ ==Known Issues
17
+
18
+ This is extremely rudimentary and not meant for any sort of production use.
19
+ There is no write support yet, nor are there any search filters.
20
+
21
+
22
+ == Changelog
23
+
24
+ ===0.0.1
25
+ * Basic record retrieval from PivotalTracker.
26
+
27
+
28
+ ==License
29
+ Copyright 2009 Brian Hogan
30
+ Licensed under the MIT license.
31
+ Use of this library must be in accordance with Pivotal Tracker's terms of service and requires an API key.
data/Rakefile ADDED
@@ -0,0 +1,121 @@
1
+
2
+
3
+ require "rubygems"
4
+ require "rake/gempackagetask"
5
+ require "rake/rdoctask"
6
+
7
+ task :default => :spec
8
+
9
+ require "spec"
10
+ require "spec/rake/spectask"
11
+ Spec::Rake::SpecTask.new do |t|
12
+ t.spec_opts = %w(--format specdoc --colour)
13
+ t.libs = ["spec"]
14
+ end
15
+
16
+ # This builds the actual gem. For details of what all these options
17
+ # mean, and other ones you can add, check the documentation here:
18
+ #
19
+ # http://rubygems.org/read/chapter/20
20
+ #
21
+ spec = Gem::Specification.new do |s|
22
+
23
+ # Change these as appropriate
24
+ s.name = "pivotalrecord"
25
+ s.version = "0.0.1"
26
+ s.summary = "Simple ActiveRecord-style interface for PivotalTracker"
27
+ s.author = "Brian Hogan"
28
+ s.email = "brianhogan@naopcs.com"
29
+ s.homepage = "http://www.napcs.com"
30
+
31
+ s.has_rdoc = true
32
+
33
+ # You should probably have a README of some kind. Change the filename
34
+ # as appropriate
35
+ s.extra_rdoc_files = %w(README.rdoc)
36
+ s.rdoc_options = %w(--main README.rdoc)
37
+
38
+ # Add any extra files to include in the gem (like your README)
39
+ s.files = %w(Rakefile) + Dir.glob("{spec,lib}/**/*")
40
+
41
+ s.require_paths = ["lib"]
42
+
43
+ # If you want to depend on other gems, add them here, along with any
44
+ # relevant versions
45
+ s.add_dependency("rest-client", "~> 1.0.3")
46
+ s.add_dependency("xml-simple", "~> 1.0.12")
47
+ s.add_dependency("activesupport", "~> 2.0.2")
48
+
49
+ s.add_development_dependency("rspec") # add any other gems for testing/development
50
+
51
+ # If you want to publish automatically to rubyforge, you'll may need
52
+ # to tweak this, and the publishing task below too.
53
+ s.rubyforge_project = "pivotalrecord"
54
+ end
55
+
56
+ # This task actually builds the gem. We also regenerate a static
57
+ # .gemspec file, which is useful if something (i.e. GitHub) will
58
+ # be automatically building a gem for this project. If you're not
59
+ # using GitHub, edit as appropriate.
60
+ Rake::GemPackageTask.new(spec) do |pkg|
61
+ pkg.gem_spec = spec
62
+
63
+ # Generate the gemspec file for github.
64
+ file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
65
+ File.open(file, "w") {|f| f << spec.to_ruby }
66
+ end
67
+
68
+ # Generate documentation
69
+ Rake::RDocTask.new do |rd|
70
+
71
+ rd.rdoc_files.include("lib/**/*.rb")
72
+ rd.rdoc_dir = "rdoc"
73
+ end
74
+
75
+ desc 'Clear out RDoc and generated packages'
76
+ task :clean => [:clobber_rdoc, :clobber_package] do
77
+ rm "#{spec.name}.gemspec"
78
+ end
79
+
80
+ # If you want to publish to RubyForge automatically, here's a simple
81
+ # task to help do that. If you don't, just get rid of this.
82
+ # Be sure to set up your Rubyforge account details with the Rubyforge
83
+ # gem; you'll need to run `rubyforge setup` and `rubyforge config` at
84
+ # the very least.
85
+ begin
86
+ require "rake/contrib/sshpublisher"
87
+ namespace :rubyforge do
88
+
89
+ desc "Release gem and RDoc documentation to RubyForge"
90
+ task :release => ["rubyforge:release:gem", "rubyforge:release:docs"]
91
+
92
+ namespace :release do
93
+ desc "Release a new version of this gem"
94
+ task :gem => [:package] do
95
+ require 'rubyforge'
96
+ rubyforge = RubyForge.new
97
+ rubyforge.configure
98
+ rubyforge.login
99
+ rubyforge.userconfig['release_notes'] = spec.summary
100
+ path_to_gem = File.join(File.dirname(__FILE__), "pkg", "#{spec.name}-#{spec.version}.gem")
101
+ puts "Publishing #{spec.name}-#{spec.version.to_s} to Rubyforge..."
102
+ rubyforge.add_release(spec.rubyforge_project, spec.name, spec.version.to_s, path_to_gem)
103
+ end
104
+
105
+ desc "Publish RDoc to RubyForge."
106
+ task :docs => [:rdoc] do
107
+ config = YAML.load(
108
+ File.read(File.expand_path('~/.rubyforge/user-config.yml'))
109
+ )
110
+
111
+ host = "#{config['username']}@rubyforge.org"
112
+ remote_dir = "/var/www/gforge-projects/pivotalrecord/" # Should be the same as the rubyforge project name
113
+ local_dir = 'rdoc'
114
+
115
+ Rake::SshDirPublisher.new(host, remote_dir, local_dir).upload
116
+ end
117
+ end
118
+ end
119
+ rescue LoadError
120
+ puts "Rake SshDirPublisher is unavailable or your rubyforge environment is not configured."
121
+ end
data/lib/pivotal.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'activesupport'
3
+ require 'rest_client'
4
+ require 'xmlsimple'
5
+
6
+ require 'lib/pivotal/base'
7
+ require 'lib/pivotal/configuration'
8
+ require 'lib/pivotal/project'
9
+ require 'lib/pivotal/story'
10
+ require 'lib/pivotal/iteration'
@@ -0,0 +1,155 @@
1
+ module Pivotal
2
+ class Base
3
+
4
+ def initialize(args = {})
5
+ args.each do |key, value|
6
+ self.send("#{key}=", value) if self.respond_to?("#{key}=")
7
+ end
8
+ end
9
+
10
+ class << self
11
+
12
+ # parses the collection from Pivotal. Invokes the class's parse_result method
13
+ # which the class should implement.
14
+ def parse_results(results)
15
+ return [] if results[self.resource].nil?
16
+ results[self.resource].collect do |item|
17
+ klass = self.name.constantize
18
+ klass.parse_result(item)
19
+ end
20
+ end
21
+
22
+
23
+ # get_content_for is used to convert the strings in the xml fil
24
+ # into actual Ruby types.
25
+ # The element can come in as a string, an array with a string inside
26
+ # or a hash, so I'm covering all bases here.
27
+ # Don't change without using the specs!
28
+ def get_content_for(element)
29
+ result = if element.is_a? String
30
+ element.to_s
31
+ elsif element.nil?
32
+ nil #doesn't exist
33
+ else
34
+ if element[0].is_a? Hash
35
+ type = element[0]["type"]
36
+ content = element[0]["content"]
37
+ cast_value(content, type)
38
+ else
39
+ element[0].to_s
40
+ end
41
+
42
+ end
43
+ end
44
+
45
+ def cast_value(content, type)
46
+ value = case type
47
+ when "integer"
48
+ content.to_i
49
+ when "datetime"
50
+ content.to_time
51
+ else
52
+ content
53
+ end
54
+ end
55
+
56
+ def resource
57
+ name.split('::').last.downcase
58
+ end
59
+
60
+ # Declares the relationship between the models.
61
+ #
62
+ # has_many :stories
63
+ #
64
+ # Creates a <tt>stories</tt> method to retrive the related records.
65
+ def has_many(things)
66
+ parent_resource = resource
67
+ object = things.to_s.classify
68
+ class_eval <<-EOF
69
+
70
+ def #{things}
71
+ Pivotal::#{object}.find_all_by_parent_id("#{parent_resource.pluralize}", self.id)
72
+ end
73
+
74
+ EOF
75
+ end
76
+
77
+ # Creates a reverse association, making it easy to grab
78
+ # the parent object.
79
+ def belongs_to(parent)
80
+ object = parent.to_s.classify
81
+ parent_id = "#{parent.to_s.downcase}_id"
82
+ class_eval <<-EOF
83
+
84
+ def #{parent.to_s}
85
+ Pivotal::#{object}.find_by_id(#{parent_id})
86
+ end
87
+
88
+ def self.find_all_by_#{parent.to_s}_id(id)
89
+ find_all_by_parent_id("#{parent.to_s.pluralize}", id)
90
+ end
91
+
92
+ EOF
93
+ end
94
+
95
+ # Retrives the token from the configuration
96
+ def token
97
+ @@token = Pivotal::Configuration.options[:api_key]
98
+ end
99
+
100
+ def debugging?
101
+ @@debugging = Pivotal::Configuration.options[:debug] == true
102
+ end
103
+
104
+ # retrieves the service url
105
+ # If not set in the configuration, the default
106
+ # value is used.
107
+ def url
108
+ @@url = Pivotal::Configuration.options[:url] || "http://www.pivotaltracker.com/services/v2"
109
+ end
110
+
111
+ # Base find method
112
+ def find(type)
113
+ if type == :all
114
+ find_all
115
+ else
116
+ find_by_id(type)
117
+ end
118
+ end
119
+
120
+ # find a specific record by id
121
+ def find_by_id(id)
122
+ query = "#{self.url}/#{self.resource.pluralize}/#{id}?token=#{self.token}"
123
+ puts "query : #{query}" if self.debugging?
124
+ results = RestClient.get query
125
+ result = XmlSimple.xml_in(results.to_s)
126
+ parse_result(result)
127
+ end
128
+
129
+ # generic finder, used by associations
130
+ def find_all_by_parent_id(parent, id)
131
+ return [] if id.nil?
132
+ query = "#{self.url}/#{parent}/#{id}/#{self.resource.pluralize}?token=#{self.token}"
133
+ puts "query : #{query}" if self.debugging?
134
+ results = RestClient.get query
135
+ results = XmlSimple.xml_in(results.to_s)
136
+ parse_results(results)
137
+ end
138
+
139
+ # find all records for the given object.
140
+ def find_all
141
+ query ="#{self.url}/#{self.resource.pluralize}?token=#{self.token}"
142
+ puts "query : #{query}" if self.debugging?
143
+ results = RestClient.get query
144
+ puts results.inspect if self.debugging?
145
+ results = XmlSimple.xml_in(results.to_s)
146
+ parse_results(results)
147
+ end
148
+
149
+
150
+
151
+
152
+ end
153
+
154
+ end
155
+ end
@@ -0,0 +1,11 @@
1
+ module Pivotal
2
+ # The class that keeps track of the global options for Haml within Rails.
3
+ module Configuration
4
+ extend self
5
+
6
+ @options = {}
7
+ # The options hash for the Pivotal library.
8
+ # Pivotal::Configuration.options[:api_key] = "something"
9
+ attr_accessor :options
10
+ end
11
+ end
@@ -0,0 +1,21 @@
1
+ module Pivotal
2
+
3
+ #==Iteration
4
+ # An iteration maps to a Pivotal Project and represents the sprint.
5
+ class Iteration < Pivotal::Base
6
+
7
+ belongs_to :project
8
+
9
+ attr_accessor :start, :finish, :id, :stories, :number
10
+
11
+ def self.parse_result(iteration)
12
+ Pivotal::Iteration.new( :id => get_content_for(iteration["id"]),
13
+ :number => get_content_for(iteration["number"]),
14
+ :start => get_content_for(iteration["start"]),
15
+ :finish => get_content_for(iteration["finish"]),
16
+ :stories => Pivotal::Story.parse_results(iteration["stories"].first)
17
+ )
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,19 @@
1
+ module Pivotal
2
+ #==Project
3
+ # Your project in PivotalTracker. Use the project to get basic info
4
+ # and to get its stories.
5
+ class Project < Pivotal::Base
6
+
7
+ attr_accessor :id, :name
8
+
9
+ has_many :stories
10
+
11
+
12
+ def self.parse_result(project)
13
+ Pivotal::Project.new( :id => get_content_for(project["id"]),
14
+ :name => get_content_for(project["name"]) )
15
+ end
16
+
17
+
18
+ end
19
+ end
@@ -0,0 +1,30 @@
1
+ module Pivotal
2
+ class Story < Pivotal::Base
3
+
4
+ attr_accessor :id, :project_id, :name, :description, :url, :estimate, :story_type, :accepted_at
5
+
6
+
7
+
8
+ belongs_to :project
9
+
10
+ private
11
+
12
+
13
+ def self.parse_result(story)
14
+ Pivotal::Story.new(
15
+ :id => get_content_for(story["id"]),
16
+ :name => get_content_for(story["name"]),
17
+ :description => get_content_for(story["description"]),
18
+ :url => get_content_for(story["url"]),
19
+ :estimate => get_content_for(story["estimate"]),
20
+ :story_type => get_content_for(story["story_type"]),
21
+ :accepted_at => get_content_for(story["accepted_at"]),
22
+ :current_state => get_content_for(story["current_state"])
23
+ )
24
+ end
25
+
26
+
27
+
28
+
29
+ end
30
+ end
@@ -0,0 +1,357 @@
1
+ require File.join(File.dirname(__FILE__), "/../spec_helper")
2
+
3
+ describe Pivotal::Iteration do
4
+ before(:each) do
5
+ @service = "http://www.pivotaltracker.com/services/v2"
6
+ @token = "abcd"
7
+ Pivotal::Configuration.options[:api_key] = @token
8
+ FakeWeb.clean_registry
9
+ FakeWeb.allow_net_connect = false
10
+ end
11
+
12
+
13
+ after(:each) do
14
+ FakeWeb.clean_registry
15
+ FakeWeb.allow_net_connect = true
16
+ end
17
+
18
+
19
+ describe "with many iterations" do
20
+ before(:each) do
21
+ xml = %Q{<?xml version="1.0" encoding="UTF-8"?>
22
+ <iterations type="array">
23
+ <iteration>
24
+ <id type="integer">1</id>
25
+ <number type="integer">1</number>
26
+ <start type="datetime">2009/01/12 00:00:00 UTC</start>
27
+ <finish type="datetime">2009/01/19 00:00:00 UTC</finish>
28
+ <stories type="array">
29
+ <story>
30
+ <id type="integer">300970</id>
31
+ <story_type>release</story_type>
32
+ <url>http://www.pivotaltracker.com/story/show/300970</url>
33
+ <current_state>accepted</current_state>
34
+ <description></description>
35
+ <name>rel_1-1-5</name>
36
+ <requested_by>Brian Hogan</requested_by>
37
+ <created_at type="datetime">2008/12/04 03:00:57 UTC</created_at>
38
+ <accepted_at type="datetime">2009/01/14 06:32:15 UTC</accepted_at>
39
+ <deadline type="datetime">2008/12/12 20:00:00 UTC</deadline>
40
+ </story>
41
+ </stories>
42
+ </iteration>
43
+ <iteration>
44
+ <id type="integer">2</id>
45
+ <number type="integer">2</number>
46
+ <start type="datetime">2009/01/19 00:00:00 UTC</start>
47
+ <finish type="datetime">2009/01/26 00:00:00 UTC</finish>
48
+ <stories type="array">
49
+ <story>
50
+ <id type="integer">302045</id>
51
+ <story_type>bug</story_type>
52
+ <url>http://www.pivotaltracker.com/story/show/302045</url>
53
+ <current_state>accepted</current_state>
54
+ <description>CSS stylesheets for greybox are still referenced in templates causing errors in the logs</description>
55
+ <name>Remove references to Greybox</name>
56
+ <requested_by>Brian Hogan</requested_by>
57
+ <owned_by>Brian Hogan</owned_by>
58
+ <created_at type="datetime">2008/12/04 18:46:43 UTC</created_at>
59
+ <accepted_at type="datetime">2009/01/19 17:22:53 UTC</accepted_at>
60
+ </story>
61
+ <story>
62
+ <id type="integer">300931</id>
63
+ <story_type>bug</story_type>
64
+ <url>http://www.pivotaltracker.com/story/show/300931</url>
65
+ <current_state>accepted</current_state>
66
+ <description>The home page has no title set.</description>
67
+ <name>Get a title on the home page</name>
68
+ <requested_by>Brian Hogan</requested_by>
69
+ <owned_by>Brian Hogan</owned_by>
70
+ <created_at type="datetime">2008/12/04 02:22:00 UTC</created_at>
71
+ <accepted_at type="datetime">2009/01/19 17:23:11 UTC</accepted_at>
72
+ </story>
73
+ </stories>
74
+ </iteration>
75
+ <iteration>
76
+ <id type="integer">3</id>
77
+ <number type="integer">3</number>
78
+ <start type="datetime">2009/01/26 00:00:00 UTC</start>
79
+ <finish type="datetime">2009/02/02 00:00:00 UTC</finish>
80
+ <stories type="array">
81
+ </stories>
82
+ </iteration>
83
+ <iteration>
84
+ <id type="integer">4</id>
85
+ <number type="integer">4</number>
86
+ <start type="datetime">2009/02/02 00:00:00 UTC</start>
87
+ <finish type="datetime">2009/02/09 00:00:00 UTC</finish>
88
+ <stories type="array">
89
+ </stories>
90
+ </iteration>
91
+ <iteration>
92
+ <id type="integer">5</id>
93
+ <number type="integer">5</number>
94
+ <start type="datetime">2009/02/09 00:00:00 UTC</start>
95
+ <finish type="datetime">2009/02/16 00:00:00 UTC</finish>
96
+ <stories type="array">
97
+ </stories>
98
+ </iteration>
99
+ <iteration>
100
+ <id type="integer">6</id>
101
+ <number type="integer">6</number>
102
+ <start type="datetime">2009/02/16 00:00:00 UTC</start>
103
+ <finish type="datetime">2009/02/23 00:00:00 UTC</finish>
104
+ <stories type="array">
105
+ </stories>
106
+ </iteration>
107
+ <iteration>
108
+ <id type="integer">7</id>
109
+ <number type="integer">7</number>
110
+ <start type="datetime">2009/02/23 00:00:00 UTC</start>
111
+ <finish type="datetime">2009/03/02 00:00:00 UTC</finish>
112
+ <stories type="array">
113
+ </stories>
114
+ </iteration>
115
+ <iteration>
116
+ <id type="integer">8</id>
117
+ <number type="integer">8</number>
118
+ <start type="datetime">2009/03/02 00:00:00 UTC</start>
119
+ <finish type="datetime">2009/03/09 00:00:00 UTC</finish>
120
+ <stories type="array">
121
+ </stories>
122
+ </iteration>
123
+ <iteration>
124
+ <id type="integer">9</id>
125
+ <number type="integer">9</number>
126
+ <start type="datetime">2009/03/09 00:00:00 UTC</start>
127
+ <finish type="datetime">2009/03/16 00:00:00 UTC</finish>
128
+ <stories type="array">
129
+ </stories>
130
+ </iteration>
131
+ <iteration>
132
+ <id type="integer">10</id>
133
+ <number type="integer">10</number>
134
+ <start type="datetime">2009/03/16 00:00:00 UTC</start>
135
+ <finish type="datetime">2009/03/23 00:00:00 UTC</finish>
136
+ <stories type="array">
137
+ </stories>
138
+ </iteration>
139
+ <iteration>
140
+ <id type="integer">11</id>
141
+ <number type="integer">11</number>
142
+ <start type="datetime">2009/03/23 00:00:00 UTC</start>
143
+ <finish type="datetime">2009/03/30 00:00:00 UTC</finish>
144
+ <stories type="array">
145
+ </stories>
146
+ </iteration>
147
+ <iteration>
148
+ <id type="integer">12</id>
149
+ <number type="integer">12</number>
150
+ <start type="datetime">2009/03/30 00:00:00 UTC</start>
151
+ <finish type="datetime">2009/04/06 00:00:00 UTC</finish>
152
+ <stories type="array">
153
+ </stories>
154
+ </iteration>
155
+ <iteration>
156
+ <id type="integer">13</id>
157
+ <number type="integer">13</number>
158
+ <start type="datetime">2009/04/06 00:00:00 UTC</start>
159
+ <finish type="datetime">2009/04/13 00:00:00 UTC</finish>
160
+ <stories type="array">
161
+ </stories>
162
+ </iteration>
163
+ <iteration>
164
+ <id type="integer">14</id>
165
+ <number type="integer">14</number>
166
+ <start type="datetime">2009/04/13 00:00:00 UTC</start>
167
+ <finish type="datetime">2009/04/20 00:00:00 UTC</finish>
168
+ <stories type="array">
169
+ </stories>
170
+ </iteration>
171
+ <iteration>
172
+ <id type="integer">15</id>
173
+ <number type="integer">15</number>
174
+ <start type="datetime">2009/04/20 00:00:00 UTC</start>
175
+ <finish type="datetime">2009/04/27 00:00:00 UTC</finish>
176
+ <stories type="array">
177
+ </stories>
178
+ </iteration>
179
+ <iteration>
180
+ <id type="integer">16</id>
181
+ <number type="integer">16</number>
182
+ <start type="datetime">2009/04/27 00:00:00 UTC</start>
183
+ <finish type="datetime">2009/05/04 00:00:00 UTC</finish>
184
+ <stories type="array">
185
+ </stories>
186
+ </iteration>
187
+ <iteration>
188
+ <id type="integer">17</id>
189
+ <number type="integer">17</number>
190
+ <start type="datetime">2009/05/04 00:00:00 UTC</start>
191
+ <finish type="datetime">2009/05/11 00:00:00 UTC</finish>
192
+ <stories type="array">
193
+ </stories>
194
+ </iteration>
195
+ <iteration>
196
+ <id type="integer">18</id>
197
+ <number type="integer">18</number>
198
+ <start type="datetime">2009/05/11 00:00:00 UTC</start>
199
+ <finish type="datetime">2009/05/18 00:00:00 UTC</finish>
200
+ <stories type="array">
201
+ </stories>
202
+ </iteration>
203
+ <iteration>
204
+ <id type="integer">19</id>
205
+ <number type="integer">19</number>
206
+ <start type="datetime">2009/05/18 00:00:00 UTC</start>
207
+ <finish type="datetime">2009/05/25 00:00:00 UTC</finish>
208
+ <stories type="array">
209
+ </stories>
210
+ </iteration>
211
+ <iteration>
212
+ <id type="integer">20</id>
213
+ <number type="integer">20</number>
214
+ <start type="datetime">2009/05/25 00:00:00 UTC</start>
215
+ <finish type="datetime">2009/06/01 00:00:00 UTC</finish>
216
+ <stories type="array">
217
+ </stories>
218
+ </iteration>
219
+ <iteration>
220
+ <id type="integer">21</id>
221
+ <number type="integer">21</number>
222
+ <start type="datetime">2009/06/01 00:00:00 UTC</start>
223
+ <finish type="datetime">2009/06/08 00:00:00 UTC</finish>
224
+ <stories type="array">
225
+ </stories>
226
+ </iteration>
227
+ <iteration>
228
+ <id type="integer">22</id>
229
+ <number type="integer">22</number>
230
+ <start type="datetime">2009/06/08 00:00:00 UTC</start>
231
+ <finish type="datetime">2009/06/15 00:00:00 UTC</finish>
232
+ <stories type="array">
233
+ </stories>
234
+ </iteration>
235
+ <iteration>
236
+ <id type="integer">23</id>
237
+ <number type="integer">23</number>
238
+ <start type="datetime">2009/06/15 00:00:00 UTC</start>
239
+ <finish type="datetime">2009/06/22 00:00:00 UTC</finish>
240
+ <stories type="array">
241
+ </stories>
242
+ </iteration>
243
+ <iteration>
244
+ <id type="integer">24</id>
245
+ <number type="integer">24</number>
246
+ <start type="datetime">2009/06/22 00:00:00 UTC</start>
247
+ <finish type="datetime">2009/06/29 00:00:00 UTC</finish>
248
+ <stories type="array">
249
+ </stories>
250
+ </iteration>
251
+ <iteration>
252
+ <id type="integer">25</id>
253
+ <number type="integer">25</number>
254
+ <start type="datetime">2009/06/29 00:00:00 UTC</start>
255
+ <finish type="datetime">2009/07/06 00:00:00 UTC</finish>
256
+ <stories type="array">
257
+ </stories>
258
+ </iteration>
259
+ <iteration>
260
+ <id type="integer">26</id>
261
+ <number type="integer">26</number>
262
+ <start type="datetime">2009/07/06 00:00:00 UTC</start>
263
+ <finish type="datetime">2009/07/13 00:00:00 UTC</finish>
264
+ <stories type="array">
265
+ </stories>
266
+ </iteration>
267
+ <iteration>
268
+ <id type="integer">27</id>
269
+ <number type="integer">27</number>
270
+ <start type="datetime">2009/07/13 00:00:00 UTC</start>
271
+ <finish type="datetime">2009/07/20 00:00:00 UTC</finish>
272
+ <stories type="array">
273
+ </stories>
274
+ </iteration>
275
+ <iteration>
276
+ <id type="integer">28</id>
277
+ <number type="integer">28</number>
278
+ <start type="datetime">2009/07/20 00:00:00 UTC</start>
279
+ <finish type="datetime">2009/07/27 00:00:00 UTC</finish>
280
+ <stories type="array">
281
+ <story>
282
+ <id type="integer">300939</id>
283
+ <story_type>feature</story_type>
284
+ <url>http://www.pivotaltracker.com/story/show/300939</url>
285
+ <estimate type="integer">1</estimate>
286
+ <current_state>started</current_state>
287
+ <description>Images need to be linked and modalbox needs testing in other browsers besides FF and Safari.</description>
288
+ <name>Finish modalbox implementation</name>
289
+ <requested_by>Brian Hogan</requested_by>
290
+ <owned_by>Brian Hogan</owned_by>
291
+ <created_at type="datetime">2008/12/04 02:33:33 UTC</created_at>
292
+ </story>
293
+ <story>
294
+ <id type="integer">300495</id>
295
+ <story_type>feature</story_type>
296
+ <url>http://www.pivotaltracker.com/story/show/300495</url>
297
+ <estimate type="integer">3</estimate>
298
+ <current_state>started</current_state>
299
+ <description>Choose an item, edit it, and notice that the dropdown for file / url is not selected on load, causing the file or url field to not display.</description>
300
+ <name>Editing an item does not select the correct type</name>
301
+ <requested_by>Brian Hogan</requested_by>
302
+ <owned_by>Brian Hogan</owned_by>
303
+ <created_at type="datetime">2008/12/04 01:55:39 UTC</created_at>
304
+ </story>
305
+ <story>
306
+ <id type="integer">300423</id>
307
+ <story_type>feature</story_type>
308
+ <url>http://www.pivotaltracker.com/story/show/300423</url>
309
+ <estimate type="integer">3</estimate>
310
+ <current_state>started</current_state>
311
+ <description>A profile should be viewable using the iPhone or Touch.
312
+ The profile page should link to the portfolios.
313
+ items should display within the portfolios.</description>
314
+ <name>iPhone interface for viewing profiles</name>
315
+ <requested_by>Brian Hogan</requested_by>
316
+ <owned_by>Brian Hogan</owned_by>
317
+ <created_at type="datetime">2008/12/04 01:48:53 UTC</created_at>
318
+ </story>
319
+ <story>
320
+ <id type="integer">300923</id>
321
+ <story_type>feature</story_type>
322
+ <url>http://www.pivotaltracker.com/story/show/300923</url>
323
+ <estimate type="integer">1</estimate>
324
+ <current_state>unstarted</current_state>
325
+ <description>Add a &quot;contact us&quot; form that sends an email</description>
326
+ <name>Contact Us form</name>
327
+ <requested_by>Brian Hogan</requested_by>
328
+ <created_at type="datetime">2008/12/04 02:14:51 UTC</created_at>
329
+ </story>
330
+ <story>
331
+ <id type="integer">300518</id>
332
+ <story_type>feature</story_type>
333
+ <url>http://www.pivotaltracker.com/story/show/300518</url>
334
+ <estimate type="integer">1</estimate>
335
+ <current_state>unstarted</current_state>
336
+ <description>Add a send to friend link on portfolios</description>
337
+ <name>Send To Friend linke</name>
338
+ <requested_by>Brian Hogan</requested_by>
339
+ <created_at type="datetime">2008/12/04 01:57:22 UTC</created_at>
340
+ </story>
341
+ </stories>
342
+ </iteration>
343
+ </iterations>}
344
+ url = "#{@service}/projects/4860/iterations?token=#{@token}"
345
+ FakeWeb.register_uri(:get, url, :string => xml, :content_type => "application/xml", :status => ["200", "OK"])
346
+ @project = Pivotal::Iteration.new("id" => "4860", :name => "FeelMySkills")
347
+ end
348
+ it "should get iterations" do
349
+ iterations = Pivotal::Iteration.find_all_by_project_id(@project.id)
350
+ iterations.each{|i| i.should be_a(Pivotal::Iteration)}
351
+ iterations.detect{|i| i.number == 28}.should be_true
352
+
353
+ end
354
+ end
355
+
356
+
357
+ end