From 66eaa9b87c83f379d1e6ab9f23f6fdca3fcfb4b1 Mon Sep 17 00:00:00 2001 From: klaus Date: Tue, 1 Aug 2017 16:52:59 +0200 Subject: [PATCH 1/4] Add build_id_path for Strings --- lib/ruby_odata/service.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/ruby_odata/service.rb b/lib/ruby_odata/service.rb index b2f7c45..1fce248 100644 --- a/lib/ruby_odata/service.rb +++ b/lib/ruby_odata/service.rb @@ -210,6 +210,8 @@ def find_id_metadata(collection_name) def build_id_path(id_value, id_metadata) if id_metadata.type == "Edm.Int64" "(#{id_value}L)" + elsif id_metadata.type == "Edm.String" + "('#{id_value}')" else "(#{id_value})" end From 4c442e88311647d35e42d445f42b1994ec44e530 Mon Sep 17 00:00:00 2001 From: Klaus Date: Wed, 2 Aug 2017 23:59:01 +0200 Subject: [PATCH 2/4] add specs for ms_dynamic_nav --- spec/dynamics_nav_spec.rb | 75 +++++++++++++++ .../ms_dynamics_nav/edmx_ms_dynamics_nav.xml | 92 +++++++++++++++++++ .../ms_dynamics_nav/result_customer.xml | 26 ++++++ .../ms_dynamics_nav/result_customer_error.xml | 5 + .../ms_dynamics_nav/result_sales_order.xml | 24 +++++ 5 files changed, 222 insertions(+) create mode 100644 spec/dynamics_nav_spec.rb create mode 100644 spec/fixtures/ms_dynamics_nav/edmx_ms_dynamics_nav.xml create mode 100644 spec/fixtures/ms_dynamics_nav/result_customer.xml create mode 100644 spec/fixtures/ms_dynamics_nav/result_customer_error.xml create mode 100644 spec/fixtures/ms_dynamics_nav/result_sales_order.xml diff --git a/spec/dynamics_nav_spec.rb b/spec/dynamics_nav_spec.rb new file mode 100644 index 0000000..af403b9 --- /dev/null +++ b/spec/dynamics_nav_spec.rb @@ -0,0 +1,75 @@ +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 return an error 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 } + byebug + svc.Customer(100013) + 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 + + 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) + 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(Customer) + end + + end + end +end \ No newline at end of file diff --git a/spec/fixtures/ms_dynamics_nav/edmx_ms_dynamics_nav.xml b/spec/fixtures/ms_dynamics_nav/edmx_ms_dynamics_nav.xml new file mode 100644 index 0000000..2e67911 --- /dev/null +++ b/spec/fixtures/ms_dynamics_nav/edmx_ms_dynamics_nav.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spec/fixtures/ms_dynamics_nav/result_customer.xml b/spec/fixtures/ms_dynamics_nav/result_customer.xml new file mode 100644 index 0000000..7f2ddfc --- /dev/null +++ b/spec/fixtures/ms_dynamics_nav/result_customer.xml @@ -0,0 +1,26 @@ + + + http://navapp1.office.zwick-edelstahl.de:7178/ZWICK-TEST-WEB/OData/Customer('10000') + + + + <updated>2017-08-02T20:02:42Z</updated> + <author> + <name /> + </author> + <content type="application/xml"> + <m:properties> + <d:No>10000</d:No> + <d:Name>Contoso AG</d:Name> + <d:Address></d:Address> + <d:Address_2></d:Address_2> + <d:Post_Code></d:Post_Code> + <d:City>Berlin</d:City> + <d:Country_Region_Code>DE</d:Country_Region_Code> + <d:ETag>28;EgAAAAJ7BTEAMAAwADAAMAAAAAAA8;791241770;</d:ETag> + </m:properties> + </content> +</entry> \ No newline at end of file diff --git a/spec/fixtures/ms_dynamics_nav/result_customer_error.xml b/spec/fixtures/ms_dynamics_nav/result_customer_error.xml new file mode 100644 index 0000000..047eba1 --- /dev/null +++ b/spec/fixtures/ms_dynamics_nav/result_customer_error.xml @@ -0,0 +1,5 @@ +<m:error + xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"> + <m:code/> + <m:message xml:lang="de-DE">Bad Request - Error in query syntax.</m:message> +</m:error> \ No newline at end of file diff --git a/spec/fixtures/ms_dynamics_nav/result_sales_order.xml b/spec/fixtures/ms_dynamics_nav/result_sales_order.xml new file mode 100644 index 0000000..99151c9 --- /dev/null +++ b/spec/fixtures/ms_dynamics_nav/result_sales_order.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<entry xml:base="http://navapp1.office.zwick-edelstahl.de:7178/ZWICK-TEST-WEB/OData/" + xmlns="http://www.w3.org/2005/Atom" + xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" + xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" m:etag="W/"'52%3BJAAAAACLAQAAAAJ7%2F0EAQgAtADEANgAwADAAMAAxADMAAAAAAA%3D%3D8%3B824184700%3B'""> + <id>http://navapp1.office.zwick-edelstahl.de:7178/ZWICK-TEST-WEB/OData/SalesOrder(Document_Type='Order',No='AB-1600013')</id> + <category term="NAV.SalesOrder" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" /> + <link rel="edit" title="SalesOrder" href="SalesOrder(Document_Type='Order',No='AB-1600013')" /> + <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/SalesOrderSalesLines" type="application/atom+xml;type=feed" title="SalesOrderSalesLines" href="SalesOrder(Document_Type='Order',No='AB-1600013')/SalesOrderSalesLines" /> + <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/SalesOrderRouting_Status_View" type="application/atom+xml;type=feed" title="SalesOrderRouting_Status_View" href="SalesOrder(Document_Type='Order',No='AB-1600013')/SalesOrderRouting_Status_View" /> + <title /> + <updated>2017-08-02T19:53:22Z</updated> + <author> + <name /> + </author> + <content type="application/xml"> + <m:properties> + <d:Document_Type>Order</d:Document_Type> + <d:No>AB-1600013</d:No> + <d:Sell_to_Customer_No>11101</d:Sell_to_Customer_No> + <d:ETag>52;JAAAAACLAQAAAAJ7/0EAQgAtADEANgAwADAAMAAxADMAAAAAAA==8;824184700;</d:ETag> + </m:properties> + </content> +</entry> \ No newline at end of file From 726c467acbfa57532918f02cdf8fe706e397fecc Mon Sep 17 00:00:00 2001 From: Klaus <kd.gundermann@zwick-edelstahl.de> Date: Thu, 3 Aug 2017 17:26:56 +0200 Subject: [PATCH 3/4] Update Specs --- spec/dynamics_nav_spec.rb | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/spec/dynamics_nav_spec.rb b/spec/dynamics_nav_spec.rb index af403b9..def25e7 100644 --- a/spec/dynamics_nav_spec.rb +++ b/spec/dynamics_nav_spec.rb @@ -46,28 +46,28 @@ module OData results.first.should be_a_kind_of(Customer) end - it "should return an error if a customer is accessed with integer id" do + 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 } - byebug - svc.Customer(100013) - 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." - } + 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 customer by its string id" do + 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') + 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(Customer) + results.first.should be_a_kind_of(SalesOrder) + results.first.No.should eq 'AB-1600013' end end From 7b94fde523673016858a326a127b7590d0ec7150 Mon Sep 17 00:00:00 2001 From: Klaus <kd.gundermann@zwick-edelstahl.de> Date: Thu, 3 Aug 2017 17:49:39 +0200 Subject: [PATCH 4/4] Implement multiple string keys --- lib/ruby_odata/service.rb | 48 +++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/lib/ruby_odata/service.rb b/lib/ruby_odata/service.rb index 1fce248..f9adc27 100644 --- a/lib/ruby_odata/service.rb +++ b/lib/ruby_odata/service.rb @@ -180,42 +180,60 @@ def build_collection_query_object(name, additional_parameters, *args) if args.empty? #nothing to add elsif args.size == 1 - if args.first.to_s =~ /\d+/ - id_metadata = find_id_metadata(name.to_s) - root << build_id_path(args.first, id_metadata) - else - root << "(#{args.first})" - end + keys = get_keys_metadata(name.to_s) + root << "(#{build_ids(keys, args.first).join(',')})" else root << "(#{args.join(',')})" end QueryBuilder.new(root, additional_parameters) end - # Finds the metadata associated with the given collection's first id property - # Remarks: This is used for single item lookup queries using the ID, e.g. Products(1), not complex primary keys + # Finds the metadata associated with the given collection's keys # # @param [String] collection_name the name of the collection - def find_id_metadata(collection_name) + def get_keys_metadata(collection_name) collection_data = @collections.fetch(collection_name) class_metadata = @class_metadata.fetch(collection_data[:type].to_s) - key = class_metadata.select{|k,h| h.is_key }.collect{|k,h| h.name }[0] - class_metadata[key] + keys = class_metadata.select{|k,h| h.is_key } + end + + # Builds the ID expression of a given id for query + # + # @param [Object] id_value the actual value to be used + # @param [PropertyMetadata] id_metadata the property metadata object for the id + # Builds the IDs expression for the given ids for query + # + # @param [Hash] keys Hash of metadata for the keys + # @param [Object/Hash] values + def build_ids(keys, values) + if keys.size == 1 + [ quote_id(values, keys.first[1]) ] + elsif values.is_a?(Hash) + ids = [] + keys.each_pair do |key, meta| + v = values[key.to_sym] + ids << "#{key}=#{quote_id(v, meta)}" + end + ids + else + values.to_a + end end # Builds the ID expression of a given id for query # # @param [Object] id_value the actual value to be used # @param [PropertyMetadata] id_metadata the property metadata object for the id - def build_id_path(id_value, id_metadata) + def quote_id(id_value, id_metadata) if id_metadata.type == "Edm.Int64" - "(#{id_value}L)" + "#{id_value}L" elsif id_metadata.type == "Edm.String" - "('#{id_value}')" + "'#{id_value}'" else - "(#{id_value})" + "#{id_value}" end end + def set_options!(options) @options = options