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.
@@ -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