ruby_odata 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -2
- data/.travis.yml +2 -1
- data/.yardopts +6 -0
- data/CHANGELOG.md +102 -0
- data/Guardfile +14 -0
- data/README.md +285 -0
- data/Rakefile +0 -7
- data/features/basic_auth.feature +3 -2
- data/features/batch_request.feature +7 -6
- data/features/cassettes/basic_auth_protected_resource.yml +57 -0
- data/features/cassettes/batch_request_additions.yml +69 -0
- data/features/cassettes/batch_request_deletes.yml +69 -0
- data/features/cassettes/batch_request_updates.yml +69 -0
- data/features/cassettes/clean_database_for_testing.yml +46 -0
- data/features/cassettes/cucumber_tags/basic_auth.yml +297 -0
- data/features/cassettes/cucumber_tags/batch_request.yml +1459 -0
- data/features/cassettes/cucumber_tags/complex_types.yml +326 -0
- data/features/cassettes/cucumber_tags/error_handling.yml +64 -0
- data/features/cassettes/cucumber_tags/query_builder.yml +2025 -0
- data/features/cassettes/cucumber_tags/service.yml +234 -0
- data/features/cassettes/cucumber_tags/service_manage.yml +937 -0
- data/features/cassettes/cucumber_tags/service_methods.yml +647 -0
- data/features/cassettes/cucumber_tags/ssl.yml +203 -0
- data/features/cassettes/cucumber_tags/type_conversion.yml +337 -0
- data/features/cassettes/service_manage_additions.yml +65 -0
- data/features/cassettes/service_manage_deletions.yml +58 -0
- data/features/cassettes/service_manage_deletions_2.yml +58 -0
- data/features/cassettes/unsecured_metadata.yml +89 -0
- data/features/complex_types.feature +4 -3
- data/features/error_handling.feature +14 -0
- data/features/query_builder.feature +30 -9
- data/features/service.feature +4 -3
- data/features/service_manage.feature +6 -5
- data/features/service_methods.feature +3 -2
- data/features/ssl.feature +8 -8
- data/features/step_definitions/service_steps.rb +38 -24
- data/features/support/env.rb +1 -3
- data/features/support/hooks.rb +3 -2
- data/features/support/pickle.rb +29 -18
- data/features/support/vcr.rb +24 -0
- data/features/type_conversion.feature +16 -17
- data/lib/ruby_odata/association.rb +7 -6
- data/lib/ruby_odata/class_builder.rb +6 -7
- data/lib/ruby_odata/exceptions.rb +4 -0
- data/lib/ruby_odata/helpers.rb +11 -0
- data/lib/ruby_odata/operation.rb +5 -6
- data/lib/ruby_odata/property_metadata.rb +4 -5
- data/lib/ruby_odata/query_builder.rb +98 -63
- data/lib/ruby_odata/service.rb +118 -103
- data/lib/ruby_odata/version.rb +3 -1
- data/lib/ruby_odata.rb +20 -18
- data/ruby_odata.gemspec +16 -12
- data/spec/query_builder_spec.rb +78 -14
- data/spec/service_spec.rb +83 -83
- data/spec/support/sample_service_matcher.rb +15 -0
- data/test/RubyODataService/RubyODataService/App_Data/.gitkeep +0 -0
- data/test/blueprints.rb +15 -9
- data/test/usage_samples/querying.rb +5 -1
- data/test/usage_samples/sample_data.rb +1 -3
- metadata +213 -39
- data/CHANGELOG.rdoc +0 -88
- data/README.rdoc +0 -259
data/spec/query_builder_spec.rb
CHANGED
@@ -1,34 +1,98 @@
|
|
1
|
-
require
|
1
|
+
require "spec_helper"
|
2
2
|
|
3
3
|
module OData
|
4
4
|
describe QueryBuilder do
|
5
5
|
describe "#initialize" do
|
6
|
-
it "handles additional parameters" do
|
7
|
-
builder = QueryBuilder.new
|
6
|
+
it "handles additional parameters" do
|
7
|
+
builder = QueryBuilder.new "Products", { :x=>1, :y=>2 }
|
8
8
|
builder.query.should eq "Products?x=1&y=2"
|
9
9
|
end
|
10
|
-
it "handles empty additional parameters" do
|
11
|
-
builder = QueryBuilder.new
|
10
|
+
it "handles empty additional parameters" do
|
11
|
+
builder = QueryBuilder.new "Products"
|
12
12
|
builder.query.should eq "Products"
|
13
|
-
end
|
14
|
-
|
15
|
-
|
16
|
-
it "should append additional parameters to the end" do
|
17
|
-
builder = QueryBuilder.new 'Products', { :x=>1, :y=>2 }
|
13
|
+
end
|
14
|
+
it "should append additional parameters to the end of the query" do
|
15
|
+
builder = QueryBuilder.new "Products", { :x=>1, :y=>2 }
|
18
16
|
builder.top(10)
|
19
17
|
builder.query.should eq "Products?$top=10&x=1&y=2"
|
20
18
|
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "#links" do
|
21
22
|
it "should properly handle queries for links" do
|
22
|
-
builder = QueryBuilder.new
|
23
|
-
builder.links(
|
23
|
+
builder = QueryBuilder.new "Categories(1)"
|
24
|
+
builder.links("Products")
|
24
25
|
builder.query.should eq "Categories(1)/$links/Products"
|
25
26
|
end
|
26
27
|
it "should properly handle queries for links with additional operations" do
|
27
|
-
builder = QueryBuilder.new
|
28
|
-
builder.links(
|
28
|
+
builder = QueryBuilder.new "Categories(1)"
|
29
|
+
builder.links("Products")
|
29
30
|
builder.top(5)
|
30
31
|
builder.query.should eq "Categories(1)/$links/Products?$top=5"
|
31
32
|
end
|
33
|
+
it "should throw an execption if count was already called on the builder" do
|
34
|
+
builder = QueryBuilder.new "Categories(1)"
|
35
|
+
builder.count
|
36
|
+
lambda { builder.links("Products") }.should raise_error(OData::NotSupportedError, "You cannot call both the `links` method and the `count` method in the same query.")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "#count" do
|
41
|
+
it "should accept the count method" do
|
42
|
+
builder = QueryBuilder.new "Products"
|
43
|
+
lambda { builder.count }.should_not raise_error
|
44
|
+
end
|
45
|
+
it "should properly handle the count method" do
|
46
|
+
builder = QueryBuilder.new "Products"
|
47
|
+
builder.count
|
48
|
+
builder.query.should eq "Products/$count"
|
49
|
+
end
|
50
|
+
it "should properly handle the count method with additional operators" do
|
51
|
+
builder = QueryBuilder.new "Products"
|
52
|
+
builder.filter("Name eq 'Widget 1'")
|
53
|
+
builder.count
|
54
|
+
builder.query.should eq "Products/$count?$filter=Name+eq+%27Widget+1%27"
|
55
|
+
end
|
56
|
+
it "should throw an execption if links was already called on the builder" do
|
57
|
+
builder = QueryBuilder.new "Categories(1)"
|
58
|
+
builder.links("Products")
|
59
|
+
lambda { builder.count }.should raise_error(OData::NotSupportedError, "You cannot call both the `links` method and the `count` method in the same query.")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "#navigate" do
|
64
|
+
it "should allow a user to drill down into a navigaion property on an initial query" do
|
65
|
+
builder = QueryBuilder.new "Genres('Horror Movies')"
|
66
|
+
builder.navigate("Titles")
|
67
|
+
builder.filter("Name eq 'Halloween'")
|
68
|
+
builder.query.should eq "Genres('Horror%20Movies')/Titles?$filter=Name+eq+%27Halloween%27"
|
69
|
+
end
|
70
|
+
it "should allow for multiple levels of drill down" do
|
71
|
+
builder = QueryBuilder.new "Genres('Horror Movies')"
|
72
|
+
builder.navigate("Titles('6aBu')")
|
73
|
+
builder.navigate("Awards")
|
74
|
+
builder.filter("Type eq 'Afi'")
|
75
|
+
builder.query.should eq "Genres('Horror%20Movies')/Titles('6aBu')/Awards?$filter=Type+eq+%27Afi%27"
|
76
|
+
end
|
77
|
+
it "should allow for a drill down plus links" do
|
78
|
+
builder = QueryBuilder.new "Genres('Horror Movies')"
|
79
|
+
builder.navigate("Titles('6aBu')")
|
80
|
+
builder.links("Awards")
|
81
|
+
builder.query.should eq "Genres('Horror%20Movies')/Titles('6aBu')/$links/Awards"
|
82
|
+
end
|
83
|
+
it "should allow for a drill down plus count" do
|
84
|
+
builder = QueryBuilder.new "Genres('Horror Movies')"
|
85
|
+
builder.navigate("Titles")
|
86
|
+
builder.count
|
87
|
+
builder.query.should eq "Genres('Horror%20Movies')/Titles/$count"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe "#query" do
|
92
|
+
it "should encode spaces in IDs" do
|
93
|
+
builder = QueryBuilder.new "Categories('Cool Stuff')"
|
94
|
+
builder.query.should eq "Categories('Cool%20Stuff')"
|
95
|
+
end
|
32
96
|
end
|
33
97
|
end
|
34
98
|
end
|
data/spec/service_spec.rb
CHANGED
@@ -3,12 +3,12 @@ require 'spec_helper'
|
|
3
3
|
module OData
|
4
4
|
describe Service do
|
5
5
|
describe "#initialize" do
|
6
|
-
it "truncates passed in end slash from uri when making the request" do
|
6
|
+
it "truncates passed in end slash from uri when making the request" do
|
7
7
|
# Required for the build_classes method
|
8
8
|
stub_request(:get, "http://test.com/test.svc/$metadata").
|
9
9
|
with(:headers => {'Accept'=>'*/*; q=0.5, application/xml', 'Accept-Encoding'=>'gzip, deflate'}).
|
10
10
|
to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/edmx_empty.xml", __FILE__)), :headers => {})
|
11
|
-
|
11
|
+
|
12
12
|
svc = OData::Service.new "http://test.com/test.svc/"
|
13
13
|
end
|
14
14
|
it "doesn't error with lowercase entities" do
|
@@ -16,10 +16,10 @@ module OData
|
|
16
16
|
stub_request(:get, "http://test.com/test.svc/$metadata").
|
17
17
|
with(:headers => {'Accept'=>'*/*; q=0.5, application/xml', 'Accept-Encoding'=>'gzip, deflate'}).
|
18
18
|
to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/edmx_lowercase.xml", __FILE__)), :headers => {})
|
19
|
-
|
19
|
+
|
20
20
|
lambda { OData::Service.new "http://test.com/test.svc" }.should_not raise_error
|
21
21
|
end
|
22
|
-
|
22
|
+
|
23
23
|
describe "additional query string parameters" do
|
24
24
|
before(:each) do
|
25
25
|
# Required for the build_classes method
|
@@ -52,7 +52,7 @@ module OData
|
|
52
52
|
end
|
53
53
|
it "should pass the parameters as part of a save" do
|
54
54
|
svc = OData::Service.new "http://test.com/test.svc/", { :additional_params => { :x=>1, :y=>2 } }
|
55
|
-
new_flight = ZDemoFlight.new
|
55
|
+
new_flight = ZDemoFlight.new
|
56
56
|
svc.AddToflight_dataCollection(new_flight)
|
57
57
|
svc.save_changes
|
58
58
|
a_request(:post, "http://test.com/test.svc/flight_dataCollection?x=1&y=2").should have_been_made
|
@@ -75,9 +75,9 @@ module OData
|
|
75
75
|
end
|
76
76
|
it "should pass the parameters as part of a batch save" do
|
77
77
|
svc = OData::Service.new "http://test.com/test.svc/", { :additional_params => { :x=>1, :y=>2 } }
|
78
|
-
new_flight = ZDemoFlight.new
|
78
|
+
new_flight = ZDemoFlight.new
|
79
79
|
svc.AddToflight_dataCollection(new_flight)
|
80
|
-
new_flight2 = ZDemoFlight.new
|
80
|
+
new_flight2 = ZDemoFlight.new
|
81
81
|
svc.AddToflight_dataCollection(new_flight2)
|
82
82
|
svc.save_changes
|
83
83
|
a_request(:post, "http://test.com/test.svc/$batch?x=1&y=2").should have_been_made
|
@@ -103,7 +103,7 @@ module OData
|
|
103
103
|
a_request(:get, "http://test.com/test.svc/get_top_flight?x=1&y=2").should have_been_made
|
104
104
|
end
|
105
105
|
end
|
106
|
-
|
106
|
+
|
107
107
|
describe "lowercase collections" do
|
108
108
|
before(:each) do
|
109
109
|
# Required for the build_classes method
|
@@ -111,12 +111,12 @@ module OData
|
|
111
111
|
with(:headers => {'Accept'=>'*/*; q=0.5, application/xml', 'Accept-Encoding'=>'gzip, deflate'}).
|
112
112
|
to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/edmx_lowercase.xml", __FILE__)), :headers => {})
|
113
113
|
end
|
114
|
-
|
114
|
+
|
115
115
|
it "should respond_to a lowercase collection" do
|
116
116
|
svc = OData::Service.new "http://test.com/test.svc"
|
117
117
|
svc.respond_to?('acronyms').should be_true
|
118
118
|
end
|
119
|
-
|
119
|
+
|
120
120
|
it "should allow a lowercase collections to be queried" do
|
121
121
|
svc = OData::Service.new "http://test.com/test.svc"
|
122
122
|
lambda { svc.send('acronyms') }.should_not raise_error
|
@@ -142,19 +142,19 @@ module OData
|
|
142
142
|
results.first.should be_a_kind_of(ZDemoFlight)
|
143
143
|
end
|
144
144
|
end
|
145
|
-
|
145
|
+
|
146
146
|
describe "collections, objects, metadata etc" do
|
147
147
|
before(:each) do
|
148
148
|
# Metadata
|
149
149
|
stub_request(:get, "http://test.com/test.svc/$metadata").
|
150
150
|
with(:headers => {'Accept'=>'*/*; q=0.5, application/xml', 'Accept-Encoding'=>'gzip, deflate'}).
|
151
151
|
to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/feed_customization/edmx_feed_customization.xml", __FILE__)), :headers => {})
|
152
|
-
|
152
|
+
|
153
153
|
# Content - Products
|
154
154
|
stub_request(:get, /http:\/\/test\.com\/test\.svc\/Products(?:.*)/).
|
155
155
|
with(:headers => {'Accept'=>'*/*; q=0.5, application/xml', 'Accept-Encoding'=>'gzip, deflate'}).
|
156
156
|
to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/feed_customization/result_feed_customization_products_expand.xml", __FILE__)), :headers => {})
|
157
|
-
|
157
|
+
|
158
158
|
# Content - Categories expanded Products
|
159
159
|
stub_request(:get, /http:\/\/test\.com\/test\.svc\/Categories(?:.*)/).
|
160
160
|
with(:headers => {'Accept'=>'*/*; q=0.5, application/xml', 'Accept-Encoding'=>'gzip, deflate'}).
|
@@ -208,9 +208,9 @@ module OData
|
|
208
208
|
meta.nullable.should eq true
|
209
209
|
meta.fc_target_path.should eq "SyndicationSummary"
|
210
210
|
meta.fc_keep_in_content.should eq false
|
211
|
-
end
|
212
|
-
end
|
213
|
-
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
214
|
describe "single class" do
|
215
215
|
it "should handle properties where a property is represented in the syndication title instead of the properties collection" do
|
216
216
|
svc = OData::Service.new "http://test.com/test.svc/"
|
@@ -224,46 +224,46 @@ module OData
|
|
224
224
|
results = svc.execute
|
225
225
|
results.first.Description.should eq "Whole grain bread"
|
226
226
|
end
|
227
|
-
end
|
228
|
-
|
227
|
+
end
|
228
|
+
|
229
229
|
describe "expanded inline class" do
|
230
230
|
it "should handle properties where a property is represented in the syndication title instead of the properties collection" do
|
231
231
|
svc = OData::Service.new "http://test.com/test.svc/"
|
232
232
|
svc.Categories
|
233
233
|
results = svc.execute
|
234
|
-
|
234
|
+
|
235
235
|
beverages = results[1]
|
236
|
-
|
236
|
+
|
237
237
|
milk = beverages.Products.first
|
238
238
|
milk.Name.should eq "Milk"
|
239
239
|
milk.Description.should eq "Low fat milk"
|
240
|
-
|
240
|
+
|
241
241
|
lemonade = beverages.Products.last
|
242
242
|
lemonade.Name.should eq "Pink Lemonade"
|
243
243
|
lemonade.Description.should eq "36 Ounce Cans (Pack of 3)"
|
244
244
|
end
|
245
245
|
end
|
246
246
|
end
|
247
|
-
|
247
|
+
|
248
248
|
describe "handling inline collections/properties" do
|
249
249
|
it "should make plural named properties arrays and not a single class" do
|
250
250
|
svc = OData::Service.new "http://test.com/test.svc/"
|
251
251
|
svc.Categories
|
252
252
|
results = svc.execute
|
253
253
|
food = results[0]
|
254
|
-
|
254
|
+
|
255
255
|
food.Products.should be_an Array
|
256
256
|
end
|
257
|
-
|
257
|
+
|
258
258
|
it "should not make an array if the navigation property name is singular" do
|
259
259
|
svc = OData::Service.new "http://test.com/test.svc/"
|
260
260
|
svc.Products
|
261
261
|
results = svc.execute
|
262
262
|
product = results.first
|
263
263
|
product.Category.should_not be_an Array
|
264
|
-
end
|
264
|
+
end
|
265
265
|
end
|
266
|
-
|
266
|
+
|
267
267
|
describe "navigation properties" do
|
268
268
|
it "should fill in PropertyMetadata for navigation properties" do
|
269
269
|
svc = OData::Service.new "http://test.com/test.svc/"
|
@@ -271,27 +271,27 @@ module OData
|
|
271
271
|
end
|
272
272
|
end
|
273
273
|
end
|
274
|
-
|
274
|
+
|
275
275
|
describe "single layer inheritance" do
|
276
276
|
before(:each) do
|
277
277
|
# Metadata
|
278
278
|
stub_request(:get, "http://test.com/test.svc/$metadata").
|
279
279
|
with(:headers => {'Accept'=>'*/*; q=0.5, application/xml', 'Accept-Encoding'=>'gzip, deflate'}).
|
280
280
|
to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/inheritance/edmx_pluralsight.xml", __FILE__)), :headers => {})
|
281
|
-
|
281
|
+
|
282
282
|
# Content - Courses
|
283
283
|
stub_request(:get, /http:\/\/test\.com\/test\.svc\/Courses(?:.*)/).
|
284
284
|
with(:headers => {'Accept'=>'*/*; q=0.5, application/xml', 'Accept-Encoding'=>'gzip, deflate'}).
|
285
285
|
to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/inheritance/result_pluralsight_courses.xml", __FILE__)), :headers => {})
|
286
286
|
end
|
287
|
-
|
287
|
+
|
288
288
|
it "should build all inherited attributes" do
|
289
289
|
OData::Service.new "http://test.com/test.svc/"
|
290
290
|
methods = Course.instance_methods.reject {|m| Object.methods.index(m)}
|
291
|
-
|
291
|
+
|
292
292
|
# Ruby 1.9 uses symbols, and 1.8 uses strings, so this normalizes the data
|
293
293
|
methods.map! {|m| m.to_sym}
|
294
|
-
|
294
|
+
|
295
295
|
methods.should include(:Title)
|
296
296
|
methods.should include(:Description)
|
297
297
|
methods.should include(:VideoLength)
|
@@ -319,7 +319,7 @@ module OData
|
|
319
319
|
course.Category.should_not be_nil
|
320
320
|
end
|
321
321
|
end
|
322
|
-
|
322
|
+
|
323
323
|
describe "handling partial collections" do
|
324
324
|
before(:each) do
|
325
325
|
# Metadata
|
@@ -361,14 +361,14 @@ module OData
|
|
361
361
|
results.count.should eq 3
|
362
362
|
end
|
363
363
|
end
|
364
|
-
|
364
|
+
|
365
365
|
describe "link queries" do
|
366
366
|
before(:each) do
|
367
367
|
# Required for the build_classes method
|
368
368
|
stub_request(:get, /http:\/\/test\.com\/test\.svc\/\$metadata(?:\?.+)?/).
|
369
369
|
with(:headers => {'Accept'=>'*/*; q=0.5, application/xml', 'Accept-Encoding'=>'gzip, deflate'}).
|
370
370
|
to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/sample_service/edmx_categories_products.xml", __FILE__)), :headers => {})
|
371
|
-
|
371
|
+
|
372
372
|
stub_request(:get, "http://test.com/test.svc/Categories(1)/$links/Products").
|
373
373
|
with(:headers => {'Accept'=>'*/*; q=0.5, application/xml', 'Accept-Encoding'=>'gzip, deflate'}).
|
374
374
|
to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/links/result_links_query.xml", __FILE__)), :headers => {})
|
@@ -381,72 +381,72 @@ module OData
|
|
381
381
|
results.first.should be_a_kind_of(URI)
|
382
382
|
results[0].path.should eq "/SampleService/RubyOData.svc/Products(1)"
|
383
383
|
results[1].path.should eq "/SampleService/RubyOData.svc/Products(2)"
|
384
|
-
results[2].path.should eq "/SampleService/RubyOData.svc/Products(3)"
|
384
|
+
results[2].path.should eq "/SampleService/RubyOData.svc/Products(3)"
|
385
385
|
end
|
386
386
|
end
|
387
|
-
|
387
|
+
|
388
388
|
describe "sample service" do
|
389
389
|
before(:each) do
|
390
390
|
# Required for the build_classes method
|
391
391
|
stub_request(:get, /http:\/\/test\.com\/test\.svc\/\$metadata(?:\?.+)?/).
|
392
392
|
with(:headers => {'Accept'=>'*/*; q=0.5, application/xml', 'Accept-Encoding'=>'gzip, deflate'}).
|
393
393
|
to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/sample_service/edmx_categories_products.xml", __FILE__)), :headers => {})
|
394
|
-
|
394
|
+
|
395
395
|
stub_request(:get, /http:\/\/test\.com\/test\.svc\/Products\(\d\)/).
|
396
396
|
with(:headers => {'Accept'=>'*/*; q=0.5, application/xml', 'Accept-Encoding'=>'gzip, deflate'}).
|
397
397
|
to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/sample_service/result_single_product.xml", __FILE__)), :headers => {})
|
398
|
-
|
398
|
+
|
399
399
|
stub_request(:get, /http:\/\/test\.com\/test\.svc\/Products\(\d{2,}\)/).
|
400
400
|
with(:headers => {'Accept'=>'*/*; q=0.5, application/xml', 'Accept-Encoding'=>'gzip, deflate'}).
|
401
401
|
to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/sample_service/result_single_product_not_found.xml", __FILE__)), :headers => {})
|
402
|
-
|
402
|
+
|
403
403
|
stub_request(:get, "http://test.com/test.svc/Products(1)/Category").
|
404
404
|
with(:headers => {'Accept'=>'*/*; q=0.5, application/xml', 'Accept-Encoding'=>'gzip, deflate'}).
|
405
405
|
to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/sample_service/result_single_category.xml", __FILE__)), :headers => {})
|
406
|
-
|
406
|
+
|
407
407
|
stub_request(:get, "http://test.com/test.svc/Categories(1)").
|
408
408
|
with(:headers => {'Accept'=>'*/*; q=0.5, application/xml', 'Accept-Encoding'=>'gzip, deflate'}).
|
409
409
|
to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/sample_service/result_single_category.xml", __FILE__)), :headers => {})
|
410
|
-
|
410
|
+
|
411
411
|
stub_request(:get, "http://test.com/test.svc/Categories(1)/Products").
|
412
412
|
with(:headers => {'Accept'=>'*/*; q=0.5, application/xml', 'Accept-Encoding'=>'gzip, deflate'}).
|
413
413
|
to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/sample_service/result_multiple_category_products.xml", __FILE__)), :headers => {})
|
414
|
-
|
414
|
+
|
415
415
|
stub_request(:post, "http://test.com/test.svc/Categories(1)/$links/Products").to_return(:status => 204)
|
416
416
|
stub_request(:post, "http://test.com/test.svc/$batch").to_return(:status => 202)
|
417
417
|
end
|
418
|
-
|
418
|
+
|
419
419
|
describe "lazy loading" do
|
420
420
|
after(:each) do
|
421
421
|
Object.send(:remove_const, 'Product') if Object.const_defined? 'Product'
|
422
422
|
Object.send(:remove_const, 'Category') if Object.const_defined? 'Category'
|
423
423
|
end
|
424
|
-
|
424
|
+
|
425
425
|
it "should have a load property method" do
|
426
426
|
svc = OData::Service.new "http://test.com/test.svc/"
|
427
427
|
svc.should respond_to(:load_property)
|
428
428
|
end
|
429
|
-
|
429
|
+
|
430
430
|
it "should throw an exception if the object isn't tracked" do
|
431
431
|
svc = OData::Service.new "http://test.com/test.svc/"
|
432
432
|
new_object = Product.new
|
433
|
-
lambda { svc.load_property(new_object, "Category") }.should raise_error(
|
433
|
+
lambda { svc.load_property(new_object, "Category") }.should raise_error(NotSupportedError, "You cannot load a property on an entity that isn't tracked")
|
434
434
|
end
|
435
|
-
|
435
|
+
|
436
436
|
it "should throw an exception if there isn't a method matching the navigation property passed in" do
|
437
437
|
svc = OData::Service.new "http://test.com/test.svc/"
|
438
438
|
svc.Products(1)
|
439
439
|
product = svc.execute.first
|
440
|
-
lambda { svc.load_property(product, "NoMatchingMethod") }.should raise_error(ArgumentError, "'NoMatchingMethod' is not a valid navigation property")
|
440
|
+
lambda { svc.load_property(product, "NoMatchingMethod") }.should raise_error(ArgumentError, "'NoMatchingMethod' is not a valid navigation property")
|
441
441
|
end
|
442
|
-
|
442
|
+
|
443
443
|
it "should throw an exception if the method passed in is a standard property (non-navigation)" do
|
444
444
|
svc = OData::Service.new "http://test.com/test.svc/"
|
445
445
|
svc.Products(1)
|
446
446
|
product = svc.execute.first
|
447
|
-
lambda { svc.load_property(product, "Name") }.should raise_error(ArgumentError, "'Name' is not a valid navigation property")
|
447
|
+
lambda { svc.load_property(product, "Name") }.should raise_error(ArgumentError, "'Name' is not a valid navigation property")
|
448
448
|
end
|
449
|
-
|
449
|
+
|
450
450
|
it "should fill a single navigation property" do
|
451
451
|
svc = OData::Service.new "http://test.com/test.svc/"
|
452
452
|
svc.Products(1)
|
@@ -456,7 +456,7 @@ module OData
|
|
456
456
|
product.Category.Id.should eq 1
|
457
457
|
product.Category.Name.should eq 'Category 1'
|
458
458
|
end
|
459
|
-
|
459
|
+
|
460
460
|
it "should fill a collection navigation property" do
|
461
461
|
svc = OData::Service.new "http://test.com/test.svc/"
|
462
462
|
svc.Categories(1)
|
@@ -467,58 +467,58 @@ module OData
|
|
467
467
|
category.Products[1].Id.should eq 2
|
468
468
|
end
|
469
469
|
end
|
470
|
-
|
470
|
+
|
471
471
|
describe "find, create, add, update, and delete" do
|
472
472
|
after(:each) do
|
473
473
|
Object.send(:remove_const, 'Product') if Object.const_defined? 'Product'
|
474
474
|
Object.send(:remove_const, 'Category') if Object.const_defined? 'Category'
|
475
475
|
end
|
476
|
-
|
476
|
+
|
477
477
|
it "should implement an AddTo method for collection" do
|
478
478
|
svc = OData::Service.new "http://test.com/test.svc/"
|
479
479
|
svc.should respond_to :AddToCategories
|
480
480
|
svc.should respond_to :AddToProducts
|
481
481
|
end
|
482
|
-
|
482
|
+
|
483
483
|
it "should create objects with an initialize method that can build the object from a hash" do
|
484
484
|
svc = OData::Service.new "http://test.com/test.svc/"
|
485
485
|
product = Product.new 'Id' => 1000, 'Name' => 'New Product'
|
486
486
|
product.Id.should eq 1000
|
487
487
|
product.Name.should eq 'New Product'
|
488
488
|
end
|
489
|
-
|
489
|
+
|
490
490
|
it "should create objects that rejects keys that don't have corresponding methods" do
|
491
491
|
svc = OData::Service.new "http://test.com/test.svc/"
|
492
492
|
lambda { Product.new 'NotAProperty' => true }.should raise_error NoMethodError
|
493
493
|
end
|
494
|
-
|
494
|
+
|
495
495
|
it "should create objects that expose a properties class method that lists the properties for the object" do
|
496
496
|
svc = OData::Service.new "http://test.com/test.svc/"
|
497
497
|
Product.properties.should include 'Id'
|
498
498
|
Product.properties.should include 'Name'
|
499
|
-
Product.properties.should include 'Category'
|
499
|
+
Product.properties.should include 'Category'
|
500
500
|
end
|
501
|
-
|
501
|
+
|
502
502
|
it "should have full metadata for a property returned from the properties method" do
|
503
503
|
svc = OData::Service.new "http://test.com/test.svc/"
|
504
504
|
Product.properties['Category'].should be_a PropertyMetadata
|
505
505
|
Product.properties['Category'].nav_prop.should be_true
|
506
506
|
end
|
507
|
-
|
507
|
+
|
508
508
|
it "should create objects that expose an id property" do
|
509
509
|
svc = OData::Service.new "http://test.com/test.svc/"
|
510
510
|
svc.Products(1)
|
511
511
|
product = svc.execute.first
|
512
512
|
product.should respond_to :id
|
513
513
|
end
|
514
|
-
|
514
|
+
|
515
515
|
it "should extract the id from the metadata" do
|
516
516
|
svc = OData::Service.new "http://test.com/test.svc/"
|
517
517
|
svc.Products(1)
|
518
518
|
product = svc.execute.first
|
519
519
|
product.id.should eq 1
|
520
520
|
end
|
521
|
-
|
521
|
+
|
522
522
|
describe "Class.first method" do
|
523
523
|
it "should exist on the create server objects" do
|
524
524
|
svc = OData::Service.new "http://test.com/test.svc/"
|
@@ -528,7 +528,7 @@ module OData
|
|
528
528
|
svc = OData::Service.new "http://test.com/test.svc/"
|
529
529
|
product = Product.first(nil)
|
530
530
|
product.should be_nil
|
531
|
-
end
|
531
|
+
end
|
532
532
|
it "should return nil if an id isn't found" do
|
533
533
|
svc = OData::Service.new "http://test.com/test.svc/"
|
534
534
|
product = Product.first(1234567890)
|
@@ -541,29 +541,29 @@ module OData
|
|
541
541
|
end
|
542
542
|
end
|
543
543
|
end
|
544
|
-
|
544
|
+
|
545
545
|
describe "namespaces" do
|
546
546
|
after(:each) do
|
547
547
|
VisoftInc::Sample::Models.send(:remove_const, 'Product') if VisoftInc::Sample::Models.const_defined? 'Product'
|
548
548
|
VisoftInc::Sample::Models.send(:remove_const, 'Category') if VisoftInc::Sample::Models.const_defined? 'Category'
|
549
|
-
|
549
|
+
|
550
550
|
VisoftInc::Sample.send(:remove_const, 'Models') if VisoftInc::Sample.const_defined? 'Models'
|
551
|
-
VisoftInc.send(:remove_const, 'Sample') if VisoftInc.const_defined? 'Sample'
|
551
|
+
VisoftInc.send(:remove_const, 'Sample') if VisoftInc.const_defined? 'Sample'
|
552
552
|
Object.send(:remove_const, 'VisoftInc') if Object.const_defined? 'VisoftInc'
|
553
553
|
end
|
554
|
-
|
554
|
+
|
555
555
|
it "should create models in the specified namespace if the option is set (using a .NET style namespace with dots)" do
|
556
556
|
svc = OData::Service.new "http://test.com/test.svc/", { :namespace => 'VisoftInc.Sample.Models' }
|
557
557
|
defined?(VisoftInc::Sample::Models::Product).nil?.should be_false, 'VisoftInc::Sample::Models::Product was expected to be defined, but was not'
|
558
558
|
defined?(VisoftInc::Sample::Models::Category).nil?.should be_false, 'VisoftInc::Sample::Models::Category was expected to be defined, but was not'
|
559
559
|
end
|
560
|
-
|
560
|
+
|
561
561
|
it "should create models in the specified namespace if the option is set (using Ruby style namespaces with double colons)" do
|
562
562
|
svc = OData::Service.new "http://test.com/test.svc/", { :namespace => 'VisoftInc::Sample::Models' }
|
563
563
|
defined?(VisoftInc::Sample::Models::Product).nil?.should be_false, 'VisoftInc::Sample::Models::Product was expected to be defined, but was not'
|
564
564
|
defined?(VisoftInc::Sample::Models::Category).nil?.should be_false, 'VisoftInc::Sample::Models::Category was expected to be defined, but was not'
|
565
565
|
end
|
566
|
-
|
566
|
+
|
567
567
|
it "should fill object defined in a namespace" do
|
568
568
|
svc = OData::Service.new "http://test.com/test.svc/", { :namespace => 'VisoftInc::Sample::Models' }
|
569
569
|
svc.Categories(1)
|
@@ -573,19 +573,19 @@ module OData
|
|
573
573
|
category.Id.should eq 1
|
574
574
|
category.Name.should eq 'Category 1'
|
575
575
|
end
|
576
|
-
|
576
|
+
|
577
577
|
it "should fill the class_metadata hash" do
|
578
578
|
svc = OData::Service.new "http://test.com/test.svc/", { :namespace => 'VisoftInc::Sample::Models' }
|
579
579
|
svc.class_metadata.should_not be_empty
|
580
580
|
end
|
581
|
-
|
581
|
+
|
582
582
|
it "should add a key (based on the name) for each property class_metadata hash" do
|
583
583
|
svc = OData::Service.new "http://test.com/test.svc/", { :namespace => 'VisoftInc::Sample::Models' }
|
584
584
|
svc.class_metadata['VisoftInc::Sample::Models::Product'].should have_key 'Id'
|
585
585
|
svc.class_metadata['VisoftInc::Sample::Models::Product'].should have_key 'Name'
|
586
586
|
svc.class_metadata['VisoftInc::Sample::Models::Product'].should have_key 'Description'
|
587
587
|
end
|
588
|
-
|
588
|
+
|
589
589
|
it "should lazy load objects defined in a namespace" do
|
590
590
|
svc = OData::Service.new "http://test.com/test.svc/", { :namespace => 'VisoftInc::Sample::Models' }
|
591
591
|
svc.Categories(1)
|
@@ -593,7 +593,7 @@ module OData
|
|
593
593
|
svc.load_property category, 'Products'
|
594
594
|
category.Products.should_not be_nil
|
595
595
|
category.Products.first.Id.should eq 1
|
596
|
-
category.Products.first.Name.should eq 'Widget 1'
|
596
|
+
category.Products.first.Name.should eq 'Widget 1'
|
597
597
|
end
|
598
598
|
end
|
599
599
|
|
@@ -602,15 +602,15 @@ module OData
|
|
602
602
|
svc = OData::Service.new "http://test.com/test.svc/"
|
603
603
|
svc.should respond_to(:add_link)
|
604
604
|
end
|
605
|
-
|
605
|
+
|
606
606
|
it "shouldn't be allowed if a parent isn't tracked" do
|
607
607
|
svc = OData::Service.new "http://test.com/test.svc/"
|
608
608
|
category = Category.new :Name => 'New Category'
|
609
609
|
property = nil # Not needed for this test
|
610
610
|
product = nil # Not needed for this test
|
611
|
-
lambda { svc.add_link(category, property, product) }.should raise_error(
|
611
|
+
lambda { svc.add_link(category, property, product) }.should raise_error(NotSupportedError, "You cannot add a link on an entity that isn't tracked (Category)")
|
612
612
|
end
|
613
|
-
|
613
|
+
|
614
614
|
it "shouldn't be allowed if a property isn't found on the parent" do
|
615
615
|
svc = OData::Service.new "http://test.com/test.svc/"
|
616
616
|
svc.Categories(1)
|
@@ -619,7 +619,7 @@ module OData
|
|
619
619
|
product = nil # Not needed for this test
|
620
620
|
lambda { svc.add_link(category, property, product) }.should raise_error(ArgumentError, "'NotAProperty' is not a valid navigation property for Category")
|
621
621
|
end
|
622
|
-
|
622
|
+
|
623
623
|
it "shouldn't be allowed if a property isn't a navigation property on the parent" do
|
624
624
|
svc = OData::Service.new "http://test.com/test.svc/"
|
625
625
|
svc.Categories(1)
|
@@ -628,16 +628,16 @@ module OData
|
|
628
628
|
product = nil # Not needed for this test
|
629
629
|
lambda { svc.add_link(category, property, product) }.should raise_error(ArgumentError, "'Name' is not a valid navigation property for Category")
|
630
630
|
end
|
631
|
-
|
631
|
+
|
632
632
|
it "shouldn't be allowed if a child isn't tracked" do
|
633
633
|
svc = OData::Service.new "http://test.com/test.svc/"
|
634
634
|
svc.Categories(1)
|
635
635
|
category = svc.execute.first
|
636
636
|
property = 'Products'
|
637
|
-
product = Product.new :Name => 'Widget 1'
|
638
|
-
lambda { svc.add_link(category, property, product) }.should raise_error(
|
637
|
+
product = Product.new :Name => 'Widget 1'
|
638
|
+
lambda { svc.add_link(category, property, product) }.should raise_error(NotSupportedError, "You cannot add a link on a child entity that isn't tracked (Product)")
|
639
639
|
end
|
640
|
-
|
640
|
+
|
641
641
|
it "should perform a post against the correct URL with the correct body on a single_save" do
|
642
642
|
svc = OData::Service.new "http://test.com/test.svc/"
|
643
643
|
svc.Categories(1)
|
@@ -705,7 +705,7 @@ module OData
|
|
705
705
|
end
|
706
706
|
end
|
707
707
|
end
|
708
|
-
|
708
|
+
|
709
709
|
describe_private OData::Service do
|
710
710
|
describe "parse value" do
|
711
711
|
before(:each) do
|
@@ -714,7 +714,7 @@ module OData
|
|
714
714
|
with(:headers => {'Accept'=>'*/*; q=0.5, application/xml', 'Accept-Encoding'=>'gzip, deflate'}).
|
715
715
|
to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/edmx_empty.xml", __FILE__)), :headers => {})
|
716
716
|
end
|
717
|
-
|
717
|
+
|
718
718
|
it "should not error on an 'out of range' date" do
|
719
719
|
# This date was returned in the Netflix OData service and failed with an ArgumentError: out of range using 1.8.7 (2010-12-23 patchlevel 330) [i386-mingw32]
|
720
720
|
svc = OData::Service.new "http://test.com/test.svc/"
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module OData
|
4
|
+
module Support
|
5
|
+
class SampleServiceMatcher
|
6
|
+
def self.call(req1, req2)
|
7
|
+
regexp = /^(https?:\/\/(?:[^@]*@)?)[^:]*(:\d+\/.*$)/i
|
8
|
+
request1 = req1.uri.match(regexp)
|
9
|
+
request2 = req2.uri.match(regexp)
|
10
|
+
|
11
|
+
(request1[1] == request2[1]) && (request1[2] == request2[2])
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
File without changes
|