rubiii-savon 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +58 -0
- data/Rakefile +48 -0
- data/VERSION +1 -0
- data/lib/savon/service.rb +95 -0
- data/lib/savon/wsdl.rb +70 -0
- data/lib/savon.rb +47 -0
- data/spec/fixtures/soap_fault.xml +8 -0
- data/spec/fixtures/user_response.xml +13 -0
- data/spec/fixtures/user_wsdl.xml +106 -0
- data/spec/savon/service_spec.rb +64 -0
- data/spec/savon/wsdl_spec.rb +65 -0
- data/spec/spec_helper.rb +100 -0
- metadata +111 -0
data/README.rdoc
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
= Savon
|
2
|
+
|
3
|
+
Savon::Service is a SOAP client library to enjoy. The goal is to minimize
|
4
|
+
the overhead of working with SOAP services and provide a lightweight
|
5
|
+
alternative to other libraries.
|
6
|
+
|
7
|
+
== Install
|
8
|
+
|
9
|
+
$ gem install smacks-savon -s http://gems.github.com
|
10
|
+
|
11
|
+
== Dependencies
|
12
|
+
|
13
|
+
smacks-apricoteatsgorilla >= 0.5.2
|
14
|
+
hpricot 0.8.241 (the latest JRuby-compatible version)
|
15
|
+
|
16
|
+
Hpricot 0.8.241 is also available at: {Apricot eats Gorilla Downloads}[http://github.com/smacks/apricoteatsgorilla/downloads]
|
17
|
+
|
18
|
+
== How to use
|
19
|
+
|
20
|
+
Instantiate a new Savon::Service instance passing in the WSDL of your service.
|
21
|
+
|
22
|
+
proxy = Savon::Service.new("http://example.com/ExampleService?wsdl")
|
23
|
+
|
24
|
+
Call the SOAP service method of your choice on your Savon::Service instance.
|
25
|
+
|
26
|
+
response = proxy.get_all_users
|
27
|
+
|
28
|
+
Or pass in a Hash of options for the SOAP service to receive.
|
29
|
+
|
30
|
+
response = proxy.find_user_by_id(:id => 123)
|
31
|
+
|
32
|
+
Or specify a custom XPath-Expression to start translating the SOAP response at.
|
33
|
+
By default the response is translated starting at "//return".
|
34
|
+
|
35
|
+
response = proxy.find_user_by_id(nil, "//user/email")
|
36
|
+
|
37
|
+
=== Check for available SOAP actions
|
38
|
+
|
39
|
+
Access the WSDL to get an Array of SOAP actions found in the WSDL document.
|
40
|
+
|
41
|
+
proxy.wsdl.soap_actions
|
42
|
+
# => [ "getAllUsers", "findUserById" ]
|
43
|
+
|
44
|
+
=== Handle HTTP error and SOAP faults
|
45
|
+
|
46
|
+
Savon::Service raises a Savon::SOAPFault in case of a SOAP fault and a
|
47
|
+
Savon::HTTPError in case of an HTTP error.
|
48
|
+
|
49
|
+
=== Logging request and response
|
50
|
+
|
51
|
+
You should specify the logger to use before working with any service.
|
52
|
+
|
53
|
+
# example for Ruby on Rails
|
54
|
+
Savon.logger = RAILS_DEFAULT_LOGGER
|
55
|
+
|
56
|
+
Of course you can also specify the log level if needed. By default it's set to :debug.
|
57
|
+
|
58
|
+
Savon.log_level = :info
|
data/Rakefile
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "rake"
|
3
|
+
require "spec/rake/spectask"
|
4
|
+
require "rake/rdoctask"
|
5
|
+
|
6
|
+
task :default => :spec
|
7
|
+
|
8
|
+
Spec::Rake::SpecTask.new do |spec|
|
9
|
+
spec.spec_files = FileList["spec/**/*_spec.rb"]
|
10
|
+
spec.spec_opts << "--color"
|
11
|
+
end
|
12
|
+
|
13
|
+
Rake::RDocTask.new do |rdoc|
|
14
|
+
rdoc.title = "Savon"
|
15
|
+
rdoc.rdoc_dir = "rdoc"
|
16
|
+
rdoc.main = "README.rdoc"
|
17
|
+
rdoc.rdoc_files.include("README.rdoc", "lib/**/*.rb")
|
18
|
+
rdoc.options = ["--line-numbers", "--inline-source"]
|
19
|
+
end
|
20
|
+
|
21
|
+
begin
|
22
|
+
require "jeweler"
|
23
|
+
Jeweler::Tasks.new do |spec|
|
24
|
+
spec.name = "savon"
|
25
|
+
spec.author = "Daniel Harrington"
|
26
|
+
spec.email = "me@d-harrington.com"
|
27
|
+
spec.homepage = "http://github.com/smacks/savon"
|
28
|
+
spec.summary = "SOAP client library to enjoy."
|
29
|
+
spec.description = spec.summary
|
30
|
+
|
31
|
+
spec.files = FileList["[A-Z]*", "{lib,spec}/**/*.{rb,xml}"]
|
32
|
+
|
33
|
+
spec.rdoc_options += [
|
34
|
+
"--title", "Savon",
|
35
|
+
"--main", "README.rdoc",
|
36
|
+
"--line-numbers",
|
37
|
+
"--inline-source"
|
38
|
+
]
|
39
|
+
|
40
|
+
spec.add_runtime_dependency("hpricot", "0.8.241")
|
41
|
+
spec.add_runtime_dependency("smacks-apricoteatsgorilla", "0.5.2")
|
42
|
+
|
43
|
+
spec.add_development_dependency("rspec", ">= 1.2.8")
|
44
|
+
spec.add_development_dependency("rr", ">= 0.10.0")
|
45
|
+
end
|
46
|
+
rescue LoadError
|
47
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
48
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.2.0
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module Savon
|
2
|
+
|
3
|
+
# == Savon::Service
|
4
|
+
#
|
5
|
+
# Savon::Service is a SOAP client library to enjoy. The goal is to minimize
|
6
|
+
# the overhead of working with SOAP services and provide a lightweight
|
7
|
+
# alternative to other libraries.
|
8
|
+
#
|
9
|
+
# ==== Example
|
10
|
+
#
|
11
|
+
# proxy = Savon::Service.new("http://example.com/ExampleService?wsdl")
|
12
|
+
# response = proxy.find_user_by_id(:id => 123)
|
13
|
+
class Service
|
14
|
+
|
15
|
+
# Initializer expects an +endpoint+ URI.
|
16
|
+
def initialize(endpoint)
|
17
|
+
raise ArgumentError, "Invalid endpoint: #{endpoint}" unless /^http.+/ === endpoint
|
18
|
+
@endpoint = URI(endpoint)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns an instance of Savon::WSDL.
|
22
|
+
def wsdl
|
23
|
+
@wsdl ||= WSDL.new(@endpoint, http)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
# Dispatches a SOAP request, handles any HTTP errors and SOAP faults
|
29
|
+
# and returns the SOAP response.
|
30
|
+
def dispatch(soap_action, soap_body, response_xpath)
|
31
|
+
ApricotEatsGorilla.nodes_to_namespace = { :wsdl => wsdl.choice_elements }
|
32
|
+
headers, body = build_request_parameters(soap_action, soap_body)
|
33
|
+
|
34
|
+
Savon.log("SOAP request: #{@endpoint}")
|
35
|
+
Savon.log(headers.map { |k, v| "#{k}: #{v}" }.join(", "))
|
36
|
+
Savon.log(body)
|
37
|
+
|
38
|
+
response = http.request_post(@endpoint.path, body, headers)
|
39
|
+
|
40
|
+
Savon.log("SOAP response (status #{response.code})")
|
41
|
+
Savon.log(response.body)
|
42
|
+
|
43
|
+
soap_fault = ApricotEatsGorilla[response.body, "//soap:Fault"]
|
44
|
+
raise_soap_fault(soap_fault) if soap_fault && !soap_fault.empty?
|
45
|
+
raise_http_error(response) if response.code.to_i >= 300
|
46
|
+
|
47
|
+
ApricotEatsGorilla[response.body, response_xpath]
|
48
|
+
end
|
49
|
+
|
50
|
+
# Expects the requested +soap_action+ and +soap_body+ and builds and
|
51
|
+
# returns the request header and body to dispatch a SOAP request.
|
52
|
+
def build_request_parameters(soap_action, soap_body)
|
53
|
+
headers = { "Content-Type" => "text/xml; charset=utf-8", "SOAPAction" => soap_action }
|
54
|
+
body = ApricotEatsGorilla.soap_envelope(:wsdl => wsdl.namespace_uri) do
|
55
|
+
ApricotEatsGorilla["wsdl:#{soap_action}" => soap_body]
|
56
|
+
end
|
57
|
+
[headers, body]
|
58
|
+
end
|
59
|
+
|
60
|
+
# Expects a Hash containing information about a SOAP fault and raises
|
61
|
+
# a Savon::SOAPFault.
|
62
|
+
def raise_soap_fault(soap_fault)
|
63
|
+
raise SOAPFault, "#{soap_fault[:faultcode]}: #{soap_fault[:faultstring]}"
|
64
|
+
end
|
65
|
+
|
66
|
+
# Expects a Net::HTTPResponse and raises a Savon::HTTPError.
|
67
|
+
def raise_http_error(response)
|
68
|
+
raise HTTPError, "#{response.message} (#{response.code}): #{response.body}"
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns a Net::HTTP instance.
|
72
|
+
def http
|
73
|
+
@http ||= Net::HTTP.new(@endpoint.host, @endpoint.port)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Catches calls to SOAP actions, checks if the method called was found in
|
77
|
+
# the WSDL and dispatches the SOAP action if it's valid. Takes an optional
|
78
|
+
# Hash of options to be passed to the SOAP action and an optional XPath-
|
79
|
+
# Expression to define a custom XML root node to start parsing the SOAP
|
80
|
+
# response at.
|
81
|
+
def method_missing(method, *args)
|
82
|
+
soap_action = camelize(method)
|
83
|
+
super unless wsdl.soap_actions.include? soap_action
|
84
|
+
soap_body = args[0] || {}
|
85
|
+
response_xpath = args[1] || "//return"
|
86
|
+
dispatch(soap_action, soap_body, response_xpath)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Converts a given +string+ from snake_case to lowerCamelCase.
|
90
|
+
def camelize(string)
|
91
|
+
string.to_s.gsub(/_(.)/) { $1.upcase } if string
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
data/lib/savon/wsdl.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
module Savon
|
2
|
+
|
3
|
+
# Savon::WSDL represents the WSDL document.
|
4
|
+
class WSDL
|
5
|
+
|
6
|
+
# Returns the namespace URI.
|
7
|
+
def namespace_uri
|
8
|
+
@namespace ||= parse_namespace_uri
|
9
|
+
end
|
10
|
+
|
11
|
+
# Returns an Array of available SOAP actions.
|
12
|
+
def soap_actions
|
13
|
+
@soap_actions ||= parse_soap_actions
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns an Array of choice elements.
|
17
|
+
def choice_elements
|
18
|
+
@choice_elements ||= parse_choice_elements
|
19
|
+
end
|
20
|
+
|
21
|
+
# Initializer expects the endpoint +uri+ and a Net::HTTP instance (+http+).
|
22
|
+
def initialize(uri, http)
|
23
|
+
@uri, @http = uri, http
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns the body of the Net::HTTPResponse from the WSDL request.
|
27
|
+
def to_s
|
28
|
+
@response ? @response.body : nil
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# Returns an Hpricot::Document of the WSDL. Retrieves the WSDL from the
|
34
|
+
# endpoint URI in case it wasn't retrieved already.
|
35
|
+
def document
|
36
|
+
unless @document
|
37
|
+
@response = @http.get("#{@uri.path}?#{@uri.query}")
|
38
|
+
@document = Hpricot.XML(@response.body)
|
39
|
+
raise ArgumentError, "Unable to find WSDL at: #{@uri}" if
|
40
|
+
!soap_actions || soap_actions.empty?
|
41
|
+
end
|
42
|
+
@document
|
43
|
+
end
|
44
|
+
|
45
|
+
# Parses the WSDL for the namespace URI.
|
46
|
+
def parse_namespace_uri
|
47
|
+
definitions = document.at("//wsdl:definitions")
|
48
|
+
definitions.get_attribute("targetNamespace") if definitions
|
49
|
+
end
|
50
|
+
|
51
|
+
# Parses the WSDL for available SOAP actions.
|
52
|
+
def parse_soap_actions
|
53
|
+
soap_actions = document.search("//soap:operation")
|
54
|
+
|
55
|
+
soap_actions.collect do |soap_action|
|
56
|
+
soap_action.parent.get_attribute("name")
|
57
|
+
end if soap_actions
|
58
|
+
end
|
59
|
+
|
60
|
+
# Parses the WSDL for choice elements.
|
61
|
+
def parse_choice_elements
|
62
|
+
choice_elements = document.search("//xs:choice//xs:element")
|
63
|
+
|
64
|
+
choice_elements.collect do |choice_element|
|
65
|
+
choice_element.get_attribute("ref").sub(/(.+):/, "")
|
66
|
+
end if choice_elements
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
data/lib/savon.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
module Savon
|
2
|
+
|
3
|
+
# Raised by the <tt>on_http_error</tt> method in case of an HTTP error.
|
4
|
+
# <tt>on_http_error</tt> may be overwritten to customize error handling.
|
5
|
+
class HTTPError < StandardError; end
|
6
|
+
|
7
|
+
# Raised by the <tt>on_soap_fault</tt> method in case of a SOAP fault.
|
8
|
+
# <tt>on_soap_fault</tt> may be overwritten to customize error handling.
|
9
|
+
class SOAPFault < StandardError; end
|
10
|
+
|
11
|
+
# The logger to use.
|
12
|
+
@@logger = nil
|
13
|
+
|
14
|
+
# The log level to use.
|
15
|
+
@@log_level = :debug
|
16
|
+
|
17
|
+
# Sets the logger to use.
|
18
|
+
def self.logger=(logger)
|
19
|
+
@@logger = logger
|
20
|
+
end
|
21
|
+
|
22
|
+
# Sets the log level to use.
|
23
|
+
def self.log_level=(log_level)
|
24
|
+
@@log_level = log_level
|
25
|
+
end
|
26
|
+
|
27
|
+
# Logs a given +message+ using the +@@logger+ instance or yields the logger
|
28
|
+
# to a given +block+ for logging multiple messages at once.
|
29
|
+
def self.log(message = nil)
|
30
|
+
if @@logger
|
31
|
+
@@logger.send(@@log_level, message) if message
|
32
|
+
yield @@logger if block_given?
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
%w(net/http uri rubygems hpricot).each do |gem|
|
39
|
+
require gem
|
40
|
+
end
|
41
|
+
|
42
|
+
#require "apricoteatsgorilla"
|
43
|
+
require File.join(File.dirname(__FILE__), "..", "..", "apricoteatsgorilla", "lib", "apricoteatsgorilla")
|
44
|
+
|
45
|
+
%w(service wsdl).each do |file|
|
46
|
+
require File.join(File.dirname(__FILE__), "savon", file)
|
47
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
|
2
|
+
<soap:Body>
|
3
|
+
<ns2:findUserResponse xmlns:ns2="http://v1_0.ws.user.example.com">
|
4
|
+
<return>
|
5
|
+
<id>123</id>
|
6
|
+
<email>thedude@example.com</email>
|
7
|
+
<username>thedude</username>
|
8
|
+
<firstname>The</firstname>
|
9
|
+
<lastname>Dude</lastname>
|
10
|
+
</return>
|
11
|
+
</ns2:findUserResponse>
|
12
|
+
</soap:Body>
|
13
|
+
</soap:Envelope>
|
@@ -0,0 +1,106 @@
|
|
1
|
+
<wsdl:definitions
|
2
|
+
name="UserWebService"
|
3
|
+
targetNamespace="http://v1_0.ws.user.example.com"
|
4
|
+
xmlns:ns1="http://cxf.apache.org/bindings/xformat"
|
5
|
+
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
|
6
|
+
xmlns:tns="http://v1_0.ws.user.example.com"
|
7
|
+
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
|
8
|
+
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
9
|
+
|
10
|
+
<wsdl:types>
|
11
|
+
<xs:schema
|
12
|
+
attributeFormDefault="unqualified"
|
13
|
+
elementFormDefault="unqualified"
|
14
|
+
targetNamespace="http://v1_0.ws.user.example.com"
|
15
|
+
xmlns:tns="http://v1_0.ws.user.example.com"
|
16
|
+
xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
17
|
+
<xs:element name="findUser" type="tns:findUser" />
|
18
|
+
<xs:element name="findUserRequest" type="tns:findUserRequest" />
|
19
|
+
<xs:element name="baseFindUserRequest" type="tns:baseFindUserRequest" />
|
20
|
+
<xs:element name="idCredential" type="tns:idCredential" />
|
21
|
+
<xs:element name="emailCredential" type="tns:emailCredential" />
|
22
|
+
<xs:element name="findUserResponse" type="tns:findUserResponse" />
|
23
|
+
<xs:element name="userResponse" type="tns:userResponse" />
|
24
|
+
|
25
|
+
<xs:complexType name="findUser">
|
26
|
+
<xs:sequence>
|
27
|
+
<xs:element minOccurs="1" name="requestData" type="tns:findUserRequest" />
|
28
|
+
</xs:sequence>
|
29
|
+
</xs:complexType>
|
30
|
+
<xs:complexType name="findUserRequest">
|
31
|
+
<xs:complexContent>
|
32
|
+
<xs:extension base="tns:baseFindUserRequest">
|
33
|
+
<xs:sequence>
|
34
|
+
<xs:element minOccurs="0" name="mandant" type="tns:mandant" />
|
35
|
+
</xs:sequence>
|
36
|
+
</xs:extension>
|
37
|
+
</xs:complexContent>
|
38
|
+
</xs:complexType>
|
39
|
+
<xs:complexType name="baseFindUserRequest">
|
40
|
+
<xs:choice>
|
41
|
+
<xs:element ref="tns:idCredential" />
|
42
|
+
<xs:element ref="tns:emailCredential" />
|
43
|
+
</xs:choice>
|
44
|
+
</xs:complexType>
|
45
|
+
<xs:complexType name="idCredential">
|
46
|
+
<xs:sequence>
|
47
|
+
<xs:element name="id" type="xs:string" />
|
48
|
+
<xs:element name="token" type="xs:string" />
|
49
|
+
</xs:sequence>
|
50
|
+
</xs:complexType>
|
51
|
+
<xs:complexType name="emailCredential">
|
52
|
+
<xs:sequence>
|
53
|
+
<xs:element name="email" type="xs:string" />
|
54
|
+
<xs:element name="token" type="xs:string" />
|
55
|
+
</xs:sequence>
|
56
|
+
</xs:complexType>
|
57
|
+
<xs:complexType name="findUserResponse">
|
58
|
+
<xs:sequence>
|
59
|
+
<xs:element minOccurs="0" name="return" type="tns:userResponse" />
|
60
|
+
</xs:sequence>
|
61
|
+
</xs:complexType>
|
62
|
+
<xs:complexType name="userResponse">
|
63
|
+
<xs:sequence>
|
64
|
+
<xs:element minOccurs="0" name="id" type="xs:string" />
|
65
|
+
<xs:element minOccurs="0" name="email" type="xs:string" />
|
66
|
+
<xs:element minOccurs="0" name="username" type="xs:string" />
|
67
|
+
<xs:element minOccurs="0" name="firstname" type="xs:string" />
|
68
|
+
<xs:element minOccurs="0" name="lastname" type="xs:string" />
|
69
|
+
</xs:sequence>
|
70
|
+
</xs:complexType>
|
71
|
+
</xs:schema>
|
72
|
+
</wsdl:types>
|
73
|
+
|
74
|
+
<wsdl:message name="findUser">
|
75
|
+
<wsdl:part element="tns:findUser" name="parameters"></wsdl:part>
|
76
|
+
</wsdl:message>
|
77
|
+
<wsdl:message name="findUserResponse">
|
78
|
+
<wsdl:part element="tns:findUserResponse" name="parameters"></wsdl:part>
|
79
|
+
</wsdl:message>
|
80
|
+
|
81
|
+
<wsdl:portType name="UserWebService">
|
82
|
+
<wsdl:operation name="findUser">
|
83
|
+
<wsdl:input message="tns:findUser" name="findUser"></wsdl:input>
|
84
|
+
<wsdl:output message="tns:findUserResponse" name="findUserResponse"></wsdl:output>
|
85
|
+
</wsdl:operation>
|
86
|
+
</wsdl:portType>
|
87
|
+
|
88
|
+
<wsdl:binding name="UserWebServiceSoapBinding" type="tns:UserWebService">
|
89
|
+
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" />
|
90
|
+
<wsdl:operation name="findUser">
|
91
|
+
<soap:operation soapAction="" style="document" />
|
92
|
+
<wsdl:input name="findUser">
|
93
|
+
<soap:body use="literal" />
|
94
|
+
</wsdl:input>
|
95
|
+
<wsdl:output name="findUserResponse">
|
96
|
+
<soap:body use="literal" />
|
97
|
+
</wsdl:output>
|
98
|
+
</wsdl:operation>
|
99
|
+
</wsdl:binding>
|
100
|
+
|
101
|
+
<wsdl:service name="UserWebService">
|
102
|
+
<wsdl:port binding="tns:UserWebServiceSoapBinding" name="UserWebServicePort">
|
103
|
+
<soap:address location="http://example.com/user/1.0/UserService" />
|
104
|
+
</wsdl:port>
|
105
|
+
</wsdl:service>
|
106
|
+
</wsdl:definitions>
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "..", "spec_helper")
|
2
|
+
|
3
|
+
describe Savon::Service do
|
4
|
+
include SpecHelper
|
5
|
+
|
6
|
+
# initialize
|
7
|
+
describe "initialize" do
|
8
|
+
it "raises an ArgumentError when called with an invalid endpoint" do
|
9
|
+
["", nil, "invalid", 123].each do |arg|
|
10
|
+
lambda { Savon::Service.new(arg) }.should raise_error(ArgumentError)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# wsdl
|
16
|
+
describe "wsdl" do
|
17
|
+
before { @service = new_service_instance }
|
18
|
+
|
19
|
+
it "returns an instance of Savon::WSDL" do
|
20
|
+
@service.wsdl.should be_a(Savon::WSDL)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "returns the exact same Savon::WSDL instance every time" do
|
24
|
+
@service.wsdl.should equal(@service.wsdl)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# method_missing
|
29
|
+
describe "method_missing" do
|
30
|
+
before { @service = new_service_instance }
|
31
|
+
|
32
|
+
it "raises a NoMethodError when called with an invalid soap_action" do
|
33
|
+
lambda { @service.invalid_action }.should raise_error(NoMethodError)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "by default returns content from the response using the '//return' XPath" do
|
37
|
+
@service.find_user.should == { :firstname => "The", :lastname => "Dude",
|
38
|
+
:email => "thedude@example.com", :username => "thedude", :id => "123" }
|
39
|
+
end
|
40
|
+
|
41
|
+
it "returns the content of the response starting at a custom XPath" do
|
42
|
+
@service.find_user(nil, "//email").should == "thedude@example.com"
|
43
|
+
end
|
44
|
+
|
45
|
+
it "returns nil if a given XPath does not match anything in the SOAP response" do
|
46
|
+
@service.find_user(nil, "//doesNotMatchAnything").should be_nil
|
47
|
+
end
|
48
|
+
|
49
|
+
it "raises a Savon::SOAPFault in case of a SOAP fault" do
|
50
|
+
@service = new_service_instance(:soap_fault => true)
|
51
|
+
lambda { @service.find_user }.should raise_error(Savon::SOAPFault)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "raises a Savon::HTTPError in case the server returned an error code and no SOAP fault" do
|
55
|
+
@service = new_service_instance(:http_error => true)
|
56
|
+
lambda { @service.find_user }.should raise_error(Savon::HTTPError)
|
57
|
+
end
|
58
|
+
|
59
|
+
it "raises a Savon::SOAPFault in case the server returned an error code and a SOAP fault" do
|
60
|
+
@service = new_service_instance(:soap_fault => true, :http_error => true)
|
61
|
+
lambda { @service.find_user }.should raise_error(Savon::SOAPFault)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "..", "spec_helper")
|
2
|
+
|
3
|
+
describe Savon::WSDL do
|
4
|
+
include SpecHelper
|
5
|
+
|
6
|
+
#namespace_uri
|
7
|
+
describe "namespace_uri" do
|
8
|
+
before { @wsdl = new_wsdl }
|
9
|
+
|
10
|
+
it "returns the namespace URI from the WSDL" do
|
11
|
+
@wsdl.namespace_uri == UserFixture.namespace_uri
|
12
|
+
end
|
13
|
+
|
14
|
+
it "returns the same object every time" do
|
15
|
+
@wsdl.namespace_uri.should equal(@wsdl.namespace_uri)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# soap_actions
|
20
|
+
describe "soap_actions" do
|
21
|
+
before { @wsdl = new_wsdl }
|
22
|
+
|
23
|
+
it "returns the SOAP actions from the WSDL" do
|
24
|
+
@wsdl.soap_actions == UserFixture.soap_actions
|
25
|
+
end
|
26
|
+
|
27
|
+
it "returns the same object every time" do
|
28
|
+
@wsdl.soap_actions.should equal(@wsdl.soap_actions)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# choice_elements
|
33
|
+
describe "choice_elements" do
|
34
|
+
before { @wsdl = new_wsdl }
|
35
|
+
|
36
|
+
it "returns the choice elements from the WSDL" do
|
37
|
+
@wsdl.choice_elements == UserFixture.choice_elements
|
38
|
+
end
|
39
|
+
|
40
|
+
it "returns the same object every time" do
|
41
|
+
@wsdl.choice_elements.should equal(@wsdl.choice_elements)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# initialize
|
46
|
+
describe "initialize" do
|
47
|
+
it "expects an endpoint URI and a Net::HTTP instance" do
|
48
|
+
@wsdl = Savon::WSDL.new(some_uri, some_http)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# to_s
|
53
|
+
describe "to_s" do
|
54
|
+
before { @wsdl = new_wsdl }
|
55
|
+
|
56
|
+
it "returns nil before the WSDL document was retrieved" do
|
57
|
+
@wsdl.to_s.should be_nil
|
58
|
+
end
|
59
|
+
|
60
|
+
it "returns the response body when available" do
|
61
|
+
@wsdl.soap_actions # trigger http request
|
62
|
+
@wsdl.to_s.should == UserFixture.user_wsdl
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
gem "rspec", ">= 1.2.8"
|
3
|
+
require "spec"
|
4
|
+
require "rr"
|
5
|
+
require File.join(File.dirname(__FILE__), "..", "lib", "savon")
|
6
|
+
|
7
|
+
Spec::Runner.configure do |config|
|
8
|
+
config.mock_with :rr
|
9
|
+
end
|
10
|
+
|
11
|
+
module SpecHelper
|
12
|
+
def some_url
|
13
|
+
"http://example.com"
|
14
|
+
end
|
15
|
+
|
16
|
+
def some_uri
|
17
|
+
URI(some_url)
|
18
|
+
end
|
19
|
+
|
20
|
+
def some_http
|
21
|
+
Net::HTTP.new(some_uri.host, some_uri.port)
|
22
|
+
end
|
23
|
+
|
24
|
+
def new_wsdl
|
25
|
+
Savon::WSDL.new(some_uri, UserFixture.http_mock)
|
26
|
+
end
|
27
|
+
|
28
|
+
def new_service_instance(options = {})
|
29
|
+
service = Savon::Service.new(some_url)
|
30
|
+
service.instance_variable_set("@http", UserFixture.http_mock(options))
|
31
|
+
service
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class UserFixture
|
36
|
+
extend RR::Adapters::RRMethods
|
37
|
+
|
38
|
+
class << self
|
39
|
+
include SpecHelper
|
40
|
+
|
41
|
+
def namespace_uri
|
42
|
+
"http://v1_0.ws.user.example.com"
|
43
|
+
end
|
44
|
+
|
45
|
+
def soap_actions
|
46
|
+
%w(findUser)
|
47
|
+
end
|
48
|
+
|
49
|
+
def choice_elements
|
50
|
+
%w(idCredential emailCredential)
|
51
|
+
end
|
52
|
+
|
53
|
+
def user_wsdl
|
54
|
+
load_fixture("user_wsdl.xml")
|
55
|
+
end
|
56
|
+
|
57
|
+
def user_response
|
58
|
+
load_fixture("user_response.xml")
|
59
|
+
end
|
60
|
+
|
61
|
+
def soap_fault
|
62
|
+
load_fixture("soap_fault.xml")
|
63
|
+
end
|
64
|
+
|
65
|
+
def http_mock(options = {})
|
66
|
+
response_body = options[:soap_fault] ? soap_fault : user_response
|
67
|
+
response_code = options[:http_error] ? 500 : 200
|
68
|
+
response_body = "" if options[:http_error] && !options[:soap_fault]
|
69
|
+
generate_http_mock(soap_response_mock(response_body, response_code))
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def load_fixture(file)
|
75
|
+
file_path = File.join(File.dirname(__FILE__), "fixtures", file)
|
76
|
+
IO.readlines(file_path, "").to_s
|
77
|
+
end
|
78
|
+
|
79
|
+
def generate_http_mock(soap_response)
|
80
|
+
mock = some_http
|
81
|
+
stub(mock).get(anything) { wsdl_response_mock(user_wsdl) }
|
82
|
+
stub(mock).request_post(anything, anything, anything) { soap_response }
|
83
|
+
mock
|
84
|
+
end
|
85
|
+
|
86
|
+
def wsdl_response_mock(response_body)
|
87
|
+
mock = mock()
|
88
|
+
stub(mock).body { response_body }
|
89
|
+
mock
|
90
|
+
end
|
91
|
+
|
92
|
+
def soap_response_mock(response_body, response_code)
|
93
|
+
mock = mock()
|
94
|
+
stub(mock).body { response_body }
|
95
|
+
stub(mock).code { response_code }
|
96
|
+
stub(mock).message { "whatever" }
|
97
|
+
mock
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
metadata
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rubiii-savon
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Daniel Harrington
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-08-30 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: hpricot
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - "="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.8.241
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: smacks-apricoteatsgorilla
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.5.2
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: rspec
|
37
|
+
type: :development
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 1.2.8
|
44
|
+
version:
|
45
|
+
- !ruby/object:Gem::Dependency
|
46
|
+
name: rr
|
47
|
+
type: :development
|
48
|
+
version_requirement:
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 0.10.0
|
54
|
+
version:
|
55
|
+
description: SOAP client library to enjoy.
|
56
|
+
email: me@d-harrington.com
|
57
|
+
executables: []
|
58
|
+
|
59
|
+
extensions: []
|
60
|
+
|
61
|
+
extra_rdoc_files:
|
62
|
+
- README.rdoc
|
63
|
+
files:
|
64
|
+
- README.rdoc
|
65
|
+
- Rakefile
|
66
|
+
- VERSION
|
67
|
+
- lib/savon.rb
|
68
|
+
- lib/savon/service.rb
|
69
|
+
- lib/savon/wsdl.rb
|
70
|
+
- spec/fixtures/soap_fault.xml
|
71
|
+
- spec/fixtures/user_response.xml
|
72
|
+
- spec/fixtures/user_wsdl.xml
|
73
|
+
- spec/savon/service_spec.rb
|
74
|
+
- spec/savon/wsdl_spec.rb
|
75
|
+
- spec/spec_helper.rb
|
76
|
+
has_rdoc: true
|
77
|
+
homepage: http://github.com/smacks/savon
|
78
|
+
post_install_message:
|
79
|
+
rdoc_options:
|
80
|
+
- --charset=UTF-8
|
81
|
+
- --title
|
82
|
+
- Savon
|
83
|
+
- --main
|
84
|
+
- README.rdoc
|
85
|
+
- --line-numbers
|
86
|
+
- --inline-source
|
87
|
+
require_paths:
|
88
|
+
- lib
|
89
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: "0"
|
94
|
+
version:
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: "0"
|
100
|
+
version:
|
101
|
+
requirements: []
|
102
|
+
|
103
|
+
rubyforge_project:
|
104
|
+
rubygems_version: 1.2.0
|
105
|
+
signing_key:
|
106
|
+
specification_version: 2
|
107
|
+
summary: SOAP client library to enjoy.
|
108
|
+
test_files:
|
109
|
+
- spec/savon/service_spec.rb
|
110
|
+
- spec/savon/wsdl_spec.rb
|
111
|
+
- spec/spec_helper.rb
|