rally_rest_api 0.6.3 → 0.6.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Manifest.txt +1 -1
- data/lib/rally_rest_api/query.rb +9 -1
- data/lib/rally_rest_api/query_result.rb +11 -0
- data/lib/rally_rest_api/rally_rest.rb +17 -11
- data/lib/rally_rest_api/rest_builder.rb +13 -3
- data/lib/rally_rest_api/rest_object.rb +41 -27
- data/lib/rally_rest_api/typedef.rb +7 -1
- data/lib/rally_rest_api/version.rb +1 -1
- data/test/query_result_spec.rb +100 -8
- data/test/rest_object_spec.rb +288 -0
- data/test/tc_rest_api.rb +9 -9
- data/test/tc_rest_query.rb +13 -1
- data/test/test_helper.rb +2 -0
- metadata +4 -4
- data/test/tc_rest_object.rb +0 -166
data/Manifest.txt
CHANGED
data/lib/rally_rest_api/query.rb
CHANGED
@@ -24,6 +24,14 @@ class NilClass
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
+
class TrueClass
|
28
|
+
alias :to_q :to_s
|
29
|
+
end
|
30
|
+
|
31
|
+
class FalseClass
|
32
|
+
alias :to_q :to_s
|
33
|
+
end
|
34
|
+
|
27
35
|
# == Generate a query string for Rally's webservice query interface.
|
28
36
|
# Arguments are:
|
29
37
|
# type - the type to query for
|
@@ -81,7 +89,7 @@ class RestQuery
|
|
81
89
|
@query_string = "query=" << URI.escape(QueryBuilder.new("and", &block).to_q) if block_given?
|
82
90
|
@query_string.gsub!("&", "%26")
|
83
91
|
@args_for_paging = {}
|
84
|
-
[:workspace, :project, :project_scope_down, :project_scope_up].each { |k| @args_for_paging[k] = args[k] if args.key?(k) }
|
92
|
+
[:workspace, :project, :project_scope_down, :project_scope_up, :order, :fetch].each { |k| @args_for_paging[k] = args[k] if args.key?(k) }
|
85
93
|
@query_params = process_args(args)
|
86
94
|
end
|
87
95
|
|
@@ -17,6 +17,8 @@ require File.dirname(__FILE__) + '/rest_object'
|
|
17
17
|
#
|
18
18
|
#
|
19
19
|
class QueryResult < RestObject
|
20
|
+
include Enumerable
|
21
|
+
|
20
22
|
attr_reader :total_result_count
|
21
23
|
attr_reader :page_size
|
22
24
|
attr_reader :start_index
|
@@ -78,4 +80,13 @@ class QueryResult < RestObject
|
|
78
80
|
false
|
79
81
|
end
|
80
82
|
|
83
|
+
def ref?(element) # :nodoc:
|
84
|
+
!element.nil? &&
|
85
|
+
!element.attributes["ref"].nil?
|
86
|
+
end
|
87
|
+
|
88
|
+
def terminal?(node)
|
89
|
+
!node.has_elements? || ref?(node)
|
90
|
+
end
|
91
|
+
|
81
92
|
end
|
@@ -7,9 +7,8 @@ require 'ostruct'
|
|
7
7
|
# RallyRestAPI - A Ruby-ized interface to Rally's REST webservice API
|
8
8
|
#
|
9
9
|
class RallyRestAPI
|
10
|
-
include RestBuilder
|
11
10
|
|
12
|
-
attr_reader :username, :password, :base_url, :raise_on_warning, :logger
|
11
|
+
attr_reader :username, :password, :base_url, :raise_on_warning, :logger, :builder
|
13
12
|
attr_accessor :parse_collections_as_hash
|
14
13
|
|
15
14
|
ALLOWED_TYPES = %w[subscription workspace project iteration release defect defect_suite test_case
|
@@ -25,29 +24,36 @@ class RallyRestAPI
|
|
25
24
|
# * raise_on_warn - true|false|Exception Class. If you want exceptions raised on warnings. Default is false. if 'true' RuntimeException will be raised. If ExceptionClass, then an instance of that will be raised.
|
26
25
|
# * logger - a Logger to log to.
|
27
26
|
#
|
28
|
-
def initialize(options = {
|
27
|
+
def initialize(options = {})
|
29
28
|
parse_options options
|
30
29
|
end
|
31
30
|
|
32
31
|
def parse_options(options)
|
33
32
|
@username = options[:username]
|
34
33
|
@password = options[:password]
|
35
|
-
@base_url = options[:base_url]
|
34
|
+
@base_url = options[:base_url] || "https://rally1.rallydev.com/slm"
|
36
35
|
@raise_on_warning = options[:raise_on_warning]
|
37
36
|
@logger = options[:logger]
|
38
37
|
@parse_collections_as_hash = options[:parse_collections_as_hash] || false
|
38
|
+
|
39
|
+
if options[:builder]
|
40
|
+
builder = options[:builder]
|
41
|
+
else
|
42
|
+
builder = RestBuilder.new(@base_url, @username, @password)
|
43
|
+
end
|
44
|
+
builder.logger = @logger if @logger
|
45
|
+
@builder = builder
|
39
46
|
end
|
40
47
|
|
41
48
|
# return an instance of User, for the currently logged in user.
|
42
49
|
def user
|
43
|
-
RestObject.new(self, read_rest("#{@base_url}/webservice/1.0/user", @username, @password))
|
50
|
+
RestObject.new(self, builder.read_rest("#{@base_url}/webservice/1.0/user", @username, @password))
|
44
51
|
end
|
45
52
|
alias :start :user # :nodoc:
|
46
53
|
|
47
54
|
# This is deprecated, use create instead
|
48
55
|
def method_missing(type, args, &block) # :nodoc:
|
49
|
-
|
50
|
-
create_rest(type, args, @username, @password)
|
56
|
+
raise "'#{type}' is not a supported method. Use create() instead"
|
51
57
|
end
|
52
58
|
|
53
59
|
# Create an object.
|
@@ -59,7 +65,7 @@ class RallyRestAPI
|
|
59
65
|
# returns the created object as a RestObject.
|
60
66
|
def create(type, values) # :yields: new_object
|
61
67
|
# raise "'#{type}' is not a supported type. Supported types are: #{ALLOWED_TYPES.inspect}" unless ALLOWED_TYPES.include?(type.to_s)
|
62
|
-
object = create_rest(type, values, @username, @password)
|
68
|
+
object = builder.create_rest(type, values, @username, @password)
|
63
69
|
yield object if block_given?
|
64
70
|
object
|
65
71
|
end
|
@@ -68,10 +74,10 @@ class RallyRestAPI
|
|
68
74
|
# Example :
|
69
75
|
# rally.find(:artifact, :page_size => 20, :start_index => 20) { equal :name, "name" }
|
70
76
|
# See RestQuery for more info.
|
71
|
-
def find(type, args = {}, &
|
77
|
+
def find(type, args = {}, &query_block)
|
72
78
|
# pass the args to RestQuery, make it generate the string and handle generating the query for the
|
73
79
|
# next page etc.
|
74
|
-
query = RestQuery.new(type, args, &
|
80
|
+
query = RestQuery.new(type, args, &query_block)
|
75
81
|
query(query)
|
76
82
|
end
|
77
83
|
|
@@ -92,7 +98,7 @@ class RallyRestAPI
|
|
92
98
|
|
93
99
|
def query(query) # :nodoc:
|
94
100
|
query_url = "#{@base_url}/webservice/1.0/#{query.type.to_s.to_camel}?" << query.to_q
|
95
|
-
xml = read_rest(query_url, @username, @password)
|
101
|
+
xml = builder.read_rest(query_url, @username, @password)
|
96
102
|
QueryResult.new(query, self, xml)
|
97
103
|
end
|
98
104
|
|
@@ -4,10 +4,15 @@ require 'rexml/document'
|
|
4
4
|
require 'rubygems'
|
5
5
|
require 'builder'
|
6
6
|
|
7
|
-
|
7
|
+
class RestBuilder # :nodoc:
|
8
|
+
|
9
|
+
attr_reader :base_url, :username, :password
|
10
|
+
attr_accessor :logger
|
11
|
+
|
12
|
+
def initialize(base_url, username, password)
|
13
|
+
@base_url, @username, @password = base_url, username, password
|
14
|
+
end
|
8
15
|
|
9
|
-
DEBUG = false
|
10
|
-
|
11
16
|
# create_rest - convert slm builder style:
|
12
17
|
# slm.feature(:name => "feature name")
|
13
18
|
#
|
@@ -72,6 +77,7 @@ module RestBuilder # :nodoc:
|
|
72
77
|
req.content_type = 'text/xml'
|
73
78
|
http = Net::HTTP.new(url.host, url.port)
|
74
79
|
http.use_ssl = true if url.scheme == "https"
|
80
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
75
81
|
body = http.start { |http| http.request(req) }.body
|
76
82
|
debug "RestBuilder#send_request result = #{body}"
|
77
83
|
check_for_errors(body)
|
@@ -175,5 +181,9 @@ module RestBuilder # :nodoc:
|
|
175
181
|
COLLECTION_TYPES.include?(type)
|
176
182
|
end
|
177
183
|
|
184
|
+
def debug(message)
|
185
|
+
@logger.debug message if @logger
|
186
|
+
end
|
187
|
+
|
178
188
|
|
179
189
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'logger'
|
2
2
|
require File.dirname(__FILE__) + '/rest_builder'
|
3
|
+
require 'builder/blankslate'
|
3
4
|
|
4
5
|
# == An interface to a Rally resource
|
5
6
|
# RestObject is an adapter that is initialized with a resource ref (URL) that represents a Rally object. The adapter
|
@@ -18,10 +19,8 @@ require File.dirname(__FILE__) + '/rest_builder'
|
|
18
19
|
#
|
19
20
|
#
|
20
21
|
class RestObject
|
21
|
-
include RestBuilder
|
22
22
|
|
23
23
|
attr_reader :username, :password, :rally_rest
|
24
|
-
alias :builder :rally_rest
|
25
24
|
|
26
25
|
def initialize(rally_rest, document_content)
|
27
26
|
@rally_rest = rally_rest
|
@@ -36,24 +35,32 @@ class RestObject
|
|
36
35
|
end
|
37
36
|
|
38
37
|
private
|
38
|
+
def terminal?(node)
|
39
|
+
!node.has_elements?
|
40
|
+
end
|
41
|
+
|
42
|
+
def terminal_object(node)
|
43
|
+
if ref?(node)
|
44
|
+
low_debug "Returning RestObject for #{ node.to_s}"
|
45
|
+
return RestObject.new(rally_rest, node.to_s)
|
46
|
+
else
|
47
|
+
low_debug "Returning text #{ node.text }"
|
48
|
+
return node.text
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
39
52
|
def parse(node)
|
40
|
-
|
53
|
+
low_debug "parse self = #{self.object_id} node = #{ node.to_s }"
|
41
54
|
|
42
|
-
if
|
43
|
-
|
44
|
-
debug "Returning RestObject for #{ node.to_s}"
|
45
|
-
return RestObject.new(@rally_rest, node.to_s)
|
46
|
-
else
|
47
|
-
debug "Returning text #{ node.text }"
|
48
|
-
return node.text
|
49
|
-
end
|
55
|
+
if terminal? node
|
56
|
+
return terminal_object(node)
|
50
57
|
end
|
58
|
+
|
51
59
|
# Convert nodes with children into Hashes
|
52
60
|
elements = {}
|
53
61
|
class << elements
|
54
62
|
def method_missing(sym, *args)
|
55
63
|
value = self[sym]
|
56
|
-
# raise "No matching element named #{sym}. Know values are #{self.keys.join(' ')}." if value.nil?
|
57
64
|
value
|
58
65
|
end
|
59
66
|
end
|
@@ -61,15 +68,15 @@ class RestObject
|
|
61
68
|
#Add all the element's children to the hash.
|
62
69
|
node.each_element do |e|
|
63
70
|
name = underscore(e.name).to_sym
|
64
|
-
|
71
|
+
low_debug "Looking at name = #{ name }"
|
65
72
|
case elements[name]
|
66
73
|
# We've not seen this element before
|
67
74
|
when NilClass
|
68
|
-
|
75
|
+
low_debug "NilClass name = #{ name }"
|
69
76
|
# if we have a collection of things with refObjectName attributes, collect them into a hash
|
70
77
|
# with the refObjectName as the key, unless we're told not to make hashs out of collections.
|
71
78
|
if named_collection?(e) && parse_collections_as_hash?
|
72
|
-
|
79
|
+
low_debug "Named collection #{ name }"
|
73
80
|
elements[name] = {}
|
74
81
|
e.elements.each do |child|
|
75
82
|
ref_name = child.attributes["refObjectName"]
|
@@ -81,20 +88,20 @@ class RestObject
|
|
81
88
|
end
|
82
89
|
end
|
83
90
|
elsif collection?(e)
|
84
|
-
|
91
|
+
low_debug "Collection #{ name }"
|
85
92
|
elements[name] = []
|
86
93
|
e.elements.each do |child|
|
87
94
|
elements[name] << parse(child)
|
88
95
|
end
|
89
96
|
else # a fully dressed object, without a name
|
90
|
-
|
97
|
+
low_debug "Fully Dressed object, without a name #{ name }"
|
91
98
|
elements[name] = parse(e)
|
92
99
|
end
|
93
100
|
|
94
101
|
#Non-unique child elements become arrays: We've already
|
95
102
|
#created the array: just add the element.
|
96
103
|
when Array
|
97
|
-
|
104
|
+
low_debug "Array name = #{ name }"
|
98
105
|
elements[name] << parse(e)
|
99
106
|
|
100
107
|
#We haven't created the array yet: do so,
|
@@ -103,7 +110,7 @@ class RestObject
|
|
103
110
|
# when Hash
|
104
111
|
# raise "Don't know how to deail with a named collection we've already seen! element = #{e}"
|
105
112
|
else
|
106
|
-
|
113
|
+
low_debug "creating and wrapping #{elements[name]}with an array name = #{ name }"
|
107
114
|
elements[name] = [elements[name]]
|
108
115
|
elements[name] << parse(e)
|
109
116
|
end
|
@@ -118,7 +125,7 @@ class RestObject
|
|
118
125
|
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
119
126
|
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
120
127
|
tr("-", "_").
|
121
|
-
downcase
|
128
|
+
downcase.gsub(/_id$/,'_i_d')
|
122
129
|
end
|
123
130
|
|
124
131
|
|
@@ -134,9 +141,9 @@ class RestObject
|
|
134
141
|
alias :to_q :ref
|
135
142
|
|
136
143
|
# The name of the object, without having to read the entire body
|
137
|
-
|
138
|
-
|
139
|
-
|
144
|
+
def name
|
145
|
+
@document.root.attributes["refObjectName"]
|
146
|
+
end
|
140
147
|
|
141
148
|
# The type of the underlying resource
|
142
149
|
def type
|
@@ -169,7 +176,7 @@ class RestObject
|
|
169
176
|
|
170
177
|
def elements(read = @elements.nil?) #:nodoc:
|
171
178
|
if read
|
172
|
-
@document_content = read_rest(self.ref, @username, @password)
|
179
|
+
@document_content = builder.read_rest(self.ref, @username, @password)
|
173
180
|
@document = REXML::Document.new @document_content
|
174
181
|
@elements = parse(@document.root)
|
175
182
|
end
|
@@ -186,14 +193,14 @@ class RestObject
|
|
186
193
|
|
187
194
|
# update the resource. This will re-read the resource after the update
|
188
195
|
def update(args)
|
189
|
-
update_rest(self.type, self.ref, args, @username, @password)
|
196
|
+
builder.update_rest(self.type, self.ref, args, @username, @password)
|
190
197
|
# need to deal with the block, setting up contexts etc
|
191
198
|
self.elements(true)
|
192
199
|
end
|
193
200
|
|
194
201
|
# delete the resource
|
195
202
|
def delete
|
196
|
-
delete_rest(self.ref, @username, @password)
|
203
|
+
builder.delete_rest(self.ref, @username, @password)
|
197
204
|
end
|
198
205
|
|
199
206
|
def method_missing(sym, *args) # :nodoc:
|
@@ -226,8 +233,15 @@ class RestObject
|
|
226
233
|
self.rally_rest.parse_collections_as_hash?
|
227
234
|
end
|
228
235
|
|
236
|
+
def low_debug(message)
|
237
|
+
# debug message
|
238
|
+
end
|
239
|
+
|
229
240
|
def debug(message)
|
230
|
-
|
241
|
+
@rally_rest.logger.debug(message) if @rally_rest.logger
|
231
242
|
end
|
232
243
|
|
244
|
+
def builder
|
245
|
+
rally_rest.builder
|
246
|
+
end
|
233
247
|
end
|
@@ -15,7 +15,13 @@ class TypeDefinition < RestObject # :nodoc:
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def self.get_type_definition(workspace, type)
|
18
|
-
|
18
|
+
# This is a hack
|
19
|
+
typedefs = case workspace.type_definitions
|
20
|
+
when Hash : workspace.type_definitions.values.flatten
|
21
|
+
when Array : workspace.type_definitions
|
22
|
+
end
|
23
|
+
typedef = typedefs.find { |td| td.element_name == type }
|
24
|
+
# end hack
|
19
25
|
TypeDefinition.new(typedef.rally_rest, typedef.body)
|
20
26
|
end
|
21
27
|
|
data/test/query_result_spec.rb
CHANGED
@@ -1,8 +1,4 @@
|
|
1
|
-
require '
|
2
|
-
require 'spec'
|
3
|
-
require 'builder'
|
4
|
-
|
5
|
-
require File.dirname(__FILE__) + '/../lib/rally_rest_api/query_result'
|
1
|
+
require 'test_helper'
|
6
2
|
|
7
3
|
context "A QueryResult" do
|
8
4
|
|
@@ -14,7 +10,7 @@ context "A QueryResult" do
|
|
14
10
|
@api.stub!(:parse_collections_as_hash?).and_return true
|
15
11
|
end
|
16
12
|
|
17
|
-
def make_result(total, page_size, start_index)
|
13
|
+
def make_result(total = 20, page_size = 20, start_index = 1)
|
18
14
|
result_count = total > page_size ? page_size : total
|
19
15
|
|
20
16
|
b = Builder::XmlMarkup.new(:indent => 2)
|
@@ -79,8 +75,104 @@ context "A QueryResult" do
|
|
79
75
|
result.results[19].name.should == "name19"
|
80
76
|
end
|
81
77
|
|
78
|
+
specify "should return RestObjects for results" do
|
79
|
+
make_result.each { |o| o.should_be_instance_of RestObject }
|
80
|
+
end
|
82
81
|
end
|
83
82
|
|
84
|
-
|
85
|
-
|
83
|
+
|
84
|
+
context "A QueryResult with full objects" do
|
85
|
+
|
86
|
+
setup do
|
87
|
+
@api = mock("RallyRestAPI")
|
88
|
+
@api.stub! :username
|
89
|
+
@api.stub! :password
|
90
|
+
@api.stub!(:logger).and_return(nil)
|
91
|
+
@api.stub!(:parse_collections_as_hash?).and_return true
|
92
|
+
end
|
93
|
+
|
94
|
+
def make_result(total = 20, page_size = 20, start_index = 1)
|
95
|
+
result_count = total > page_size ? page_size : total
|
96
|
+
|
97
|
+
b = Builder::XmlMarkup.new(:indent => 2)
|
98
|
+
xml = b.QueryResult {
|
99
|
+
b.TotalResultCount total
|
100
|
+
b.PageSize page_size
|
101
|
+
b.StartIndex start_index
|
102
|
+
|
103
|
+
if (result_count > 0)
|
104
|
+
b.Results {
|
105
|
+
result_count.times do |i|
|
106
|
+
b.RefElement(:ref => "http", :refObjectName => "name#{i}") {
|
107
|
+
b.Name("This is the name for #{i}")
|
108
|
+
}
|
109
|
+
end
|
110
|
+
}
|
111
|
+
else
|
112
|
+
b.Results
|
113
|
+
end
|
114
|
+
}
|
115
|
+
|
116
|
+
QueryResult.new(nil, @api, xml)
|
117
|
+
end
|
118
|
+
|
119
|
+
def query_result_xml(&block)
|
120
|
+
QueryResult.new(nil, @api, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>#{xml &block}")
|
121
|
+
end
|
122
|
+
|
123
|
+
def xml
|
124
|
+
yield Builder::XmlMarkup.new(:indent => 2)
|
125
|
+
end
|
126
|
+
|
127
|
+
specify "should return RestObjects for results" do
|
128
|
+
make_result.each { |o| o.should_be_instance_of RestObject }
|
129
|
+
end
|
130
|
+
|
131
|
+
specify "full object collections should lazy load only once" do
|
132
|
+
rest_builder = mock("RestBuilder")
|
133
|
+
rest_builder.
|
134
|
+
should_receive(:read_rest).
|
135
|
+
twice.
|
136
|
+
with(:any_args).
|
137
|
+
and_return( xml do |b|
|
138
|
+
b.iteration {
|
139
|
+
b.Name("name1")
|
140
|
+
b.StartDate("12/12/01")
|
141
|
+
}
|
142
|
+
end )
|
143
|
+
|
144
|
+
@api = RallyRestAPI.new(:builder => rest_builder)
|
145
|
+
|
146
|
+
object = query_result_xml do |b|
|
147
|
+
b.QueryResult {
|
148
|
+
b.TotalResultCount 2
|
149
|
+
b.PageSize 20
|
150
|
+
b.StartIndex 1
|
151
|
+
|
152
|
+
b.Results {
|
153
|
+
b.Card(:ref => "http", :refObjectName => "Card1") {
|
154
|
+
b.Name("Card1")
|
155
|
+
b.Description("Description1")
|
156
|
+
b.Iteration(:ref => "http", :refObjectName => "name1")
|
157
|
+
}
|
158
|
+
b.Card(:ref => "http", :refObjectName => "Card2") {
|
159
|
+
b.Name("Card2")
|
160
|
+
b.Description("Description2")
|
161
|
+
b.Iteration(:ref => "http", :refObjectName => "name1")
|
162
|
+
}
|
163
|
+
}
|
164
|
+
}
|
165
|
+
end
|
166
|
+
|
167
|
+
object.total_result_count.should_be 2
|
168
|
+
object.results.should_be_instance_of Array
|
169
|
+
object.each_with_index do |c, i|
|
170
|
+
c.name.should == "Card#{i + 1}"
|
171
|
+
c.description.should == "Description#{i + 1}"
|
172
|
+
c.iteration.start_date.should == "12/12/01"
|
173
|
+
c.iteration.start_date.should == "12/12/01"
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
|
86
178
|
end
|
@@ -0,0 +1,288 @@
|
|
1
|
+
|
2
|
+
require 'test_helper'
|
3
|
+
|
4
|
+
context "a RestObject" do
|
5
|
+
|
6
|
+
setup do
|
7
|
+
@api = RallyRestAPI.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def rest_object(xml)
|
11
|
+
RestObject.new(@api, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>#{xml}")
|
12
|
+
end
|
13
|
+
|
14
|
+
def rest_object_xml(&block)
|
15
|
+
RestObject.new(@api, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>#{xml &block}")
|
16
|
+
end
|
17
|
+
|
18
|
+
def b
|
19
|
+
@b ||= Builder::XmlMarkup.new(:indent => 2)
|
20
|
+
end
|
21
|
+
|
22
|
+
def xml
|
23
|
+
yield Builder::XmlMarkup.new(:indent => 2)
|
24
|
+
end
|
25
|
+
|
26
|
+
specify "should return the type of the resource from a ref" do
|
27
|
+
o = rest_object_xml do |b|
|
28
|
+
b.Object(:refObjectName => "name", :ref => "ref", :type => "Defect")
|
29
|
+
end
|
30
|
+
o.type.should == "Defect"
|
31
|
+
end
|
32
|
+
|
33
|
+
specify "should return the type of the resource from full object" do
|
34
|
+
o = rest_object_xml do |b|
|
35
|
+
b.Defect(:refObjectName => "name", :ref => "ref") {
|
36
|
+
b.Name("name")
|
37
|
+
}
|
38
|
+
end
|
39
|
+
o.type.should == "Defect"
|
40
|
+
end
|
41
|
+
|
42
|
+
specify "should return the ref of the resource " do
|
43
|
+
o = rest_object_xml do |b|
|
44
|
+
b.Defect(:refObjectName => "name", :ref => "ref") {
|
45
|
+
b.Name("name")
|
46
|
+
}
|
47
|
+
end
|
48
|
+
o.ref.should == "ref"
|
49
|
+
end
|
50
|
+
|
51
|
+
specify "should underscore element names" do
|
52
|
+
o = rest_object(%Q(<Object ref="bla">
|
53
|
+
<TextNode>text</TextNode>
|
54
|
+
</Object>))
|
55
|
+
o.text_node.should_not_be nil
|
56
|
+
o.TextNode.should_be nil
|
57
|
+
o.Text_Node.should_be nil
|
58
|
+
o.textnode.should_be nil
|
59
|
+
end
|
60
|
+
|
61
|
+
specify "should underscore elements ending in 'ID' correctly" do
|
62
|
+
o = rest_object(%Q(<Object ref="bla">
|
63
|
+
<SalesforceCaseID>12345</SalesforceCaseID>
|
64
|
+
</Object>))
|
65
|
+
o.salesforce_case_i_d.should == "12345"
|
66
|
+
end
|
67
|
+
|
68
|
+
specify "should return text nodes" do
|
69
|
+
xml = %Q(<Object ref="bla">
|
70
|
+
<TextNode>text</TextNode>
|
71
|
+
</Object>)
|
72
|
+
rest_object(xml).text_node.should == "text"
|
73
|
+
end
|
74
|
+
|
75
|
+
specify "should return nested text nodes" do
|
76
|
+
xml = %Q(<Object ref="bla">
|
77
|
+
<Nested>
|
78
|
+
<TextNode>text</TextNode>
|
79
|
+
</Nested>
|
80
|
+
</Object>)
|
81
|
+
rest_object(xml).nested.text_node.should == "text"
|
82
|
+
end
|
83
|
+
|
84
|
+
specify "ref elements should lazy read" do
|
85
|
+
rest_builder = mock("RestBuilder")
|
86
|
+
rest_builder.
|
87
|
+
should_receive(:read_rest).
|
88
|
+
with(:any_args).
|
89
|
+
and_return( xml do |b|
|
90
|
+
b.TestCase {
|
91
|
+
b.Name("name1")
|
92
|
+
b.Description("Description")
|
93
|
+
}
|
94
|
+
end )
|
95
|
+
|
96
|
+
@api = RallyRestAPI.new(:builder => rest_builder)
|
97
|
+
|
98
|
+
object = rest_object_xml do |b|
|
99
|
+
b.TestCase(:ref => "http", :name => "name1")
|
100
|
+
end
|
101
|
+
object.description.should == "Description"
|
102
|
+
end
|
103
|
+
|
104
|
+
specify "nested ref elements should lazy read" do
|
105
|
+
rest_builder = mock("RestBuilder")
|
106
|
+
rest_builder.
|
107
|
+
should_receive(:read_rest).
|
108
|
+
with(:any_args).
|
109
|
+
and_return( xml do |b|
|
110
|
+
b.TestCase {
|
111
|
+
b.Name("name1")
|
112
|
+
b.Description("Description")
|
113
|
+
}
|
114
|
+
end )
|
115
|
+
|
116
|
+
@api = RallyRestAPI.new(:builder => rest_builder)
|
117
|
+
|
118
|
+
object = rest_object_xml do |b|
|
119
|
+
b.Defect(:ref => "http") {
|
120
|
+
b.TestCase(:ref => "http", :refObjectName => "name1")
|
121
|
+
}
|
122
|
+
end
|
123
|
+
object.test_case.should_be_instance_of RestObject
|
124
|
+
object.test_case.type.should == "TestCase"
|
125
|
+
object.test_case.description.should == "Description"
|
126
|
+
end
|
127
|
+
|
128
|
+
specify "nested ref elements should lazy read only once" do
|
129
|
+
rest_builder = mock("RestBuilder")
|
130
|
+
rest_builder.
|
131
|
+
should_receive(:read_rest).
|
132
|
+
once.
|
133
|
+
with(:any_args).
|
134
|
+
and_return( xml do |b|
|
135
|
+
b.TestCase {
|
136
|
+
b.Name("name1")
|
137
|
+
b.Description("Description")
|
138
|
+
}
|
139
|
+
end )
|
140
|
+
|
141
|
+
@api = RallyRestAPI.new(:builder => rest_builder)
|
142
|
+
|
143
|
+
object = rest_object_xml do |b|
|
144
|
+
b.Defect(:ref => "http") {
|
145
|
+
b.TestCase(:ref => "http", :refObjectName => "name1")
|
146
|
+
}
|
147
|
+
end
|
148
|
+
object.test_case.description.should == "Description"
|
149
|
+
object.test_case.description.should == "Description"
|
150
|
+
end
|
151
|
+
|
152
|
+
specify "full object collections should lazy load only once" do
|
153
|
+
rest_builder = mock("RestBuilder")
|
154
|
+
rest_builder.
|
155
|
+
should_receive(:read_rest).
|
156
|
+
once.
|
157
|
+
with(:any_args).
|
158
|
+
and_return( xml do |b|
|
159
|
+
b.iteration {
|
160
|
+
b.Name("name1")
|
161
|
+
b.StartDate("12/12/01")
|
162
|
+
}
|
163
|
+
end )
|
164
|
+
|
165
|
+
@api = RallyRestAPI.new(:builder => rest_builder)
|
166
|
+
|
167
|
+
object = rest_object_xml do |b|
|
168
|
+
b.QueryResult {
|
169
|
+
b.Results {
|
170
|
+
b.Card(:ref => "http", :refObjectName => "Card1") {
|
171
|
+
b.Name("Card1")
|
172
|
+
b.Description("Description1")
|
173
|
+
b.Iteration(:ref => "http", :refObjectName => "name1")
|
174
|
+
}
|
175
|
+
b.Card(:ref => "http", :refObjectName => "Card2") {
|
176
|
+
b.Name("Card2")
|
177
|
+
b.Description("Description2")
|
178
|
+
b.Iteration(:ref => "http", :refObjectName => "name1")
|
179
|
+
}
|
180
|
+
}
|
181
|
+
}
|
182
|
+
end
|
183
|
+
|
184
|
+
object.results.length.should_be 2
|
185
|
+
object.results.should_be_instance_of Array
|
186
|
+
object.results.first.description.should == "Description1"
|
187
|
+
object.results.first.iteration.start_date.should == "12/12/01"
|
188
|
+
object.results.first.iteration.start_date.should == "12/12/01"
|
189
|
+
end
|
190
|
+
|
191
|
+
specify "collections are parsed as arrays " do
|
192
|
+
@api = RallyRestAPI.new(:parse_collections_as_hash => false)
|
193
|
+
|
194
|
+
object = rest_object_xml do |b|
|
195
|
+
b.Story(:refObjectName => "story", :ref => "http") {
|
196
|
+
b.Tasks {
|
197
|
+
b.Task(:refObjectName => "task1", :ref => "ref")
|
198
|
+
b.Task(:refObjectName => "task2", :ref => "ref")
|
199
|
+
b.Task(:refObjectName => "task3", :ref => "ref")
|
200
|
+
}
|
201
|
+
}
|
202
|
+
end
|
203
|
+
object.tasks.should_be_instance_of Array
|
204
|
+
object.tasks.length.should_be 3
|
205
|
+
end
|
206
|
+
|
207
|
+
specify "collections are parsed as hashes " do
|
208
|
+
@api = RallyRestAPI.new(:parse_collections_as_hash => true)
|
209
|
+
|
210
|
+
object = rest_object_xml do |b|
|
211
|
+
b.Story(:refObjectName => "story", :ref => "http") {
|
212
|
+
b.Tasks {
|
213
|
+
b.Task(:refObjectName => "task1", :ref => "ref")
|
214
|
+
b.Task(:refObjectName => "task2", :ref => "ref")
|
215
|
+
b.Task(:refObjectName => "task3", :ref => "ref")
|
216
|
+
}
|
217
|
+
}
|
218
|
+
end
|
219
|
+
object.tasks.should_be_instance_of Hash
|
220
|
+
object.tasks.length.should_be 3
|
221
|
+
end
|
222
|
+
|
223
|
+
specify "when collections are parsed as hashes, dup names should be in arrays " do
|
224
|
+
@api = RallyRestAPI.new(:parse_collections_as_hash => true)
|
225
|
+
|
226
|
+
object = rest_object_xml do |b|
|
227
|
+
b.Story(:refObjectName => "story", :ref => "http") {
|
228
|
+
b.Tasks {
|
229
|
+
b.Task(:refObjectName => "task1", :ref => "ref")
|
230
|
+
b.Task(:refObjectName => "task2", :ref => "ref")
|
231
|
+
b.Task(:refObjectName => "task2", :ref => "ref")
|
232
|
+
}
|
233
|
+
}
|
234
|
+
end
|
235
|
+
object.tasks.should_be_instance_of Hash
|
236
|
+
object.tasks.length.should_be 2
|
237
|
+
object.tasks["task2"].should_be_instance_of Array
|
238
|
+
object.tasks["task2"].length.should_be 2
|
239
|
+
end
|
240
|
+
|
241
|
+
specify "when collections are parsed as hashes, unnamed elements should be in arrays " do
|
242
|
+
@api = RallyRestAPI.new(:parse_collections_as_hash => true)
|
243
|
+
|
244
|
+
object = rest_object_xml do |b|
|
245
|
+
b.Story(:refObjectName => "story", :ref => "http") {
|
246
|
+
b.Tasks {
|
247
|
+
b.Task(:ref => "ref")
|
248
|
+
b.Task(:ref => "ref")
|
249
|
+
b.Task(:ref => "ref")
|
250
|
+
}
|
251
|
+
}
|
252
|
+
end
|
253
|
+
object.tasks.should_be_instance_of Array
|
254
|
+
object.tasks.length.should_be 3
|
255
|
+
end
|
256
|
+
|
257
|
+
|
258
|
+
specify "should lazy read from collections" do
|
259
|
+
rest_builder = mock("RestBuilder")
|
260
|
+
rest_builder.
|
261
|
+
should_receive(:read_rest).
|
262
|
+
twice.
|
263
|
+
with(:any_args).
|
264
|
+
and_return( xml do |b|
|
265
|
+
b.Task {
|
266
|
+
b.Name("name1")
|
267
|
+
b.Description("Description")
|
268
|
+
}
|
269
|
+
end )
|
270
|
+
|
271
|
+
@api = RallyRestAPI.new(:builder => rest_builder, :parse_collections_as_hash => false)
|
272
|
+
|
273
|
+
object = rest_object_xml do |b|
|
274
|
+
b.Story(:refObjectName => "story", :ref => "http") {
|
275
|
+
b.Tasks {
|
276
|
+
b.Task(:refObjectName => "task1", :ref => "ref")
|
277
|
+
b.Task(:refObjectName => "task2", :ref => "ref")
|
278
|
+
b.Task(:refObjectName => "task3", :ref => "ref")
|
279
|
+
}
|
280
|
+
}
|
281
|
+
end
|
282
|
+
|
283
|
+
object.tasks.first.description.should == "Description"
|
284
|
+
object.tasks.first.description.should == "Description"
|
285
|
+
object.tasks.last.description.should == "Description"
|
286
|
+
end
|
287
|
+
|
288
|
+
end
|
data/test/tc_rest_api.rb
CHANGED
@@ -9,9 +9,9 @@ class RestApiTestCase < Test::Unit::TestCase
|
|
9
9
|
# We need to override post_xml from RestBuilder, So redefine it here
|
10
10
|
# We want this post_xml to set the xml that was received onto the testcase
|
11
11
|
# So create an xml= on the test, and pass the test into the RallyRestAPI
|
12
|
-
|
13
|
-
def
|
14
|
-
|
12
|
+
class LocalBuilder < RestBuilder
|
13
|
+
def initialize(test)
|
14
|
+
@test = test
|
15
15
|
end
|
16
16
|
def post_xml(url, xml, username, password)
|
17
17
|
@test.xml = xml
|
@@ -25,11 +25,7 @@ class RestApiTestCase < Test::Unit::TestCase
|
|
25
25
|
|
26
26
|
def setup
|
27
27
|
super
|
28
|
-
@api = RallyRestAPI.new
|
29
|
-
class << @api
|
30
|
-
include LocalBuilder
|
31
|
-
end
|
32
|
-
@api.test = self
|
28
|
+
@api = RallyRestAPI.new(:builder => LocalBuilder.new(self))
|
33
29
|
end
|
34
30
|
|
35
31
|
def preamble
|
@@ -41,7 +37,7 @@ class RestApiTestCase < Test::Unit::TestCase
|
|
41
37
|
# - String values
|
42
38
|
def test_basic_create
|
43
39
|
xml = "#{preamble}<Feature><Name>name</Name></Feature>"
|
44
|
-
@api.
|
40
|
+
@api.create(:feature, :name => "name")
|
45
41
|
assert_equal(xml, @xml)
|
46
42
|
end
|
47
43
|
|
@@ -95,5 +91,9 @@ class RestApiTestCase < Test::Unit::TestCase
|
|
95
91
|
@api.create(:feature, :dependents => [uc1, uc2])
|
96
92
|
assert_equal(xml, @xml)
|
97
93
|
end
|
94
|
+
|
95
|
+
def test_default_base_url_should_be_rally_proudction
|
96
|
+
assert_equal("https://rally1.rallydev.com/slm", @api.base_url)
|
97
|
+
end
|
98
98
|
|
99
99
|
end
|
data/test/tc_rest_query.rb
CHANGED
@@ -175,7 +175,19 @@ class TestRestQuery < Test::Unit::TestCase
|
|
175
175
|
:order => [:package, :owner, :desc]) { equal :name, "name"}.to_q
|
176
176
|
assert_equal("query=(Name = name)&order=Package, Owner desc", URI.unescape(query_string) )
|
177
177
|
end
|
178
|
-
|
178
|
+
|
179
|
+
def test_new_with_true_fetch_arg
|
180
|
+
query_string = RestQuery.new(:artifact,
|
181
|
+
:fetch => true) { equal :name, "name"}.to_q
|
182
|
+
assert_equal("query=(Name = name)&fetch=true", URI.unescape(query_string) )
|
183
|
+
end
|
184
|
+
|
185
|
+
def test_new_with_false_fetch_arg
|
186
|
+
query_string = RestQuery.new(:artifact,
|
187
|
+
:fetch => false) { equal :name, "name"}.to_q
|
188
|
+
assert_equal("query=(Name = name)&fetch=false", URI.unescape(query_string) )
|
189
|
+
end
|
190
|
+
|
179
191
|
def test_next_page
|
180
192
|
q = RestQuery.new(:artifact,
|
181
193
|
:pagesize => 10,
|
data/test/test_helper.rb
CHANGED
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.0
|
|
3
3
|
specification_version: 1
|
4
4
|
name: rally_rest_api
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.6.
|
7
|
-
date: 2007-
|
6
|
+
version: 0.6.6
|
7
|
+
date: 2007-02-06 00:00:00 -07:00
|
8
8
|
summary: A ruby-ized interface to Rally's REST webservices API
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -49,18 +49,18 @@ files:
|
|
49
49
|
- test/spec_typedef.rb
|
50
50
|
- test/tc_query_result.rb
|
51
51
|
- test/tc_rest_api.rb
|
52
|
-
- test/
|
52
|
+
- test/rest_object_spec.rb
|
53
53
|
- test/tc_rest_query.rb
|
54
54
|
- test/test_helper.rb
|
55
55
|
test_files:
|
56
56
|
- test/tc_rest_api.rb
|
57
|
-
- test/tc_rest_object.rb
|
58
57
|
- test/query_result_spec.rb
|
59
58
|
- test/tc_rest_query.rb
|
60
59
|
- test/test_helper.rb
|
61
60
|
- test/tc_query_result.rb
|
62
61
|
- test/spec_typedef.rb
|
63
62
|
- test/attribute_definition_spec.rb
|
63
|
+
- test/rest_object_spec.rb
|
64
64
|
rdoc_options: []
|
65
65
|
|
66
66
|
extra_rdoc_files: []
|
data/test/tc_rest_object.rb
DELETED
@@ -1,166 +0,0 @@
|
|
1
|
-
|
2
|
-
require 'test/unit'
|
3
|
-
require 'test/unit/testcase'
|
4
|
-
require 'logger'
|
5
|
-
|
6
|
-
require 'rubygems'
|
7
|
-
require 'rally_rest_api'
|
8
|
-
|
9
|
-
|
10
|
-
# We need to override read_rest from RestBuilder, So redefine it here
|
11
|
-
# We need a way to inject the xml that will be read by a RestObject when
|
12
|
-
# it is converting itself from a ref.
|
13
|
-
# So change the RestBuilder, which RestObject includes, and add a test=
|
14
|
-
# so we can send in this instance of the test case
|
15
|
-
module MyRestBuilder
|
16
|
-
def read_rest(url, username, password)
|
17
|
-
@@test.url = url
|
18
|
-
@@test.read_xml
|
19
|
-
end
|
20
|
-
|
21
|
-
def MyRestBuilder.test=(test)
|
22
|
-
@@test = test
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
$GLOBAL_BINDING = binding
|
27
|
-
|
28
|
-
class RestObjectTestCase < Test::Unit::TestCase
|
29
|
-
def url=(url)
|
30
|
-
@url = url
|
31
|
-
end
|
32
|
-
|
33
|
-
def setup
|
34
|
-
super
|
35
|
-
@api = RallyRestAPI.new
|
36
|
-
|
37
|
-
s = <<-"EOF"
|
38
|
-
class Class
|
39
|
-
alias :old_new :new
|
40
|
-
def new(*args)
|
41
|
-
result = old_new(*args)
|
42
|
-
if result.kind_of?(RestObject)
|
43
|
-
class << result
|
44
|
-
include MyRestBuilder
|
45
|
-
end
|
46
|
-
end
|
47
|
-
return result
|
48
|
-
end
|
49
|
-
end
|
50
|
-
EOF
|
51
|
-
eval(s, $GLOBAL_BINDING)
|
52
|
-
|
53
|
-
end
|
54
|
-
|
55
|
-
def teardown
|
56
|
-
super
|
57
|
-
s = <<-"EOF"
|
58
|
-
class Class
|
59
|
-
alias :new :old_new
|
60
|
-
end
|
61
|
-
EOF
|
62
|
-
eval(s, $GLOBAL_BINDING)
|
63
|
-
end
|
64
|
-
|
65
|
-
def rest_object(xml)
|
66
|
-
o = RestObject.new(@api, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>#{xml}")
|
67
|
-
MyRestBuilder.test = self
|
68
|
-
o
|
69
|
-
end
|
70
|
-
|
71
|
-
def test_equal_same_instance
|
72
|
-
xml = %Q(<Object ref="http"><TextNode>text</TextNode></Object>)
|
73
|
-
r1 = r2 = rest_object(xml)
|
74
|
-
assert(r1 == r2)
|
75
|
-
end
|
76
|
-
|
77
|
-
def test_equal_with_nil
|
78
|
-
xml = %Q(<Object ref="http"><TextNode>text</TextNode></Object>)
|
79
|
-
r1 = rest_object(xml)
|
80
|
-
assert(r1 != nil)
|
81
|
-
end
|
82
|
-
|
83
|
-
def test_equal_refs
|
84
|
-
r1 = rest_object(%Q(<Object ref="bla"><TextNode>text</TextNode></Object>))
|
85
|
-
r2 = rest_object(%Q(<Object ref="bla"><TextNode>not text</TextNode></Object>))
|
86
|
-
assert_equal(r1, r2)
|
87
|
-
end
|
88
|
-
|
89
|
-
|
90
|
-
def test_basic_text_node
|
91
|
-
xml = %Q(<Object ref="bla"><TextNode>text</TextNode></Object>)
|
92
|
-
assert_equal("text", rest_object(xml).text_node)
|
93
|
-
end
|
94
|
-
|
95
|
-
def test_nested_text_node
|
96
|
-
xml = %Q(<Object ref="bla"><Nested ref="bla/nested"><TextNode>text</TextNode></Nested></Object>)
|
97
|
-
assert_equal("text", rest_object(xml).nested.text_node)
|
98
|
-
end
|
99
|
-
|
100
|
-
# make sure RestObject are being created
|
101
|
-
def test_ref_node
|
102
|
-
xml = %Q(<Object ref="http"><RefElement ref="http" refObjectName="name1"/></Object>)
|
103
|
-
assert_equal(RestObject, rest_object(xml).class)
|
104
|
-
end
|
105
|
-
|
106
|
-
def test_ref_node_reading
|
107
|
-
xml = %Q(<Object ref="bla"><Top ref="blabla"><RefElement ref="http" refObjectName="name1"/></Top></Object>)
|
108
|
-
# This will be the xml returned from read_rest
|
109
|
-
class << self
|
110
|
-
def read_xml
|
111
|
-
%Q(<RefElement><Foo>foo</Foo></RefElement>)
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
assert_equal("foo", rest_object(xml).top.ref_element.foo)
|
116
|
-
end
|
117
|
-
|
118
|
-
def test_ref_node_collection_reading
|
119
|
-
xml = %Q(<Object ref="bla"><Top ref="bla"><RefElements><RefElement ref="http1" refObjectName="name1"/><RefElement ref="http2" refObjectName="name2"/></RefElements></Top></Object>)
|
120
|
-
# This will be the xml returned from read_rest
|
121
|
-
class << self
|
122
|
-
def read_xml
|
123
|
-
%Q(<RefElement ref="http3" refObjectName="name1"><Name>name</Name><Description>Desc</Description></RefElement>)
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
assert_equal(2, rest_object(xml).top.ref_elements.length)
|
128
|
-
ref_element = rest_object(xml).top.ref_elements[0]
|
129
|
-
|
130
|
-
assert_equal("http1", ref_element.ref)
|
131
|
-
assert_equal("Desc", ref_element.description) # Force a read
|
132
|
-
assert_equal("http3", ref_element.ref) # Post read
|
133
|
-
end
|
134
|
-
|
135
|
-
def test_ref_collection_with_dups_is_array
|
136
|
-
xml = %Q(<Object ref="bla"><Top ref="bla"><RefElements><RefElement ref="http1" refObjectName="name1"/><RefElement ref="http2" refObjectName="name2"/><RefElement ref="http3" refObjectName="name2"/><RefElement ref="http3" refObjectName="name2"/></RefElements></Top></Object>)
|
137
|
-
# This will be the xml returned from read_rest
|
138
|
-
class << self
|
139
|
-
def read_xml
|
140
|
-
%Q(<RefElement ref="http3" refObjectName="name1"><Name>name</Name><Description>Desc</Description></RefElement>)
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
assert_equal(4, rest_object(xml).top.ref_elements.length)
|
145
|
-
end
|
146
|
-
|
147
|
-
def test_named_collection_reading
|
148
|
-
xml = %Q(<Object ref="bla"><Top ref="bla"><NamedElements><NamedElement ref="http1" refObjectName="name1"><Name>name1</Name></NamedElement><NamedElement ref="http2" refObjectName="name2"><Name>name2</Name></NamedElement></NamedElements></Top></Object>)
|
149
|
-
|
150
|
-
assert_equal(2, rest_object(xml).top.named_elements.length)
|
151
|
-
named_element1 = rest_object(xml).top.named_elements[0]
|
152
|
-
assert_equal("name1", named_element1.name)
|
153
|
-
|
154
|
-
named_element2 = rest_object(xml).top.named_elements[1]
|
155
|
-
assert_equal("name2", named_element2.name)
|
156
|
-
end
|
157
|
-
|
158
|
-
def test_unnamed_collection_reading
|
159
|
-
xml = %Q(<Object ref="bla"><Top ref="bla"><NamedElements><NamedElement ref="http1"><Name>name1</Name></NamedElement><NamedElement ref="http2"><Name>name2</Name></NamedElement></NamedElements></Top></Object>)
|
160
|
-
|
161
|
-
assert_equal(2, rest_object(xml).top.named_elements.length)
|
162
|
-
assert_equal("name1", rest_object(xml).top.named_elements[0].name)
|
163
|
-
assert_equal("name2", rest_object(xml).top.named_elements[1].name)
|
164
|
-
end
|
165
|
-
|
166
|
-
end
|