rally_rest_api 0.6.3 → 0.6.6

Sign up to get free protection for your applications and to get access to all the features.
data/Manifest.txt CHANGED
@@ -18,6 +18,6 @@ test/query_result_spec.rb
18
18
  test/spec_typedef.rb
19
19
  test/tc_query_result.rb
20
20
  test/tc_rest_api.rb
21
- test/tc_rest_object.rb
21
+ test/rest_object_spec.rb
22
22
  test/tc_rest_query.rb
23
23
  test/test_helper.rb
@@ -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 = {:base_url => "https://rally1.rallydev.com/slm"})
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
- # raise "'#{type}' is not a supported type. Supported types are: #{ALLOWED_TYPES.inspect}" unless ALLOWED_TYPES.include?(type.to_s)
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 = {}, &query)
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, &query)
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
- module RestBuilder # :nodoc:
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
- debug "parse self = #{self.object_id} node = #{ node.to_s }"
53
+ low_debug "parse self = #{self.object_id} node = #{ node.to_s }"
41
54
 
42
- if !node.has_elements?
43
- if ref?(node)
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
- debug "Looking at name = #{ name }"
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
- debug "NilClass name = #{ name }"
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
- debug "Named collection #{ name }"
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
- debug "Collection #{ name }"
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
- debug "Fully Dressed object, without a name #{ name }"
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
- debug "Array name = #{ name }"
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
- debug "creating and wrapping #{elements[name]}with an array name = #{ name }"
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
- def name
138
- @document.root.attributes["refObjectName"]
139
- end
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
- # @rally_rest.logger.debug(message) if @rally_rest.logger
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
- typedef = workspace.type_definitions.find { |td| td.element_name == type }
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
 
@@ -2,7 +2,7 @@ module RallyRestVersion #:nodoc:
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 0
4
4
  MINOR = 6
5
- TINY = 3
5
+ TINY = 6
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
@@ -1,8 +1,4 @@
1
- require 'rubygems'
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
- context "A QueryResult with 2 pages" do
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
- module LocalBuilder
13
- def test=(test)
14
- @test = test
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.feature(:name => "name")
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
@@ -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
@@ -1,2 +1,4 @@
1
1
  require 'test/unit'
2
+ require 'rubygems'
3
+ require 'spec'
2
4
  require File.dirname(__FILE__) + '/../lib/rally_rest_api'
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.3
7
- date: 2007-01-03 00:00:00 -07:00
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/tc_rest_object.rb
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: []
@@ -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