savon 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|