savon 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -25,7 +25,7 @@ module Savon
25
25
  # Returns the HTTP error message.
26
26
  def to_s
27
27
  return "" unless present?
28
-
28
+
29
29
  @message ||= begin
30
30
  message = "HTTP error (#{http.code})"
31
31
  message << ": #{http.body}" unless http.body.empty?
@@ -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
@@ -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
@@ -1,5 +1,5 @@
1
1
  module Savon
2
2
 
3
- VERSION = "1.1.0"
3
+ VERSION = "1.2.0"
4
4
 
5
5
  end
@@ -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
- it "returns the result in a CDATA tag" do
6
- client = Savon.client("http://www.webservicex.net/stockquote.asmx?WSDL")
7
- response = client.request(:get_quote, :body => { :symbol => "AAPL" })
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
- cdata = response[:get_quote_response][:get_quote_result]
10
- result = Nori.parse(cdata)
11
- result[:stock_quotes][:stock][:symbol].should == "AAPL"
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
- it "passes Strings as they are" do
15
- client = Savon.client("http://ws.cdyne.com/emailverify/Emailvernotestemail.asmx?wsdl")
16
- response = client.request(:verify_email, :body => { :email => "soap@example.com", "LicenseKey" => "?" })
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
- response_text = response[:verify_email_response][:verify_email_result][:response_text]
19
- response_text.should == "Email Domain Not Found"
57
+ values.uniq.size.should == values.size
58
+ end
20
59
  end
21
60
 
22
61
  end
@@ -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(Savon::Wasabi::Document) }
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(Savon::Wasabi::Document)
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(Savon::Wasabi::Document)
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(Savon::Wasabi::Document) }
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(Savon::Wasabi::Document)
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
- context "with a single argument (Symbol)" do
86
- it "should set the input tag to result in <getUser>" do
87
- client.request(:get_user) { soap.input.should == [nil, :getUser, {}] }
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 "should set the target namespace with the default identifier" do
91
- namespace = 'xmlns:wsdl="http://v1_0.ws.auth.order.example.com/"'
92
- HTTPI::Request.any_instance.expects(:body=).with { |value| value.include? namespace }
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 "should not set the target namespace if soap.namespace was set to nil" do
98
- namespace = 'wsdl="http://v1_0.ws.auth.order.example.com/"'
99
- HTTPI::Request.any_instance.expects(:body=).with { |value| !value.include?(namespace) }
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
- context "when the wsdl's operation namespace identifier matches a document identifier" do
105
- before do
106
- client.wsdl.operations[:authenticate][:namespace_identifier] = "tns"
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 (String)" do
124
- it "should set the input tag to result in <get_user>" do
125
- client.request("get_user") { soap.input.should == [nil, :get_user, {}] }
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 "should set the input tag to result in <getUser active='true'>" do
131
- client.request(:get_user, :active => true) { soap.input.should == [nil, :getUser, { :active => true }] }
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 "should use the :soap_action key to set the SOAPAction header" do
135
- client.request(:get_user, :soap_action => :test_action) { http.headers["SOAPAction"].should == %{"testAction"} }
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 "should set the input tag to result in <wsdl:getUser>" do
141
- client.request(:v1, :get_user) { soap.input.should == [:v1, :getUser, {}] }
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 with the given identifier" do
145
- namespace = 'xmlns:v1="http://v1_0.ws.auth.order.example.com/"'
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 "should set the input tag to result in <wsdl:getUser active='true'>" do
161
- client.request(:wsdl, :get_user, :active => true) { soap.input.should == [:wsdl, :getUser, { :active => true }] }
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 :soap_action key to set the SOAPAction header" do
165
- client.request(:wsdl, :get_user, :soap_action => :test_action) { http.headers["SOAPAction"].should == %{"testAction"} }
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
- context "with a block expecting one argument" do
170
- it "should yield the SOAP object" do
171
- client.request(:authenticate) { |soap| soap.should be_a(Savon::SOAP::XML) }
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 expecting two arguments" do
176
- it "should yield the SOAP and WSDL objects" do
177
- client.request(:authenticate) do |soap, wsdl|
178
- soap.should be_a(Savon::SOAP::XML)
179
- wsdl.should be_an(Savon::Wasabi::Document)
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
- context "with a block expecting three arguments" do
185
- it "should yield the SOAP, WSDL and HTTP objects" do
186
- client.request(:authenticate) do |soap, wsdl, http|
187
- soap.should be_a(Savon::SOAP::XML)
188
- wsdl.should be_an(Savon::Wasabi::Document)
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
- context "with a block expecting four arguments" do
195
- it "should yield the SOAP, WSDL, HTTP and WSSE objects" do
196
- client.request(:authenticate) do |soap, wsdl, http, wsse|
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
- context "with a block expecting no arguments" do
206
- it "should let you access the SOAP object" do
207
- client.request(:authenticate) { soap.should be_a(Savon::SOAP::XML) }
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
- it "should let you access the HTTP object" do
211
- client.request(:authenticate) { http.should be_an(HTTPI::Request) }
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
- it "should let you access the WSSE object" do
215
- client.request(:authenticate) { wsse.should be_a(Akami::WSSE) }
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
- it "should let you access the WSDL object" do
219
- client.request(:authenticate) { wsdl.should be_a(Savon::Wasabi::Document) }
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
- let(:client) { Savon::Client.new { wsdl.document = "spec/fixtures/wsdl/taxcloud.xml" } }
433
+ context "with a single namespace" do
434
+ let(:client) { Savon::Client.new { wsdl.document = "spec/fixtures/wsdl/taxcloud.xml" } }
387
435
 
388
- before do
389
- HTTPI.stubs(:get).returns(new_response(:body => Fixture.wsdl(:taxcloud)))
390
- HTTPI.stubs(:post).returns(new_response)
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
- it "should namespaces each Array item as expected" do
394
- HTTPI::Request.any_instance.expects(:body=).with do |value|
395
- value.include?("<ins0:cartItems><ins0:CartItem>") && value.include?("<tns:ItemID>SKU-TEST</tns:ItemID>")
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
- address = { "Address1" => "888 6th Ave", "Address2" => nil, "City" => "New York", "State" => "NY", "Zip5" => "10001", "Zip4" => nil }
399
- cart_item = { "Index" => 0, "ItemID" => "SKU-TEST", "TIC" => "00000", "Price" => 50.0, "Qty" => 1 }
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
- client.request :lookup, :body => {
402
- "customerID" => 123,
403
- "cartID" => 456,
404
- "cartItems" => { "CartItem" => [cart_item] },
405
- "origin" => address,
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