diff --git a/Gemfile b/Gemfile
index f1f2238..f005a1f 100644
--- a/Gemfile
+++ b/Gemfile
@@ -2,5 +2,3 @@ source "http://rubygems.org"
# Specify your gem's dependencies in ruby_odata.gemspec
gemspec
-
-gem 'faraday_middleware', github: 'lostisland/faraday_middleware'
diff --git a/ruby_odata.gemspec b/ruby_odata.gemspec
index 96ad47a..a186355 100644
--- a/ruby_odata.gemspec
+++ b/ruby_odata.gemspec
@@ -19,10 +19,10 @@ Gem::Specification.new do |s|
s.add_dependency("addressable", ">= 2.3.4")
s.add_dependency("i18n", ">= 0.7.0")
- s.add_dependency("activesupport", ">= 3.0.0")
+ s.add_dependency("activesupport", "~> 3.0.0")
s.add_dependency("excon", "~> 0.45.3")
+ s.add_dependency("faraday")
s.add_dependency("faraday_middleware")
- s.add_dependency("faraday", "~> 0.9.1")
s.add_dependency("nokogiri", ">= 1.4.2")
s.add_development_dependency("rake", ">= 12.0.0")
@@ -31,7 +31,7 @@ Gem::Specification.new do |s|
s.add_development_dependency("cucumber", "~> 2.0.0")
s.add_development_dependency("pickle", "~> 0.5.1")
s.add_development_dependency("machinist", "~> 2.0")
- s.add_development_dependency("webmock", "~> 1.21.0")
+ s.add_development_dependency("webmock")
s.add_development_dependency("guard", "~> 2.12.5")
s.add_development_dependency("guard-rspec", "~> 4.5.0")
s.add_development_dependency("guard-cucumber", "~> 1.6.0")
diff --git a/spec/class_builder_spec.rb b/spec/class_builder_spec.rb
deleted file mode 100644
index ac66d11..0000000
--- a/spec/class_builder_spec.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-require 'spec_helper'
-
-module OData
- describe ClassBuilder do
- describe "#initialize" do
- before(:each) do
- @methods = []
- @nav_props = []
- @svc = nil
- @namespace = nil
- end
- it "handles lowercase entities" do
- klass = ClassBuilder.new 'product', @methods, @nav_props, @svc, @namespace
- result = klass.build
- result.should eq Product
- end
- it "should take in an instance of the service" do
- klass = ClassBuilder.new 'product', @methods, @nav_props, @svc, @namespace
- end
- end
- end
-end
\ No newline at end of file
diff --git a/spec/dynamics_nav_spec.rb b/spec/dynamics_nav_spec.rb
deleted file mode 100644
index def25e7..0000000
--- a/spec/dynamics_nav_spec.rb
+++ /dev/null
@@ -1,75 +0,0 @@
-require 'spec_helper'
-
-module OData
- describe Service do
-
- describe "handling of Microsoft Dynamics Nav OData WebService" do
- let(:username) { "blabla" }
- let(:password) { "" }
-
- before(:each) do
- auth_string = "#{username}:#{password}"
- authorization_header = { authorization: "Basic #{Base64::encode64(auth_string).strip}" }
- headers = DEFAULT_HEADERS.merge(authorization_header)
-
- # Required for the build_classes method
- stub_request(:get, "http://test.com/nav.svc/$metadata").
- with(:headers => headers).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/ms_dynamics_nav/edmx_ms_dynamics_nav.xml", __FILE__)), :headers => {})
-
- stub_request(:get, "http://test.com/nav.svc/Customer").
- with(:headers => headers).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/ms_dynamics_nav/result_customer.xml", __FILE__)), :headers => {})
-
- stub_request(:get, "http://test.com/nav.svc/Customer('100013')").
- with(:headers => headers).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/ms_dynamics_nav/result_customer.xml", __FILE__)), :headers => {})
-
- stub_request(:get, "http://test.com/nav.svc/Customer(100013)").
- with(:headers => headers).
- to_return(:status => 400, :body => File.new(File.expand_path("../fixtures/ms_dynamics_nav/result_customer_error.xml", __FILE__)), :headers => {})
-
- stub_request(:get, "http://test.com/nav.svc/SalesOrder(Document_Type='Order',No='AB-1600013')").
- with(:headers => headers).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/ms_dynamics_nav/result_sales_order.xml", __FILE__)), :headers => {})
- end
-
- after(:all) do
- Object.send(:remove_const, 'Customer')
- Object.send(:remove_const, 'SalesOrder')
- end
-
- it "should successfully parse null valued string properties" do
- svc = OData::Service.new "http://test.com/nav.svc/", { :username => username, :password => password, :verify_ssl => false }
- svc.Customer
- results = svc.execute
- results.first.should be_a_kind_of(Customer)
- end
-
- it "should successfully return a customer by its string id" do
- svc = OData::Service.new "http://test.com/nav.svc/", { :username => username, :password => password, :verify_ssl => false }
- svc.Customer('100013')
- results = svc.execute
- results.first.should be_a_kind_of(Customer)
- results.first.Name.should eq 'Contoso AG'
- end
-
- it "should cast to string if a customer is accessed with integer id" do
- svc = OData::Service.new "http://test.com/nav.svc/", { :username => username, :password => password, :verify_ssl => false }
- svc.Customer(100013)
- results = svc.execute
- results.first.should be_a_kind_of(Customer)
- results.first.Name.should eq 'Contoso AG'
- end
-
- it "should successfully return a sales_order by its composite string ids" do
- svc = OData::Service.new "http://test.com/nav.svc/", { :username => username, :password => password, :verify_ssl => false }
- svc.SalesOrder(Document_Type: 'Order', No: 'AB-1600013')
- results = svc.execute
- results.first.should be_a_kind_of(SalesOrder)
- results.first.No.should eq 'AB-1600013'
- end
-
- end
- end
-end
\ No newline at end of file
diff --git a/spec/fixtures/decimal/metadata.xml b/spec/fixtures/decimal/metadata.xml
index c4855f7..ceb6b86 100644
--- a/spec/fixtures/decimal/metadata.xml
+++ b/spec/fixtures/decimal/metadata.xml
@@ -1 +1,1284 @@
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spec/fixtures/odata.org/edmx_odata_v3.xml b/spec/fixtures/odata.org/edmx_odata_v3.xml
new file mode 100644
index 0000000..97be127
--- /dev/null
+++ b/spec/fixtures/odata.org/edmx_odata_v3.xml
@@ -0,0 +1,169 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spec/fixtures/odata.org/edmx_odata_v4.xml b/spec/fixtures/odata.org/edmx_odata_v4.xml
new file mode 100644
index 0000000..62317cd
--- /dev/null
+++ b/spec/fixtures/odata.org/edmx_odata_v4.xml
@@ -0,0 +1,143 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spec/fixtures/odata.org/odata_v3_products.xml b/spec/fixtures/odata.org/odata_v3_products.xml
new file mode 100644
index 0000000..6615a29
--- /dev/null
+++ b/spec/fixtures/odata.org/odata_v3_products.xml
@@ -0,0 +1,297 @@
+
+
+ http://services.odata.org/V3/OData/OData.svc/Products
+ Products
+ 2017-09-07T21:23:43Z
+
+
+ http://services.odata.org/V3/OData/OData.svc/Products(0)
+
+
+
+
+
+ Bread
+ Whole grain bread
+ 2017-09-07T21:23:43Z
+
+
+
+
+
+
+
+
+ 0
+ 1992-01-01T00:00:00
+
+ 4
+ 2.5
+
+
+
+
+ http://services.odata.org/V3/OData/OData.svc/Products(1)
+
+
+
+
+
+ Milk
+ Low fat milk
+ 2017-09-07T21:23:43Z
+
+
+
+
+
+
+
+
+ 1
+ 1995-10-01T00:00:00
+
+ 3
+ 3.5
+
+
+
+
+ http://services.odata.org/V3/OData/OData.svc/Products(2)
+
+
+
+
+
+ Vint soda
+ Americana Variety - Mix of 6 flavors
+ 2017-09-07T21:23:43Z
+
+
+
+
+
+
+
+
+ 2
+ 2000-10-01T00:00:00
+
+ 3
+ 20.9
+
+
+
+
+ http://services.odata.org/V3/OData/OData.svc/Products(3)
+
+
+
+
+
+ Havina Cola
+ The Original Key Lime Cola
+ 2017-09-07T21:23:43Z
+
+
+
+
+
+
+
+
+ 3
+ 2005-10-01T00:00:00
+ 2006-10-01T00:00:00
+ 3
+ 19.9
+
+
+
+
+ http://services.odata.org/V3/OData/OData.svc/Products(4)
+
+
+
+
+
+ Fruit Punch
+ Mango flavor, 8.3 Ounce Cans (Pack of 24)
+ 2017-09-07T21:23:43Z
+
+
+
+
+
+
+
+
+ 4
+ 2003-01-05T00:00:00
+
+ 3
+ 22.99
+
+
+
+
+ http://services.odata.org/V3/OData/OData.svc/Products(5)
+
+
+
+
+
+ Cranberry Juice
+ 16-Ounce Plastic Bottles (Pack of 12)
+ 2017-09-07T21:23:43Z
+
+
+
+
+
+
+
+
+ 5
+ 2006-08-04T00:00:00
+
+ 3
+ 22.8
+
+
+
+
+ http://services.odata.org/V3/OData/OData.svc/Products(6)
+
+
+
+
+
+ Pink Lemonade
+ 36 Ounce Cans (Pack of 3)
+ 2017-09-07T21:23:43Z
+
+
+
+
+
+
+
+
+ 6
+ 2006-11-05T00:00:00
+
+ 3
+ 18.8
+
+
+
+
+ http://services.odata.org/V3/OData/OData.svc/Products(7)
+
+
+
+
+
+ DVD Player
+ 1080P Upconversion DVD Player
+ 2017-09-07T21:23:43Z
+
+
+
+
+
+
+
+
+ 7
+ 2006-11-15T00:00:00
+
+ 5
+ 35.88
+
+
+
+
+ http://services.odata.org/V3/OData/OData.svc/Products(8)
+
+
+
+
+
+ LCD HDTV
+ 42 inch 1080p LCD with Built-in Blu-ray Disc Player
+ 2017-09-07T21:23:43Z
+
+
+
+
+
+
+
+
+ 8
+ 2008-05-08T00:00:00
+
+ 3
+ 1088.8
+
+
+
+
+ http://services.odata.org/V3/OData/OData.svc/Products(9)
+
+
+
+
+
+
+ Lemonade
+ Classic, refreshing lemonade (Single bottle)
+ 2017-09-07T21:23:43Z
+
+
+
+
+
+
+
+
+
+ 9
+ 1970-01-01T00:00:00
+
+ 7
+ 1.01
+
+
+
+
+ http://services.odata.org/V3/OData/OData.svc/Products(10)
+
+
+
+
+
+
+ Coffee
+ Bulk size can of instant coffee
+ 2017-09-07T21:23:43Z
+
+
+
+
+
+
+
+
+
+ 10
+ 1982-12-31T00:00:00
+
+ 1
+ 6.99
+
+
+
+
\ No newline at end of file
diff --git a/spec/fixtures/odata.org/odata_v3_service.xml b/spec/fixtures/odata.org/odata_v3_service.xml
new file mode 100644
index 0000000..d2207b0
--- /dev/null
+++ b/spec/fixtures/odata.org/odata_v3_service.xml
@@ -0,0 +1,27 @@
+
+
+
+ Default
+
+ Products
+
+
+ ProductDetails
+
+
+ Categories
+
+
+ Suppliers
+
+
+ Persons
+
+
+ PersonDetails
+
+
+ Advertisements
+
+
+
\ No newline at end of file
diff --git a/spec/fixtures/odata.org/odata_v4_products.json b/spec/fixtures/odata.org/odata_v4_products.json
new file mode 100644
index 0000000..b9c0e9a
--- /dev/null
+++ b/spec/fixtures/odata.org/odata_v4_products.json
@@ -0,0 +1,106 @@
+{
+ "@odata.context": "http://services.odata.org/V4/OData/OData.svc/$metadata#Products",
+ "value": [
+ {
+ "Description": "Whole grain bread",
+ "DiscontinuedDate": null,
+ "ID": 0,
+ "Name": "Bread",
+ "Price": 2.5,
+ "Rating": 4,
+ "ReleaseDate": "1992-01-01T00:00:00Z"
+ },
+ {
+ "Description": "Low fat milk",
+ "DiscontinuedDate": null,
+ "ID": 1,
+ "Name": "Milk",
+ "Price": 3.5,
+ "Rating": 3,
+ "ReleaseDate": "1995-10-01T00:00:00Z"
+ },
+ {
+ "Description": "Americana Variety - Mix of 6 flavors",
+ "DiscontinuedDate": null,
+ "ID": 2,
+ "Name": "Vint soda",
+ "Price": 20.9,
+ "Rating": 3,
+ "ReleaseDate": "2000-10-01T00:00:00Z"
+ },
+ {
+ "Description": "The Original Key Lime Cola",
+ "DiscontinuedDate": "2006-10-01T00:00:00Z",
+ "ID": 3,
+ "Name": "Havina Cola",
+ "Price": 19.9,
+ "Rating": 3,
+ "ReleaseDate": "2005-10-01T00:00:00Z"
+ },
+ {
+ "Description": "Mango flavor, 8.3 Ounce Cans (Pack of 24)",
+ "DiscontinuedDate": null,
+ "ID": 4,
+ "Name": "Fruit Punch",
+ "Price": 22.99,
+ "Rating": 3,
+ "ReleaseDate": "2003-01-05T00:00:00Z"
+ },
+ {
+ "Description": "16-Ounce Plastic Bottles (Pack of 12)",
+ "DiscontinuedDate": null,
+ "ID": 5,
+ "Name": "Cranberry Juice",
+ "Price": 22.8,
+ "Rating": 3,
+ "ReleaseDate": "2006-08-04T00:00:00Z"
+ },
+ {
+ "Description": "36 Ounce Cans (Pack of 3)",
+ "DiscontinuedDate": null,
+ "ID": 6,
+ "Name": "Pink Lemonade",
+ "Price": 18.8,
+ "Rating": 3,
+ "ReleaseDate": "2006-11-05T00:00:00Z"
+ },
+ {
+ "Description": "1080P Upconversion DVD Player",
+ "DiscontinuedDate": null,
+ "ID": 7,
+ "Name": "DVD Player",
+ "Price": 35.88,
+ "Rating": 5,
+ "ReleaseDate": "2006-11-15T00:00:00Z"
+ },
+ {
+ "Description": "42 inch 1080p LCD with Built-in Blu-ray Disc Player",
+ "DiscontinuedDate": null,
+ "ID": 8,
+ "Name": "LCD HDTV",
+ "Price": 1088.8,
+ "Rating": 3,
+ "ReleaseDate": "2008-05-08T00:00:00Z"
+ },
+ {
+ "@odata.type": "#ODataDemo.FeaturedProduct",
+ "Description": "Classic, refreshing lemonade (Single bottle)",
+ "DiscontinuedDate": null,
+ "ID": 9,
+ "Name": "Lemonade",
+ "Price": 1.01,
+ "Rating": 7,
+ "ReleaseDate": "1970-01-01T00:00:00Z"
+ },
+ {
+ "@odata.type": "#ODataDemo.FeaturedProduct",
+ "Description": "Bulk size can of instant coffee",
+ "DiscontinuedDate": null,
+ "ID": 10,
+ "Name": "Coffee",
+ "Price": 6.99,
+ "Rating": 1,
+ "ReleaseDate": "1982-12-31T00:00:00Z"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/spec/fixtures/odata.org/odata_v4_service.xml b/spec/fixtures/odata.org/odata_v4_service.xml
new file mode 100644
index 0000000..75a0b40
--- /dev/null
+++ b/spec/fixtures/odata.org/odata_v4_service.xml
@@ -0,0 +1,27 @@
+
+
+
+ Default
+
+ Products
+
+
+ ProductDetails
+
+
+ Categories
+
+
+ Suppliers
+
+
+ Persons
+
+
+ PersonDetails
+
+
+ Advertisements
+
+
+
\ No newline at end of file
diff --git a/spec/fixtures/partial/partial_feed_metadata.xml b/spec/fixtures/partial/partial_feed_metadata.xml
index 66e26cd..ce934ac 100644
--- a/spec/fixtures/partial/partial_feed_metadata.xml
+++ b/spec/fixtures/partial/partial_feed_metadata.xml
@@ -1,25 +1,25 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spec/fixtures/sample_service/edmx_categories_products.xml b/spec/fixtures/sample_service/edmx_categories_products.xml
index 201838a..d80324d 100644
--- a/spec/fixtures/sample_service/edmx_categories_products.xml
+++ b/spec/fixtures/sample_service/edmx_categories_products.xml
@@ -1 +1,71 @@
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spec/fixtures/sample_service/result_category_names.xml b/spec/fixtures/sample_service/result_category_names.xml
index e2988c4..c48895f 100644
--- a/spec/fixtures/sample_service/result_category_names.xml
+++ b/spec/fixtures/sample_service/result_category_names.xml
@@ -1,5 +1,5 @@
-Test Category 1
-Test Category 2
-Test Category 3
+ Test Category 1
+ Test Category 2
+ Test Category 3
\ No newline at end of file
diff --git a/spec/association_spec.rb b/spec/internal/association_spec.rb
similarity index 83%
rename from spec/association_spec.rb
rename to spec/internal/association_spec.rb
index ab7b5aa..c84f4f7 100644
--- a/spec/association_spec.rb
+++ b/spec/internal/association_spec.rb
@@ -2,16 +2,22 @@
module OData
describe Association do
+
before(:all) do
stub_request(:get, /http:\/\/test\.com\/test\.svc\/\$metadata(?:\?.+)?/).
with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/sample_service/edmx_categories_products.xml", __FILE__)), :headers => {})
+ to_return(:status => 200, :body => Fixtures.load("/sample_service/edmx_categories_products.xml"), :headers => {})
- @svc = OData::Service.new "http://test.com/test.svc/$metadata"
+ @service = OData::Service.new "http://test.com/test.svc/$metadata"
@product_category = RSpecSupport::ElementHelpers.string_to_element('')
end
- describe "#initialize singlular navigation property" do
- before { @association = Association.new @product_category, @svc.edmx }
+
+ after(:all) do
+ remove_classes @service
+ end
+
+ describe "#initialize singular navigation property" do
+ before { @association = Association.new @product_category, @service.edmx }
subject { @association }
it "sets the association name" do
@@ -49,4 +55,4 @@ module OData
end
end
end
-end
\ No newline at end of file
+end
diff --git a/spec/internal/class_builder_spec.rb b/spec/internal/class_builder_spec.rb
new file mode 100644
index 0000000..a9bf7f6
--- /dev/null
+++ b/spec/internal/class_builder_spec.rb
@@ -0,0 +1,66 @@
+require 'spec_helper'
+
+module OData
+ describe ClassBuilder do
+
+ before(:each) do
+ @methods = []
+ @nav_props = []
+ @svc = nil
+ @namespace = nil
+ end
+
+ after(:each) do
+ Object.send(:remove_const, 'Product') if Object.const_defined? 'Product'
+ Object.send(:remove_const, 'Namespace') if Object.const_defined? 'Namespace'
+ end
+
+ context "Building the class" do
+ subject { ClassBuilder.new('Product', @methods, @nav_props, @svc, @namespace).build }
+
+ it "should take in an instance of the service" do
+ subject.should eq Product
+ end
+
+ it "creates the :first class method" do
+ expect(subject).to respond_to(:first)
+ end
+
+ it "creates :__metadata method" do
+ expect(subject.new).to respond_to(:__metadata)
+ end
+
+ it "creates :as_json method" do
+ expect(subject.new).to respond_to(:as_json)
+ end
+ end
+
+ context "with additional params" do
+
+ it "handles lowercase entities" do
+ klass = ClassBuilder.new 'product', @methods, @nav_props, @svc, @namespace
+ result = klass.build
+ result.should eq Product
+ end
+
+ it "creates additional methods" do
+ klass = ClassBuilder.new 'Product', [:method1], @nav_props, @svc, @namespace
+ result = klass.build
+ expect(result.new).to respond_to(:method1)
+ end
+
+ it "creates nav_props" do
+ klass = ClassBuilder.new 'Product', @methods, [:navigate], @svc, @namespace
+ result = klass.build
+ expect(result.new).to respond_to(:navigate)
+ end
+
+ it "creates the class within a namespace" do
+ klass = ClassBuilder.new 'Namespace::Product', @methods, @nav_props, @svc, "Namespace"
+ result = klass.build
+ expect(result).to eq Namespace::Product
+ end
+
+ end
+ end
+end
\ No newline at end of file
diff --git a/spec/internal/json_serialization_spec.rb b/spec/internal/json_serialization_spec.rb
new file mode 100644
index 0000000..3c42f8a
--- /dev/null
+++ b/spec/internal/json_serialization_spec.rb
@@ -0,0 +1,46 @@
+require 'spec_helper'
+
+module OData
+ describe "JSON serialization of objects" do
+ let(:username) { "blabla" }
+ let(:password) { "" }
+
+ before(:each) do
+ # Required for the build_classes method
+ stub_request(:get, "http://test.com/test.svc/$metadata").
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("/ms_system_center/edmx_ms_system_center.xml"), :headers => {})
+
+ stub_request(:get, "http://test.com/test.svc/VirtualMachines").
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("/ms_system_center/virtual_machines.xml"), :headers => {})
+
+ @service = OData::Service.new "http://test.com/test.svc/", { :username => username, :password => password, :verify_ssl => false, :namespace => "VMM" }
+ end
+
+ subject do
+ @service.VirtualMachines
+ results = @service.execute
+ results.first.as_json
+ end
+
+ after(:each) do
+ remove_classes @service
+ end
+
+ it "Should quote Edm.Int64 properties" do
+ subject["PerfDiskBytesWrite"].should be_a(String)
+ end
+
+ it "Should output collections with metadata" do
+ subject["VMNetworkAssignments"].should be_a(Hash)
+ subject["VMNetworkAssignments"].should have_key("__metadata")
+ subject["VMNetworkAssignments"]["__metadata"].should be_a(Hash)
+ subject["VMNetworkAssignments"]["__metadata"].should have_key("type")
+ subject["VMNetworkAssignments"]["__metadata"]["type"].should eq("Collection(VMM.VMNetworkAssignment)")
+ subject["VMNetworkAssignments"].should have_key("results")
+ subject["VMNetworkAssignments"]["results"].should be_a(Array)
+ subject["VMNetworkAssignments"]["results"].should eq([])
+ end
+ end
+end
\ No newline at end of file
diff --git a/spec/internal/keys_spec.rb b/spec/internal/keys_spec.rb
new file mode 100644
index 0000000..d1545c7
--- /dev/null
+++ b/spec/internal/keys_spec.rb
@@ -0,0 +1,101 @@
+require 'spec_helper'
+require 'base64'
+
+module OData
+ describe "Keys" do
+ describe "Collection with an int64 key Named 'id'" do
+
+ before(:all) do
+ stub_request(:get, "http://test.com/test.svc/$metadata").
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("/int64_ids/edmx_car_service.xml"), :headers => {})
+ @service = OData::Service.new "http://test.com/test.svc/"
+ end
+
+ after(:all) do
+ remove_classes @service
+ end
+
+ context "has the correct metadata" do
+ before(:all) do
+ @id_meta = @service.class_metadata['Car']['id']
+ end
+
+ subject { @id_meta }
+
+ its(:name) { should eq('id') }
+ its(:type) { should eq('Edm.Int64') }
+ its(:nullable) { should eq false }
+ its(:fc_target_path) { should be_nil }
+ its(:fc_keep_in_content) { should be_nil }
+ end
+
+ context "can parse Id correctly" do
+ before(:each) do
+ stub_request(:get, "http://test.com/test.svc/Cars(213L)").
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("/int64_ids/result_cars.xml"), :headers => {})
+
+ @service.Cars(213)
+ results = @service.execute
+ @car = results.first
+ end
+
+ subject { @car }
+
+ its(:id) { should eq(213) }
+ its(:color) { should eq('peach') }
+ end
+ end
+
+ describe "Collection with an int64 key named 'KeyId'" do
+
+ before(:all) do
+ stub_request(:get, "http://test.com/test.svc/$metadata").
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("/int64_ids/edmx_boat_service.xml"), :headers => {})
+ @service = OData::Service.new "http://test.com/test.svc/"
+ end
+
+ after(:all) do
+ remove_classes @service
+ end
+
+ context "has the correct metadata" do
+ before(:all) do
+ @id_meta = @service.class_metadata['Boat']['KeyId']
+ end
+
+ subject { @id_meta }
+
+ its(:name) { should eq('KeyId') }
+ its(:type) { should eq('Edm.Int64') }
+ its(:nullable) { should eq(false) }
+ its(:fc_target_path) { should be_nil }
+ its(:fc_keep_in_content) { should be_nil }
+ end
+
+ context "can parse Id correctly" do
+ before(:each) do
+ stub_request(:get, "http://test.com/test.svc/Boats(213L)").
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("/int64_ids/result_boats.xml"), :headers => {})
+
+ @service.Boats(213)
+ results = @service.execute
+ @boat = results.first
+ end
+
+ subject { @boat }
+
+ its(:id) { should eq(213) }
+ its(:color) { should eq('blue') }
+ end
+ end
+
+ describe "Collection with a string key named 'xxx" do
+ end
+
+ end
+
+end
diff --git a/spec/internal/parse_value_spec.rb b/spec/internal/parse_value_spec.rb
new file mode 100644
index 0000000..6819410
--- /dev/null
+++ b/spec/internal/parse_value_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+module OData
+ describe_private OData::Service do
+ describe "parse value" do
+ before(:each) do
+ # Required for the build_classes method
+ stub_request(:get, "http://test.com/test.svc/$metadata").
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("edmx_empty.xml" ), :headers => {})
+ end
+
+ it "should not error on an 'out of range' date" do
+ # 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]
+ @service =OData::Service.new "http://test.com/test.svc/"
+ element_to_parse = Nokogiri::XML.parse('2100-01-01T00:00:00').elements[0]
+ lambda { @service.parse_value_xml(element_to_parse) }.should_not raise_exception
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/spec/property_metadata_spec.rb b/spec/internal/property_metadata_spec.rb
similarity index 100%
rename from spec/property_metadata_spec.rb
rename to spec/internal/property_metadata_spec.rb
diff --git a/spec/query_builder_spec.rb b/spec/internal/query_builder_spec.rb
similarity index 100%
rename from spec/query_builder_spec.rb
rename to spec/internal/query_builder_spec.rb
diff --git a/spec/revised_service_spec.rb b/spec/revised_service_spec.rb
deleted file mode 100644
index 5a2b8d4..0000000
--- a/spec/revised_service_spec.rb
+++ /dev/null
@@ -1,328 +0,0 @@
-require 'spec_helper'
-require 'base64'
-
-module OData
-
- describe Service do
- before(:all) do
- stub_request(:get, /http:\/\/test\.com\/test\.svc\/\$metadata(?:\?.+)?/).
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/sample_service/edmx_categories_products.xml", __FILE__)), :headers => {})
-
- @cat_prod_service = OData::Service.new "http://test.com/test.svc"
- end
- subject { @cat_prod_service }
-
- context "methods" do
- it { should respond_to :update_object }
- it { should respond_to :delete_object }
- it { should respond_to :save_changes }
- it { should respond_to :load_property }
- it { should respond_to :add_link }
- it { should respond_to :execute }
- it { should respond_to :partial? }
- it { should respond_to :next }
- it { should respond_to :classes }
- it { should respond_to :class_metadata }
- it { should respond_to :collections }
- it { should respond_to :options }
- it { should respond_to :function_imports }
-
- context "after parsing metadata" do
- it { should respond_to :Products }
- it { should respond_to :Categories }
- it { should respond_to :AddToProducts }
- it { should respond_to :AddToCategories }
- end
- end
- context "collections method" do
- subject { @cat_prod_service.collections }
- it { should include 'Products' }
- it { should include 'Categories' }
- it "should expose the edmx type of objects" do
- subject['Products'][:edmx_type].should eq 'RubyODataService.Product'
- subject['Categories'][:edmx_type].should eq 'RubyODataService.Category'
- end
- it "should expose the local model type" do
- subject['Products'][:type].should eq Product
- subject['Categories'][:type].should eq Category
- end
- end
- context "class metadata" do
- subject { @cat_prod_service.class_metadata }
- it { should_not be_empty}
- it { should have_key 'Product' }
- it { should have_key 'Category' }
-
- context "should have keys for each property" do
- subject { @cat_prod_service.class_metadata['Category'] }
- it { should have_key 'Id' }
- it { should have_key 'Name' }
- it { should have_key 'Products' }
- it "should return a PropertyMetadata object for each property" do
- subject['Id'].should be_a PropertyMetadata
- subject['Name'].should be_a PropertyMetadata
- subject['Products'].should be_a PropertyMetadata
- end
- it "should have correct PropertyMetadata for Category.Id" do
- meta = subject['Id']
- meta.name.should eq 'Id'
- meta.type.should eq 'Edm.Int32'
- meta.nullable.should eq false
- meta.fc_target_path.should be_nil
- meta.fc_keep_in_content.should be_nil
- meta.nav_prop.should eq false
- meta.is_key.should eq true
- end
- it "should have correct PropertyMetadata for Category.Name" do
- meta = subject['Name']
- meta.name.should eq 'Name'
- meta.type.should eq 'Edm.String'
- meta.nullable.should eq false
- meta.fc_target_path.should be_nil
- meta.fc_keep_in_content.should be_nil
- meta.nav_prop.should eq false
- meta.is_key.should eq false
- end
- it "should have correct PropertyMetadata for Category.Products" do
- meta = subject['Products']
- meta.name.should eq 'Products'
- meta.type.should be_nil
- meta.nullable.should eq true
- meta.fc_target_path.should be_nil
- meta.fc_keep_in_content.should be_nil
- meta.nav_prop.should eq true
- meta.association.should_not be_nil
- meta.is_key.should eq false
- end
- end
- end
- context "function_imports method" do
- subject { @cat_prod_service.function_imports }
- it { should_not be_empty}
- it { should have_key 'CleanDatabaseForTesting' }
- it { should have_key 'EntityCategoryWebGet' }
- it { should have_key 'EntitySingleCategoryWebGet' }
- it "should expose the http method" do
- subject['CleanDatabaseForTesting'][:http_method].should eq 'POST'
- subject['EntityCategoryWebGet'][:http_method].should eq 'GET'
- subject['EntitySingleCategoryWebGet'][:http_method].should eq 'GET'
- end
- it "should expose the return type" do
- subject['CleanDatabaseForTesting'][:return_typo].should be_nil
- subject['EntityCategoryWebGet'][:return_type].should eq Array
- subject['EntityCategoryWebGet'][:inner_return_type].should eq Category
- subject['EntitySingleCategoryWebGet'][:return_type].should eq Category
- subject['EntitySingleCategoryWebGet'][:inner_return_type].should be_nil
- subject['CategoryNames'][:return_type].should eq Array
- subject['CategoryNames'][:inner_return_type].should eq String
- end
- it "should provide a hash of parameters" do
- subject['EntityCategoryWebGet'][:parameters].should be_nil
- subject['EntitySingleCategoryWebGet'][:parameters].should be_a Hash
- subject['EntitySingleCategoryWebGet'][:parameters]['id'].should eq 'Edm.Int32'
- end
- context "after parsing function imports" do
- subject { @cat_prod_service }
- it { should respond_to :CleanDatabaseForTesting }
- it { should respond_to :EntityCategoryWebGet }
- it { should respond_to :EntitySingleCategoryWebGet }
- it { should respond_to :CategoryNames }
- end
- context "error checking" do
- subject { @cat_prod_service }
- it "should throw an exception if a parameter is passed in to a method that doesn't require one" do
- lambda { subject.EntityCategoryWebGet(1) }.should raise_error(ArgumentError, "wrong number of arguments (1 for 0)")
- end
- it "should throw and exception if more parameters are passed in than required by the function" do
- lambda { subject.EntitySingleCategoryWebGet(1,2) }.should raise_error(ArgumentError, "wrong number of arguments (2 for 1)")
- end
- end
- context "url and http method checks" do
- subject { @cat_prod_service }
- before { stub_request(:any, /http:\/\/test\.com\/test\.svc\/(.*)/) }
- it "should call the correct url with the correct http method for a post with no parameters" do
- subject.CleanDatabaseForTesting
- a_request(:post, "http://test.com/test.svc/CleanDatabaseForTesting").should have_been_made
- end
- it "should call the correct url with the correct http method for a get with no parameters" do
- subject.EntityCategoryWebGet
- a_request(:get, "http://test.com/test.svc/EntityCategoryWebGet").should have_been_made
- end
- it "should call the correct url with the correct http method for a get with parameters" do
- subject.EntitySingleCategoryWebGet(1)
- a_request(:get, "http://test.com/test.svc/EntitySingleCategoryWebGet?id=1").should have_been_made
- end
- end
- context "function import result parsing" do
- subject { @cat_prod_service }
- before(:each) do
- stub_request(:post, "http://test.com/test.svc/CleanDatabaseForTesting").to_return(:status => 204)
-
- stub_request(:get, "http://test.com/test.svc/EntityCategoryWebGet").
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/sample_service/result_entity_category_web_get.xml", __FILE__)), :headers => {})
-
- stub_request(:get, "http://test.com/test.svc/EntitySingleCategoryWebGet?id=1").
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/sample_service/result_entity_single_category_web_get.xml", __FILE__)), :headers => {})
-
- stub_request(:get, "http://test.com/test.svc/CategoryNames").
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/sample_service/result_category_names.xml", __FILE__)), :headers => {})
-
- stub_request(:get, "http://test.com/test.svc/FirstCategoryId").
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/sample_service/result_first_category_id.xml", __FILE__)), :headers => {})
- end
- it "should return true if a function import post that returns successfully and doesn't have a return value (HTTP 204)" do
- result = subject.CleanDatabaseForTesting
- expect(result).to eq true
- end
- it "should return a collection of entities for a collection" do
- result = subject.EntityCategoryWebGet
- result.should be_an Enumerable
- result.first.should be_a Category
- result.first.Name.should eq "Test Category"
- end
- it "should return a single entity if it isn't a collection" do
- result = subject.EntitySingleCategoryWebGet(1)
- result.should be_a Category
- result.Name.should eq "Test Category"
- end
- it "should return a collection of primitive types" do
- result = subject.CategoryNames
- result.should be_an Enumerable
- result.first.should be_a String
- result.first.should eq "Test Category 1"
- end
- it "should return a single primitive type" do
- result = subject.FirstCategoryId
- result.should be_a Integer
- result.should eq 1
- end
- end
- end
- end
-
- describe "Keys" do
- describe "Collection with an int64 key Named 'id'" do
-
- before(:all) do
- stub_request(:get, "http://test.com/test.svc/$metadata").
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/int64_ids/edmx_car_service.xml", __FILE__)), :headers => {})
- @svc = OData::Service.new "http://test.com/test.svc/"
- end
-
- context "has the correct metadata" do
- before(:all) do
- @id_meta = @svc.class_metadata['Car']['id']
- end
-
- subject { @id_meta }
-
- its(:name) { should eq('id') }
- its(:type) { should eq('Edm.Int64') }
- its(:nullable) { should eq false }
- its(:fc_target_path) { should be_nil }
- its(:fc_keep_in_content) { should be_nil }
- end
-
- context "can parse Id correctly" do
- before(:each) do
- stub_request(:get, "http://test.com/test.svc/Cars(213L)").
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/int64_ids/result_cars.xml", __FILE__)), :headers => {})
-
- @svc.Cars(213)
- results = @svc.execute
- @car = results.first
- end
-
- subject { @car }
-
- its(:id) { should eq(213) }
- its(:color) { should eq('peach') }
- end
- end
-
- describe "Collection with an int64 key named 'KeyId'" do
-
- before(:all) do
- stub_request(:get, "http://test.com/test.svc/$metadata").
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/int64_ids/edmx_boat_service.xml", __FILE__)), :headers => {})
- @svc = OData::Service.new "http://test.com/test.svc/"
- end
-
- context "has the correct metadata" do
- before(:all) do
- @id_meta = @svc.class_metadata['Boat']['KeyId']
- end
-
- subject { @id_meta }
-
- its(:name) { should eq('KeyId') }
- its(:type) { should eq('Edm.Int64') }
- its(:nullable) { should eq(false) }
- its(:fc_target_path) { should be_nil }
- its(:fc_keep_in_content) { should be_nil }
- end
-
- context "can parse Id correctly" do
- before(:each) do
- stub_request(:get, "http://test.com/test.svc/Boats(213L)").
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/int64_ids/result_boats.xml", __FILE__)), :headers => {})
-
- @svc.Boats(213)
- results = @svc.execute
- @boat = results.first
- end
-
- subject { @boat }
-
- its(:id) { should eq(213) }
- its(:color) { should eq('blue') }
- end
- end
- end
-
- describe "Dual Namespaces" do
- before(:all) do
- auth_string = "xxxx\\yyyy:zzzz"
- authorization_header = { authorization: "Basic #{Base64::encode64(auth_string).strip}" }
- stub_request(:get, "http://test.com/test.svc/$metadata").
- with(:headers => DEFAULT_HEADERS.merge(authorization_header)).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/ms_system_center/edmx_ms_system_center_v2.xml", __FILE__)), :headers => {})
- end
-
- after(:all) do
- VMM.constants.each do |constant|
- VMM.send :remove_const, constant
- end
- end
-
- it "should parse the service without errors" do
- lambda { OData::Service.new "http://test.com/test.svc/", { :username => "xxxx\\yyyy", :password=> "zzzz", :verify_ssl => false, :namespace => "VMM" } }.should_not raise_error
- end
- end
-
- describe "Dual Services" do
- before(:all) do
- stub_request(:get, "http://service1.com/test.svc/$metadata").
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/sample_service/edmx_categories_products.xml", __FILE__)), :headers => {})
-
- stub_request(:get, "http://service2.com/test.svc/$metadata").
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/int64_ids/edmx_car_service.xml", __FILE__)), :headers => {})
-
-
- @service1 = OData::Service.new "http://service1.com/test.svc"
- @service2 = OData::Service.new "http://service2.com/test.svc"
- end
-
- it "should use the correct service uri" do
- expect(@service1.class_metadata[:uri]).to eq 'http://service1.com/test.svc'
- expect(@service2.class_metadata[:uri]).to eq 'http://service2.com/test.svc'
- end
- end
-end
diff --git a/spec/service_detailed_spec.rb b/spec/service_detailed_spec.rb
new file mode 100644
index 0000000..a9a4743
--- /dev/null
+++ b/spec/service_detailed_spec.rb
@@ -0,0 +1,579 @@
+require 'spec_helper'
+
+module OData
+ describe Service do
+
+ after(:each) do
+ remove_classes @service
+ end
+
+ describe "#initialize" do
+ it "truncates passed in end slash from uri when making the request" do
+ # Required for the build_classes method
+ stub_request(:get, "http://test.com/test.svc/$metadata").
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("edmx_empty.xml"), :headers => {})
+
+ @service =OData::Service.new "http://test.com/test.svc/"
+ end
+ it "doesn't error with lowercase entities" do
+ # Required for the build_classes method
+ stub_request(:get, "http://test.com/test.svc/$metadata").
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("edmx_lowercase.xml"), :headers => {})
+
+ expect { @service = OData::Service.new "http://test.com/test.svc" }.not_to raise_error
+ end
+
+ describe "additional query string parameters" do
+ before(:each) do
+ # Required for the build_classes method
+ stub_request(:get, /http:\/\/test\.com\/test\.svc\/\$metadata(?:\?.+)?/).
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("edmx_empty.xml"), :headers => {})
+ end
+ it "should accept additional query string parameters" do
+ @service =OData::Service.new "http://test.com/test.svc/", { :additional_params => { :x=>1, :y=>2 } }
+ @service.options[:additional_params].should eq Hash[:x=>1, :y=>2]
+ end
+ it "should call the correct metadata uri when additional_params are passed in" do
+ @service =OData::Service.new "http://test.com/test.svc/", { :additional_params => { :x => 1, :y => 2 } }
+ a_request(:get, "http://test.com/test.svc/$metadata?x=1&y=2").should have_been_made
+ end
+ end
+
+ describe "rest-client options" do
+ before(:each) do
+ # Required for the build_classes method
+ stub_request(:get, /http:\/\/test\.com\/test\.svc\/\$metadata(?:\?.+)?/).
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("edmx_empty.xml"), :headers => {})
+ end
+ it "should accept in options that will be passed to the rest-client lib" do
+ @service =OData::Service.new "http://test.com/test.svc/", { :rest_options => { :ssl_ca_file => "ca_certificate.pem" } }
+ @service.options[:rest_options].should eq Hash[:ssl_ca_file => "ca_certificate.pem"]
+ end
+ it "should merge the rest options with the built in options" do
+ @service =OData::Service.new "http://test.com/test.svc/", { :rest_options => { :ssl_ca_file => "ca_certificate.pem" } }
+ @service.instance_variable_get(:@rest_options).should eq Hash[:verify_ssl => 1, :user => nil, :password => nil, :ssl_ca_file => "ca_certificate.pem"]
+ end
+ end
+ end
+
+ describe "additional query string parameters" do
+ before(:each) do
+ # Required for the build_classes method
+ stub_request(:any, /http:\/\/test\.com\/test\.svc(?:.*)/).
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("/sap/edmx_sap_demo_flight.xml"), :headers => {})
+ end
+ it "should pass the parameters as part of a query" do
+ @service =OData::Service.new "http://test.com/test.svc/", { :additional_params => { :x=>1, :y=>2 } }
+ @service.flight_dataCollection
+ @service.execute
+ a_request(:get, "http://test.com/test.svc/flight_dataCollection?x=1&y=2").should have_been_made
+ end
+ it "should pass the parameters as part of a save" do
+ @service =OData::Service.new "http://test.com/test.svc/", { :additional_params => { :x=>1, :y=>2 } }
+ new_flight = ZDemoFlight.new
+ @service.AddToflight_dataCollection(new_flight)
+ @service.save_changes
+ a_request(:post, "http://test.com/test.svc/flight_dataCollection?x=1&y=2").should have_been_made
+ end
+ it "should pass the parameters as part of an update" do
+ @service =OData::Service.new "http://test.com/test.svc/", { :additional_params => { :x=>1, :y=>2 } }
+ existing_flight = ZDemoFlight.new
+ existing_flight.__metadata = { :uri => "http://test.com/test.svc/flight_dataCollection/1" }
+ @service.update_object(existing_flight)
+ @service.save_changes
+ a_request(:put, "http://test.com/test.svc/flight_dataCollection/1?x=1&y=2").should have_been_made
+ end
+ it "should pass the parameters as part of a delete" do
+ @service =OData::Service.new "http://test.com/test.svc/", { :additional_params => { :x=>1, :y=>2 } }
+ existing_flight = ZDemoFlight.new
+ existing_flight.__metadata = { :uri => "http://test.com/test.svc/flight_dataCollection/1" }
+ @service.delete_object(existing_flight)
+ @service.save_changes
+ a_request(:delete, "http://test.com/test.svc/flight_dataCollection/1?x=1&y=2").should have_been_made
+ end
+ it "should pass the parameters as part of a batch save" do
+ @service =OData::Service.new "http://test.com/test.svc/", { :additional_params => { :x=>1, :y=>2 } }
+ new_flight = ZDemoFlight.new
+ @service.AddToflight_dataCollection(new_flight)
+ new_flight2 = ZDemoFlight.new
+ @service.AddToflight_dataCollection(new_flight2)
+ @service.save_changes
+ a_request(:post, "http://test.com/test.svc/$batch?x=1&y=2").should have_been_made
+ end
+ it "should pass the parameters as part of an add link" do
+ @service =OData::Service.new "http://test.com/test.svc/", { :additional_params => { :x=>1, :y=>2 } }
+ existing_flight1 = ZDemoFlight.new
+ existing_flight1.__metadata = { :uri => "http://test.com/test.svc/flight_dataCollection/1" }
+ existing_flight2 = ZDemoFlight.new
+ existing_flight2.__metadata = { :uri => "http://test.com/test.svc/flight_dataCollection/2" }
+ @service.add_link(existing_flight1, "flight_data_r", existing_flight2)
+ @service.save_changes
+ a_request(:post, "http://test.com/test.svc/flight_dataCollection/1/$links/flight_data_r?x=1&y=2").should have_been_made
+ end
+ it "should pass the parameters as part of a function import with a parameter" do
+ @service =OData::Service.new "http://test.com/test.svc/", { :additional_params => { :x=>1, :y=>2 } }
+ @service.get_flight(1)
+ a_request(:get, "http://test.com/test.svc/get_flight?id=1&x=1&y=2").should have_been_made
+ end
+ it "should pass the parameters as part of a function import without parameters" do
+ @service =OData::Service.new "http://test.com/test.svc/", { :additional_params => { :x=>1, :y=>2 } }
+ @service.get_top_flight
+ a_request(:get, "http://test.com/test.svc/get_top_flight?x=1&y=2").should have_been_made
+ end
+ end
+
+ describe "exception handling" do
+ before(:each) do
+ stub_request(:get, "http://test.com/test.svc/$metadata").
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("sample_service/edmx_categories_products.xml"), :headers => {})
+
+ stub_request(:get, "http://test.com/test.svc/Categories?$select=Price").
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 400, :body => Fixtures.load("error_without_message.xml"), :headers => {})
+ end
+
+ it "includes a generic message if the error is not in the response" do
+ @service =OData::Service.new "http://test.com/test.svc/"
+ @service.Categories.select "Price"
+ expect { @service.execute }.to raise_error(OData::ServiceError) { |error|
+ error.http_code.should eq 400
+ error.message.should eq "Server returned error but no message."
+ }
+ end
+ end
+
+ describe "lowercase collections" do
+ before(:each) do
+ # Required for the build_classes method
+ stub_request(:get, "http://test.com/test.svc/$metadata").
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("edmx_lowercase.xml"), :headers => {})
+ end
+
+ it "should respond_to a lowercase collection" do
+ @service =OData::Service.new "http://test.com/test.svc"
+ expect(@service.respond_to?('acronyms')).to eq true
+ end
+
+ it "should allow a lowercase collections to be queried" do
+ @service =OData::Service.new "http://test.com/test.svc"
+ lambda { @service.send('acronyms') }.should_not raise_error
+ end
+ end
+
+
+ describe "collections, objects, metadata etc" do
+ before(:each) do
+ # Metadata
+ stub_request(:get, "http://test.com/test.svc/$metadata").
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("feed_customization/edmx_feed_customization.xml"), :headers => {})
+
+ # Content - Products
+ stub_request(:get, /http:\/\/test\.com\/test\.svc\/Products(?:.*)/).
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("feed_customization/result_feed_customization_products_expand.xml"), :headers => {})
+
+ # Content - Categories expanded Products
+ stub_request(:get, /http:\/\/test\.com\/test\.svc\/Categories(?:.*)/).
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("feed_customization/result_feed_customization_categories_expand.xml"), :headers => {})
+ end
+
+ describe "handling feed customizations" do
+ describe "property metadata" do
+ it "should fill the class_metadata hash" do
+ @service =OData::Service.new "http://test.com/test.svc/"
+ @service.class_metadata.should_not be_empty
+ end
+ it "should add a key (based on the name) for each property class_metadata hash" do
+ @service =OData::Service.new "http://test.com/test.svc/"
+ @service.class_metadata['Product'].should have_key 'ID'
+ @service.class_metadata['Product'].should have_key 'Name'
+ @service.class_metadata['Product'].should have_key 'Description'
+ end
+ it "should have a PropertyMetadata object for each property class_metadata hash" do
+ @service =OData::Service.new "http://test.com/test.svc/"
+ @service.class_metadata['Product']['ID'].should be_a OData::PropertyMetadata
+ @service.class_metadata['Product']['Name'].should be_a OData::PropertyMetadata
+ @service.class_metadata['Product']['Description'].should be_a OData::PropertyMetadata
+ end
+ it "should have the correct PropertyMetadata object for Id" do
+ @service =OData::Service.new "http://test.com/test.svc/"
+ meta = @service.class_metadata['Product']['ID']
+ meta.name.should eq 'ID'
+ meta.type.should eq 'Edm.Int32'
+ meta.nullable.should eq false
+ meta.fc_target_path.should be_nil
+ meta.fc_keep_in_content.should be_nil
+ end
+ it "should have the correct PropertyMetadata object for Name" do
+ @service =OData::Service.new "http://test.com/test.svc/"
+ meta = @service.class_metadata['Product']['Name']
+ meta.name.should eq 'Name'
+ meta.type.should eq 'Edm.String'
+ meta.nullable.should eq true
+ meta.fc_target_path.should eq "SyndicationTitle"
+ meta.fc_keep_in_content.should eq false
+ end
+ it "should have the correct PropertyMetadata object for Description" do
+ @service =OData::Service.new "http://test.com/test.svc/"
+ meta = @service.class_metadata['Product']['Description']
+ meta.name.should eq 'Description'
+ meta.type.should eq 'Edm.String'
+ meta.nullable.should eq true
+ meta.fc_target_path.should eq "SyndicationSummary"
+ meta.fc_keep_in_content.should eq false
+ end
+ end
+
+ describe "single class" do
+ it "should handle properties where a property is represented in the syndication title instead of the properties collection" do
+ @service =OData::Service.new "http://test.com/test.svc/"
+ @service.Products
+ results = @service.execute
+ results.first.Name.should eq "Bread"
+ end
+ it "should handle properties where a property is represented in the syndication summary instead of the properties collection" do
+ @service =OData::Service.new "http://test.com/test.svc/"
+ @service.Products
+ results = @service.execute
+ results.first.Description.should eq "Whole grain bread"
+ end
+ end
+
+ describe "expanded inline class" do
+ it "should handle properties where a property is represented in the syndication title instead of the properties collection" do
+ @service =OData::Service.new "http://test.com/test.svc/"
+ @service.Categories
+ results = @service.execute
+
+ beverages = results[1]
+
+ milk = beverages.Products.first
+ milk.Name.should eq "Milk"
+ milk.Description.should eq "Low fat milk"
+
+ lemonade = beverages.Products.last
+ lemonade.Name.should eq "Pink Lemonade"
+ lemonade.Description.should eq "36 Ounce Cans (Pack of 3)"
+ end
+ end
+ end
+
+ describe "handling inline collections/properties" do
+ it "should make plural named properties arrays and not a single class" do
+ @service =OData::Service.new "http://test.com/test.svc/"
+ @service.Categories
+ results = @service.execute
+ food = results[0]
+
+ food.Products.should be_an Array
+ end
+
+ it "should not make an array if the navigation property name is singular" do
+ @service =OData::Service.new "http://test.com/test.svc/"
+ @service.Products
+ results = @service.execute
+ product = results.first
+ product.Category.should_not be_an Array
+ end
+ end
+
+ describe "navigation properties" do
+ it "should fill in PropertyMetadata for navigation properties" do
+ @service =OData::Service.new "http://test.com/test.svc/"
+ @service.class_metadata['Product'].should have_key 'Category'
+ end
+ end
+ end
+
+ describe "single layer inheritance" do
+ before(:each) do
+ # Metadata
+ stub_request(:get, "http://test.com/test.svc/$metadata").
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("inheritance/edmx_pluralsight.xml"), :headers => {})
+
+ # Content - Courses
+ stub_request(:get, /http:\/\/test\.com\/test\.svc\/Courses(?:.*)/).
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("inheritance/result_pluralsight_courses.xml"), :headers => {})
+ end
+
+ it "should build all inherited attributes" do
+ @service = OData::Service.new "http://test.com/test.svc/"
+ methods = Course.instance_methods.reject {|m| Object.methods.index(m)}
+
+ # Ruby 1.9 uses symbols, and 1.8 uses strings, so this normalizes the data
+ methods.map! {|m| m.to_sym}
+
+ methods.should include(:Title)
+ methods.should include(:Description)
+ methods.should include(:VideoLength)
+ methods.should include(:Category)
+
+ methods.should include(:Title=)
+ methods.should include(:Description=)
+ methods.should include(:VideoLength=)
+ methods.should include(:Category=)
+ end
+
+ it "should not build abstract classes" do
+ @service = OData::Service.new "http://test.com/test.svc/"
+ defined?(ModelItemBase).should eq nil
+ end
+
+ it "should fill inherited properties" do
+ @service =OData::Service.new "http://test.com/test.svc/"
+ @service.Courses
+ courses = @service.execute
+ course = courses.first
+ course.Title.should_not be_nil
+ course.Description.should_not be_nil
+ course.VideoLength.should_not be_nil
+ course.Category.should_not be_nil
+ end
+ end
+
+ describe "handling partial collections" do
+ before(:each) do
+ # Metadata
+ stub_request(:get, "http://test.com/test.svc/$metadata").
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("partial/partial_feed_metadata.xml"), :headers => {})
+
+ # Content - Partial
+ stub_request(:get, "http://test.com/test.svc/Partials").
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("partial/partial_feed_part_1.xml"), :headers => {})
+
+ stub_request(:get, "http://test.com/test.svc/Partials?$skiptoken='ERNSH'").
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("partial/partial_feed_part_2.xml"), :headers => {})
+
+ stub_request(:get, "http://test.com/test.svc/Partials?$skiptoken='ERNSH2'").
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("partial/partial_feed_part_3.xml"), :headers => {})
+
+ end
+
+ it "should return the whole collection by default" do
+ @service =OData::Service.new "http://test.com/test.svc/"
+ @service.Partials
+ results = @service.execute
+ results.count.should eq 3
+ end
+
+ it "should return only the partial when specified by options" do
+ @service =OData::Service.new("http://test.com/test.svc/", :eager_partial => false)
+ @service.Partials
+ results = @service.execute
+ results.count.should eq 1
+ @service.should be_partial
+ while @service.partial?
+ results.concat @service.next
+ end
+ results.count.should eq 3
+ end
+
+ context "with additional_params" do
+ before(:each) do
+ stub_request(:get, "http://test.com/test.svc/$metadata?extra_param=value").
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("partial/partial_feed_metadata.xml"), :headers => {})
+
+ stub_request(:get, "http://test.com/test.svc/Partials?extra_param=value").
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("partial/partial_feed_part_1.xml"), :headers => {})
+
+ stub_request(:get, "http://test.com/test.svc/Partials?$skiptoken='ERNSH'&extra_param=value").
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("partial/partial_feed_part_2.xml"), :headers => {})
+ end
+
+ it "should persist the additional parameters for the next call" do
+ @service =OData::Service.new("http://test.com/test.svc/", :eager_partial => false, :additional_params => { :extra_param => 'value' })
+ @service.Partials
+ @service.execute
+ @service.next
+
+ a_request(:get, "http://test.com/test.svc/Partials?$skiptoken='ERNSH'&extra_param=value").should have_been_made
+ end
+ end
+ end
+
+ describe "link queries" do
+ before(:each) do
+ # Required for the build_classes method
+ stub_request(:get, /http:\/\/test\.com\/test\.svc\/\$metadata(?:\?.+)?/).
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("sample_service/edmx_categories_products.xml"), :headers => {})
+
+ stub_request(:get, "http://test.com/test.svc/Categories(1)/$links/Products").
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("links/result_links_query.xml"), :headers => {})
+ end
+ it "should be able to parse the results of a links query" do
+ @service =OData::Service.new "http://test.com/test.svc/"
+ @service.Categories(1).links('Products')
+ results = @service.execute
+ results.count.should eq 3
+ results.first.should be_a_kind_of(URI)
+ results[0].path.should eq "/SampleService/RubyOData.svc/Products(1)"
+ results[1].path.should eq "/SampleService/RubyOData.svc/Products(2)"
+ results[2].path.should eq "/SampleService/RubyOData.svc/Products(3)"
+ end
+ end
+
+
+
+
+ describe "handling of nested expands" do
+ before(:each) do
+ stub_request(:get, "http://test.com/test.svc/$metadata").
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("nested_expands/edmx_northwind.xml"), :headers => {})
+
+ stub_request(:get, "http://test.com/test.svc/Products?$expand=Category,Category/Products&$top=2").
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("nested_expands/northwind_products_category_expands.xml"), :headers => {})
+ end
+ after(:each) do
+ #Object.send(:remove_const, 'Product') if Object.const_defined? 'Product'
+ end
+
+ it "should successfully parse the results" do
+ @service =OData::Service.new "http://test.com/test.svc", { :namespace => "NW" }
+ @service.Products.expand('Category').expand('Category/Products').top(2)
+ lambda { @service.execute }.should_not raise_exception
+ end
+
+ it "should successfully parse a Category as a Category" do
+ @service =OData::Service.new "http://test.com/test.svc", { :namespace => "NW" }
+ @service.Products.expand('Category').expand('Category/Products').top(2)
+ products = @service.execute
+ products.first.Category.should be_a_kind_of(NW::Category)
+ end
+
+ it "should successfully parse the Category properties" do
+ @service =OData::Service.new "http://test.com/test.svc", { :namespace => "NW" }
+ @service.Products.expand('Category').expand('Category/Products').top(2)
+ products = @service.execute
+ products.first.Category.CategoryID.should eq 1
+ end
+
+ it "should successfully parse the Category children Products" do
+ @service =OData::Service.new "http://test.com/test.svc", { :namespace => "NW" }
+ @service.Products.expand('Category').expand('Category/Products').top(2)
+ products = @service.execute
+ products.first.Category.Products.length.should eq 12
+ end
+
+ it "should successfully parse the Category's child Product properties" do
+ @service =OData::Service.new "http://test.com/test.svc", { :namespace => "NW" }
+ @service.Products.expand('Category').expand('Category/Products').top(2)
+ products = @service.execute
+ products.first.Category.Products.first.ProductName.should eq "Chai"
+ end
+ end
+
+ describe "handling of custom select queries" do
+
+ context "when results are found" do
+ before(:each) do
+ stub_request(:get, "http://test.com/test.svc/$metadata").
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("/sample_service/edmx_categories_products.xml"), :headers => {})
+
+ stub_request(:get, "http://test.com/test.svc/Products?$select=Name,Price").
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("/sample_service/result_select_products_name_price.xml"), :headers => {})
+ end
+
+ before(:each) do
+ @service =OData::Service.new "http://test.com/test.svc/"
+ @service.Products.select "Name", "Price"
+ @result = @service.execute
+ end
+
+ it "returns an Array og Products" do
+ expect(@result).to be_an Array
+ expect(@result).not_to be_empty
+ expect(@result.first).to be_a Product
+ end
+ end
+
+ context "when there isn't a property by the name specified" do
+ before(:each) do
+ stub_request(:get, "http://test.com/test.svc/$metadata").
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("sample_service/edmx_categories_products.xml"), :headers => {})
+
+ stub_request(:get, "http://test.com/test.svc/Categories?$select=Price").
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 400, :body => Fixtures.load("sample_service/result_select_categories_no_property.xml"), :headers => {})
+ end
+
+ it "raises an exception" do
+ @service =OData::Service.new "http://test.com/test.svc/"
+ @service.Categories.select "Price"
+ expect { @service.execute }.to raise_error(OData::ServiceError) { |error|
+ error.http_code.should eq 400
+ error.message.should eq "Type 'RubyODataService.Category' does not have a property named 'Price' or there is no type with 'Price' name."
+ }
+ end
+ end
+
+ context "when a property requires $expand to traverse", focus: true do
+ before(:each) do
+ stub_request(:get, "http://test.com/test.svc/$metadata").
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("sample_service/edmx_categories_products.xml"), :headers => {})
+
+ stub_request(:get, "http://test.com/test.svc/Categories?$select=Name,Products/Name").
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 400, :body => Fixtures.load("sample_service/result_select_categories_travsing_no_expand.xml"), :headers => {})
+
+ stub_request(:get, "http://test.com/test.svc/Categories?$select=Name,Products/Name&$expand=Products").
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("sample_service/result_select_categories_expand.xml"), :headers => {})
+
+ stub_request(:get, "http://test.com/test.svc/Categories").
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("sample_service/result_select_categories_expand.xml"), :headers => {})
+
+ end
+
+ it "retursn Categoris" do
+ @service =OData::Service.new "http://test.com/test.svc/"
+ @service.Categories
+ c = @service.execute
+ end
+
+ it "doesn't error" do
+ @service =OData::Service.new "http://test.com/test.svc/"
+ @service.Categories.select "Name", "Products/Name"
+ expect { @service.execute }.to_not raise_error
+ end
+
+ it "returns the classes with the properties filled in" do
+ @service =OData::Service.new "http://test.com/test.svc/"
+ @service.Categories.select "Name", "Products/Name"
+ results = @service.execute
+ category = results.first
+ category.Name.should eq "Category 0001"
+ product = category.Products.first
+ product.Name.should eq "Widget 0001"
+ end
+ end
+ end
+ end
+
+end
diff --git a/spec/service_spec.rb b/spec/service_spec.rb
index 42d6790..5c39e14 100644
--- a/spec/service_spec.rb
+++ b/spec/service_spec.rb
@@ -1,1079 +1,275 @@
require 'spec_helper'
+require 'base64'
module OData
- describe Service do
- describe "#initialize" do
- it "truncates passed in end slash from uri when making the request" do
- # Required for the build_classes method
- stub_request(:get, "http://test.com/test.svc/$metadata").
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/edmx_empty.xml", __FILE__)), :headers => {})
-
- svc = OData::Service.new "http://test.com/test.svc/"
- end
- it "doesn't error with lowercase entities" do
- # Required for the build_classes method
- stub_request(:get, "http://test.com/test.svc/$metadata").
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/edmx_lowercase.xml", __FILE__)), :headers => {})
-
- lambda { OData::Service.new "http://test.com/test.svc" }.should_not raise_error
- end
-
- describe "additional query string parameters" do
- before(:each) do
- # Required for the build_classes method
- stub_request(:get, /http:\/\/test\.com\/test\.svc\/\$metadata(?:\?.+)?/).
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/edmx_empty.xml", __FILE__)), :headers => {})
- end
- it "should accept additional query string parameters" do
- svc = OData::Service.new "http://test.com/test.svc/", { :additional_params => { :x=>1, :y=>2 } }
- svc.options[:additional_params].should eq Hash[:x=>1, :y=>2]
- end
- it "should call the correct metadata uri when additional_params are passed in" do
- svc = OData::Service.new "http://test.com/test.svc/", { :additional_params => { :x => 1, :y => 2 } }
- a_request(:get, "http://test.com/test.svc/$metadata?x=1&y=2").should have_been_made
- end
- end
-
- describe "rest-client options" do
- before(:each) do
- # Required for the build_classes method
- stub_request(:get, /http:\/\/test\.com\/test\.svc\/\$metadata(?:\?.+)?/).
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/edmx_empty.xml", __FILE__)), :headers => {})
- end
- it "should accept in options that will be passed to the rest-client lib" do
- svc = OData::Service.new "http://test.com/test.svc/", { :rest_options => { :ssl_ca_file => "ca_certificate.pem" } }
- svc.options[:rest_options].should eq Hash[:ssl_ca_file => "ca_certificate.pem"]
- end
- it "should merge the rest options with the built in options" do
- svc = OData::Service.new "http://test.com/test.svc/", { :rest_options => { :ssl_ca_file => "ca_certificate.pem" } }
- svc.instance_variable_get(:@rest_options).should eq Hash[:verify_ssl => 1, :user => nil, :password => nil, :ssl_ca_file => "ca_certificate.pem"]
- end
- end
- end
- describe "additional query string parameters" do
- before(:each) do
- # Required for the build_classes method
- stub_request(:any, /http:\/\/test\.com\/test\.svc(?:.*)/).
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/sap/edmx_sap_demo_flight.xml", __FILE__)), :headers => {})
- end
- it "should pass the parameters as part of a query" do
- svc = OData::Service.new "http://test.com/test.svc/", { :additional_params => { :x=>1, :y=>2 } }
- svc.flight_dataCollection
- svc.execute
- a_request(:get, "http://test.com/test.svc/flight_dataCollection?x=1&y=2").should have_been_made
- end
- it "should pass the parameters as part of a save" do
- svc = OData::Service.new "http://test.com/test.svc/", { :additional_params => { :x=>1, :y=>2 } }
- new_flight = ZDemoFlight.new
- svc.AddToflight_dataCollection(new_flight)
- svc.save_changes
- a_request(:post, "http://test.com/test.svc/flight_dataCollection?x=1&y=2").should have_been_made
- end
- it "should pass the parameters as part of an update" do
- svc = OData::Service.new "http://test.com/test.svc/", { :additional_params => { :x=>1, :y=>2 } }
- existing_flight = ZDemoFlight.new
- existing_flight.__metadata = { :uri => "http://test.com/test.svc/flight_dataCollection/1" }
- svc.update_object(existing_flight)
- svc.save_changes
- a_request(:put, "http://test.com/test.svc/flight_dataCollection/1?x=1&y=2").should have_been_made
- end
- it "should pass the parameters as part of a delete" do
- svc = OData::Service.new "http://test.com/test.svc/", { :additional_params => { :x=>1, :y=>2 } }
- existing_flight = ZDemoFlight.new
- existing_flight.__metadata = { :uri => "http://test.com/test.svc/flight_dataCollection/1" }
- svc.delete_object(existing_flight)
- svc.save_changes
- a_request(:delete, "http://test.com/test.svc/flight_dataCollection/1?x=1&y=2").should have_been_made
- end
- it "should pass the parameters as part of a batch save" do
- svc = OData::Service.new "http://test.com/test.svc/", { :additional_params => { :x=>1, :y=>2 } }
- new_flight = ZDemoFlight.new
- svc.AddToflight_dataCollection(new_flight)
- new_flight2 = ZDemoFlight.new
- svc.AddToflight_dataCollection(new_flight2)
- svc.save_changes
- a_request(:post, "http://test.com/test.svc/$batch?x=1&y=2").should have_been_made
- end
- it "should pass the parameters as part of an add link" do
- svc = OData::Service.new "http://test.com/test.svc/", { :additional_params => { :x=>1, :y=>2 } }
- existing_flight1 = ZDemoFlight.new
- existing_flight1.__metadata = { :uri => "http://test.com/test.svc/flight_dataCollection/1" }
- existing_flight2 = ZDemoFlight.new
- existing_flight2.__metadata = { :uri => "http://test.com/test.svc/flight_dataCollection/2" }
- svc.add_link(existing_flight1, "flight_data_r", existing_flight2)
- svc.save_changes
- a_request(:post, "http://test.com/test.svc/flight_dataCollection/1/$links/flight_data_r?x=1&y=2").should have_been_made
- end
- it "should pass the parameters as part of a function import with a parameter" do
- svc = OData::Service.new "http://test.com/test.svc/", { :additional_params => { :x=>1, :y=>2 } }
- svc.get_flight(1)
- a_request(:get, "http://test.com/test.svc/get_flight?id=1&x=1&y=2").should have_been_made
- end
- it "should pass the parameters as part of a function import without parameters" do
- svc = OData::Service.new "http://test.com/test.svc/", { :additional_params => { :x=>1, :y=>2 } }
- svc.get_top_flight
- a_request(:get, "http://test.com/test.svc/get_top_flight?x=1&y=2").should have_been_made
- end
- end
-
- describe "exception handling" do
- before(:each) do
- stub_request(:get, "http://test.com/test.svc/$metadata").
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/sample_service/edmx_categories_products.xml", __FILE__)), :headers => {})
- stub_request(:get, "http://test.com/test.svc/Categories?$select=Price").
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 400, :body => File.new(File.expand_path("../fixtures/error_without_message.xml", __FILE__)), :headers => {})
- end
-
- it "includes a generic message if the error is not in the response" do
- svc = OData::Service.new "http://test.com/test.svc/"
- svc.Categories.select "Price"
- expect { svc.execute }.to raise_error(OData::ServiceError) { |error|
- error.http_code.should eq 400
- error.message.should eq "Server returned error but no message."
- }
- end
- end
-
- describe "lowercase collections" do
- before(:each) do
- # Required for the build_classes method
- stub_request(:get, "http://test.com/test.svc/$metadata").
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/edmx_lowercase.xml", __FILE__)), :headers => {})
- end
-
- it "should respond_to a lowercase collection" do
- svc = OData::Service.new "http://test.com/test.svc"
- expect(svc.respond_to?('acronyms')).to eq true
- end
+ describe Service do
+ before(:each) do
+ stub_request(:get, "http://test.com/test.svc/$metadata").
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("/sample_service/edmx_categories_products.xml"), :headers => {})
- it "should allow a lowercase collections to be queried" do
- svc = OData::Service.new "http://test.com/test.svc"
- lambda { svc.send('acronyms') }.should_not raise_error
- end
+ @service = OData::Service.new "http://test.com/test.svc"
end
- describe "handling of SAP results" do
- before(:each) do
- # Required for the build_classes method
- stub_request(:get, "http://test.com/test.svc/$metadata").
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/sap/edmx_sap_demo_flight.xml", __FILE__)), :headers => {})
-
- stub_request(:get, "http://test.com/test.svc/z_demo_flightCollection").
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/sap/result_sap_demo_flight_missing_category.xml", __FILE__)), :headers => {})
- end
-
- it "should handle entities without a category element" do
- svc = OData::Service.new "http://test.com/test.svc/"
- svc.z_demo_flightCollection
- results = svc.execute
- results.first.should be_a_kind_of(ZDemoFlight)
- end
+ after(:each) do
+ remove_classes @service
end
- describe "collections, objects, metadata etc" do
- before(:each) do
- # Metadata
- stub_request(:get, "http://test.com/test.svc/$metadata").
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/feed_customization/edmx_feed_customization.xml", __FILE__)), :headers => {})
+ subject { @service }
- # Content - Products
- stub_request(:get, /http:\/\/test\.com\/test\.svc\/Products(?:.*)/).
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/feed_customization/result_feed_customization_products_expand.xml", __FILE__)), :headers => {})
+ context "methods" do
+ it { should respond_to :collections }
+ it { should respond_to :class_metadata }
+ it { should respond_to :function_imports }
+ it { should respond_to :classes }
- # Content - Categories expanded Products
- stub_request(:get, /http:\/\/test\.com\/test\.svc\/Categories(?:.*)/).
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/feed_customization/result_feed_customization_categories_expand.xml", __FILE__)), :headers => {})
- end
- after(:each) do
- Object.send(:remove_const, 'Product')
- Object.send(:remove_const, 'Category')
- end
- describe "handling feed customizations" do
- describe "property metadata" do
- it "should fill the class_metadata hash" do
- svc = OData::Service.new "http://test.com/test.svc/"
- svc.class_metadata.should_not be_empty
- end
- it "should add a key (based on the name) for each property class_metadata hash" do
- svc = OData::Service.new "http://test.com/test.svc/"
- svc.class_metadata['Product'].should have_key 'ID'
- svc.class_metadata['Product'].should have_key 'Name'
- svc.class_metadata['Product'].should have_key 'Description'
- end
- it "should have a PropertyMetadata object for each property class_metadata hash" do
- svc = OData::Service.new "http://test.com/test.svc/"
- svc.class_metadata['Product']['ID'].should be_a OData::PropertyMetadata
- svc.class_metadata['Product']['Name'].should be_a OData::PropertyMetadata
- svc.class_metadata['Product']['Description'].should be_a OData::PropertyMetadata
- end
- it "should have the correct PropertyMetadata object for Id" do
- svc = OData::Service.new "http://test.com/test.svc/"
- meta = svc.class_metadata['Product']['ID']
- meta.name.should eq 'ID'
- meta.type.should eq 'Edm.Int32'
- meta.nullable.should eq false
- meta.fc_target_path.should be_nil
- meta.fc_keep_in_content.should be_nil
- end
- it "should have the correct PropertyMetadata object for Name" do
- svc = OData::Service.new "http://test.com/test.svc/"
- meta = svc.class_metadata['Product']['Name']
- meta.name.should eq 'Name'
- meta.type.should eq 'Edm.String'
- meta.nullable.should eq true
- meta.fc_target_path.should eq "SyndicationTitle"
- meta.fc_keep_in_content.should eq false
- end
- it "should have the correct PropertyMetadata object for Description" do
- svc = OData::Service.new "http://test.com/test.svc/"
- meta = svc.class_metadata['Product']['Description']
- meta.name.should eq 'Description'
- meta.type.should eq 'Edm.String'
- meta.nullable.should eq true
- meta.fc_target_path.should eq "SyndicationSummary"
- meta.fc_keep_in_content.should eq false
- end
- end
-
- describe "single class" do
- it "should handle properties where a property is represented in the syndication title instead of the properties collection" do
- svc = OData::Service.new "http://test.com/test.svc/"
- svc.Products
- results = svc.execute
- results.first.Name.should eq "Bread"
- end
- it "should handle properties where a property is represented in the syndication summary instead of the properties collection" do
- svc = OData::Service.new "http://test.com/test.svc/"
- svc.Products
- results = svc.execute
- results.first.Description.should eq "Whole grain bread"
- end
- end
-
- describe "expanded inline class" do
- it "should handle properties where a property is represented in the syndication title instead of the properties collection" do
- svc = OData::Service.new "http://test.com/test.svc/"
- svc.Categories
- results = svc.execute
-
- beverages = results[1]
-
- milk = beverages.Products.first
- milk.Name.should eq "Milk"
- milk.Description.should eq "Low fat milk"
-
- lemonade = beverages.Products.last
- lemonade.Name.should eq "Pink Lemonade"
- lemonade.Description.should eq "36 Ounce Cans (Pack of 3)"
- end
- end
- end
-
- describe "handling inline collections/properties" do
- it "should make plural named properties arrays and not a single class" do
- svc = OData::Service.new "http://test.com/test.svc/"
- svc.Categories
- results = svc.execute
- food = results[0]
-
- food.Products.should be_an Array
- end
+ it { should respond_to :execute }
- it "should not make an array if the navigation property name is singular" do
- svc = OData::Service.new "http://test.com/test.svc/"
- svc.Products
- results = svc.execute
- product = results.first
- product.Category.should_not be_an Array
- end
- end
+ it { should respond_to :update_object }
+ it { should respond_to :delete_object }
+ it { should respond_to :save_changes }
+ it { should respond_to :load_property }
+ it { should respond_to :add_link }
+ it { should respond_to :partial? }
+ it { should respond_to :next }
+ it { should respond_to :options }
- describe "navigation properties" do
- it "should fill in PropertyMetadata for navigation properties" do
- svc = OData::Service.new "http://test.com/test.svc/"
- svc.class_metadata['Product'].should have_key 'Category'
- end
+ context "after parsing metadata" do
+ it { should respond_to :Products }
+ it { should respond_to :Categories }
+ it { should respond_to :AddToProducts }
+ it { should respond_to :AddToCategories }
end
end
- describe "single layer inheritance" do
- before(:each) do
- # Metadata
- stub_request(:get, "http://test.com/test.svc/$metadata").
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/inheritance/edmx_pluralsight.xml", __FILE__)), :headers => {})
+ context "collections method" do
+ subject { @service.collections }
- # Content - Courses
- stub_request(:get, /http:\/\/test\.com\/test\.svc\/Courses(?:.*)/).
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/inheritance/result_pluralsight_courses.xml", __FILE__)), :headers => {})
+ it { should include 'Products' }
+ it { should include 'Categories' }
+ it "should expose the edmx type of objects" do
+ subject['Products'][:edmx_type].should eq 'RubyODataService.Product'
+ subject['Categories'][:edmx_type].should eq 'RubyODataService.Category'
end
-
- it "should build all inherited attributes" do
- OData::Service.new "http://test.com/test.svc/"
- methods = Course.instance_methods.reject {|m| Object.methods.index(m)}
-
- # Ruby 1.9 uses symbols, and 1.8 uses strings, so this normalizes the data
- methods.map! {|m| m.to_sym}
-
- methods.should include(:Title)
- methods.should include(:Description)
- methods.should include(:VideoLength)
- methods.should include(:Category)
-
- methods.should include(:Title=)
- methods.should include(:Description=)
- methods.should include(:VideoLength=)
- methods.should include(:Category=)
- end
-
- it "should not build abstract classes" do
- OData::Service.new "http://test.com/test.svc/"
- defined?(ModelItemBase).should eq nil
- end
-
- it "should fill inherited properties" do
- svc = OData::Service.new "http://test.com/test.svc/"
- svc.Courses
- courses = svc.execute
- course = courses.first
- course.Title.should_not be_nil
- course.Description.should_not be_nil
- course.VideoLength.should_not be_nil
- course.Category.should_not be_nil
+ it "should expose the local model type" do
+ subject['Products'][:type].should eq Product
+ subject['Categories'][:type].should eq Category
end
end
- describe "handling partial collections" do
- before(:each) do
- # Metadata
- stub_request(:get, "http://test.com/test.svc/$metadata").
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/partial/partial_feed_metadata.xml", __FILE__)), :headers => {})
-
- # Content - Partial
- stub_request(:get, "http://test.com/test.svc/Partials").
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/partial/partial_feed_part_1.xml", __FILE__)), :headers => {})
-
- stub_request(:get, "http://test.com/test.svc/Partials?$skiptoken='ERNSH'").
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/partial/partial_feed_part_2.xml", __FILE__)), :headers => {})
-
- stub_request(:get, "http://test.com/test.svc/Partials?$skiptoken='ERNSH2'").
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/partial/partial_feed_part_3.xml", __FILE__)), :headers => {})
-
- end
-
- it "should return the whole collection by default" do
- svc = OData::Service.new "http://test.com/test.svc/"
- svc.Partials
- results = svc.execute
- results.count.should eq 3
- end
-
- it "should return only the partial when specified by options" do
- svc = OData::Service.new("http://test.com/test.svc/", :eager_partial => false)
- svc.Partials
- results = svc.execute
- results.count.should eq 1
- svc.should be_partial
- while svc.partial?
- results.concat svc.next
- end
- results.count.should eq 3
- end
-
- context "with additional_params" do
- before(:each) do
- stub_request(:get, "http://test.com/test.svc/$metadata?extra_param=value").
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/partial/partial_feed_metadata.xml", __FILE__)), :headers => {})
-
- stub_request(:get, "http://test.com/test.svc/Partials?extra_param=value").
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/partial/partial_feed_part_1.xml", __FILE__)), :headers => {})
-
- stub_request(:get, "http://test.com/test.svc/Partials?$skiptoken='ERNSH'&extra_param=value").
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/partial/partial_feed_part_2.xml", __FILE__)), :headers => {})
- end
-
- it "should persist the additional parameters for the next call" do
- svc = OData::Service.new("http://test.com/test.svc/", :eager_partial => false, :additional_params => { :extra_param => 'value' })
- svc.Partials
- svc.execute
- svc.next
-
- a_request(:get, "http://test.com/test.svc/Partials?$skiptoken='ERNSH'&extra_param=value").should have_been_made
+ context "class metadata" do
+ subject { @service.class_metadata }
+
+ it { should_not be_empty}
+ it { should have_key 'Product' }
+ it { should have_key 'Category' }
+
+ context "should have keys for each property" do
+ subject { @service.class_metadata['Category'] }
+ it { should have_key 'Id' }
+ it { should have_key 'Name' }
+ it { should have_key 'Products' }
+ it "should return a PropertyMetadata object for each property" do
+ subject['Id'].should be_a PropertyMetadata
+ subject['Name'].should be_a PropertyMetadata
+ subject['Products'].should be_a PropertyMetadata
+ end
+ it "should have correct PropertyMetadata for Category.Id" do
+ meta = subject['Id']
+ meta.name.should eq 'Id'
+ meta.type.should eq 'Edm.Int32'
+ meta.nullable.should eq false
+ meta.fc_target_path.should be_nil
+ meta.fc_keep_in_content.should be_nil
+ meta.nav_prop.should eq false
+ meta.is_key.should eq true
+ end
+ it "should have correct PropertyMetadata for Category.Name" do
+ meta = subject['Name']
+ meta.name.should eq 'Name'
+ meta.type.should eq 'Edm.String'
+ meta.nullable.should eq false
+ meta.fc_target_path.should be_nil
+ meta.fc_keep_in_content.should be_nil
+ meta.nav_prop.should eq false
+ meta.is_key.should eq false
+ end
+ it "should have correct PropertyMetadata for Category.Products" do
+ meta = subject['Products']
+ meta.name.should eq 'Products'
+ meta.type.should be_nil
+ meta.nullable.should eq true
+ meta.fc_target_path.should be_nil
+ meta.fc_keep_in_content.should be_nil
+ meta.nav_prop.should eq true
+ meta.association.should_not be_nil
+ meta.is_key.should eq false
end
end
end
- describe "link queries" do
- before(:each) do
- # Required for the build_classes method
- stub_request(:get, /http:\/\/test\.com\/test\.svc\/\$metadata(?:\?.+)?/).
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/sample_service/edmx_categories_products.xml", __FILE__)), :headers => {})
-
- stub_request(:get, "http://test.com/test.svc/Categories(1)/$links/Products").
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/links/result_links_query.xml", __FILE__)), :headers => {})
- end
- it "should be able to parse the results of a links query" do
- svc = OData::Service.new "http://test.com/test.svc/"
- svc.Categories(1).links('Products')
- results = svc.execute
- results.count.should eq 3
- results.first.should be_a_kind_of(URI)
- results[0].path.should eq "/SampleService/RubyOData.svc/Products(1)"
- results[1].path.should eq "/SampleService/RubyOData.svc/Products(2)"
- results[2].path.should eq "/SampleService/RubyOData.svc/Products(3)"
- end
- end
-
- describe "sample service" do
- before(:each) do
- # Required for the build_classes method
- stub_request(:get, /http:\/\/test\.com\/test\.svc\/\$metadata(?:\?.+)?/).
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/sample_service/edmx_categories_products.xml", __FILE__)), :headers => {})
-
- stub_request(:get, /http:\/\/test\.com\/test\.svc\/Products\(\d\)/).
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/sample_service/result_single_product.xml", __FILE__)), :headers => {})
-
- stub_request(:get, /http:\/\/test\.com\/test\.svc\/Products\(\d{2,}\)/).
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/sample_service/result_single_product_not_found.xml", __FILE__)), :headers => {})
-
- stub_request(:get, "http://test.com/test.svc/Products(1)/Category").
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/sample_service/result_single_category.xml", __FILE__)), :headers => {})
-
- stub_request(:get, "http://test.com/test.svc/Categories(1)").
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/sample_service/result_single_category.xml", __FILE__)), :headers => {})
-
- stub_request(:get, "http://test.com/test.svc/Categories(1)/Products").
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/sample_service/result_multiple_category_products.xml", __FILE__)), :headers => {})
-
- stub_request(:post, "http://test.com/test.svc/Categories(1)/$links/Products").to_return(:status => 204)
- stub_request(:post, "http://test.com/test.svc/$batch").to_return(:status => 202)
- end
-
- describe "lazy loading" do
- after(:each) do
- Object.send(:remove_const, 'Product') if Object.const_defined? 'Product'
- Object.send(:remove_const, 'Category') if Object.const_defined? 'Category'
- end
-
- it "should have a load property method" do
- svc = OData::Service.new "http://test.com/test.svc/"
- svc.should respond_to(:load_property)
- end
-
- it "should throw an exception if the object isn't tracked" do
- svc = OData::Service.new "http://test.com/test.svc/"
- new_object = Product.new
- lambda { svc.load_property(new_object, "Category") }.should raise_error(NotSupportedError, "You cannot load a property on an entity that isn't tracked")
- end
-
- it "should throw an exception if there isn't a method matching the navigation property passed in" do
- svc = OData::Service.new "http://test.com/test.svc/"
- svc.Products(1)
- product = svc.execute.first
- lambda { svc.load_property(product, "NoMatchingMethod") }.should raise_error(ArgumentError, "'NoMatchingMethod' is not a valid navigation property")
- end
-
- it "should throw an exception if the method passed in is a standard property (non-navigation)" do
- svc = OData::Service.new "http://test.com/test.svc/"
- svc.Products(1)
- product = svc.execute.first
- lambda { svc.load_property(product, "Name") }.should raise_error(ArgumentError, "'Name' is not a valid navigation property")
- end
-
- it "should fill a single navigation property" do
- svc = OData::Service.new "http://test.com/test.svc/"
- svc.Products(1)
- product = svc.execute.first
- svc.load_property(product, "Category")
- product.Category.should_not be_nil
- product.Category.Id.should eq 1
- product.Category.Name.should eq 'Category 1'
- end
-
- it "should fill a collection navigation property" do
- svc = OData::Service.new "http://test.com/test.svc/"
- svc.Categories(1)
- category = svc.execute.first
- svc.load_property(category, "Products")
- category.Products.first.should be_a Product
- category.Products[0].Id.should eq 1
- category.Products[1].Id.should eq 2
- end
-
- it "should not mutate the object's metadata" do
- svc = OData::Service.new "http://test.com/test.svc/"
- svc.Products(1)
- product = svc.execute.first
- original_metadata = product.__metadata.to_json
- svc.load_property(product, "Category")
- product.__metadata.to_json.should == original_metadata
- end
- end
-
- describe "find, create, add, update, and delete" do
- after(:each) do
- Object.send(:remove_const, 'Product') if Object.const_defined? 'Product'
- Object.send(:remove_const, 'Category') if Object.const_defined? 'Category'
- end
-
- it "should implement an AddTo method for collection" do
- svc = OData::Service.new "http://test.com/test.svc/"
- svc.should respond_to :AddToCategories
- svc.should respond_to :AddToProducts
- end
-
- it "should create objects with an initialize method that can build the object from a hash" do
- svc = OData::Service.new "http://test.com/test.svc/"
- product = Product.new 'Id' => 1000, 'Name' => 'New Product'
- product.Id.should eq 1000
- product.Name.should eq 'New Product'
- end
-
- it "should create objects that rejects keys that don't have corresponding methods" do
- svc = OData::Service.new "http://test.com/test.svc/"
- lambda { Product.new 'NotAProperty' => true }.should raise_error NoMethodError
- end
-
- it "should create objects that expose a properties class method that lists the properties for the object" do
- svc = OData::Service.new "http://test.com/test.svc/"
- Product.properties.should include 'Id'
- Product.properties.should include 'Name'
- Product.properties.should include 'Category'
- end
-
- it "should have full metadata for a property returned from the properties method" do
- svc = OData::Service.new "http://test.com/test.svc/"
- Product.properties['Category'].should be_a PropertyMetadata
- expect(Product.properties['Category'].nav_prop).to eq true
- end
-
- it "should create objects that expose an id property" do
- svc = OData::Service.new "http://test.com/test.svc/"
- svc.Products(1)
- product = svc.execute.first
- expect(product).to respond_to :id
- end
-
- it "should extract the id from the metadata" do
- svc = OData::Service.new "http://test.com/test.svc/"
- svc.Products(1)
- product = svc.execute.first
- expect(product.id).to eq 1
- end
-
- describe "Class.first method" do
- it "should exist on the create server objects" do
- svc = OData::Service.new "http://test.com/test.svc/"
- expect(Product).to respond_to :first
- end
- it "should return nil if an id isn't passed in" do
- svc = OData::Service.new "http://test.com/test.svc/"
- product = Product.first(nil)
- product.should be_nil
- end
- it "should return nil if an id isn't found" do
- svc = OData::Service.new "http://test.com/test.svc/"
- product = Product.first(1234567890)
- product.should be_nil
- end
- it "should return a product if an id is found" do
- svc = OData::Service.new "http://test.com/test.svc/"
- product = Product.first(1)
- product.should_not be_nil
- end
- end
- end
-
- describe "namespaces" do
- after(:each) do
- VisoftInc::Sample::Models.send(:remove_const, 'Product') if VisoftInc::Sample::Models.const_defined? 'Product'
- VisoftInc::Sample::Models.send(:remove_const, 'Category') if VisoftInc::Sample::Models.const_defined? 'Category'
-
- VisoftInc::Sample.send(:remove_const, 'Models') if VisoftInc::Sample.const_defined? 'Models'
- VisoftInc.send(:remove_const, 'Sample') if VisoftInc.const_defined? 'Sample'
- Object.send(:remove_const, 'VisoftInc') if Object.const_defined? 'VisoftInc'
- end
-
- it "should create models in the specified namespace if the option is set (using a .NET style namespace with dots)" do
- svc = OData::Service.new "http://test.com/test.svc/", { :namespace => 'VisoftInc.Sample.Models' }
- expect(defined?(VisoftInc::Sample::Models::Product).nil?).to eq false
- expect(defined?(VisoftInc::Sample::Models::Category).nil?).to eq false
- end
-
- it "should create models in the specified namespace if the option is set (using Ruby style namespaces with double colons)" do
- svc = OData::Service.new "http://test.com/test.svc/", { :namespace => 'VisoftInc::Sample::Models' }
- defined?(VisoftInc::Sample::Models::Product).nil?.should eq false
- defined?(VisoftInc::Sample::Models::Category).nil?.should eq false
- end
-
- it "should fill object defined in a namespace" do
- svc = OData::Service.new "http://test.com/test.svc/", { :namespace => 'VisoftInc::Sample::Models' }
- svc.Categories(1)
- categories = svc.execute
- categories.should_not be_nil
- category = categories.first
- category.Id.should eq 1
- category.Name.should eq 'Category 1'
- end
-
- it "should fill the class_metadata hash" do
- svc = OData::Service.new "http://test.com/test.svc/", { :namespace => 'VisoftInc::Sample::Models' }
- svc.class_metadata.should_not be_empty
- end
-
- it "should add a key (based on the name) for each property class_metadata hash" do
- svc = OData::Service.new "http://test.com/test.svc/", { :namespace => 'VisoftInc::Sample::Models' }
- svc.class_metadata['VisoftInc::Sample::Models::Product'].should have_key 'Id'
- svc.class_metadata['VisoftInc::Sample::Models::Product'].should have_key 'Name'
- svc.class_metadata['VisoftInc::Sample::Models::Product'].should have_key 'Description'
- end
-
- it "should lazy load objects defined in a namespace" do
- svc = OData::Service.new "http://test.com/test.svc/", { :namespace => 'VisoftInc::Sample::Models' }
- svc.Categories(1)
- category = svc.execute.first
- svc.load_property category, 'Products'
- category.Products.should_not be_nil
- category.Products.first.Id.should eq 1
- category.Products.first.Name.should eq 'Widget 1'
- end
- end
-
- describe "add_link method" do
- it "should exist as a method on the service" do
- svc = OData::Service.new "http://test.com/test.svc/"
- svc.should respond_to(:add_link)
- end
+ context "function_imports method" do
+ subject { @service.function_imports }
+
+ it { should_not be_empty}
+ it { should have_key 'CleanDatabaseForTesting' }
+ it { should have_key 'EntityCategoryWebGet' }
+ it { should have_key 'EntitySingleCategoryWebGet' }
+ it "should expose the http method" do
+ subject['CleanDatabaseForTesting'][:http_method].should eq 'POST'
+ subject['EntityCategoryWebGet'][:http_method].should eq 'GET'
+ subject['EntitySingleCategoryWebGet'][:http_method].should eq 'GET'
+ end
+ it "should expose the return type" do
+ subject['CleanDatabaseForTesting'][:return_type].should be_nil
+ subject['EntityCategoryWebGet'][:return_type].should eq Array
+ subject['EntityCategoryWebGet'][:inner_return_type].should eq Category
+ subject['EntitySingleCategoryWebGet'][:return_type].should eq Category
+ subject['EntitySingleCategoryWebGet'][:inner_return_type].should be_nil
+ subject['CategoryNames'][:return_type].should eq Array
+ subject['CategoryNames'][:inner_return_type].should eq String
+ end
+ it "should provide a hash of parameters" do
+ subject['EntityCategoryWebGet'][:parameters].should be_nil
+ subject['EntitySingleCategoryWebGet'][:parameters].should be_a Hash
+ subject['EntitySingleCategoryWebGet'][:parameters]['id'].should eq 'Edm.Int32'
+ end
+ context "after parsing function imports" do
+ subject { @service }
+ it { should respond_to :CleanDatabaseForTesting }
+ it { should respond_to :EntityCategoryWebGet }
+ it { should respond_to :EntitySingleCategoryWebGet }
+ it { should respond_to :CategoryNames }
+ end
+ context "error checking" do
+ subject { @service }
+ it "should throw an exception if a parameter is passed in to a method that doesn't require one" do
+ lambda { subject.EntityCategoryWebGet(1) }.should raise_error(ArgumentError, "wrong number of arguments (1 for 0)")
+ end
+ it "should throw and exception if more parameters are passed in than required by the function" do
+ lambda { subject.EntitySingleCategoryWebGet(1,2) }.should raise_error(ArgumentError, "wrong number of arguments (2 for 1)")
+ end
+ end
+ context "url and http method checks" do
+ subject { @service }
+ before { stub_request(:any, /http:\/\/test\.com\/test\.svc\/(.*)/) }
+ it "should call the correct url with the correct http method for a post with no parameters" do
+ subject.CleanDatabaseForTesting
+ a_request(:post, "http://test.com/test.svc/CleanDatabaseForTesting").should have_been_made
+ end
+ it "should call the correct url with the correct http method for a get with no parameters" do
+ subject.EntityCategoryWebGet
+ a_request(:get, "http://test.com/test.svc/EntityCategoryWebGet").should have_been_made
+ end
+ it "should call the correct url with the correct http method for a get with parameters" do
+ subject.EntitySingleCategoryWebGet(1)
+ a_request(:get, "http://test.com/test.svc/EntitySingleCategoryWebGet?id=1").should have_been_made
+ end
+ end
+ context "function import result parsing" do
+ subject { @service }
+ before(:each) do
+ stub_request(:post, "http://test.com/test.svc/CleanDatabaseForTesting").to_return(:status => 204)
- it "shouldn't be allowed if a parent isn't tracked" do
- svc = OData::Service.new "http://test.com/test.svc/"
- category = Category.new :Name => 'New Category'
- property = nil # Not needed for this test
- product = nil # Not needed for this test
- lambda { svc.add_link(category, property, product) }.should raise_error(NotSupportedError, "You cannot add a link on an entity that isn't tracked (Category)")
- end
+ stub_request(:get, "http://test.com/test.svc/EntityCategoryWebGet").
+ to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/sample_service/result_entity_category_web_get.xml", __FILE__)), :headers => {})
- it "shouldn't be allowed if a property isn't found on the parent" do
- svc = OData::Service.new "http://test.com/test.svc/"
- svc.Categories(1)
- category = svc.execute.first
- property = 'NotAProperty'
- product = nil # Not needed for this test
- lambda { svc.add_link(category, property, product) }.should raise_error(ArgumentError, "'NotAProperty' is not a valid navigation property for Category")
- end
+ stub_request(:get, "http://test.com/test.svc/EntitySingleCategoryWebGet?id=1").
+ to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/sample_service/result_entity_single_category_web_get.xml", __FILE__)), :headers => {})
- it "shouldn't be allowed if a property isn't a navigation property on the parent" do
- svc = OData::Service.new "http://test.com/test.svc/"
- svc.Categories(1)
- category = svc.execute.first
- property = 'Name'
- product = nil # Not needed for this test
- lambda { svc.add_link(category, property, product) }.should raise_error(ArgumentError, "'Name' is not a valid navigation property for Category")
- end
+ stub_request(:get, "http://test.com/test.svc/CategoryNames").
+ to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/sample_service/result_category_names.xml", __FILE__)), :headers => {})
- it "shouldn't be allowed if a child isn't tracked" do
- svc = OData::Service.new "http://test.com/test.svc/"
- svc.Categories(1)
- category = svc.execute.first
- property = 'Products'
- product = Product.new :Name => 'Widget 1'
- 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)")
+ stub_request(:get, "http://test.com/test.svc/FirstCategoryId").
+ to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/sample_service/result_first_category_id.xml", __FILE__)), :headers => {})
end
-
- it "should perform a post against the correct URL with the correct body on a single_save" do
- svc = OData::Service.new "http://test.com/test.svc/"
- svc.Categories(1)
- category = svc.execute.first
- svc.Products(1)
- product = svc.execute.first
- property = 'Products'
- svc.add_link(category, property, product)
- svc.save_changes
-
- if RUBY_VERSION.start_with? '2.3'
- a_request(:post, "http://test.com/test.svc/Categories(1)/$links/Products").
- with(:body => '{"uri":"http://test.com/test.svc/Products(1)"}',
- :headers => DEFAULT_HEADERS.merge({'Content-Type' => 'application/json'})).should have_been_made
- else
- a_request(:post, "http://test.com/test.svc/Categories(1)/$links/Products").
- with(:body => '"{\"uri\":\"http://test.com/test.svc/Products(1)\"}"',
- :headers => DEFAULT_HEADERS.merge({'Content-Type' => 'application/json'})).should have_been_made
- end
+ it "should return true if a function import post that returns successfully and doesn't have a return value (HTTP 204)" do
+ result = subject.CleanDatabaseForTesting
+ expect(result).to eq true
end
-
- it "should add the child to the parent's navigation property on a single_save" do
- svc = OData::Service.new "http://test.com/test.svc/"
- svc.Categories(1)
- category = svc.execute.first
- svc.Products(1)
- product = svc.execute.first
- property = 'Products'
- svc.add_link(category, property, product)
- svc.save_changes
- category.Products.should include product
+ it "should return a collection of entities for a collection" do
+ result = subject.EntityCategoryWebGet
+ result.should be_an Enumerable
+ result.first.should be_a Category
+ result.first.Name.should eq "Test Category"
end
-
- it "should add the parent to the child's navigation property on a single_save" do
- svc = OData::Service.new "http://test.com/test.svc/"
- svc.Categories(1)
- category = svc.execute.first
- svc.Products(1)
- product = svc.execute.first
- property = 'Products'
- svc.add_link(category, property, product)
- svc.save_changes
- product.Category.should eq category
+ it "should return a single entity if it isn't a collection" do
+ result = subject.EntitySingleCategoryWebGet(1)
+ result.should be_a Category
+ result.Name.should eq "Test Category"
end
-
- describe "batch_save" do
- before(:each) do
- @svc = OData::Service.new "http://test.com/test.svc/"
- @category = Category.first(1)
- @product = Product.first(1)
- new_category = Category.new
- @svc.AddToCategories(new_category)
- @svc.add_link(@category, 'Products', @product)
- @svc.save_changes
- end
-
- it "should perform a post with the correct URL and body on a batch_save" do
- if RUBY_VERSION.start_with? '2.3'
- WebMock.should have_requested(:post, "http://test.com/test.svc/$batch").with { |request|
- request.body.include? "POST http://test.com/test.svc/Categories(1)/$links/Products HTTP/1.1"
- request.body.include? '{"uri":"http://test.com/test.svc/Products(1)"}'
- }
- else
- WebMock.should have_requested(:post, "http://test.com/test.svc/$batch").with { |request|
- request.body.include? "POST http://test.com/test.svc/Categories(1)/$links/Products HTTP/1.1"
- request.body.include? '{\"uri\":\"http://test.com/test.svc/Products(1)\"}'
- }
- end
- end
- context "child is a part of the parent's collection" do
- subject { @category.Products }
- it { should include @product }
- end
- context "parent object should be filled in on the child" do
- subject { @product.Category }
- it { should eq @category }
- end
+ it "should return a collection of primitive types" do
+ result = subject.CategoryNames
+ result.should be_an Enumerable
+ result.first.should be_a String
+ result.first.should eq "Test Category 1"
end
-
- describe "serializes nested collections" do
- # Compy with oData Deep Insert capabilities
- # http://docs.oasis-open.org/odata/odata-json-format/v4.0/os/odata-json-format-v4.0-os.html#_Toc372793073
-
- before :each do
- category = Category.new
- category.Products = [Product.new(Name: "First"), Product.new(Name: "Last")]
- @json = JSON.parse(category.to_json(type: :add))
- end
-
- it "should have an array for the Products key" do
- @json["Products"].should be_a_kind_of Array
- end
-
- it "should have a hash for each product" do
- @json["Products"].each{|x| x.should be_a_kind_of Hash}
- end
-
- it "should have the same data we put into the products" do
- @json["Products"].first["Name"].should eq "First"
- @json["Products"].last["Name"].should eq "Last"
- end
+ it "should return a single primitive type" do
+ result = subject.FirstCategoryId
+ result.should be_a Integer
+ result.should eq 1
end
end
end
- describe "JSON serialization of objects" do
- let(:username) { "blabla" }
- let(:password) { "" }
+ context "classes method" do
+ subject { @service.classes }
- before(:each) do
- # Required for the build_classes method
- stub_request(:get, "http://test.com/test.svc/$metadata").
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/ms_system_center/edmx_ms_system_center.xml", __FILE__)), :headers => {})
+ it { should include 'EdmMetadata' }
- stub_request(:get, "http://test.com/test.svc/VirtualMachines").
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/ms_system_center/virtual_machines.xml", __FILE__)), :headers => {})
- svc = OData::Service.new "http://test.com/test.svc/", { :username => username, :password => password, :verify_ssl => false, :namespace => "VMM" }
- svc.VirtualMachines
- results = svc.execute
- @json = results.first.as_json
- end
+ it { should include 'AuditFields' } # ComplexType
- it "Should quote Edm.Int64 properties" do
- @json["PerfDiskBytesWrite"].should be_a(String)
- end
+ it { should include 'Product' } # EntityType
+ it { should include 'Category' } # EntityType
- it "Should output collections with metadata" do
- @json["VMNetworkAssignments"].should be_a(Hash)
- @json["VMNetworkAssignments"].should have_key("__metadata")
- @json["VMNetworkAssignments"]["__metadata"].should be_a(Hash)
- @json["VMNetworkAssignments"]["__metadata"].should have_key("type")
- @json["VMNetworkAssignments"]["__metadata"]["type"].should eq("Collection(VMM.VMNetworkAssignment)")
- @json["VMNetworkAssignments"].should have_key("results")
- @json["VMNetworkAssignments"]["results"].should be_a(Array)
- @json["VMNetworkAssignments"]["results"].should eq([])
- end
end
- describe "handling of Microsoft System Center 2012" do
- let(:username) { "blabla" }
- let(:password) { "" }
-
- before(:each) do
- auth_string = "#{username}:#{password}"
- authorization_header = { authorization: "Basic #{Base64::encode64(auth_string).strip}" }
- headers = DEFAULT_HEADERS.merge(authorization_header)
-
- # Required for the build_classes method
- stub_request(:get, "http://test.com/test.svc/$metadata").
- with(:headers => headers).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/ms_system_center/edmx_ms_system_center.xml", __FILE__)), :headers => {})
-
- stub_request(:get, "http://test.com/test.svc/VirtualMachines").
- with(:headers => headers).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/ms_system_center/virtual_machines.xml", __FILE__)), :headers => {})
-
- stub_request(:get, "http://test.com/test.svc/HardwareProfiles?$filter=Memory%20eq%203500").
- with(:headers => headers).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/ms_system_center/hardware_profiles.xml", __FILE__)), :headers => {})
-
- stub_request(:get, "http://test.com/test.svc/VMTemplates").
- with(:headers => headers).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/ms_system_center/vm_templates.xml", __FILE__)), :headers => {})
- end
-
- after(:all) do
- VMM.constants.each do |constant|
- VMM.send :remove_const, constant
- end
- end
-
- it "should successfully parse null valued string properties" do
- svc = OData::Service.new "http://test.com/test.svc/", { :username => username, :password => password, :verify_ssl => false, :namespace => "VMM" }
- svc.VirtualMachines
- results = svc.execute
- results.first.should be_a_kind_of(VMM::VirtualMachine)
- results.first.CostCenter.should be_nil
- end
-
- it "should successfully return a virtual machine" do
- svc = OData::Service.new "http://test.com/test.svc/", { :username => username, :password => password, :verify_ssl => false, :namespace => "VMM" }
- svc.VirtualMachines
- results = svc.execute
- results.first.should be_a_kind_of(VMM::VirtualMachine)
- end
-
- it "should successfully return a hardware profile for results that include a collection of complex types" do
- svc = OData::Service.new "http://test.com/test.svc/", { :username => username, :password => password, :verify_ssl => false, :namespace => "VMM" }
- svc.HardwareProfiles.filter("Memory eq 3500")
- results = svc.execute
- results.first.should be_a_kind_of(VMM::HardwareProfile)
- end
-
- it "should successfully return a collection of complex types" do
- svc = OData::Service.new "http://test.com/test.svc/", { :username => username, :password => password, :verify_ssl => false, :namespace => "VMM" }
- svc.HardwareProfiles.filter("Memory eq 3500")
- results = svc.execute
- granted_list = results.first.GrantedToList
- granted_list.should be_a_kind_of(Array)
- granted_list.first.should be_a_kind_of(VMM::UserAndRole)
- granted_list.first.RoleName.should == "Important Tenant"
- end
-
+ end
- it "should successfully return results that include a collection of Edm types" do
- svc = OData::Service.new "http://test.com/test.svc/", { :username => username, :password => password, :verify_ssl => false, :namespace => "VMM" }
- svc.VMTemplates
- results = svc.execute
- results.first.should be_a_kind_of(VMM::VMTemplate)
- end
- it "should successfully return a collection of Edm types" do
- svc = OData::Service.new "http://test.com/test.svc/", { :username => username, :password => password, :verify_ssl => false, :namespace => "VMM" }
- svc.VMTemplates
- results = svc.execute
- boot_order = results.first.BootOrder
- boot_order.should be_a_kind_of(Array)
- boot_order.first.should be_a_kind_of(String)
- boot_order.should eq ['CD', 'IdeHardDrive', 'PxeBoot', 'Floppy']
- end
+ describe "Dual Namespaces" do
+ before(:each) do
+ auth_string = "xxxx\\yyyy:zzzz"
+ authorization_header = { authorization: "Basic #{Base64::encode64(auth_string).strip}" }
+ stub_request(:get, "http://test.com/test.svc/$metadata").
+ with(:headers => DEFAULT_HEADERS.merge(authorization_header)).
+ to_return(:status => 200, :body => Fixtures.load("/ms_system_center/edmx_ms_system_center_v2.xml"), :headers => {})
end
- describe "handling of nested expands" do
- before(:each) do
- stub_request(:get, "http://test.com/test.svc/$metadata").
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/nested_expands/edmx_northwind.xml", __FILE__)), :headers => {})
-
- stub_request(:get, "http://test.com/test.svc/Products?$expand=Category,Category/Products&$top=2").
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/nested_expands/northwind_products_category_expands.xml", __FILE__)), :headers => {})
- end
-
- it "should successfully parse the results" do
- svc = OData::Service.new "http://test.com/test.svc", { :namespace => "NW" }
- svc.Products.expand('Category').expand('Category/Products').top(2)
- lambda { svc.execute }.should_not raise_exception
- end
-
- it "should successfully parse a Category as a Category" do
- svc = OData::Service.new "http://test.com/test.svc", { :namespace => "NW" }
- svc.Products.expand('Category').expand('Category/Products').top(2)
- products = svc.execute
- products.first.Category.should be_a_kind_of(NW::Category)
- end
-
- it "should successfully parse the Category properties" do
- svc = OData::Service.new "http://test.com/test.svc", { :namespace => "NW" }
- svc.Products.expand('Category').expand('Category/Products').top(2)
- products = svc.execute
- products.first.Category.CategoryID.should eq 1
- end
-
- it "should successfully parse the Category children Products" do
- svc = OData::Service.new "http://test.com/test.svc", { :namespace => "NW" }
- svc.Products.expand('Category').expand('Category/Products').top(2)
- products = svc.execute
- products.first.Category.Products.length.should eq 12
- end
-
- it "should successfully parse the Category's child Product properties" do
- svc = OData::Service.new "http://test.com/test.svc", { :namespace => "NW" }
- svc.Products.expand('Category').expand('Category/Products').top(2)
- products = svc.execute
- products.first.Category.Products.first.ProductName.should eq "Chai"
- end
+ after(:each) do
+ remove_classes @service
end
- describe "handling of custom select queries" do
-
- context "when results are found" do
- before(:each) do
- stub_request(:get, "http://test.com/test.svc/$metadata").
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/sample_service/edmx_categories_products.xml", __FILE__)), :headers => {})
-
- stub_request(:get, "http://test.com/test.svc/Products?$select=Name,Price").
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/sample_service/result_select_products_name_price.xml", __FILE__)), :headers => {})
- end
-
- subject do
- svc = OData::Service.new "http://test.com/test.svc/"
- svc.Products.select "Name", "Price"
- svc.execute
- end
-
- it { should be_an Array }
- it { should_not be_empty }
- its(:first) { should be_a Product }
- end
-
- context "when there isn't a property by the name specified" do
- before(:each) do
- stub_request(:get, "http://test.com/test.svc/$metadata").
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/sample_service/edmx_categories_products.xml", __FILE__)), :headers => {})
-
- stub_request(:get, "http://test.com/test.svc/Categories?$select=Price").
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 400, :body => File.new(File.expand_path("../fixtures/sample_service/result_select_categories_no_property.xml", __FILE__)), :headers => {})
- end
-
- it "raises an exception" do
- svc = OData::Service.new "http://test.com/test.svc/"
- svc.Categories.select "Price"
- expect { svc.execute }.to raise_error(OData::ServiceError) { |error|
- error.http_code.should eq 400
- error.message.should eq "Type 'RubyODataService.Category' does not have a property named 'Price' or there is no type with 'Price' name."
- }
- end
- end
+ it "should parse the service without errors" do
+ lambda { @service = OData::Service.new "http://test.com/test.svc/", { :username => "xxxx\\yyyy", :password=> "zzzz", :verify_ssl => false, :namespace => "VMM" } }.should_not raise_error
+ end
- context "when a property requires $expand to traverse" do
- before(:each) do
- stub_request(:get, "http://test.com/test.svc/$metadata").
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/sample_service/edmx_categories_products.xml", __FILE__)), :headers => {})
+ end
- stub_request(:get, "http://test.com/test.svc/Categories?$select=Name,Products/Name").
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 400, :body => File.new(File.expand_path("../fixtures/sample_service/result_select_categories_travsing_no_expand.xml", __FILE__)), :headers => {})
+ describe "Dual Services" do
+ before(:each) do
+ stub_request(:get, "http://service1.com/test.svc/$metadata").
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("/edmx_empty.xml"), :headers => {})
- stub_request(:get, "http://test.com/test.svc/Categories?$select=Name,Products/Name&$expand=Products").
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/sample_service/result_select_categories_expand.xml", __FILE__)), :headers => {})
- end
+ stub_request(:get, "http://service2.com/test.svc/$metadata").
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("/edmx_empty.xml"), :headers => {})
- it "doesn't error" do
- svc = OData::Service.new "http://test.com/test.svc/"
- svc.Categories.select "Name", "Products/Name"
- expect { svc.execute }.to_not raise_error
- end
- it "returns the classes with the properties filled in" do
- svc = OData::Service.new "http://test.com/test.svc/"
- svc.Categories.select "Name", "Products/Name"
- results = svc.execute
- category = results.first
- category.Name.should eq "Category 0001"
- product = category.Products.first
- product.Name.should eq "Widget 0001"
- end
- end
+ @service1 = OData::Service.new "http://service1.com/test.svc"
+ @service2 = OData::Service.new "http://service2.com/test.svc"
end
- end
- describe_private OData::Service do
- describe "parse value" do
- before(:each) do
- # Required for the build_classes method
- stub_request(:get, "http://test.com/test.svc/$metadata").
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/edmx_empty.xml", __FILE__)), :headers => {})
- end
+ after(:each) do
+ remove_classes @service1
+ remove_classes @service2
+ end
- it "should not error on an 'out of range' date" do
- # 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]
- svc = OData::Service.new "http://test.com/test.svc/"
- element_to_parse = Nokogiri::XML.parse('2100-01-01T00:00:00').elements[0]
- lambda { svc.parse_value_xml(element_to_parse) }.should_not raise_exception
- end
+ it "should use the correct service uri" do
+ expect(@service1.class_metadata[:uri]).to eq 'http://service1.com/test.svc'
+ expect(@service2.class_metadata[:uri]).to eq 'http://service2.com/test.svc'
end
end
end
diff --git a/spec/service_v4_spec.rb b/spec/service_v4_spec.rb
index bb5f603..e7d924c 100644
--- a/spec/service_v4_spec.rb
+++ b/spec/service_v4_spec.rb
@@ -1,101 +1,111 @@
require 'spec_helper'
-describe "V4 Service" do
- before(:all) do
- stub_request(:get, /http:\/\/test\.com\/test\.svc\/\$metadata(?:\?.+)?/).
- with(:headers => DEFAULT_HEADERS).
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/v4/edmx_metadata.xml", __FILE__)), :headers => {})
+module OData
- stub_request(:get, "http://test.com/test.svc/Categories").
- to_return(:status => 200, :body => File.new(File.expand_path("../fixtures/v4/result_categories.xml", __FILE__)), :headers => {})
+ describe "V4 Service" do
+ before(:all) do
+ stub_request(:get, "http://test.com/test.svc/$metadata").
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("/v4/edmx_metadata.xml"), :headers => {})
- @service = OData::Service.new "http://test.com/test.svc"
- end
- subject { @service }
-
- context "methods" do
- it { should respond_to :update_object }
- it { should respond_to :delete_object }
- it { should respond_to :save_changes }
- it { should respond_to :load_property }
- it { should respond_to :add_link }
- it { should respond_to :execute }
- it { should respond_to :partial? }
- it { should respond_to :next }
- it { should respond_to :classes }
- it { should respond_to :class_metadata }
- it { should respond_to :collections }
- it { should respond_to :options }
- it { should respond_to :function_imports }
+ stub_request(:get, "http://test.com/test.svc/Categories").
+ to_return(:status => 200, :body => Fixtures.load("/v4/result_categories.xml"), :headers => {})
- context "after parsing metadata" do
- it { should respond_to :Products }
- it { should respond_to :Categories }
- it { should respond_to :AddToProducts }
- it { should respond_to :AddToCategories }
+ @service = OData::Service.new "http://test.com/test.svc"
end
- end
- context "collections method" do
- subject { @service.collections }
- it { should include 'Products' }
- it { should include 'Categories' }
- it "should expose the edmx type of objects" do
- subject['Products'][:edmx_type].should eq 'ODataDemo.Product'
- subject['Categories'][:edmx_type].should eq 'ODataDemo.Category'
- end
- it "should expose the local model type" do
- subject['Products'][:type].should eq Product
- subject['Categories'][:type].should eq Category
+ after(:all) do
+ remove_classes @service
end
- end
- context "class metadata" do
- subject { @service.class_metadata }
- it { should_not be_empty}
- it { should have_key 'Product' }
- it { should have_key 'Category' }
+ subject { @service }
+
+ context "methods" do
+ it { should respond_to :collections }
+ it { should respond_to :class_metadata }
+ it { should respond_to :function_imports }
+ it { should respond_to :classes }
+
+ it { should respond_to :execute }
- context "should have keys for each property" do
- subject { @service.class_metadata['Category'] }
- it { should have_key 'ID' }
- it { should have_key 'Name' }
- it { should have_key 'Products' }
- it "should return a PropertyMetadata object for each property" do
- subject['ID'].should be_an OData::PropertyMetadata
- subject['Name'].should be_an OData::PropertyMetadata
- subject['Products'].should be_an OData::PropertyMetadata
+ it { should respond_to :update_object }
+ it { should respond_to :delete_object }
+ it { should respond_to :save_changes }
+ it { should respond_to :load_property }
+ it { should respond_to :add_link }
+ it { should respond_to :partial? }
+ it { should respond_to :next }
+ it { should respond_to :options }
+
+ context "after parsing metadata" do
+ it { should respond_to :Products }
+ it { should respond_to :Categories }
+ it { should respond_to :AddToProducts }
+ it { should respond_to :AddToCategories }
end
- it "should have correct PropertyMetadata for Category.Id" do
- meta = subject['ID']
- meta.name.should eq 'ID'
- meta.type.should eq 'Edm.Int32'
- meta.nullable.should eq false
- meta.fc_target_path.should be_nil
- meta.fc_keep_in_content.should be_nil
- meta.nav_prop.should eq false
- meta.is_key.should eq true
+ end
+
+ context "collections method" do
+ subject { @service.collections }
+ it { should include 'Products' }
+ it { should include 'Categories' }
+ it "should expose the edmx type of objects" do
+ subject['Products'][:edmx_type].should eq 'ODataDemo.Product'
+ subject['Categories'][:edmx_type].should eq 'ODataDemo.Category'
end
- it "should have correct PropertyMetadata for Category.Name" do
- meta = subject['Name']
- meta.name.should eq 'Name'
- meta.type.should eq 'Edm.String'
- meta.nullable.should eq false
- meta.fc_target_path.should be_nil
- meta.fc_keep_in_content.should be_nil
- meta.nav_prop.should eq false
- meta.is_key.should eq false
+ it "should expose the local model type" do
+ subject['Products'][:type].should eq Product
+ subject['Categories'][:type].should eq Category
end
- it "should have correct PropertyMetadata for Category.Products" do
- meta = subject['Products']
- meta.name.should eq 'Products'
- meta.type.should eq 'Collection(ODataDemo.Product)'
- meta.nullable.should eq true
- meta.fc_target_path.should be_nil
- meta.fc_keep_in_content.should be_nil
- meta.nav_prop.should eq true
- meta.association.should_not be_nil
- meta.is_key.should eq false
+ end
+
+ context "class metadata" do
+ subject { @service.class_metadata }
+ it { should_not be_empty}
+ it { should have_key 'Product' }
+ it { should have_key 'Category' }
+
+ context "should have keys for each property" do
+ subject { @service.class_metadata['Category'] }
+ it { should have_key 'ID' }
+ it { should have_key 'Name' }
+ it { should have_key 'Products' }
+ it "should return a PropertyMetadata object for each property" do
+ subject['ID'].should be_an OData::PropertyMetadata
+ subject['Name'].should be_an OData::PropertyMetadata
+ subject['Products'].should be_an OData::PropertyMetadata
+ end
+ it "should have correct PropertyMetadata for Category.Id" do
+ meta = subject['ID']
+ meta.name.should eq 'ID'
+ meta.type.should eq 'Edm.Int32'
+ meta.nullable.should eq false
+ meta.fc_target_path.should be_nil
+ meta.fc_keep_in_content.should be_nil
+ meta.nav_prop.should eq false
+ meta.is_key.should eq true
+ end
+ it "should have correct PropertyMetadata for Category.Name" do
+ meta = subject['Name']
+ meta.name.should eq 'Name'
+ meta.type.should eq 'Edm.String'
+ meta.nullable.should eq false
+ meta.fc_target_path.should be_nil
+ meta.fc_keep_in_content.should be_nil
+ meta.nav_prop.should eq false
+ meta.is_key.should eq false
+ end
+ it "should have correct PropertyMetadata for Category.Products" do
+ meta = subject['Products']
+ meta.name.should eq 'Products'
+ meta.type.should eq 'Collection(ODataDemo.Product)'
+ meta.nullable.should eq true
+ meta.fc_target_path.should be_nil
+ meta.fc_keep_in_content.should be_nil
+ meta.nav_prop.should eq true
+ meta.association.should_not be_nil
+ meta.is_key.should eq false
+ end
end
end
end
diff --git a/spec/services/dynamics_nav_spec.rb b/spec/services/dynamics_nav_spec.rb
new file mode 100644
index 0000000..854bf53
--- /dev/null
+++ b/spec/services/dynamics_nav_spec.rb
@@ -0,0 +1,73 @@
+require 'spec_helper'
+
+module OData
+ describe Service do
+
+ describe "handling of Microsoft Dynamics Nav OData WebService" do
+
+ before(:each) do
+ # Required for the build_classes method
+ username = "blabla"
+ password = ""
+ auth_string = "#{username}:#{password}"
+ authorization_header = { authorization: "Basic #{Base64::encode64(auth_string).strip}" }
+ headers = DEFAULT_HEADERS.merge(authorization_header)
+
+ stub_request(:get, "http://test.com/nav.svc/$metadata").
+ with(:headers => headers).
+ to_return(:status => 200, :body => Fixtures.load("/ms_dynamics_nav/edmx_ms_dynamics_nav.xml"), :headers => {})
+
+ stub_request(:get, "http://test.com/nav.svc/Customer").
+ with(:headers => headers).
+ to_return(:status => 200, :body => Fixtures.load("/ms_dynamics_nav/result_customer.xml"), :headers => {})
+
+ stub_request(:get, "http://test.com/nav.svc/Customer('100013')").
+ with(:headers => headers).
+ to_return(:status => 200, :body => Fixtures.load("/ms_dynamics_nav/result_customer.xml"), :headers => {})
+
+ stub_request(:get, "http://test.com/nav.svc/Customer(100013)").
+ with(:headers => headers).
+ to_return(:status => 400, :body => Fixtures.load("/ms_dynamics_nav/result_customer_error.xml"), :headers => {})
+
+ stub_request(:get, "http://test.com/nav.svc/SalesOrder(Document_Type='Order',No='AB-1600013')").
+ with(:headers => headers).
+ to_return(:status => 200, :body => Fixtures.load("/ms_dynamics_nav/result_sales_order.xml"), :headers => {})
+
+ @service = OData::Service.new "http://test.com/nav.svc/", { :username => username, :password => password, :verify_ssl => false }
+
+ end
+
+ after(:each) do
+ remove_classes @service
+ end
+
+ it "should successfully parse null valued string properties" do
+ @service.Customer
+ results = @service.execute
+ results.first.should be_a_kind_of(Customer)
+ end
+
+ it "should successfully return a customer by its string id" do
+ @service.Customer('100013')
+ results = @service.execute
+ results.first.should be_a_kind_of(Customer)
+ results.first.Name.should eq 'Contoso AG'
+ end
+
+ it "should cast to string if a customer is accessed with integer id" do
+ @service.Customer(100013)
+ results = @service.execute
+ results.first.should be_a_kind_of(Customer)
+ results.first.Name.should eq 'Contoso AG'
+ end
+
+ it "should successfully return a sales_order by its composite string ids" do
+ @service.SalesOrder(Document_Type: 'Order', No: 'AB-1600013')
+ results = @service.execute
+ results.first.should be_a_kind_of(SalesOrder)
+ results.first.No.should eq 'AB-1600013'
+ end
+
+ end
+ end
+end
\ No newline at end of file
diff --git a/spec/services/microsoft_system_center_spec.rb b/spec/services/microsoft_system_center_spec.rb
new file mode 100644
index 0000000..1a1924e
--- /dev/null
+++ b/spec/services/microsoft_system_center_spec.rb
@@ -0,0 +1,82 @@
+require 'spec_helper'
+require 'base64'
+
+module OData
+ describe "handling of Microsoft System Center 2012" do
+ let(:username) { "blabla" }
+ let(:password) { "" }
+
+ before(:each) do
+ auth_string = "#{username}:#{password}"
+ authorization_header = { authorization: "Basic #{Base64::encode64(auth_string).strip}" }
+ headers = DEFAULT_HEADERS.merge(authorization_header)
+
+ # Required for the build_classes method
+ stub_request(:get, "http://test.com/test.svc/$metadata").
+ with(:headers => headers).
+ to_return(:status => 200, :body => Fixtures.load("/ms_system_center/edmx_ms_system_center.xml"), :headers => {})
+
+ stub_request(:get, "http://test.com/test.svc/VirtualMachines").
+ with(:headers => headers).
+ to_return(:status => 200, :body => Fixtures.load("/ms_system_center/virtual_machines.xml"), :headers => {})
+
+ stub_request(:get, "http://test.com/test.svc/HardwareProfiles?$filter=Memory%20eq%203500").
+ with(:headers => headers).
+ to_return(:status => 200, :body => Fixtures.load("/ms_system_center/hardware_profiles.xml"), :headers => {})
+
+ stub_request(:get, "http://test.com/test.svc/VMTemplates").
+ with(:headers => headers).
+ to_return(:status => 200, :body => Fixtures.load("/ms_system_center/vm_templates.xml"), :headers => {})
+
+ @service = OData::Service.new "http://test.com/test.svc/", { :username => username, :password => password, :verify_ssl => false, :namespace => "VMM" }
+ end
+
+ after(:all) do
+ remove_classes @service
+ end
+
+ it "should successfully parse null valued string properties" do
+ @service.VirtualMachines
+ results = @service.execute
+ results.first.should be_a_kind_of(VMM::VirtualMachine)
+ results.first.CostCenter.should be_nil
+ end
+
+ it "should successfully return a virtual machine" do
+ @service.VirtualMachines
+ results = @service.execute
+ results.first.should be_a_kind_of(VMM::VirtualMachine)
+ end
+
+ it "should successfully return a hardware profile for results that include a collection of complex types" do
+ @service.HardwareProfiles.filter("Memory eq 3500")
+ results = @service.execute
+ results.first.should be_a_kind_of(VMM::HardwareProfile)
+ end
+
+ it "should successfully return a collection of complex types" do
+ @service.HardwareProfiles.filter("Memory eq 3500")
+ results = @service.execute
+ granted_list = results.first.GrantedToList
+ granted_list.should be_a_kind_of(Array)
+ granted_list.first.should be_a_kind_of(VMM::UserAndRole)
+ granted_list.first.RoleName.should == "Important Tenant"
+ end
+
+
+ it "should successfully return results that include a collection of Edm types" do
+ @service.VMTemplates
+ results = @service.execute
+ results.first.should be_a_kind_of(VMM::VMTemplate)
+ end
+
+ it "should successfully return a collection of Edm types" do
+ @service.VMTemplates
+ results = @service.execute
+ boot_order = results.first.BootOrder
+ boot_order.should be_a_kind_of(Array)
+ boot_order.first.should be_a_kind_of(String)
+ boot_order.should eq ['CD', 'IdeHardDrive', 'PxeBoot', 'Floppy']
+ end
+ end
+end
diff --git a/spec/services/sample_service_spec.rb b/spec/services/sample_service_spec.rb
new file mode 100644
index 0000000..39c98ef
--- /dev/null
+++ b/spec/services/sample_service_spec.rb
@@ -0,0 +1,371 @@
+require 'spec_helper'
+
+module OData
+ describe "sample service" do
+
+ before(:each) do
+ # Required for the build_classes method
+ stub_request(:get, /http:\/\/test\.com\/test\.svc\/\$metadata(?:\?.+)?/).
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("/sample_service/edmx_categories_products.xml"), :headers => {})
+
+ stub_request(:get, /http:\/\/test\.com\/test\.svc\/Products\(\d\)/).
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("/sample_service/result_single_product.xml"), :headers => {})
+
+ stub_request(:get, /http:\/\/test\.com\/test\.svc\/Products\(\d{2,}\)/).
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("/sample_service/result_single_product_not_found.xml"), :headers => {})
+
+ stub_request(:get, "http://test.com/test.svc/Products(1)/Category").
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("/sample_service/result_single_category.xml"), :headers => {})
+
+ stub_request(:get, "http://test.com/test.svc/Categories(1)").
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("/sample_service/result_single_category.xml"), :headers => {})
+
+ stub_request(:get, "http://test.com/test.svc/Categories(1)/Products").
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("/sample_service/result_multiple_category_products.xml"), :headers => {})
+
+ stub_request(:post, "http://test.com/test.svc/Categories(1)/$links/Products").to_return(:status => 204)
+ stub_request(:post, "http://test.com/test.svc/$batch").to_return(:status => 202)
+ end
+
+ describe "lazy loading" do
+ after(:each) do
+ Object.send(:remove_const, 'Product') if Object.const_defined? 'Product'
+ Object.send(:remove_const, 'Category') if Object.const_defined? 'Category'
+ end
+
+ it "should have a load property method" do
+ svc = OData::Service.new "http://test.com/test.svc/"
+ svc.should respond_to(:load_property)
+ end
+
+ it "should throw an exception if the object isn't tracked" do
+ svc = OData::Service.new "http://test.com/test.svc/"
+ new_object = Product.new
+ lambda { svc.load_property(new_object, "Category") }.should raise_error(NotSupportedError, "You cannot load a property on an entity that isn't tracked")
+ end
+
+ it "should throw an exception if there isn't a method matching the navigation property passed in" do
+ svc = OData::Service.new "http://test.com/test.svc/"
+ svc.Products(1)
+ product = svc.execute.first
+ lambda { svc.load_property(product, "NoMatchingMethod") }.should raise_error(ArgumentError, "'NoMatchingMethod' is not a valid navigation property")
+ end
+
+ it "should throw an exception if the method passed in is a standard property (non-navigation)" do
+ svc = OData::Service.new "http://test.com/test.svc/"
+ svc.Products(1)
+ product = svc.execute.first
+ lambda { svc.load_property(product, "Name") }.should raise_error(ArgumentError, "'Name' is not a valid navigation property")
+ end
+
+ it "should fill a single navigation property" do
+ svc = OData::Service.new "http://test.com/test.svc/"
+ svc.Products(1)
+ product = svc.execute.first
+ svc.load_property(product, "Category")
+ product.Category.should_not be_nil
+ product.Category.Id.should eq 1
+ product.Category.Name.should eq 'Category 1'
+ end
+
+ it "should fill a collection navigation property" do
+ svc = OData::Service.new "http://test.com/test.svc/"
+ svc.Categories(1)
+ category = svc.execute.first
+ svc.load_property(category, "Products")
+ category.Products.first.should be_a Product
+ category.Products[0].Id.should eq 1
+ category.Products[1].Id.should eq 2
+ end
+
+ it "should not mutate the object's metadata" do
+ svc = OData::Service.new "http://test.com/test.svc/"
+ svc.Products(1)
+ product = svc.execute.first
+ original_metadata = product.__metadata.to_json
+ svc.load_property(product, "Category")
+ product.__metadata.to_json.should == original_metadata
+ end
+ end
+
+ describe "find, create, add, update, and delete" do
+ after(:each) do
+ Object.send(:remove_const, 'Product') if Object.const_defined? 'Product'
+ Object.send(:remove_const, 'Category') if Object.const_defined? 'Category'
+ end
+
+ it "should implement an AddTo method for collection" do
+ svc = OData::Service.new "http://test.com/test.svc/"
+ svc.should respond_to :AddToCategories
+ svc.should respond_to :AddToProducts
+ end
+
+ it "should create objects with an initialize method that can build the object from a hash" do
+ svc = OData::Service.new "http://test.com/test.svc/"
+ product = Product.new 'Id' => 1000, 'Name' => 'New Product'
+ product.Id.should eq 1000
+ product.Name.should eq 'New Product'
+ end
+
+ it "should create objects that rejects keys that don't have corresponding methods" do
+ svc = OData::Service.new "http://test.com/test.svc/"
+ lambda { Product.new 'NotAProperty' => true }.should raise_error NoMethodError
+ end
+
+ it "should create objects that expose a properties class method that lists the properties for the object" do
+ svc = OData::Service.new "http://test.com/test.svc/"
+ Product.properties.should include 'Id'
+ Product.properties.should include 'Name'
+ Product.properties.should include 'Category'
+ end
+
+ it "should have full metadata for a property returned from the properties method" do
+ svc = OData::Service.new "http://test.com/test.svc/"
+ Product.properties['Category'].should be_a PropertyMetadata
+ expect(Product.properties['Category'].nav_prop).to eq true
+ end
+
+ it "should create objects that expose an id property" do
+ svc = OData::Service.new "http://test.com/test.svc/"
+ svc.Products(1)
+ product = svc.execute.first
+ expect(product).to respond_to :id
+ end
+
+ it "should extract the id from the metadata" do
+ svc = OData::Service.new "http://test.com/test.svc/"
+ svc.Products(1)
+ product = svc.execute.first
+ expect(product.id).to eq 1
+ end
+
+ describe "Class.first method" do
+ it "should exist on the create server objects" do
+ svc = OData::Service.new "http://test.com/test.svc/"
+ expect(Product).to respond_to :first
+ end
+ it "should return nil if an id isn't passed in" do
+ svc = OData::Service.new "http://test.com/test.svc/"
+ product = Product.first(nil)
+ product.should be_nil
+ end
+ it "should return nil if an id isn't found" do
+ svc = OData::Service.new "http://test.com/test.svc/"
+ product = Product.first(1234567890)
+ product.should be_nil
+ end
+ it "should return a product if an id is found" do
+ svc = OData::Service.new "http://test.com/test.svc/"
+ product = Product.first(1)
+ product.should_not be_nil
+ end
+ end
+ end
+
+ describe "namespaces" do
+ after(:each) do
+ VisoftInc::Sample::Models.send(:remove_const, 'Product') if VisoftInc::Sample::Models.const_defined? 'Product'
+ VisoftInc::Sample::Models.send(:remove_const, 'Category') if VisoftInc::Sample::Models.const_defined? 'Category'
+
+ VisoftInc::Sample.send(:remove_const, 'Models') if VisoftInc::Sample.const_defined? 'Models'
+ VisoftInc.send(:remove_const, 'Sample') if VisoftInc.const_defined? 'Sample'
+ Object.send(:remove_const, 'VisoftInc') if Object.const_defined? 'VisoftInc'
+ end
+
+ it "should create models in the specified namespace if the option is set (using a .NET style namespace with dots)" do
+ svc = OData::Service.new "http://test.com/test.svc/", { :namespace => 'VisoftInc.Sample.Models' }
+ expect(defined?(VisoftInc::Sample::Models::Product).nil?).to eq false
+ expect(defined?(VisoftInc::Sample::Models::Category).nil?).to eq false
+ end
+
+ it "should create models in the specified namespace if the option is set (using Ruby style namespaces with double colons)" do
+ svc = OData::Service.new "http://test.com/test.svc/", { :namespace => 'VisoftInc::Sample::Models' }
+ defined?(VisoftInc::Sample::Models::Product).nil?.should eq false
+ defined?(VisoftInc::Sample::Models::Category).nil?.should eq false
+ end
+
+ it "should fill object defined in a namespace" do
+ svc = OData::Service.new "http://test.com/test.svc/", { :namespace => 'VisoftInc::Sample::Models' }
+ svc.Categories(1)
+ categories = svc.execute
+ categories.should_not be_nil
+ category = categories.first
+ category.Id.should eq 1
+ category.Name.should eq 'Category 1'
+ end
+
+ it "should fill the class_metadata hash" do
+ svc = OData::Service.new "http://test.com/test.svc/", { :namespace => 'VisoftInc::Sample::Models' }
+ svc.class_metadata.should_not be_empty
+ end
+
+ it "should add a key (based on the name) for each property class_metadata hash" do
+ svc = OData::Service.new "http://test.com/test.svc/", { :namespace => 'VisoftInc::Sample::Models' }
+ svc.class_metadata['VisoftInc::Sample::Models::Product'].should have_key 'Id'
+ svc.class_metadata['VisoftInc::Sample::Models::Product'].should have_key 'Name'
+ svc.class_metadata['VisoftInc::Sample::Models::Product'].should have_key 'Description'
+ end
+
+ it "should lazy load objects defined in a namespace" do
+ svc = OData::Service.new "http://test.com/test.svc/", { :namespace => 'VisoftInc::Sample::Models' }
+ svc.Categories(1)
+ category = svc.execute.first
+ svc.load_property category, 'Products'
+ category.Products.should_not be_nil
+ category.Products.first.Id.should eq 1
+ category.Products.first.Name.should eq 'Widget 1'
+ end
+ end
+
+ describe "add_link method" do
+ it "should exist as a method on the service" do
+ svc = OData::Service.new "http://test.com/test.svc/"
+ svc.should respond_to(:add_link)
+ end
+
+ it "shouldn't be allowed if a parent isn't tracked" do
+ svc = OData::Service.new "http://test.com/test.svc/"
+ category = Category.new :Name => 'New Category'
+ property = nil # Not needed for this test
+ product = nil # Not needed for this test
+ lambda { svc.add_link(category, property, product) }.should raise_error(NotSupportedError, "You cannot add a link on an entity that isn't tracked (Category)")
+ end
+
+ it "shouldn't be allowed if a property isn't found on the parent" do
+ svc = OData::Service.new "http://test.com/test.svc/"
+ svc.Categories(1)
+ category = svc.execute.first
+ property = 'NotAProperty'
+ product = nil # Not needed for this test
+ lambda { svc.add_link(category, property, product) }.should raise_error(ArgumentError, "'NotAProperty' is not a valid navigation property for Category")
+ end
+
+ it "shouldn't be allowed if a property isn't a navigation property on the parent" do
+ svc = OData::Service.new "http://test.com/test.svc/"
+ svc.Categories(1)
+ category = svc.execute.first
+ property = 'Name'
+ product = nil # Not needed for this test
+ lambda { svc.add_link(category, property, product) }.should raise_error(ArgumentError, "'Name' is not a valid navigation property for Category")
+ end
+
+ it "shouldn't be allowed if a child isn't tracked" do
+ svc = OData::Service.new "http://test.com/test.svc/"
+ svc.Categories(1)
+ category = svc.execute.first
+ property = 'Products'
+ product = Product.new :Name => 'Widget 1'
+ 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)")
+ end
+
+ it "should perform a post against the correct URL with the correct body on a single_save" do
+ svc = OData::Service.new "http://test.com/test.svc/"
+ svc.Categories(1)
+ category = svc.execute.first
+ svc.Products(1)
+ product = svc.execute.first
+ property = 'Products'
+ svc.add_link(category, property, product)
+ svc.save_changes
+
+ if RUBY_VERSION.start_with? '2.3'
+ a_request(:post, "http://test.com/test.svc/Categories(1)/$links/Products").
+ with(:body => '{"uri":"http://test.com/test.svc/Products(1)"}',
+ :headers => DEFAULT_HEADERS.merge({'Content-Type' => 'application/json'})).should have_been_made
+ else
+ a_request(:post, "http://test.com/test.svc/Categories(1)/$links/Products").
+ with(:body => '"{\"uri\":\"http://test.com/test.svc/Products(1)\"}"',
+ :headers => DEFAULT_HEADERS.merge({'Content-Type' => 'application/json'})).should have_been_made
+ end
+ end
+
+ it "should add the child to the parent's navigation property on a single_save" do
+ svc = OData::Service.new "http://test.com/test.svc/"
+ svc.Categories(1)
+ category = svc.execute.first
+ svc.Products(1)
+ product = svc.execute.first
+ property = 'Products'
+ svc.add_link(category, property, product)
+ svc.save_changes
+ category.Products.should include product
+ end
+
+ it "should add the parent to the child's navigation property on a single_save" do
+ svc = OData::Service.new "http://test.com/test.svc/"
+ svc.Categories(1)
+ category = svc.execute.first
+ svc.Products(1)
+ product = svc.execute.first
+ property = 'Products'
+ svc.add_link(category, property, product)
+ svc.save_changes
+ product.Category.should eq category
+ end
+
+ describe "batch_save" do
+ before(:each) do
+ @svc = OData::Service.new "http://test.com/test.svc/"
+ @category = Category.first(1)
+ @product = Product.first(1)
+ new_category = Category.new
+ @svc.AddToCategories(new_category)
+ @svc.add_link(@category, 'Products', @product)
+ @svc.save_changes
+ end
+
+ it "should perform a post with the correct URL and body on a batch_save" do
+ if RUBY_VERSION.start_with? '2.3'
+ WebMock.should have_requested(:post, "http://test.com/test.svc/$batch").with { |request|
+ request.body.include? "POST http://test.com/test.svc/Categories(1)/$links/Products HTTP/1.1"
+ request.body.include? '{"uri":"http://test.com/test.svc/Products(1)"}'
+ }
+ else
+ WebMock.should have_requested(:post, "http://test.com/test.svc/$batch").with { |request|
+ request.body.include? "POST http://test.com/test.svc/Categories(1)/$links/Products HTTP/1.1"
+ request.body.include? '{\"uri\":\"http://test.com/test.svc/Products(1)\"}'
+ }
+ end
+ end
+ context "child is a part of the parent's collection" do
+ subject { @category.Products }
+ it { should include @product }
+ end
+ context "parent object should be filled in on the child" do
+ subject { @product.Category }
+ it { should eq @category }
+ end
+ end
+
+ describe "serializes nested collections" do
+ # Compy with oData Deep Insert capabilities
+ # http://docs.oasis-open.org/odata/odata-json-format/v4.0/os/odata-json-format-v4.0-os.html#_Toc372793073
+
+ before :each do
+ category = Category.new
+ category.Products = [Product.new(Name: "First"), Product.new(Name: "Last")]
+ @json = JSON.parse(category.to_json(type: :add))
+ end
+
+ it "should have an array for the Products key" do
+ @json["Products"].should be_a_kind_of Array
+ end
+
+ it "should have a hash for each product" do
+ @json["Products"].each{|x| x.should be_a_kind_of Hash}
+ end
+
+ it "should have the same data we put into the products" do
+ @json["Products"].first["Name"].should eq "First"
+ @json["Products"].last["Name"].should eq "Last"
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/sap_spec.rb b/spec/services/sap_spec.rb
new file mode 100644
index 0000000..238b2a6
--- /dev/null
+++ b/spec/services/sap_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+module OData
+
+ describe "handling of SAP results" do
+ before(:each) do
+ # Required for the build_classes method
+ stub_request(:get, "http://test.com/test.svc/$metadata").
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("/sap/edmx_sap_demo_flight.xml"), :headers => {})
+
+ stub_request(:get, "http://test.com/test.svc/z_demo_flightCollection").
+ with(:headers => DEFAULT_HEADERS).
+ to_return(:status => 200, :body => Fixtures.load("/sap/result_sap_demo_flight_missing_category.xml"), :headers => {})
+ end
+
+ it "should handle entities without a category element" do
+ svc = OData::Service.new "http://test.com/test.svc/"
+ svc.z_demo_flightCollection
+ results = svc.execute
+ results.first.should be_a_kind_of(ZDemoFlight)
+ end
+ end
+
+end
\ No newline at end of file
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 8b54526..5550b87 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -2,12 +2,14 @@
require 'webmock/rspec'
require 'simplecov'
require 'rspec/its'
+
# require 'coveralls'
# Coveralls.wear_merged!
Dir[File.expand_path('../support/**/*.rb', __FILE__)].each { |f| require f }
WebMock.disable_net_connect!(allow_localhost: true)
+
DEFAULT_HEADERS = {
'Accept'=>'*/*; q=0.5, application/xml',
'Accept-Encoding'=>'gzip,deflate'
diff --git a/spec/support/fixtures_load.rb b/spec/support/fixtures_load.rb
new file mode 100644
index 0000000..9f01730
--- /dev/null
+++ b/spec/support/fixtures_load.rb
@@ -0,0 +1,8 @@
+class Fixtures
+
+ FIXTURES = File.expand_path('../../fixtures', __FILE__)
+
+ def self.load(file)
+ File.new(FIXTURES + "/" + file)
+ end
+end
\ No newline at end of file
diff --git a/spec/support/remove_classes.rb b/spec/support/remove_classes.rb
new file mode 100644
index 0000000..b270a05
--- /dev/null
+++ b/spec/support/remove_classes.rb
@@ -0,0 +1,28 @@
+#
+# Removes the classes which were created by the odata_service
+#
+def remove_classes(service)
+ return if service.nil?
+
+ service.class_metadata.each_key do |klass|
+ next unless (String === klass )
+
+ namespaces = klass.split(/\.|::/)
+ (0..namespaces.count).each do |index|
+ index = namespaces.count-index-1
+ name = namespaces[index]
+ if index == 0
+ Object.send(:remove_const,name) if Object.const_defined? name
+ else
+ current_ns = namespaces[0..index-1].join '::'
+ if !current_ns.blank? and Object.const_defined? current_ns
+ if eval "#{current_ns}.const_defined? '#{name}'"
+ eval "#{current_ns}.send(:remove_const, '#{name}')"
+ end
+ end
+ end
+ end
+
+ # Object.send(:remove_const, klass) if (String === klass and Object.const_defined? klass)
+ end
+end