savon 1.1.0 → 1.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/CHANGELOG.md +127 -67
- data/lib/savon/client.rb +62 -83
- data/lib/savon/http/error.rb +1 -1
- data/lib/savon/soap/request_builder.rb +205 -0
- data/lib/savon/soap/xml.rb +1 -1
- data/lib/savon/version.rb +1 -1
- data/spec/fixtures/wsdl/multiple_namespaces.xml +31 -0
- data/spec/integration/request_spec.rb +50 -11
- data/spec/savon/client_spec.rb +181 -104
- data/spec/savon/http/error_spec.rb +1 -1
- data/spec/savon/soap/fault_spec.rb +1 -1
- data/spec/savon/soap/request_builder_spec.rb +207 -0
- metadata +42 -40
- data/lib/savon/wasabi/document.rb +0 -47
- data/spec/savon/wasabi/document_spec.rb +0 -58
data/lib/savon/http/error.rb
CHANGED
@@ -0,0 +1,205 @@
|
|
1
|
+
module Savon
|
2
|
+
module SOAP
|
3
|
+
|
4
|
+
# = Savon::SOAP::RequestBuilder
|
5
|
+
#
|
6
|
+
# Savon::SOAP::RequestBuilder builds Savon::SOAP::Request instances.
|
7
|
+
# The RequestBuilder is configured by the client that instantiates it.
|
8
|
+
# It uses the options set by the client to build an appropriate request.
|
9
|
+
class RequestBuilder
|
10
|
+
|
11
|
+
# Initialize a new +RequestBuilder+ with the given SOAP operation.
|
12
|
+
# The operation may be specified using a symbol or a string.
|
13
|
+
def initialize(operation, options = {})
|
14
|
+
@operation = operation
|
15
|
+
assign_options(options)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Writer for the <tt>HTTPI::Request</tt> object.
|
19
|
+
attr_writer :http
|
20
|
+
|
21
|
+
# Writer for the <tt>Savon::SOAP::XML</tt> object.
|
22
|
+
attr_writer :soap
|
23
|
+
|
24
|
+
# Writer for the <tt>Akami::WSSE</tt> object.
|
25
|
+
attr_writer :wsse
|
26
|
+
|
27
|
+
# Writer for the <tt>Wasabi::Document</tt> object.
|
28
|
+
attr_writer :wsdl
|
29
|
+
|
30
|
+
# Writer for the <tt>Savon::Config</tt> object.
|
31
|
+
attr_writer :config
|
32
|
+
|
33
|
+
# Writer for the attributes of the SOAP input tag. Accepts a Hash.
|
34
|
+
attr_writer :attributes
|
35
|
+
|
36
|
+
# Writer for the namespace identifer of the <tt>Savon::SOAP::XML</tt>
|
37
|
+
# object.
|
38
|
+
attr_writer :namespace_identifier
|
39
|
+
|
40
|
+
# Writer for the SOAP action of the <tt>Savon::SOAP::XML</tt> object.
|
41
|
+
attr_writer :soap_action
|
42
|
+
|
43
|
+
# Reader for the operation of the request being built by the request builder.
|
44
|
+
attr_reader :operation
|
45
|
+
|
46
|
+
# Builds and returns a <tt>Savon::SOAP::Request</tt> object. You may optionally
|
47
|
+
# pass a block to the method that will be run after the initial configuration of
|
48
|
+
# the dependencies. +self+ will be yielded to the block if the block accepts an
|
49
|
+
# argument.
|
50
|
+
def request(&post_configuration_block)
|
51
|
+
configure_dependencies
|
52
|
+
|
53
|
+
if post_configuration_block
|
54
|
+
# Only yield self to the block if our block takes an argument
|
55
|
+
args = [] and (args << self if post_configuration_block.arity == 1)
|
56
|
+
post_configuration_block.call(*args)
|
57
|
+
end
|
58
|
+
|
59
|
+
Request.new(config, http, soap)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns the identifier for the default namespace. If an operation namespace
|
63
|
+
# identifier is defined for the current operation in the WSDL document, this
|
64
|
+
# namespace identifier is used. Otherwise, the +@namespace_identifier+ instance
|
65
|
+
# variable is used.
|
66
|
+
def namespace_identifier
|
67
|
+
if operation_namespace_defined_in_wsdl?
|
68
|
+
wsdl.operations[operation][:namespace_identifier].to_sym
|
69
|
+
else
|
70
|
+
@namespace_identifier
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns the namespace identifier to be used for the the SOAP input tag.
|
75
|
+
# If +@namespace_identifier+ is not +nil+, it will be returned. Otherwise, the
|
76
|
+
# default namespace identifier as returned by +namespace_identifier+ will be
|
77
|
+
# returned.
|
78
|
+
def input_namespace_identifier
|
79
|
+
@namespace_identifier || namespace_identifier
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns the default namespace to be used for the SOAP request. If a namespace
|
83
|
+
# is defined for the operation in the WSDL document, this namespace will be
|
84
|
+
# returned. Otherwise, the default WSDL document namespace will be returned.
|
85
|
+
def namespace
|
86
|
+
if operation_namespace_defined_in_wsdl?
|
87
|
+
wsdl.parser.namespaces[namespace_identifier.to_s]
|
88
|
+
else
|
89
|
+
wsdl.namespace
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Returns true if the operation's namespace is defined within the WSDL
|
94
|
+
# document.
|
95
|
+
def operation_namespace_defined_in_wsdl?
|
96
|
+
return false unless wsdl.document?
|
97
|
+
(operation = wsdl.operations[self.operation]) && operation[:namespace_identifier]
|
98
|
+
end
|
99
|
+
|
100
|
+
# Returns the SOAP action. If +@soap_action+ has been defined, this will
|
101
|
+
# be returned. Otherwise, if there is a WSDL document defined, the SOAP
|
102
|
+
# action corresponding to the operation will be returned. Failing this,
|
103
|
+
# the operation name will be used to form the SOAP action.
|
104
|
+
def soap_action
|
105
|
+
return @soap_action if @soap_action
|
106
|
+
|
107
|
+
if wsdl.document?
|
108
|
+
wsdl.soap_action(operation.to_sym)
|
109
|
+
else
|
110
|
+
Gyoku::XMLKey.create(operation).to_sym
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Returns the SOAP operation input tag. If there is a WSDL document defined,
|
115
|
+
# and the operation's input tag is defined in the document, this will be
|
116
|
+
# returned. Otherwise, the operation name will be used to form the input tag.
|
117
|
+
def soap_input_tag
|
118
|
+
if wsdl.document? && (input = wsdl.soap_input(operation.to_sym))
|
119
|
+
input
|
120
|
+
else
|
121
|
+
Gyoku::XMLKey.create(operation)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# Changes the body of the SOAP request to +body+.
|
126
|
+
def body=(body)
|
127
|
+
soap.body = body
|
128
|
+
end
|
129
|
+
|
130
|
+
# Returns the body of the SOAP request.
|
131
|
+
def body
|
132
|
+
soap.body
|
133
|
+
end
|
134
|
+
|
135
|
+
# Returns the attributes of the SOAP input tag. Defaults to
|
136
|
+
# an empty Hash.
|
137
|
+
def attributes
|
138
|
+
@attributes ||= {}
|
139
|
+
end
|
140
|
+
|
141
|
+
# Returns the <tt>Savon::Config</tt> object for the request. Defaults
|
142
|
+
# to a clone of <tt>Savon.config</tt>.
|
143
|
+
def config
|
144
|
+
@config ||= Savon.config.clone
|
145
|
+
end
|
146
|
+
|
147
|
+
# Returns the <tt>HTTPI::Request</tt> object.
|
148
|
+
def http
|
149
|
+
@http ||= HTTPI::Request.new
|
150
|
+
end
|
151
|
+
|
152
|
+
# Returns the <tt>SOAP::XML</tt> object.
|
153
|
+
def soap
|
154
|
+
@soap ||= XML.new(config)
|
155
|
+
end
|
156
|
+
|
157
|
+
# Returns the <tt>Wasabi::Document</tt> object.
|
158
|
+
def wsdl
|
159
|
+
@wsdl ||= Wasabi::Document.new
|
160
|
+
end
|
161
|
+
|
162
|
+
# Returns the <tt>Akami::WSSE</tt> object.
|
163
|
+
def wsse
|
164
|
+
@wsse ||= Akami.wsse
|
165
|
+
end
|
166
|
+
|
167
|
+
private
|
168
|
+
|
169
|
+
def configure_dependencies
|
170
|
+
soap.endpoint = wsdl.endpoint
|
171
|
+
soap.element_form_default = wsdl.element_form_default
|
172
|
+
soap.wsse = wsse
|
173
|
+
|
174
|
+
soap.namespace = namespace
|
175
|
+
soap.namespace_identifier = namespace_identifier
|
176
|
+
|
177
|
+
add_wsdl_namespaces_to_soap
|
178
|
+
add_wsdl_types_to_soap
|
179
|
+
|
180
|
+
soap.input = [input_namespace_identifier, soap_input_tag.to_sym, attributes]
|
181
|
+
|
182
|
+
http.headers["SOAPAction"] = %{"#{soap_action}"}
|
183
|
+
end
|
184
|
+
|
185
|
+
def add_wsdl_namespaces_to_soap
|
186
|
+
wsdl.type_namespaces.each do |path, uri|
|
187
|
+
soap.use_namespace(path, uri)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def add_wsdl_types_to_soap
|
192
|
+
wsdl.type_definitions.each do |path, type|
|
193
|
+
soap.types[path] = type
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def assign_options(options)
|
198
|
+
options.each do |option, value|
|
199
|
+
send(:"#{option}=", value) if value
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
data/lib/savon/soap/xml.rb
CHANGED
@@ -220,7 +220,7 @@ module Savon
|
|
220
220
|
|
221
221
|
def add_namespaces_to_body(hash, path = [input[1].to_s])
|
222
222
|
return unless hash
|
223
|
-
return hash if hash.kind_of?(Array)
|
223
|
+
return hash.map { |value| add_namespaces_to_body(value, path) } if hash.kind_of?(Array)
|
224
224
|
return hash.to_s unless hash.kind_of? Hash
|
225
225
|
|
226
226
|
hash.inject({}) do |newhash, (key, value)|
|
data/lib/savon/version.rb
CHANGED
@@ -19,8 +19,20 @@
|
|
19
19
|
</s:sequence>
|
20
20
|
</s:complexType>
|
21
21
|
</s:element>
|
22
|
+
<s:element name="Lookup">
|
23
|
+
<s:complexType>
|
24
|
+
<s:sequence>
|
25
|
+
<s:element minOccurs="0" maxOccurs="1" name="articles" type="article:ArrayOfArticle" />
|
26
|
+
</s:sequence>
|
27
|
+
</s:complexType>
|
28
|
+
</s:element>
|
22
29
|
</s:schema>
|
23
30
|
<s:schema elementFormDefault="qualified" targetNamespace="http://example.com/article">
|
31
|
+
<s:complexType name="ArrayOfArticle">
|
32
|
+
<s:sequence>
|
33
|
+
<s:element minOccurs="0" maxOccurs="unbounded" name="Article" type="article:Article"/>
|
34
|
+
</s:sequence>
|
35
|
+
</s:complexType>
|
24
36
|
<s:complexType name="Article">
|
25
37
|
<s:sequence>
|
26
38
|
<s:element minOccurs="0" name="Author" type="s:string"/>
|
@@ -35,11 +47,21 @@
|
|
35
47
|
<message name="SaveSoapOut">
|
36
48
|
<part name="parameters" element="actions:SaveResponse"/>
|
37
49
|
</message>
|
50
|
+
<message name="LookupSoapIn">
|
51
|
+
<part name="parameters" element="actions:Lookup"/>
|
52
|
+
</message>
|
53
|
+
<message name="LookupSoapOut">
|
54
|
+
<part name="parameters" element="actions:LookupResponse"/>
|
55
|
+
</message>
|
38
56
|
<portType name="ArticleSoap">
|
39
57
|
<operation name="Save">
|
40
58
|
<input message="actions:SaveSoapIn"/>
|
41
59
|
<output message="actions:SaveSoapOut"/>
|
42
60
|
</operation>
|
61
|
+
<operation name="Lookup">
|
62
|
+
<input message="actions:LookupSoapIn"/>
|
63
|
+
<input message="actions:LookupSoapOut"/>
|
64
|
+
</operation>
|
43
65
|
</portType>
|
44
66
|
<binding name="ArticleSoap" type="actions:ArticleSoap">
|
45
67
|
<soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/>
|
@@ -52,6 +74,15 @@
|
|
52
74
|
<soap:body use="literal"/>
|
53
75
|
</output>
|
54
76
|
</operation>
|
77
|
+
<operation name="Lookup">
|
78
|
+
<soap:operation soapAction="http://example.com/actions.Lookup" style="document"/>
|
79
|
+
<input>
|
80
|
+
<soap:body use="literal"/>
|
81
|
+
</input>
|
82
|
+
<output>
|
83
|
+
<soap:body use="literal"/>
|
84
|
+
</output>
|
85
|
+
</operation>
|
55
86
|
</binding>
|
56
87
|
<service name="StudyMDL">
|
57
88
|
<port name="StudyMDLSoap" binding="actions:StudyMDLSoap">
|
@@ -2,21 +2,60 @@ require "spec_helper"
|
|
2
2
|
|
3
3
|
describe "Integration" do
|
4
4
|
|
5
|
-
|
6
|
-
client = Savon.client(
|
7
|
-
|
5
|
+
subject(:client) {
|
6
|
+
client = Savon.client(service_endpoint)
|
7
|
+
client.http.open_timeout = 10
|
8
|
+
client.http.read_timeout = 10
|
9
|
+
client
|
10
|
+
}
|
8
11
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
+
context "stockquote" do
|
13
|
+
let(:service_endpoint) { "http://www.webservicex.net/stockquote.asmx?WSDL" }
|
14
|
+
|
15
|
+
it "returns the result in a CDATA tag" do
|
16
|
+
response = client.request(:get_quote, :body => { :symbol => "AAPL" })
|
17
|
+
|
18
|
+
cdata = response[:get_quote_response][:get_quote_result]
|
19
|
+
result = Nori.parse(cdata)
|
20
|
+
result[:stock_quotes][:stock][:symbol].should == "AAPL"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context "email" do
|
25
|
+
let(:service_endpoint) { "http://ws.cdyne.com/emailverify/Emailvernotestemail.asmx?wsdl" }
|
26
|
+
|
27
|
+
it "passes Strings as they are" do
|
28
|
+
response = client.request(:verify_email, :body => { :email => "soap@example.com", "LicenseKey" => "?" })
|
29
|
+
|
30
|
+
response_text = response[:verify_email_response][:verify_email_result][:response_text]
|
31
|
+
response_text.should == "Email Domain Not Found"
|
32
|
+
end
|
12
33
|
end
|
13
34
|
|
14
|
-
|
15
|
-
|
16
|
-
|
35
|
+
context "zip code" do
|
36
|
+
let(:service_endpoint) { "http://www.thomas-bayer.com/axis2/services/BLZService?wsdl" }
|
37
|
+
|
38
|
+
it "supports threads making requests simultaneously" do
|
39
|
+
mutex = Mutex.new
|
40
|
+
|
41
|
+
request_data = [70070010, 24050110, 20050550]
|
42
|
+
threads_waiting = request_data.size
|
43
|
+
|
44
|
+
threads = request_data.map do |blz|
|
45
|
+
Thread.new do
|
46
|
+
response = client.request :get_bank, :body => { :blz => blz }
|
47
|
+
Thread.current[:value] = response[:get_bank_response][:details]
|
48
|
+
mutex.synchronize { threads_waiting -= 1 }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
sleep(1) until threads_waiting == 0
|
53
|
+
|
54
|
+
threads.each &:kill
|
55
|
+
values = threads.map { |thr| thr[:value] }.compact
|
17
56
|
|
18
|
-
|
19
|
-
|
57
|
+
values.uniq.size.should == values.size
|
58
|
+
end
|
20
59
|
end
|
21
60
|
|
22
61
|
end
|
data/spec/savon/client_spec.rb
CHANGED
@@ -14,14 +14,14 @@ describe Savon::Client do
|
|
14
14
|
|
15
15
|
context "with a block expecting one argument" do
|
16
16
|
it "should yield the WSDL object" do
|
17
|
-
Savon::Client.new { |wsdl| wsdl.should be_a(
|
17
|
+
Savon::Client.new { |wsdl| wsdl.should be_a(Wasabi::Document) }
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
21
|
context "with a block expecting two arguments" do
|
22
22
|
it "should yield the WSDL and HTTP objects" do
|
23
23
|
Savon::Client.new do |wsdl, http|
|
24
|
-
wsdl.should be_an(
|
24
|
+
wsdl.should be_an(Wasabi::Document)
|
25
25
|
http.should be_an(HTTPI::Request)
|
26
26
|
end
|
27
27
|
end
|
@@ -30,7 +30,7 @@ describe Savon::Client do
|
|
30
30
|
context "with a block expecting three arguments" do
|
31
31
|
it "should yield the WSDL, HTTP and WSSE objects" do
|
32
32
|
Savon::Client.new do |wsdl, http, wsse|
|
33
|
-
wsdl.should be_an(
|
33
|
+
wsdl.should be_an(Wasabi::Document)
|
34
34
|
http.should be_an(HTTPI::Request)
|
35
35
|
wsse.should be_an(Akami::WSSE)
|
36
36
|
end
|
@@ -39,7 +39,7 @@ describe Savon::Client do
|
|
39
39
|
|
40
40
|
context "with a block expecting no arguments" do
|
41
41
|
it "should let you access the WSDL object" do
|
42
|
-
Savon::Client.new { wsdl.should be_a(
|
42
|
+
Savon::Client.new { wsdl.should be_a(Wasabi::Document) }
|
43
43
|
end
|
44
44
|
|
45
45
|
it "should let you access the HTTP object" do
|
@@ -54,7 +54,7 @@ describe Savon::Client do
|
|
54
54
|
|
55
55
|
describe "#wsdl" do
|
56
56
|
it "should return the Savon::Wasabi::Document" do
|
57
|
-
client.wsdl.should be_a(
|
57
|
+
client.wsdl.should be_a(Wasabi::Document)
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
@@ -71,9 +71,16 @@ describe Savon::Client do
|
|
71
71
|
end
|
72
72
|
|
73
73
|
describe "#request" do
|
74
|
+
let(:request_builder) { stub_everything('request_builder') }
|
75
|
+
let(:response) { mock('response') }
|
76
|
+
|
74
77
|
before do
|
75
78
|
HTTPI.stubs(:get).returns(new_response(:body => Fixture.wsdl(:authentication)))
|
76
79
|
HTTPI.stubs(:post).returns(new_response)
|
80
|
+
|
81
|
+
Savon::SOAP::RequestBuilder.stubs(:new).returns(request_builder)
|
82
|
+
request_builder.stubs(:request).returns(stub(:response => response))
|
83
|
+
response.stubs(:http).returns(new_response)
|
77
84
|
end
|
78
85
|
|
79
86
|
context "without any arguments" do
|
@@ -82,73 +89,61 @@ describe Savon::Client do
|
|
82
89
|
end
|
83
90
|
end
|
84
91
|
|
85
|
-
|
86
|
-
it "
|
87
|
-
|
92
|
+
describe "setting dependencies of the request builder" do
|
93
|
+
it "sets the wsdl property with the client's WSDL document" do
|
94
|
+
request_builder.expects(:wsdl=).with(client.wsdl)
|
95
|
+
client.request(:get_user)
|
88
96
|
end
|
89
97
|
|
90
|
-
it "
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
client.request :get_user
|
98
|
+
it "sets the http property with an HTTPI::Request object" do
|
99
|
+
request_builder.expects(:http=).with { |http| http.is_a?(HTTPI::Request) }
|
100
|
+
client.request(:get_user)
|
95
101
|
end
|
96
102
|
|
97
|
-
it "
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
client.request(:get_user) { soap.namespace = nil }
|
103
|
+
it "sets the wsse property with an Akami:WSSE object" do
|
104
|
+
request_builder.expects(:wsse=).with { |wsse| wsse.is_a?(Akami::WSSE) }
|
105
|
+
client.request(:get_user)
|
102
106
|
end
|
103
107
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
end
|
108
|
-
|
109
|
-
it "sets the soap's namespace identifier to the matching operation's namespace identifier" do
|
110
|
-
client.request(:authenticate) { soap.namespace_identifier.should == :tns }
|
111
|
-
end
|
112
|
-
|
113
|
-
it "sets the soap's namespace to the namspace matching the identifier" do
|
114
|
-
client.request(:authenticate) { soap.namespace.should == "http://v1_0.ws.auth.order.example.com/" }
|
115
|
-
end
|
116
|
-
|
117
|
-
it "sets the input tag to result in <tns:authenticate>" do
|
118
|
-
client.request(:authenticate) { soap.input.should == [:tns, :authenticate, {}] }
|
119
|
-
end
|
108
|
+
it "sets the config property with a Savon::Config object" do
|
109
|
+
request_builder.expects(:config=).with { |config| config.is_a?(Savon::Config) }
|
110
|
+
client.request(:get_user)
|
120
111
|
end
|
121
112
|
end
|
122
113
|
|
123
|
-
context "with a single argument
|
124
|
-
it "
|
125
|
-
|
114
|
+
context "with a single argument" do
|
115
|
+
it "sets the operation of the request builder to the argument" do
|
116
|
+
expect_request_builder_to_receive(:get_user)
|
117
|
+
client.request(:get_user)
|
126
118
|
end
|
127
119
|
end
|
128
120
|
|
129
121
|
context "with a Symbol and a Hash" do
|
130
|
-
it "
|
131
|
-
|
122
|
+
it "uses the hash to set attributes of the request builder" do
|
123
|
+
expect_request_builder_to_receive(:get_user, :attributes => { :active => true })
|
124
|
+
client.request(:get_user, :active => true)
|
132
125
|
end
|
133
126
|
|
134
|
-
it "
|
135
|
-
|
127
|
+
it "uses the :soap_action key of the hash to set the SOAP action of the request builder" do
|
128
|
+
expect_request_builder_to_receive(:get_user, :soap_action => :test_action)
|
129
|
+
client.request(:get_user, :soap_action => :test_action)
|
130
|
+
end
|
131
|
+
|
132
|
+
it "uses the :body key of the hash to set the SOAP body of the request builder" do
|
133
|
+
expect_request_builder_to_receive(:get_user, :body => { :foo => "bar" })
|
134
|
+
client.request(:get_user, :body => { :foo => "bar" })
|
136
135
|
end
|
137
136
|
end
|
138
137
|
|
139
138
|
context "with two Symbols" do
|
140
|
-
it "
|
141
|
-
|
139
|
+
it "uses the first symbol to set the namespace and the second symbol to set the operation of the request builder" do
|
140
|
+
expect_request_builder_to_receive(:get_user, :namespace_identifier => :v1)
|
141
|
+
client.request(:v1, :get_user)
|
142
142
|
end
|
143
143
|
|
144
|
-
it "should set the target namespace
|
145
|
-
|
146
|
-
HTTPI::Request.any_instance.expects(:body=).with { |value| value.include? namespace }
|
147
|
-
|
148
|
-
client.request :v1, :get_user
|
149
|
-
end
|
144
|
+
it "should not set the target namespace if soap.namespace was set to nil in the post-configuration block" do
|
145
|
+
Savon::SOAP::RequestBuilder.unstub(:new)
|
150
146
|
|
151
|
-
it "should not set the target namespace if soap.namespace was set to nil" do
|
152
147
|
namespace = 'xmlns:v1="http://v1_0.ws.auth.order.example.com/"'
|
153
148
|
HTTPI::Request.any_instance.expects(:body=).with { |value| !value.include?(namespace) }
|
154
149
|
|
@@ -157,66 +152,118 @@ describe Savon::Client do
|
|
157
152
|
end
|
158
153
|
|
159
154
|
context "with two Symbols and a Hash" do
|
160
|
-
it "
|
161
|
-
|
155
|
+
it "uses the first symbol to set the namespace and the second symbol to set the operation of the request builder" do
|
156
|
+
expect_request_builder_to_receive(:get_user, :namespace_identifier => :wsdl)
|
157
|
+
client.request(:wsdl, :get_user)
|
162
158
|
end
|
163
159
|
|
164
|
-
it "should use the
|
165
|
-
|
160
|
+
it "should use the hash to set the attributes of the request builder" do
|
161
|
+
expect_request_builder_to_receive(:get_user, :namespace_identifier => :wsdl, :attributes => { :active => true })
|
162
|
+
client.request(:wsdl, :get_user, :active => true)
|
166
163
|
end
|
167
|
-
end
|
168
164
|
|
169
|
-
|
170
|
-
|
171
|
-
client.request(:
|
165
|
+
it "should use the :soap_action key of the hash to set the SOAP action of the request builder" do
|
166
|
+
expect_request_builder_to_receive(:get_user, :namespace_identifier => :wsdl, :soap_action => :test_action)
|
167
|
+
client.request(:wsdl, :get_user, :soap_action => :test_action)
|
168
|
+
end
|
169
|
+
|
170
|
+
it "should use the :body key of the hash to set the SOAP body of the request builder" do
|
171
|
+
expect_request_builder_to_receive(:get_user, :namespace_identifier => :wsdl, :body => { :foo => "bar" })
|
172
|
+
client.request(:wsdl, :get_user, :body => { :foo => "bar" })
|
172
173
|
end
|
173
174
|
end
|
174
175
|
|
175
|
-
context "with a block
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
176
|
+
context "with a block" do
|
177
|
+
before do
|
178
|
+
Savon::SOAP::RequestBuilder.unstub(:new)
|
179
|
+
end
|
180
|
+
|
181
|
+
it "passes the block to the request builder" do
|
182
|
+
# this is painful. it would be trivial to test in > Ruby 1.9, but this is
|
183
|
+
# the only way I know how in < 1.9.
|
184
|
+
dummy = Object.new
|
185
|
+
dummy.instance_eval do
|
186
|
+
class << self; attr_accessor :request_builder, :block_given; end
|
187
|
+
def request
|
188
|
+
self.block_given = block_given?
|
189
|
+
request_builder.request
|
190
|
+
end
|
191
|
+
|
192
|
+
def method_missing(_, *args)
|
193
|
+
end
|
180
194
|
end
|
195
|
+
|
196
|
+
dummy.request_builder = request_builder
|
197
|
+
Savon::SOAP::RequestBuilder.stubs(:new).returns(dummy)
|
198
|
+
|
199
|
+
blk = lambda {}
|
200
|
+
client.request(:authenticate, &blk)
|
201
|
+
|
202
|
+
dummy.block_given.should == true
|
181
203
|
end
|
182
|
-
end
|
183
204
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
http.should be_an(HTTPI::Request)
|
205
|
+
it "executes the block in the context of the request builder" do
|
206
|
+
Savon::SOAP::RequestBuilder.class_eval do
|
207
|
+
def who
|
208
|
+
self
|
209
|
+
end
|
190
210
|
end
|
211
|
+
|
212
|
+
client.request(:authenticate) { who.should be_a Savon::SOAP::RequestBuilder }
|
191
213
|
end
|
192
|
-
end
|
193
214
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
soap.should be_a(Savon::SOAP::XML)
|
198
|
-
wsdl.should be_a(Savon::Wasabi::Document)
|
199
|
-
http.should be_an(HTTPI::Request)
|
200
|
-
wsse.should be_a(Akami::WSSE)
|
215
|
+
context "with a block expecting one argument" do
|
216
|
+
it "should yield the SOAP object" do
|
217
|
+
client.request(:authenticate) { |soap| soap.should be_a Savon::SOAP::XML }
|
201
218
|
end
|
202
219
|
end
|
203
|
-
end
|
204
220
|
|
205
|
-
|
206
|
-
|
207
|
-
|
221
|
+
context "with a block expecting two arguments" do
|
222
|
+
it "should yield the SOAP and WSDL objects" do
|
223
|
+
client.request(:authenticate) do |soap, wsdl|
|
224
|
+
soap.should be_a(Savon::SOAP::XML)
|
225
|
+
wsdl.should be_an(Wasabi::Document)
|
226
|
+
end
|
227
|
+
end
|
208
228
|
end
|
209
229
|
|
210
|
-
|
211
|
-
|
230
|
+
context "with a block expecting three arguments" do
|
231
|
+
it "should yield the SOAP, WSDL and HTTP objects" do
|
232
|
+
client.request(:authenticate) do |soap, wsdl, http|
|
233
|
+
soap.should be_a(Savon::SOAP::XML)
|
234
|
+
wsdl.should be_an(Wasabi::Document)
|
235
|
+
http.should be_an(HTTPI::Request)
|
236
|
+
end
|
237
|
+
end
|
212
238
|
end
|
213
239
|
|
214
|
-
|
215
|
-
|
240
|
+
context "with a block expecting four arguments" do
|
241
|
+
it "should yield the SOAP, WSDL, HTTP and WSSE objects" do
|
242
|
+
client.request(:authenticate) do |soap, wsdl, http, wsse|
|
243
|
+
soap.should be_a(Savon::SOAP::XML)
|
244
|
+
wsdl.should be_a(Wasabi::Document)
|
245
|
+
http.should be_an(HTTPI::Request)
|
246
|
+
wsse.should be_a(Akami::WSSE)
|
247
|
+
end
|
248
|
+
end
|
216
249
|
end
|
217
250
|
|
218
|
-
|
219
|
-
|
251
|
+
context "with a block expecting no arguments" do
|
252
|
+
it "should let you access the SOAP object" do
|
253
|
+
client.request(:authenticate) { soap.should be_a(Savon::SOAP::XML) }
|
254
|
+
end
|
255
|
+
|
256
|
+
it "should let you access the HTTP object" do
|
257
|
+
client.request(:authenticate) { http.should be_an(HTTPI::Request) }
|
258
|
+
end
|
259
|
+
|
260
|
+
it "should let you access the WSSE object" do
|
261
|
+
client.request(:authenticate) { wsse.should be_a(Akami::WSSE) }
|
262
|
+
end
|
263
|
+
|
264
|
+
it "should let you access the WSDL object" do
|
265
|
+
client.request(:authenticate) { wsdl.should be_a(Wasabi::Document) }
|
266
|
+
end
|
220
267
|
end
|
221
268
|
end
|
222
269
|
|
@@ -383,29 +430,55 @@ describe Savon::Client do
|
|
383
430
|
end
|
384
431
|
|
385
432
|
context "with an Array of namespaced items" do
|
386
|
-
|
433
|
+
context "with a single namespace" do
|
434
|
+
let(:client) { Savon::Client.new { wsdl.document = "spec/fixtures/wsdl/taxcloud.xml" } }
|
387
435
|
|
388
|
-
|
389
|
-
|
390
|
-
|
436
|
+
before do
|
437
|
+
HTTPI.stubs(:get).returns(new_response(:body => Fixture.wsdl(:taxcloud)))
|
438
|
+
HTTPI.stubs(:post).returns(new_response)
|
439
|
+
end
|
440
|
+
|
441
|
+
it "should namespaces each Array item as expected" do
|
442
|
+
HTTPI::Request.any_instance.expects(:body=).with do |value|
|
443
|
+
xml = Nokogiri::XML(value)
|
444
|
+
!!xml.at_xpath(".//tc:cartItems/tc:CartItem/tc:ItemID", { "tc" => "http://taxcloud.net" })
|
445
|
+
end
|
446
|
+
|
447
|
+
address = { "Address1" => "888 6th Ave", "Address2" => nil, "City" => "New York", "State" => "NY", "Zip5" => "10001", "Zip4" => nil }
|
448
|
+
cart_item = { "Index" => 0, "ItemID" => "SKU-TEST", "TIC" => "00000", "Price" => 50.0, "Qty" => 1 }
|
449
|
+
|
450
|
+
client.request :lookup, :body => {
|
451
|
+
"customerID" => 123,
|
452
|
+
"cartID" => 456,
|
453
|
+
"cartItems" => { "CartItem" => [cart_item] },
|
454
|
+
"origin" => address,
|
455
|
+
"destination" => address
|
456
|
+
}
|
457
|
+
end
|
391
458
|
end
|
392
459
|
|
393
|
-
|
394
|
-
|
395
|
-
|
460
|
+
context "with multiple namespaces" do
|
461
|
+
let(:client) { Savon::Client.new { wsdl.document = "spec/fixtures/wsdl/multiple_namespaces.xml" } }
|
462
|
+
|
463
|
+
before do
|
464
|
+
HTTPI.stubs(:get).returns(new_response(:body => Fixture.wsdl(:multiple_namespaces)))
|
465
|
+
HTTPI.stubs(:post).returns(new_response)
|
396
466
|
end
|
397
467
|
|
398
|
-
|
399
|
-
|
468
|
+
it "should namespace each Array item as expected" do
|
469
|
+
HTTPI::Request.any_instance.expects(:body=).with do |value|
|
470
|
+
xml = Nokogiri::XML(value)
|
471
|
+
namespaces = { "actions" => "http://example.com/actions", "article" => "http://example.com/article" }
|
472
|
+
!!xml.at_xpath(".//actions:Lookup/actions:articles/article:Article/article:Author", namespaces)
|
473
|
+
end
|
400
474
|
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
"destination" => address
|
407
|
-
}
|
475
|
+
article = { "Author" => "John Smith", "Title" => "Modern SOAP" }
|
476
|
+
client.request :lookup, :body => {
|
477
|
+
"articles" => { "Article" => [article] }
|
478
|
+
}
|
479
|
+
end
|
408
480
|
end
|
481
|
+
|
409
482
|
end
|
410
483
|
|
411
484
|
context "without a WSDL document" do
|
@@ -476,4 +549,8 @@ describe Savon::Client do
|
|
476
549
|
HTTPI::Response.new response[:code], response[:headers], response[:body]
|
477
550
|
end
|
478
551
|
|
552
|
+
def expect_request_builder_to_receive(operation, options = {})
|
553
|
+
Savon::SOAP::RequestBuilder.expects(:new).with(operation, options).returns(request_builder)
|
554
|
+
end
|
555
|
+
|
479
556
|
end
|