rally_rest_api 0.6.13 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest.txt +1 -2
- data/README.txt +28 -8
- data/lib/rally_rest_api.rb +7 -1
- data/lib/rally_rest_api/query.rb +9 -7
- data/lib/rally_rest_api/query_result.rb +3 -3
- data/lib/rally_rest_api/rally_rest.rb +18 -13
- data/lib/rally_rest_api/rest_builder.rb +12 -9
- data/lib/rally_rest_api/rest_object.rb +29 -14
- data/lib/rally_rest_api/ruport.rb +30 -0
- data/lib/rally_rest_api/timeout_catching_rest_builder.rb +1 -2
- data/lib/rally_rest_api/typedef.rb +12 -5
- data/lib/rally_rest_api/version.rb +2 -2
- data/test/attribute_definition_spec.rb +11 -11
- data/test/query_result_spec.rb +32 -32
- data/test/rest_builder_spec.rb +4 -4
- data/test/rest_object_spec.rb +65 -36
- data/test/spec_typedef.rb +26 -21
- metadata +4 -3
data/Manifest.txt
CHANGED
@@ -9,6 +9,7 @@ lib/rally_rest_api/rest_builder.rb
|
|
9
9
|
lib/rally_rest_api/timeout_catching_rest_builder.rb
|
10
10
|
lib/rally_rest_api/rest_object.rb
|
11
11
|
lib/rally_rest_api/typedef.rb
|
12
|
+
lib/rally_rest_api/ruport.rb
|
12
13
|
lib/rally_rest_api/version.rb
|
13
14
|
Manifest.txt
|
14
15
|
Rakefile
|
@@ -23,5 +24,3 @@ test/rest_object_spec.rb
|
|
23
24
|
test/tc_rest_query.rb
|
24
25
|
test/test_helper.rb
|
25
26
|
test/rest_builder_spec.rb
|
26
|
-
lib/rally_rest_api/timeout_catching_rest_builder.rb
|
27
|
-
|
data/README.txt
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
rally-rest-api -- A Ruby-ized interface to Rally's REST webservice API
|
2
2
|
|
3
3
|
==Introduction:
|
4
|
-
Rally Software Development's on-demand agile software life-cycle management
|
4
|
+
Rally Software Development's on-demand agile software life-cycle management service offers webservices API's for its customers. The API comes in both SOAP and REST style interfaces. This library is for accessing the REST API using Ruby. For more information about Rally's webservice APIs see https://rally1.rallydev.com/slm/doc/webservice/index.jsp.
|
5
5
|
|
6
6
|
This API provides full access to all CRUD operations and a rich interface to the query facility. An Enumerable interface is provided for the paginated query results.
|
7
7
|
|
@@ -16,13 +16,20 @@ RallyRestAPI is the entry point to the api. Each instance corresponds to one use
|
|
16
16
|
:password => Your Rally login password. Username and password will be remembered by this
|
17
17
|
instance of the API and all objects created and read by this instance.
|
18
18
|
:base_url => The base url for the system you are talking to. Defaults to https://rally1.rallydev.com/slm/
|
19
|
-
:raise_on_warning => true|false or the exception class you would like raised. If true, RuntimeError will be raised.
|
20
19
|
:logger => A logger to log to. There is interesting logging info for DEBUG and INFO
|
20
|
+
:builder => A builder is the Class responsible for the HTTP level protocol. Defaults to RestBuilder
|
21
|
+
:version => The version of the API you want to talk to. Defaults to "current"
|
21
22
|
|
22
23
|
== Rest Object:
|
23
|
-
All rally resources referenced by the api are of type RestObject, there
|
24
|
+
All rally resources referenced by the api are of type RestObject, there only a few subclasses. In its initial form a RestObject is just a URL representing a resource. This URL is accessed using RestObject#ref. When more information is requested about the object, the API will read the content of that resource. This read is done lazily and transparently.
|
24
25
|
|
25
|
-
|
26
|
+
RestObject makes heavy use of method_missing to achieve its dynamism. The XML for each resource is parsed into a nested Hash where the keys of the Hash are the Ruby-ized Elements names of the XML, and the values of the hash are the string values of the elements, or other RestObjects (in the case of references between objects), or collections of both. This allows the API to respond to chained method invocations like:
|
27
|
+
|
28
|
+
rally.user.subscription.workspaces.first.projects.first.iterations.first.name
|
29
|
+
|
30
|
+
Traversals across object references (RestObjects) cause a lazy read of that resource.
|
31
|
+
|
32
|
+
When establishing object relationships between objects, during create or update, they are done using RestObjects. For example, to associate a user story to a defect, you would use the 'requirement' association on defect to reference the User Story. Here 'defect' and 'user_story' already exist, and the variables contain RestObjects representing them:
|
26
33
|
|
27
34
|
defect.update(:requirement => user_story)
|
28
35
|
|
@@ -92,10 +99,15 @@ RallyRestAPI#find is the interface to the query syntax of Rally's webservice API
|
|
92
99
|
In addition to the type, #find accepts other arguments as a hash:
|
93
100
|
|
94
101
|
:pagesize => <number> - The number of results per page. Max of 100
|
95
|
-
:start => <number> - The record number to start with. Assuming more
|
96
|
-
:fetch => <boolean> - If this is set to true then entire objects will be returned inside the query
|
97
|
-
|
98
|
-
|
102
|
+
:start => <number> - The record number to start with. Assuming more than page size records.
|
103
|
+
:fetch => <boolean> - If this is set to true then entire objects will be returned inside the query
|
104
|
+
result. If set to false (the default) then only object references will be returned. Fetching full
|
105
|
+
objects will prevent another read when utilizing the results.
|
106
|
+
:workspace - If not present, then the query will run in the user's default workspace. If present,
|
107
|
+
this should be the RestObject containing the workspace the user wants to search in.
|
108
|
+
:project - If not set, or specified as "null" then the "parent project" in the given workspace is used.
|
109
|
+
If set, this should be the RestObject containing the project. Furthermore, if set you may omit the workspace
|
110
|
+
parameter because the workspace will be inherited from the project.
|
99
111
|
:project_scope_up - Default is true. In addition to the specified project, include projects above the specified one.
|
100
112
|
:project_scope_down - Default is true. In addition to the specified project, include child projects below the current one.
|
101
113
|
|
@@ -108,4 +120,12 @@ The return from #find is always a QueryResult. The QueryResult provides an inter
|
|
108
120
|
|
109
121
|
Because of the paginated nature of the result list, deleting elements while using #each is undefined.
|
110
122
|
|
123
|
+
If you have Ruport installed (http://www.rubyreports.org), QueryResult will have a #to_table method included. #to_table takes an array of symbols that define the columns in the table:
|
124
|
+
|
125
|
+
defects = rally.find(:defect, :fetch => true) { equal :state, "Open }
|
126
|
+
table = defects.to_table([:name, :severity, :priority, :owner])
|
127
|
+
table.to_pdf
|
128
|
+
|
129
|
+
See rubyreports.org for more information about creating reports with Ruport.
|
130
|
+
|
111
131
|
See the rdoc for RestQuery for more query examples.
|
data/lib/rally_rest_api.rb
CHANGED
@@ -1 +1,7 @@
|
|
1
|
-
Dir[File.join(File.dirname(__FILE__), 'rally_rest_api/**/*.rb')].sort.each { |lib| require lib }
|
1
|
+
Dir[File.join(File.dirname(__FILE__), 'rally_rest_api/**/*.rb')].sort.each { |lib| require lib }
|
2
|
+
begin
|
3
|
+
require 'rubygems'
|
4
|
+
require 'ruport'
|
5
|
+
require 'rally_rest_api/ruport'
|
6
|
+
rescue LoadError
|
7
|
+
end
|
data/lib/rally_rest_api/query.rb
CHANGED
@@ -8,27 +8,27 @@ class String # :nodoc: all
|
|
8
8
|
alias :to_q :to_s
|
9
9
|
end
|
10
10
|
|
11
|
-
class Fixnum
|
11
|
+
class Fixnum # :nodoc: all
|
12
12
|
alias :to_q :to_s
|
13
13
|
end
|
14
14
|
|
15
15
|
class Symbol # :nodoc: all
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
def to_q
|
17
|
+
self.to_s.to_camel
|
18
|
+
end
|
19
19
|
end
|
20
20
|
|
21
|
-
class NilClass
|
21
|
+
class NilClass # :nodoc: all
|
22
22
|
def to_q
|
23
23
|
"null"
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
class TrueClass
|
27
|
+
class TrueClass # :nodoc: all
|
28
28
|
alias :to_q :to_s
|
29
29
|
end
|
30
30
|
|
31
|
-
class FalseClass
|
31
|
+
class FalseClass # :nodoc: all
|
32
32
|
alias :to_q :to_s
|
33
33
|
end
|
34
34
|
|
@@ -81,6 +81,8 @@ end
|
|
81
81
|
# \_or_ works in the same fashion. _and_s and _or_s may be nested as needed. See the test cases for RestQuery for
|
82
82
|
# more complex examples
|
83
83
|
#
|
84
|
+
# If you have ruport installed, you may also call to_table on a QueryResult to convert the result to a Ruport::Data:Table
|
85
|
+
#
|
84
86
|
class RestQuery
|
85
87
|
attr_reader :type
|
86
88
|
|
@@ -4,7 +4,7 @@ require File.dirname(__FILE__) + '/rest_object'
|
|
4
4
|
#
|
5
5
|
# QueryResult is a wrapper around the xml returned from a webservice
|
6
6
|
# query operation. A query could result in a large number of hits
|
7
|
-
# being returned, therefore the
|
7
|
+
# being returned, therefore the results are paged into page_size
|
8
8
|
# chunks (20 by default). QueryResult will seamlessly deal with the
|
9
9
|
# paging when using the #each iterator.
|
10
10
|
#
|
@@ -76,7 +76,7 @@ class QueryResult < RestObject
|
|
76
76
|
end
|
77
77
|
|
78
78
|
protected
|
79
|
-
def parse_collections_as_hash?
|
79
|
+
def parse_collections_as_hash? # :nodoc:
|
80
80
|
false
|
81
81
|
end
|
82
82
|
|
@@ -85,7 +85,7 @@ class QueryResult < RestObject
|
|
85
85
|
!element.attributes["ref"].nil?
|
86
86
|
end
|
87
87
|
|
88
|
-
def terminal?(node)
|
88
|
+
def terminal?(node) # :nodoc:
|
89
89
|
!node.has_elements? || ref?(node)
|
90
90
|
end
|
91
91
|
|
@@ -8,7 +8,7 @@ require 'ostruct'
|
|
8
8
|
#
|
9
9
|
class RallyRestAPI
|
10
10
|
|
11
|
-
attr_reader :username, :password, :base_url, :
|
11
|
+
attr_reader :username, :password, :base_url, :logger, :builder
|
12
12
|
attr_accessor :parse_collections_as_hash
|
13
13
|
|
14
14
|
ALLOWED_TYPES = %w[subscription workspace project iteration release defect defect_suite test_case
|
@@ -21,7 +21,8 @@ class RallyRestAPI
|
|
21
21
|
# * username - The Rally username
|
22
22
|
# * password - The password for the named user
|
23
23
|
# * base_url - The base url of the system. Defaults to https://rally1.rallydev.com/slm
|
24
|
-
# *
|
24
|
+
# * version - The RallyWebservices Version. Defaults to 'current', which will always be the most
|
25
|
+
# recent version of the api. Pass the value as a String, "1.0", "1.01" for example.
|
25
26
|
# * logger - a Logger to log to.
|
26
27
|
#
|
27
28
|
def initialize(options = {})
|
@@ -32,30 +33,34 @@ class RallyRestAPI
|
|
32
33
|
@username = options[:username]
|
33
34
|
@password = options[:password]
|
34
35
|
@base_url = options[:base_url] || "https://rally1.rallydev.com/slm"
|
35
|
-
@
|
36
|
+
@version = options[:version] || "current"
|
36
37
|
@logger = options[:logger]
|
37
38
|
@parse_collections_as_hash = options[:parse_collections_as_hash] || false
|
38
39
|
|
39
40
|
if options[:builder]
|
40
41
|
builder = options[:builder]
|
41
42
|
else
|
42
|
-
builder = RestBuilder.new(@base_url, @username, @password)
|
43
|
+
builder = RestBuilder.new(@base_url, @username, @password, @version)
|
43
44
|
end
|
44
45
|
builder.logger = @logger if @logger
|
45
46
|
@builder = builder
|
46
47
|
end
|
47
48
|
|
49
|
+
def marshal_dump
|
50
|
+
[@username, @password, @base_url, @version, @builder, @parse_collections_as_hash]
|
51
|
+
end
|
52
|
+
|
53
|
+
def marshal_load(stuff)
|
54
|
+
@username, @password, @base_url, @version, @builder, @parse_collections_as_hash = *stuff
|
55
|
+
end
|
56
|
+
|
57
|
+
|
48
58
|
# return an instance of User, for the currently logged in user.
|
49
59
|
def user
|
50
|
-
RestObject.new(self, builder.read_rest("#{@base_url}/webservice/
|
60
|
+
RestObject.new(self, builder.read_rest("#{@base_url}/webservice/#{@version}/user", @username, @password))
|
51
61
|
end
|
52
62
|
alias :start :user # :nodoc:
|
53
63
|
|
54
|
-
# This is deprecated, use create instead
|
55
|
-
def method_missing(type, args, &block) # :nodoc:
|
56
|
-
raise "'#{type}' is not a supported method. Use create() instead"
|
57
|
-
end
|
58
|
-
|
59
64
|
# Create an object.
|
60
65
|
# type - The type to create, as a symbol (e.g. :test_case)
|
61
66
|
# values - The attributes of the new object.
|
@@ -82,7 +87,7 @@ class RallyRestAPI
|
|
82
87
|
query(query)
|
83
88
|
end
|
84
89
|
|
85
|
-
# find all object of a given type. Base types work also (
|
90
|
+
# find all object of a given type. Base types work also (e.g. :artifact)
|
86
91
|
def find_all(type, args = {})
|
87
92
|
find(type, args) { gt :object_i_d, "0" }
|
88
93
|
end
|
@@ -98,12 +103,12 @@ class RallyRestAPI
|
|
98
103
|
end
|
99
104
|
|
100
105
|
def query(query) # :nodoc:
|
101
|
-
query_url = "#{@base_url}/webservice
|
106
|
+
query_url = "#{@base_url}/webservice/#{@version}/#{query.type.to_s.to_camel}?" << query.to_q
|
102
107
|
xml = builder.read_rest(query_url, @username, @password)
|
103
108
|
QueryResult.new(query, self, xml)
|
104
109
|
end
|
105
110
|
|
106
|
-
# Should rest_objects parse into hashes
|
111
|
+
# Should rest_objects collection parse into hashes
|
107
112
|
def parse_collections_as_hash?
|
108
113
|
@parse_collections_as_hash
|
109
114
|
end
|
@@ -9,8 +9,16 @@ class RestBuilder # :nodoc:
|
|
9
9
|
attr_reader :base_url, :username, :password
|
10
10
|
attr_accessor :logger
|
11
11
|
|
12
|
-
def initialize(base_url, username, password)
|
13
|
-
@base_url, @username, @password = base_url, username, password
|
12
|
+
def initialize(base_url, username, password, version = "current")
|
13
|
+
@base_url, @username, @password, @version = base_url, username, password, version
|
14
|
+
end
|
15
|
+
|
16
|
+
def marshal_dump
|
17
|
+
[@username, @password, @base_url, @version]
|
18
|
+
end
|
19
|
+
|
20
|
+
def marshal_load(stuff)
|
21
|
+
@username, @password, @base_url, @version = *stuff
|
14
22
|
end
|
15
23
|
|
16
24
|
# create_rest - convert slm builder style:
|
@@ -27,7 +35,7 @@ class RestBuilder # :nodoc:
|
|
27
35
|
b = create_builder
|
28
36
|
xml = b.__send__(type, &builder_block(args))
|
29
37
|
|
30
|
-
result = post_xml("#{self.base_url}/webservice
|
38
|
+
result = post_xml("#{self.base_url}/webservice/#{@version}/#{type}/create", xml, username, password)
|
31
39
|
doc = REXML::Document.new result
|
32
40
|
doc.root.elements["Object"].to_s
|
33
41
|
end
|
@@ -99,12 +107,6 @@ class RestBuilder # :nodoc:
|
|
99
107
|
sym.to_s.split("_").map { |word| word.capitalize }.join
|
100
108
|
end
|
101
109
|
|
102
|
-
# def cameler(sym)
|
103
|
-
# cam = camel_case_word(sym).split("").to_a
|
104
|
-
# cam[0] = (cam.first.downcase)
|
105
|
-
# cam.to_s
|
106
|
-
# end
|
107
|
-
|
108
110
|
# Because we are adapting to the xml builder as such:
|
109
111
|
# We say to the RallyRestAPI:
|
110
112
|
# slm.create(:feature, :name => "feature name")
|
@@ -163,6 +165,7 @@ class RestBuilder # :nodoc:
|
|
163
165
|
end
|
164
166
|
end
|
165
167
|
|
168
|
+
|
166
169
|
# Convert the values of the hash passed to the RestApi appropiatly
|
167
170
|
# RestObject --> a hash with the ref value
|
168
171
|
# nil --> "null" ref values
|
@@ -18,6 +18,13 @@ require 'builder/blankslate'
|
|
18
18
|
# will be carried out as the same user as their RallyRestAPI.
|
19
19
|
#
|
20
20
|
#
|
21
|
+
|
22
|
+
class FancyHash < Hash # :nodoc: all
|
23
|
+
def method_missing(sym, *args)
|
24
|
+
self[sym]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
21
28
|
class RestObject
|
22
29
|
|
23
30
|
attr_reader :username, :password, :rally_rest
|
@@ -26,6 +33,10 @@ class RestObject
|
|
26
33
|
@rally_rest = rally_rest
|
27
34
|
@document_content = document_content
|
28
35
|
@username, @password = @rally_rest.username, @rally_rest.password
|
36
|
+
parse_document
|
37
|
+
end
|
38
|
+
|
39
|
+
def parse_document
|
29
40
|
@document = REXML::Document.new @document_content
|
30
41
|
if !ref?(@document.root)
|
31
42
|
@elements = parse(@document.root)
|
@@ -34,6 +45,15 @@ class RestObject
|
|
34
45
|
end
|
35
46
|
end
|
36
47
|
|
48
|
+
def marshal_dump
|
49
|
+
[@rally_rest, @document_content, @username, @password]
|
50
|
+
end
|
51
|
+
|
52
|
+
def marshal_load(stuff)
|
53
|
+
@rally_rest, @document_content, @username, @password = *stuff
|
54
|
+
parse_document
|
55
|
+
end
|
56
|
+
|
37
57
|
private
|
38
58
|
def terminal?(node)
|
39
59
|
!node.has_elements?
|
@@ -57,13 +77,7 @@ class RestObject
|
|
57
77
|
end
|
58
78
|
|
59
79
|
# Convert nodes with children into Hashes
|
60
|
-
elements =
|
61
|
-
class << elements
|
62
|
-
def method_missing(sym, *args)
|
63
|
-
value = self[sym]
|
64
|
-
value
|
65
|
-
end
|
66
|
-
end
|
80
|
+
elements = FancyHash.new
|
67
81
|
|
68
82
|
#Add all the element's children to the hash.
|
69
83
|
node.each_element do |e|
|
@@ -141,9 +155,10 @@ class RestObject
|
|
141
155
|
alias :to_q :ref
|
142
156
|
|
143
157
|
# The name of the object, without having to read the entire body
|
144
|
-
|
145
|
-
|
146
|
-
|
158
|
+
def name
|
159
|
+
@document.root.attributes["refObjectName"]
|
160
|
+
end
|
161
|
+
alias :to_s :name
|
147
162
|
|
148
163
|
# The type of the underlying resource
|
149
164
|
def type
|
@@ -202,7 +217,6 @@ class RestObject
|
|
202
217
|
# update the resource. This will re-read the resource after the update
|
203
218
|
def update(args)
|
204
219
|
builder.update_rest(self.type, self.ref, args, @username, @password)
|
205
|
-
# need to deal with the block, setting up contexts etc
|
206
220
|
self.elements(true)
|
207
221
|
end
|
208
222
|
|
@@ -212,9 +226,10 @@ class RestObject
|
|
212
226
|
end
|
213
227
|
|
214
228
|
def method_missing(sym, *args) # :nodoc:
|
215
|
-
|
216
|
-
#
|
217
|
-
|
229
|
+
self.elements[sym]
|
230
|
+
# Sometimes the xml returned has no element for things that are simply null. Without
|
231
|
+
# asking the typedef, I have no way to know if the element exists, or has been ommited.
|
232
|
+
# It would not be hard to ask the typedef, but they are expensive to load. It should be an option
|
218
233
|
end
|
219
234
|
|
220
235
|
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# Add Ruport (http://rubyreports.org/) support to the RallyRestAPI.
|
2
|
+
# This will add a method, #to_table, to QueryResult
|
3
|
+
#
|
4
|
+
# For example:
|
5
|
+
# table = rally_api.find(:defect) { equal :state, "Open" }.to_table([:formatted_i_d, :name, :owner])
|
6
|
+
# table.to_pdf
|
7
|
+
#
|
8
|
+
begin
|
9
|
+
require 'rubygems'
|
10
|
+
require 'ruport'
|
11
|
+
rescue LoadError ; raise "You must 'gem install ruport' to use rally_rest_api/ruport" ; end
|
12
|
+
|
13
|
+
# Ruport can deal with anything that has a #to_hash
|
14
|
+
class RestObject
|
15
|
+
def to_hash # :nodoc
|
16
|
+
elements
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class QueryResult
|
21
|
+
# return a Ruport::Data::Table. Takes an array of columns for the report
|
22
|
+
# defects = rally.find(:defect, :fetch => true) { equal :state, "Open }
|
23
|
+
# table = defects.to_table([:name, :severity, :priority, :owner])
|
24
|
+
# table.to_pdf
|
25
|
+
def to_table(columns = [])
|
26
|
+
table = Ruport::Data::Table.new(:column_names => columns)
|
27
|
+
self.each { |i| table << i }
|
28
|
+
table
|
29
|
+
end
|
30
|
+
end
|
@@ -15,14 +15,17 @@ class TypeDefinition < RestObject # :nodoc:
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def self.get_type_definition(workspace, type)
|
18
|
+
get_type_definitions(workspace).find { |td| td.element_name == type }
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.get_type_definitions(workspace)
|
18
22
|
# This is a hack - having to do with parse_collections_as_hash?
|
19
23
|
typedefs = case workspace.type_definitions
|
20
|
-
|
21
|
-
|
24
|
+
when Hash : workspace.type_definitions.values.flatten
|
25
|
+
when Array : workspace.type_definitions
|
22
26
|
end
|
23
|
-
|
24
|
-
|
25
|
-
TypeDefinition.new(typedef.rally_rest, typedef.body)
|
27
|
+
# end hack
|
28
|
+
typedefs.map { |td| TypeDefinition.new(td.rally_rest, td.body) }
|
26
29
|
end
|
27
30
|
|
28
31
|
def self.make_key(workspace, type)
|
@@ -97,6 +100,10 @@ class TypeDefinition < RestObject # :nodoc:
|
|
97
100
|
typedef
|
98
101
|
end
|
99
102
|
|
103
|
+
def type_as_symbol
|
104
|
+
underscore(element_name).intern
|
105
|
+
end
|
106
|
+
|
100
107
|
protected
|
101
108
|
def parse_collections_as_hash?
|
102
109
|
true
|
@@ -6,9 +6,9 @@ require 'logger'
|
|
6
6
|
require 'rally_rest_api'
|
7
7
|
|
8
8
|
|
9
|
-
|
9
|
+
describe "An attribute definition for a custom dropdown" do
|
10
10
|
|
11
|
-
|
11
|
+
before(:each) do
|
12
12
|
@b = Builder::XmlMarkup.new(:indent => 2)
|
13
13
|
@b.instruct!
|
14
14
|
|
@@ -33,27 +33,27 @@ context "An attribute definition for a custom dropdown" do
|
|
33
33
|
@attrdef = AttributeDefinition.new(fake_rally_rest, RestObject.new(fake_rally_rest, xml).elements)
|
34
34
|
end
|
35
35
|
|
36
|
-
|
36
|
+
it "should have 'Custom Dropdown' as the name" do
|
37
37
|
@attrdef.name.should == "Custom Dropdown"
|
38
38
|
end
|
39
39
|
|
40
|
-
|
41
|
-
@attrdef.allowed_values.
|
40
|
+
it "should return an arry of allowed values" do
|
41
|
+
@attrdef.allowed_values.should be_instance_of(Array)
|
42
42
|
end
|
43
|
-
|
44
|
-
|
43
|
+
|
44
|
+
it "should have 2 allowd values" do
|
45
45
|
@attrdef.allowed_values.length.should == 2
|
46
46
|
end
|
47
47
|
|
48
|
-
|
49
|
-
@attrdef.allowed_values.each { |value| value.
|
48
|
+
it "should not return an array of hashes" do
|
49
|
+
@attrdef.allowed_values.each { |value| value.should_not be_instance_of(Hash) }
|
50
50
|
end
|
51
51
|
|
52
|
-
|
52
|
+
it "allowed_values should match" do
|
53
53
|
@attrdef.allowed_values.should == [nil, "Actor"]
|
54
54
|
end
|
55
55
|
|
56
|
-
|
56
|
+
it "should have 'AttributeDefinition' as the type" do
|
57
57
|
@attrdef.type.should == "AttributeDefinition"
|
58
58
|
end
|
59
59
|
|
data/test/query_result_spec.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
|
-
|
3
|
+
describe "A QueryResult" do
|
4
4
|
|
5
|
-
|
5
|
+
before(:each) do
|
6
6
|
@api = RallyRestAPI.new(:parse_collections_as_hash => true)
|
7
7
|
end
|
8
8
|
|
@@ -27,59 +27,59 @@ context "A QueryResult" do
|
|
27
27
|
QueryResult.new(nil, @api, xml)
|
28
28
|
end
|
29
29
|
|
30
|
-
|
31
|
-
make_result(20, 20, 1).total_result_count.
|
30
|
+
it "should have the total result count" do
|
31
|
+
make_result(20, 20, 1).total_result_count.should equal(20)
|
32
32
|
end
|
33
33
|
|
34
|
-
|
35
|
-
make_result(20, 20, 1).page_size.
|
34
|
+
it "should have the page size" do
|
35
|
+
make_result(20, 20, 1).page_size.should equal(20)
|
36
36
|
end
|
37
37
|
|
38
|
-
|
39
|
-
make_result(20, 20, 1).start_index.
|
38
|
+
it "should have the start_index" do
|
39
|
+
make_result(20, 20, 1).start_index.should equal(1)
|
40
40
|
end
|
41
41
|
|
42
|
-
|
43
|
-
make_result(0, 20, 0).more_pages?.
|
42
|
+
it "should have no more pages when no results are returned" do
|
43
|
+
make_result(0, 20, 0).more_pages?.should equal(false)
|
44
44
|
end
|
45
45
|
|
46
|
-
|
47
|
-
make_result(1, 20, 1).more_pages?.
|
48
|
-
make_result(19, 20, 1).more_pages?.
|
49
|
-
make_result(20, 20, 1).more_pages?.
|
46
|
+
it "should have not more pages when the total result count is less then the page size" do
|
47
|
+
make_result(1, 20, 1).more_pages?.should equal(false)
|
48
|
+
make_result(19, 20, 1).more_pages?.should equal(false)
|
49
|
+
make_result(20, 20, 1).more_pages?.should equal(false)
|
50
50
|
end
|
51
51
|
|
52
|
-
|
53
|
-
make_result(21, 20, 1).more_pages?.
|
54
|
-
make_result(39, 20, 1).more_pages?.
|
52
|
+
it "should have more pages when total result count is more then the page size" do
|
53
|
+
make_result(21, 20, 1).more_pages?.should equal(true)
|
54
|
+
make_result(39, 20, 1).more_pages?.should equal(true)
|
55
55
|
end
|
56
56
|
|
57
|
-
|
57
|
+
it "an empty query should not iteratate on each" do
|
58
58
|
start = 0
|
59
59
|
make_result(0, 20, 0).each { start += 1 }
|
60
|
-
start.
|
60
|
+
start.should equal(0)
|
61
61
|
end
|
62
62
|
|
63
|
-
|
64
|
-
make_result(20, 20, 1).results.length.
|
63
|
+
it "a page of results should match the page size" do
|
64
|
+
make_result(20, 20, 1).results.length.should equal(20)
|
65
65
|
end
|
66
66
|
|
67
|
-
|
67
|
+
it "results should be in the same order as returned" do
|
68
68
|
result = make_result(20, 20, 1)
|
69
|
-
result.results.length.
|
69
|
+
result.results.length.should equal(20)
|
70
70
|
result.results[0].name.should == "name0"
|
71
71
|
result.results[19].name.should == "name19"
|
72
72
|
end
|
73
73
|
|
74
|
-
|
75
|
-
make_result.each { |o| o.
|
74
|
+
it "should return RestObjects for results" do
|
75
|
+
make_result.each { |o| o.should be_instance_of(RestObject) }
|
76
76
|
end
|
77
77
|
end
|
78
78
|
|
79
79
|
|
80
|
-
|
80
|
+
describe "A QueryResult with full objects" do
|
81
81
|
|
82
|
-
|
82
|
+
before(:each) do
|
83
83
|
@api = RallyRestAPI.new(:parse_collections_as_hash => true)
|
84
84
|
end
|
85
85
|
|
@@ -116,11 +116,11 @@ context "A QueryResult with full objects" do
|
|
116
116
|
yield Builder::XmlMarkup.new(:indent => 2)
|
117
117
|
end
|
118
118
|
|
119
|
-
|
120
|
-
make_result.each { |o| o.
|
119
|
+
it "should return RestObjects for results" do
|
120
|
+
make_result.each { |o| o.should be_instance_of(RestObject) }
|
121
121
|
end
|
122
122
|
|
123
|
-
|
123
|
+
it "full object collections should lazy load only once" do
|
124
124
|
rest_builder = mock("RestBuilder")
|
125
125
|
rest_builder.
|
126
126
|
should_receive(:read_rest).
|
@@ -156,8 +156,8 @@ context "A QueryResult with full objects" do
|
|
156
156
|
}
|
157
157
|
end
|
158
158
|
|
159
|
-
object.total_result_count.
|
160
|
-
object.results.
|
159
|
+
object.total_result_count.should equal(2)
|
160
|
+
object.results.should be_instance_of(Array)
|
161
161
|
object.each_with_index do |c, i|
|
162
162
|
c.name.should == "Card#{i + 1}"
|
163
163
|
c.description.should == "Description#{i + 1}"
|
data/test/rest_builder_spec.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
|
-
|
3
|
+
describe "a RestBuilder " do
|
4
4
|
|
5
5
|
def xml
|
6
6
|
b = Builder::XmlMarkup.new
|
@@ -8,13 +8,13 @@ context "a RestBuilder " do
|
|
8
8
|
yield b
|
9
9
|
end
|
10
10
|
|
11
|
-
|
11
|
+
before(:each) do
|
12
12
|
@username = "username"
|
13
13
|
@password = "password"
|
14
14
|
@builder = RestBuilder.new(nil, @username, @password)
|
15
15
|
end
|
16
16
|
|
17
|
-
|
17
|
+
it "should produce correct xml for flat xml" do
|
18
18
|
expected_xml = xml do |b|
|
19
19
|
b.defect(:ref => "url") {
|
20
20
|
b.Name "foo"
|
@@ -24,7 +24,7 @@ context "a RestBuilder " do
|
|
24
24
|
@builder.update_rest(:defect, "url", {:name => "foo"}, @username, @password)
|
25
25
|
end
|
26
26
|
|
27
|
-
|
27
|
+
it "should produce correct xml for nested values" do
|
28
28
|
expected_xml = xml do |b|
|
29
29
|
b.defect(:ref => "url") {
|
30
30
|
b.Name "foo"
|
data/test/rest_object_spec.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
|
2
2
|
require 'test_helper'
|
3
3
|
|
4
|
-
|
4
|
+
describe RestObject do
|
5
5
|
|
6
|
-
|
6
|
+
before(:each) do
|
7
7
|
@api = RallyRestAPI.new
|
8
8
|
end
|
9
9
|
|
@@ -23,14 +23,14 @@ context "a RestObject" do
|
|
23
23
|
yield Builder::XmlMarkup.new(:indent => 2)
|
24
24
|
end
|
25
25
|
|
26
|
-
|
26
|
+
it "should return the type of the resource from a ref" do
|
27
27
|
o = rest_object_xml do |b|
|
28
28
|
b.Object(:refObjectName => "name", :ref => "ref", :type => "Defect")
|
29
29
|
end
|
30
30
|
o.type.should == "Defect"
|
31
31
|
end
|
32
32
|
|
33
|
-
|
33
|
+
it "should return the type of the resource from full object" do
|
34
34
|
o = rest_object_xml do |b|
|
35
35
|
b.Defect(:refObjectName => "name", :ref => "ref") {
|
36
36
|
b.Name("name")
|
@@ -39,7 +39,7 @@ context "a RestObject" do
|
|
39
39
|
o.type.should == "Defect"
|
40
40
|
end
|
41
41
|
|
42
|
-
|
42
|
+
it "should return the ref of the resource " do
|
43
43
|
o = rest_object_xml do |b|
|
44
44
|
b.Defect(:refObjectName => "name", :ref => "ref") {
|
45
45
|
b.Name("name")
|
@@ -48,7 +48,7 @@ context "a RestObject" do
|
|
48
48
|
o.ref.should == "ref"
|
49
49
|
end
|
50
50
|
|
51
|
-
|
51
|
+
it "should return the oid" do
|
52
52
|
o = rest_object_xml do |b|
|
53
53
|
b.Defect(:refObjectName => "name", :ref => "ref") {
|
54
54
|
b.Name("name")
|
@@ -58,31 +58,31 @@ context "a RestObject" do
|
|
58
58
|
o.oid.should == "12345"
|
59
59
|
end
|
60
60
|
|
61
|
-
|
61
|
+
it "should underscore element names" do
|
62
62
|
o = rest_object(%Q(<Object ref="bla">
|
63
63
|
<TextNode>text</TextNode>
|
64
64
|
</Object>))
|
65
|
-
o.text_node.
|
66
|
-
o.TextNode.
|
67
|
-
o.Text_Node.
|
68
|
-
o.textnode.
|
65
|
+
o.text_node.should_not equal nil
|
66
|
+
o.TextNode.should equal(nil)
|
67
|
+
o.Text_Node.should equal(nil)
|
68
|
+
o.textnode.should equal(nil)
|
69
69
|
end
|
70
70
|
|
71
|
-
|
71
|
+
it "should underscore elements ending in 'ID' correctly" do
|
72
72
|
o = rest_object(%Q(<Object ref="bla">
|
73
73
|
<SalesforceCaseID>12345</SalesforceCaseID>
|
74
74
|
</Object>))
|
75
75
|
o.salesforce_case_i_d.should == "12345"
|
76
76
|
end
|
77
77
|
|
78
|
-
|
78
|
+
it "should return text nodes" do
|
79
79
|
xml = %Q(<Object ref="bla">
|
80
80
|
<TextNode>text</TextNode>
|
81
81
|
</Object>)
|
82
82
|
rest_object(xml).text_node.should == "text"
|
83
83
|
end
|
84
84
|
|
85
|
-
|
85
|
+
it "should return nested text nodes" do
|
86
86
|
xml = %Q(<Object ref="bla">
|
87
87
|
<Nested>
|
88
88
|
<TextNode>text</TextNode>
|
@@ -91,7 +91,7 @@ context "a RestObject" do
|
|
91
91
|
rest_object(xml).nested.text_node.should == "text"
|
92
92
|
end
|
93
93
|
|
94
|
-
|
94
|
+
it "should lazy read ref elements" do
|
95
95
|
rest_builder = mock("RestBuilder")
|
96
96
|
rest_builder.
|
97
97
|
should_receive(:read_rest).
|
@@ -111,7 +111,7 @@ context "a RestObject" do
|
|
111
111
|
object.description.should == "Description"
|
112
112
|
end
|
113
113
|
|
114
|
-
|
114
|
+
it "should lazy read nested ref elements" do
|
115
115
|
rest_builder = mock("RestBuilder")
|
116
116
|
rest_builder.
|
117
117
|
should_receive(:read_rest).
|
@@ -130,12 +130,12 @@ context "a RestObject" do
|
|
130
130
|
b.TestCase(:ref => "http", :refObjectName => "name1")
|
131
131
|
}
|
132
132
|
end
|
133
|
-
object.test_case.
|
133
|
+
object.test_case.should be_instance_of(RestObject)
|
134
134
|
object.test_case.type.should == "TestCase"
|
135
135
|
object.test_case.description.should == "Description"
|
136
136
|
end
|
137
137
|
|
138
|
-
|
138
|
+
it "should lazy read only once nested ref elements" do
|
139
139
|
rest_builder = mock("RestBuilder")
|
140
140
|
rest_builder.
|
141
141
|
should_receive(:read_rest).
|
@@ -159,7 +159,7 @@ context "a RestObject" do
|
|
159
159
|
object.test_case.description.should == "Description"
|
160
160
|
end
|
161
161
|
|
162
|
-
|
162
|
+
it "should lazy load only once full object collections" do
|
163
163
|
rest_builder = mock("RestBuilder")
|
164
164
|
rest_builder.
|
165
165
|
should_receive(:read_rest).
|
@@ -191,14 +191,43 @@ context "a RestObject" do
|
|
191
191
|
}
|
192
192
|
end
|
193
193
|
|
194
|
-
object.results.length.
|
195
|
-
object.results.
|
194
|
+
object.results.length.should equal(2)
|
195
|
+
object.results.should be_instance_of(Array)
|
196
196
|
object.results.first.description.should == "Description1"
|
197
197
|
object.results.first.iteration.start_date.should == "12/12/01"
|
198
198
|
object.results.first.iteration.start_date.should == "12/12/01"
|
199
199
|
end
|
200
200
|
|
201
|
-
|
201
|
+
it "should dump and load" do
|
202
|
+
|
203
|
+
@api = RallyRestAPI.new
|
204
|
+
|
205
|
+
object = rest_object_xml do |b|
|
206
|
+
b.QueryResult {
|
207
|
+
b.Results {
|
208
|
+
b.Card(:ref => "http", :refObjectName => "Card1") {
|
209
|
+
b.Name("Card1")
|
210
|
+
b.Description("Description1")
|
211
|
+
b.Iteration(:ref => "http", :refObjectName => "name1")
|
212
|
+
}
|
213
|
+
b.Card(:ref => "http", :refObjectName => "Card2") {
|
214
|
+
b.Name("Card2")
|
215
|
+
b.Description("Description2")
|
216
|
+
b.Iteration(:ref => "http", :refObjectName => "name1")
|
217
|
+
}
|
218
|
+
}
|
219
|
+
}
|
220
|
+
end
|
221
|
+
|
222
|
+
new_object = Marshal.load(Marshal.dump(object))
|
223
|
+
|
224
|
+
new_object.results.length.should equal(2)
|
225
|
+
new_object.results.should be_instance_of(Array)
|
226
|
+
new_object.results.first.description.should == "Description1"
|
227
|
+
end
|
228
|
+
|
229
|
+
|
230
|
+
it "should parse collections as arrays " do
|
202
231
|
@api = RallyRestAPI.new(:parse_collections_as_hash => false)
|
203
232
|
|
204
233
|
object = rest_object_xml do |b|
|
@@ -210,11 +239,11 @@ context "a RestObject" do
|
|
210
239
|
}
|
211
240
|
}
|
212
241
|
end
|
213
|
-
object.tasks.
|
214
|
-
object.tasks.length.
|
242
|
+
object.tasks.should be_instance_of(Array)
|
243
|
+
object.tasks.length.should equal(3)
|
215
244
|
end
|
216
245
|
|
217
|
-
|
246
|
+
it "should parse collections as hashes " do
|
218
247
|
@api = RallyRestAPI.new(:parse_collections_as_hash => true)
|
219
248
|
|
220
249
|
object = rest_object_xml do |b|
|
@@ -226,11 +255,11 @@ context "a RestObject" do
|
|
226
255
|
}
|
227
256
|
}
|
228
257
|
end
|
229
|
-
object.tasks.
|
230
|
-
object.tasks.length.
|
258
|
+
object.tasks.should be_instance_of(Hash)
|
259
|
+
object.tasks.length.should equal(3)
|
231
260
|
end
|
232
261
|
|
233
|
-
|
262
|
+
it "should dup names into arrays, when collections are parsed as hashes" do
|
234
263
|
@api = RallyRestAPI.new(:parse_collections_as_hash => true)
|
235
264
|
|
236
265
|
object = rest_object_xml do |b|
|
@@ -242,13 +271,13 @@ context "a RestObject" do
|
|
242
271
|
}
|
243
272
|
}
|
244
273
|
end
|
245
|
-
object.tasks.
|
246
|
-
object.tasks.length.
|
247
|
-
object.tasks["task2"].
|
248
|
-
object.tasks["task2"].length.
|
274
|
+
object.tasks.should be_instance_of(Hash)
|
275
|
+
object.tasks.length.should equal(2)
|
276
|
+
object.tasks["task2"].should be_instance_of(Array)
|
277
|
+
object.tasks["task2"].length.should equal(2)
|
249
278
|
end
|
250
279
|
|
251
|
-
|
280
|
+
it "should parse unnamed elements into arrays when collections are parsed as hashes" do
|
252
281
|
@api = RallyRestAPI.new(:parse_collections_as_hash => true)
|
253
282
|
|
254
283
|
object = rest_object_xml do |b|
|
@@ -260,12 +289,12 @@ context "a RestObject" do
|
|
260
289
|
}
|
261
290
|
}
|
262
291
|
end
|
263
|
-
object.tasks.
|
264
|
-
object.tasks.length.
|
292
|
+
object.tasks.should be_instance_of(Array)
|
293
|
+
object.tasks.length.should equal(3)
|
265
294
|
end
|
266
295
|
|
267
296
|
|
268
|
-
|
297
|
+
it "should lazy read from collections" do
|
269
298
|
rest_builder = mock("RestBuilder")
|
270
299
|
rest_builder.
|
271
300
|
should_receive(:read_rest).
|
data/test/spec_typedef.rb
CHANGED
@@ -4,9 +4,9 @@ require 'logger'
|
|
4
4
|
require 'rally_rest_api'
|
5
5
|
|
6
6
|
|
7
|
-
|
7
|
+
describe "A Test Case type definition with 2 collection attribues, 1 object attributes, and 1 string attribute" do
|
8
8
|
|
9
|
-
|
9
|
+
before(:each) do
|
10
10
|
@b = Builder::XmlMarkup.new(:indent => 2)
|
11
11
|
@b.instruct!
|
12
12
|
|
@@ -36,48 +36,53 @@ context "A Test Case type definition with 2 collection attribues, 1 object attri
|
|
36
36
|
@typedef = TypeDefinition.new(OpenStruct.new(:username => "", :password => "", :logger => nil), xml)
|
37
37
|
end
|
38
38
|
|
39
|
-
|
40
|
-
@typedef.attributes.keys.each { |a| a.
|
39
|
+
it "all attribute keys are symbols" do
|
40
|
+
@typedef.attributes.keys.each { |a| a.should be_instance_of(Symbol) }
|
41
41
|
end
|
42
42
|
|
43
|
-
|
43
|
+
it "there should be 2 collection attributes" do
|
44
44
|
@typedef.collection_attributes.size.should == 2
|
45
45
|
end
|
46
46
|
|
47
|
-
|
48
|
-
@typedef.collection_attributes[:test_case_result].
|
47
|
+
it "the :test_case_result attribute should exist" do
|
48
|
+
@typedef.collection_attributes[:test_case_result].should_not be_nil
|
49
49
|
end
|
50
50
|
|
51
|
-
|
52
|
-
@typedef.collection_attributes.keys.each { |a| a.
|
51
|
+
it "All the collection attributes keys should be symbols" do
|
52
|
+
@typedef.collection_attributes.keys.each { |a| a.should be_instance_of(Symbol) }
|
53
53
|
end
|
54
54
|
|
55
|
-
|
55
|
+
it "there should be 1 object attribute" do
|
56
56
|
@typedef.object_attributes.size.should == 1
|
57
57
|
end
|
58
58
|
|
59
|
-
|
60
|
-
@typedef.object_attributes[:requirement].
|
59
|
+
it "the :requirement attribute should exist" do
|
60
|
+
@typedef.object_attributes[:requirement].should_not be_nil
|
61
61
|
end
|
62
62
|
|
63
|
-
|
63
|
+
it "should have 2 constrained attributes" do
|
64
64
|
@typedef.constrained_attributes.size.should == 2
|
65
65
|
end
|
66
66
|
|
67
|
-
|
67
|
+
it "should have 2 cusom attributes" do
|
68
68
|
@typedef.custom_attributes.size.should == 2
|
69
69
|
end
|
70
70
|
|
71
|
-
|
72
|
-
@typedef.custom_constrained_attributes.size.
|
73
|
-
@typedef.custom_dropdown_attributes.size.
|
71
|
+
it "should have 1 custom_constrained_attributes" do
|
72
|
+
@typedef.custom_constrained_attributes.size.should equal(1)
|
73
|
+
@typedef.custom_dropdown_attributes.size.should equal(1)
|
74
74
|
end
|
75
75
|
|
76
|
-
|
77
|
-
@typedef.attributes.values.each { |attrdef| attrdef.
|
76
|
+
it "#attributes should return instances of AttributeDefinitions" do
|
77
|
+
@typedef.attributes.values.each { |attrdef| attrdef.should be_instance_of(AttributeDefinition) }
|
78
78
|
end
|
79
79
|
|
80
|
-
|
81
|
-
@typedef.parent.
|
80
|
+
it "should have a nil parent" do
|
81
|
+
@typedef.parent.should be_nil
|
82
82
|
end
|
83
|
+
|
84
|
+
it "should not throw exception Marshalling" do
|
85
|
+
lambda { Marshal.dump(@typedef) }.should_not raise_error Exception
|
86
|
+
end
|
87
|
+
|
83
88
|
end
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.2
|
|
3
3
|
specification_version: 1
|
4
4
|
name: rally_rest_api
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.
|
7
|
-
date: 2007-
|
6
|
+
version: 0.7.0
|
7
|
+
date: 2007-06-04 00:00:00 -06:00
|
8
8
|
summary: A ruby-ized interface to Rally's REST webservices API
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -40,6 +40,7 @@ files:
|
|
40
40
|
- lib/rally_rest_api/timeout_catching_rest_builder.rb
|
41
41
|
- lib/rally_rest_api/rest_object.rb
|
42
42
|
- lib/rally_rest_api/typedef.rb
|
43
|
+
- lib/rally_rest_api/ruport.rb
|
43
44
|
- lib/rally_rest_api/version.rb
|
44
45
|
- Manifest.txt
|
45
46
|
- Rakefile
|
@@ -91,5 +92,5 @@ dependencies:
|
|
91
92
|
requirements:
|
92
93
|
- - ">="
|
93
94
|
- !ruby/object:Gem::Version
|
94
|
-
version: 1.
|
95
|
+
version: 1.2.0
|
95
96
|
version:
|