Savon + Rails 2 How to modify XML's request structure - ruby-on-rails

I'm using Savon to develop a Web Service Client. Since I'm a beginner I decided to try at first with an example WDSL, which in my case is:
http://www.webservicex.com/CurrencyConvertor.asmx?wsdl
My controller is very simple:
require 'savon'
class WebServiceController < ApplicationController
def index
puts "web_service: IN"
client = Savon::Client.new do
wsdl.document = "http://www.webservicex.com/CurrencyConvertor.asmx?wsdl"
end
response = client.request :conversion_rate do
soap.body = {
:from_currency => 'USD',
:to_currency => 'EUR'
}
end
puts response.to_hash;
render :text => response.to_hash
end
end
The XML produced by that code is:
<env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:wsdl="http://www.webserviceX.NET/"
xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
<env:Body>
<ConversionRate>
<wsdl:fromCurrency>USD</wsdl:fromCurrency>
<wsdl:toCurrency>EUR</wsdl:toCurrency>
</ConversionRate>
</env:Body>
</env:Envelope>
However, the XML should be (and I know this because I'm using soapUI):
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:web="http://www.webserviceX.NET/">
<soapenv:Header/>
<soapenv:Body>
<web:ConversionRate>
<web:FromCurrency>USD</web:FromCurrency>
<web:ToCurrency>EUR</web:ToCurrency>
</web:ConversionRate>
</soapenv:Body>
</soapenv:Envelope>
I know my XML Request isn't working because I always get '0' (zero) as response, and with the "right" XML Request generated by soapUI I get correct values (such as '0.6959' ...).
Is something missing in my code?
Thank you!!!

two things:
you need to add :wsdl to the call
you need to make sure the spelling of the tags is correct
change to
response = client.request :wsdl, :conversion_rate do
and to
"FromCurrency" => 'USD',
"ToCurrency" => 'EUR'
that should do it for you.

Related

invalid SOAP request needs

I'm using savon to make requests against a SOAP service.
Regardless, I'm having trouble making Savon client call.
below mentioned are my ruby code
client = Savon.client(
wsdl: "https://<URL>",
soap_header: { 'Content-Type:' => "text/xml"},
log: true,
pretty_print_xml: true
)
client.call(:get_active_employees_info,:xmlns =>"http://tempuri.org/") do
message("AppID" => "*******","Username" => "*****","Password" => "******")
end
which return
Savon::HTTPError: HTTP error (400)
but same snippets run on Postman
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
<soap12:Body>
<GetActiveEmployeesInfo xmlns="http://tempuri.org/">
<AppID>**********</AppID>
<Username>********</Username>
<Password>**********</Password>
</GetActiveEmployeesInfo>
</soap12:Body>
</soap12:Envelope>
I think that the problem is related to the request body that you are building.
Since you have a working example, I would start by checking the request's body build by savon. To do this, use the following code:
ops = client.operation(:get_active_employees_info)
puts ops.build(message: {"AppID" => "*******","Username" => "*****","Password" => "******"}).pretty
Then, compare generated message with the one from Postman. I hope it helps.

Rails WebMock against XML request

I am having a hard time figuring out how to match stub_request with XML requests using webmock.
Request:
<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<env:Header>
<RequesterCredentials xmlns="urn:ebay:api:PayPalAPI" xmlns:n1="urn:ebay:apis:eBLBaseComponents" env:mustUnderstand="0">
<n1:Credentials>
<n1:Username>XYZ</n1:Username>
<n1:Password>ABC</n1:Password><n1:Subject/>
<n1:Signature>HUGE-STRING</n1:Signature>
</n1:Credentials>
</RequesterCredentials>
</env:Header>
<env:Body>
<ManageRecurringPaymentsProfileStatusReq xmlns="urn:ebay:api:PayPalAPI">
<ManageRecurringPaymentsProfileStatusRequest xmlns:n2="urn:ebay:apis:eBLBaseComponents">
<n2:Version>124</n2:Version>
<n2:ManageRecurringPaymentsProfileStatusRequestDetails>
<ProfileID>sdaddsadsd</ProfileID>
<n2:Action>Cancel</n2:Action>
</n2:ManageRecurringPaymentsProfileStatusRequestDetails>
</ManageRecurringPaymentsProfileStatusRequest>
</ManageRecurringPaymentsProfileStatusReq>
</env:Body>
</env:Envelope>
Stubs I tried:
stub_request(:any, /.*.sandbox.paypal.com\/2.0\//).
with(:body => WebMock::Matchers::HashIncludingMatcher.new({'n2:Action'=>'Cancel'}),
:headers => {'User-Agent'=>'Ruby'}).
to_return(:status => 200, ...
or
stub_request(:any, /.*.sandbox.paypal.com\/2.0\//).
with(query:
hash_including({ProfileID: 'sdaddsadsd'}),
:headers => {'User-Agent'=>'Ruby'}).
to_return(:status => 200, ...
(etc.)
have failed.
Anyone?
Provided you can save your XML to be stubbed into separate XML file (say request.xml), it is quite easy:
stub_request(:any, 'https://sandbox.paypal.com')
.with(body: File.read('request.xml').strip)
Headers can be omitted.
How to use webmock regex matcher? might help you with URL matching.
P.S. strip is to remove ending line feed

Need to remove SOAP Action from Savon generated request

I need to remove the SOAPAction from my request using Savon 2.
This is how I build the client:
client = Savon.client do
wsdl "http://servername:port/PingService?wsdl"
convert_request_keys_to :none
env_namespace :soapenv
namespaces({
'xmlns:pin' => 'http://servername:port/pingService_v1'
})
end
I use the following call to make the request:
client.call(:invoke, message: { "pin:PingReq" => { "pin:PingDB" => "true", "pin:PingG" => "true" }})
This is the request that is sent:
<soapenv:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tns="http://support.cxf.transport.mule.org/" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:pin="http://servername/pingService_v1">
<soapenv:Body>
<tns:invoke>
<pin:PingReq>
<pin:PingDB>true</pin:PingDB>
<pin:PingG>true</pin:PingG>
</pin:PingReq>
</tns:invoke>
</soapenv:Body>
</soapenv:Envelope>
A valid request would have everything except for the tns:invoke tags.
With Savon 2.x you can roll your own body by doing this:
client.call(:method, xml: "<tns:invoke><pin:PingReq> ...")

Using Webmock to fake successful ActiveMerchant response

I'm using ActiveMerchant to integrate with Authorize.net CIM. I'm in the middle of writing up automated tests, and I've begun putting into place Webmock calls so that my tests aren't actually hitting Authorize.net each time that they run.
I've created XML files from the responses of raw request data, and for the most part, it's working well. However, when I mock up a successful response, ActiveMerchant for some reason still tells me that Response.success? is not true.
My function
if self.cim_customer_profile_id.nil?
ActiveMerchant::Billing::Base.mode = :test
customer_profile_information = {
:profile => {
:merchant_customer_id => self.customer.username.first(20),
:email => self.customer.email
}
}
gateway = ActiveMerchant::Billing::AuthorizeNetCimGateway.new(
:login => AUTHORIZE_NET_API_LOGIN_ID,
:password => AUTHORIZE_NET_API_TRANSACTION_KEY
)
response = gateway.create_customer_profile(customer_profile_information)
if response.success?
self.cim_customer_profile_id = response.authorization
else
raise StandardError, response.message
end
end
And then my stubbed response is:
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
<?xml version="1.0" encoding="utf-8"?>
<createCustomerProfileResponse xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns='AnetApi/xml/v1/schema/AnetApiSchema.xsd'>
<messages>
<resultCode>
Ok
</resultCode>
<message>
<code>
I00001
</code>
<text>
Successful.
</text>
</message>
</messages>
<customerProfileId>10793616</customerProfileId>
<customerPaymentProfileIdList/>
<customerShippingAddressIdList/>
<validationDirectResponseList/>
</createCustomerProfileResponse>
Is there any reason why ActiveMerchant won't function with a successful, stubbed, request? Or am I missing something that ActiveMerchant requires in order to register that the response is actually successful?
Ah, I'm so dumb. I added new lines after all of my XML tags for readability, but they're interfering with how ActiveMerchant parses and evaluates the response.
So the correct XML response mock would be:
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
<?xml version="1.0" encoding="utf-8"?>
<createCustomerProfileResponse xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns='AnetApi/xml/v1/schema/AnetApiSchema.xsd'>
<messages>
<resultCode>Ok</resultCode>
<message>
<code>I00001</code>
<text>Successful.</text>
</message>
</messages>
<customerProfileId>10793616</customerProfileId>
<customerPaymentProfileIdList/>
<customerShippingAddressIdList/>
<validationDirectResponseList/>
</createCustomerProfileResponse>

Enforcing XML structure order in Savon

I'm currently trying to build a provisioning service of a kind for a local instance of Open-Xchange, using Rails 2.3.8, Savon and OX's SOAP API.
Via the console, I can issue the following commands that will work;
>> client = Savon::client("http://192.168.2.195/servlet/axis2/services/OXContextService?wsdl")
=> #<Savon::Client:0x2b5151bad790 #wsdl=#<Savon::Wasabi::Document:0x2b5151bad678 #document="http://192.168.2.195/servlet/axis2//services/OXContextService?wsdl", #request=#<HTTPI::Request:0x2b5151bad628>>, #http=#<HTTPI::Request:0x2b5151bad628>, #config=#<struct Savon::Config _logger=#<Savon::Logger:0x2b5151bad6c8 #device=#<IO:0x2b514a775ad0>>, pretty_print_xml=nil, raise_errors=true, soap_version=1, env_namespace=nil, soap_header=nil>>
>> client.request :list_by_database do
?> soap.body = {
?> :auth => {
?> :login => "oxadminmaster",
?> :password => "admin_master_password"
>> },
?> :db => {
?> :id => 3
>> }
>> }
>> end
HTTPI executes HTTP GET using the net_http adapter
SOAP request: https://192.168.2.195/servlet/axis2/services/OXContextService.OXContextServiceHttpsSoap11Endpoint/
SOAPAction: "urn:listByDatabase", Content-Type: text/xml;charset=UTF-8, Content-Length: 657
<?xml version="1.0" encoding="UTF-8"?><env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ins0="http://soap.admin.openexchange.com" xmlns:ns="http://soap.admin.openexchange.com" xmlns:ins1="http://dataobjects.soap.admin.openexchange.com/xsd" xmlns:ins2="http://dataobjects.rmi.admin.openexchange.com/xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"><env:Body><ins0:listByDatabase><ins0:db><ins1:id>3</ins1:id></ins0:db><ins0:auth><ins2:login>oxadminmaster</ins2:login><ins2:password>admin_master_password</ins2:password></ins0:auth></ins0:listByDatabase></env:Body></env:Envelope>
HTTPI executes HTTP POST using the net_http adapter
warning: peer certificate won't be verified in this SSL session
SOAP response (status 200):
It works, no problems there. However, when I try the following code...
class Ox_Context_Service
extend Savon::Model
attr_accessor :data, :returnCode
document "http://192.168.2.195/servlet/axis2/services/OXContextService?wsdl"
def list_by_database oxMasterUser, oxMasterPassword
begin
response = client.request :list_by_database do
soap.body = {
:auth => {
#for the sake of testing
:login => "#{oxMasterUser}",
:password => "#{oxMasterPassword}"
},
:db => {
:id => 3
#For the sake of testing
}
}
end
if response.success?
data = response.body[:list_by_database_response][:return]
if data
#data = data
#returnCode = "#{response.http.code}"
end
end
end
rescue Savon::Error => fault
#data = {}
#returnCode = "#{fault}"
end
end
Using the following CURL...
curl localhost:4545/oxContextService/list_by_database -d 'oxUsername=oxadminmaster' -d 'oxPassword=admin_master_password'
I get...
HTTPI executes HTTP GET using the net_http adapter
SOAP request: https://192.168.2.195/servlet/axis2/services/OXContextService.OXContextServiceHttpsSoap11Endpoint/
SOAPAction: "urn:listByDatabase", Content-Type: text/xml;charset=UTF-8, Content-Length: 657
<?xml version="1.0" encoding="UTF-8"?><env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ins0="http://soap.admin.openexchange.com" xmlns:ns="http://soap.admin.openexchange.com" xmlns:ins1="http://dataobjects.soap.admin.openexchange.com/xsd" xmlns:ins2="http://dataobjects.rmi.admin.openexchange.com/xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"><env:Body><ins0:listByDatabase><ins0:auth><ins2:login>oxadminmaster</ins2:login><ins2:password>admin_master_password</ins2:password></ins0:auth><ins0:db><ins1:id>3</ins1:id></ins0:db></ins0:listByDatabase></env:Body></env:Envelope>
HTTPI executes HTTP POST using the net_http adapter
warning: peer certificate won't be verified in this SSL session
SOAP response (status 500):
<?xml version='1.0' encoding='UTF-8'?><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"><soapenv:Body><soapenv:Fault><faultcode>soapenv:Server</faultcode><faultstring>Authentication failed</faultstring><detail /></soapenv:Fault></soapenv:Body></soapenv:Envelope>
So- two identical calls, two different results. What could be going wrong here, SO?
Edit: The Rails call fails because the Auth block is sent first, instead of last. I think the new question is how to enforce XML structure order without explicitly writing out the XML.
soap.body = {
:auth => {
#for the sake of testing
:login => "#{oxMasterUser}",
:password => "#{oxMasterPassword}"
},
:db => {
:id => 3
#For the sake of testing
}
:order! => [:db, :auth]
}
The order! will force a specific order based on that array. Lesson learned: Read the manual.

Resources