savon 1.2.0 → 2.0.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.
Files changed (66) hide show
  1. data/CHANGELOG.md +119 -104
  2. data/README.md +12 -11
  3. data/Rakefile +0 -6
  4. data/lib/savon.rb +16 -14
  5. data/lib/savon/block_interface.rb +26 -0
  6. data/lib/savon/builder.rb +142 -0
  7. data/lib/savon/client.rb +36 -135
  8. data/lib/savon/header.rb +42 -0
  9. data/lib/savon/http_error.rb +27 -0
  10. data/lib/savon/log_message.rb +23 -25
  11. data/lib/savon/message.rb +35 -0
  12. data/lib/savon/mock.rb +5 -0
  13. data/lib/savon/mock/expectation.rb +70 -0
  14. data/lib/savon/mock/spec_helper.rb +62 -0
  15. data/lib/savon/model.rb +39 -61
  16. data/lib/savon/operation.rb +62 -0
  17. data/lib/savon/options.rb +265 -0
  18. data/lib/savon/qualified_message.rb +49 -0
  19. data/lib/savon/request.rb +92 -0
  20. data/lib/savon/response.rb +97 -0
  21. data/lib/savon/soap_fault.rb +40 -0
  22. data/lib/savon/version.rb +1 -1
  23. data/savon.gemspec +10 -8
  24. data/spec/integration/options_spec.rb +536 -0
  25. data/spec/integration/request_spec.rb +31 -16
  26. data/spec/integration/support/application.rb +80 -0
  27. data/spec/integration/support/server.rb +84 -0
  28. data/spec/savon/builder_spec.rb +81 -0
  29. data/spec/savon/client_spec.rb +90 -488
  30. data/spec/savon/http_error_spec.rb +49 -0
  31. data/spec/savon/log_message_spec.rb +33 -0
  32. data/spec/savon/mock_spec.rb +127 -0
  33. data/spec/savon/model_spec.rb +110 -99
  34. data/spec/savon/observers_spec.rb +92 -0
  35. data/spec/savon/operation_spec.rb +49 -0
  36. data/spec/savon/request_spec.rb +145 -0
  37. data/spec/savon/{soap/response_spec.rb → response_spec.rb} +22 -59
  38. data/spec/savon/soap_fault_spec.rb +94 -0
  39. data/spec/spec_helper.rb +5 -3
  40. data/spec/support/fixture.rb +5 -1
  41. metadata +202 -197
  42. data/lib/savon/config.rb +0 -46
  43. data/lib/savon/error.rb +0 -6
  44. data/lib/savon/hooks/group.rb +0 -68
  45. data/lib/savon/hooks/hook.rb +0 -61
  46. data/lib/savon/http/error.rb +0 -42
  47. data/lib/savon/logger.rb +0 -39
  48. data/lib/savon/null_logger.rb +0 -10
  49. data/lib/savon/soap.rb +0 -21
  50. data/lib/savon/soap/fault.rb +0 -59
  51. data/lib/savon/soap/invalid_response_error.rb +0 -13
  52. data/lib/savon/soap/request.rb +0 -86
  53. data/lib/savon/soap/request_builder.rb +0 -205
  54. data/lib/savon/soap/response.rb +0 -117
  55. data/lib/savon/soap/xml.rb +0 -257
  56. data/spec/savon/config_spec.rb +0 -38
  57. data/spec/savon/hooks/group_spec.rb +0 -71
  58. data/spec/savon/hooks/hook_spec.rb +0 -16
  59. data/spec/savon/http/error_spec.rb +0 -52
  60. data/spec/savon/logger_spec.rb +0 -51
  61. data/spec/savon/savon_spec.rb +0 -33
  62. data/spec/savon/soap/fault_spec.rb +0 -89
  63. data/spec/savon/soap/request_builder_spec.rb +0 -207
  64. data/spec/savon/soap/request_spec.rb +0 -112
  65. data/spec/savon/soap/xml_spec.rb +0 -357
  66. data/spec/savon/soap_spec.rb +0 -16
@@ -1,22 +1,23 @@
1
- require "spec_helper"
1
+ require "spec_helper"
2
2
 
3
- describe "Integration" do
3
+ describe "Requests" do
4
4
 
5
5
  subject(:client) {
6
- client = Savon.client(service_endpoint)
7
- client.http.open_timeout = 10
8
- client.http.read_timeout = 10
9
- client
6
+ Savon.client(:wsdl => service_endpoint, :open_timeout => 10, :read_timeout => 10,
7
+ :raise_errors => false, :log => false)
10
8
  }
11
9
 
12
10
  context "stockquote" do
13
11
  let(:service_endpoint) { "http://www.webservicex.net/stockquote.asmx?WSDL" }
14
12
 
15
13
  it "returns the result in a CDATA tag" do
16
- response = client.request(:get_quote, :body => { :symbol => "AAPL" })
14
+ response = client.call(:get_quote, :message => { :symbol => "AAPL" })
15
+
16
+ cdata = response.body[:get_quote_response][:get_quote_result]
17
+
18
+ nori_options = { :convert_tags_to => lambda { |tag| tag.snakecase.to_sym } }
19
+ result = Nori.new(nori_options).parse(cdata)
17
20
 
18
- cdata = response[:get_quote_response][:get_quote_result]
19
- result = Nori.parse(cdata)
20
21
  result[:stock_quotes][:stock][:symbol].should == "AAPL"
21
22
  end
22
23
  end
@@ -25,10 +26,15 @@ describe "Integration" do
25
26
  let(:service_endpoint) { "http://ws.cdyne.com/emailverify/Emailvernotestemail.asmx?wsdl" }
26
27
 
27
28
  it "passes Strings as they are" do
28
- response = client.request(:verify_email, :body => { :email => "soap@example.com", "LicenseKey" => "?" })
29
+ response = client.call(:verify_email, :message => { :email => "soap@example.com", "LicenseKey" => "?" })
29
30
 
30
- response_text = response[:verify_email_response][:verify_email_result][:response_text]
31
- response_text.should == "Email Domain Not Found"
31
+ response_text = response.body[:verify_email_response][:verify_email_result][:response_text]
32
+
33
+ if response_text == "Current license key only allows so many checks"
34
+ pending "API limit exceeded"
35
+ else
36
+ response_text.should == "Email Domain Not Found"
37
+ end
32
38
  end
33
39
  end
34
40
 
@@ -42,20 +48,29 @@ describe "Integration" do
42
48
  threads_waiting = request_data.size
43
49
 
44
50
  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]
51
+ thread = Thread.new do
52
+ response = client.call :get_bank, :message => { :blz => blz }
53
+ Thread.current[:value] = response.body[:get_bank_response][:details]
48
54
  mutex.synchronize { threads_waiting -= 1 }
49
55
  end
56
+
57
+ thread.abort_on_exception = true
58
+ thread
50
59
  end
51
60
 
52
61
  sleep(1) until threads_waiting == 0
53
62
 
54
- threads.each &:kill
63
+ threads.each(&:kill)
55
64
  values = threads.map { |thr| thr[:value] }.compact
56
65
 
57
66
  values.uniq.size.should == values.size
58
67
  end
59
68
  end
60
69
 
70
+ context "redirectes" do
71
+ it "follows 301 redirects"
72
+ it "follows 302 redirects"
73
+ it "follows 307 redirects"
74
+ end
75
+
61
76
  end
@@ -0,0 +1,80 @@
1
+ require "rack/builder"
2
+
3
+ class IntegrationServer
4
+
5
+ def self.respond_with(options = {})
6
+ code = options.fetch(:code, 200)
7
+ body = options.fetch(:body, "")
8
+ headers = { "Content-Type" => "text/plain", "Content-Length" => body.size.to_s }
9
+
10
+ [code, headers, [body]]
11
+ end
12
+
13
+ Application = Rack::Builder.new do
14
+
15
+ map "/" do
16
+ run lambda { |env|
17
+ IntegrationServer.respond_with :body => env["REQUEST_METHOD"].downcase
18
+ }
19
+ end
20
+
21
+ map "/repeat" do
22
+ run lambda { |env|
23
+ # stupid way of extracting the value from a query string (e.g. "code=500") [dh, 2012-12-08]
24
+ IntegrationServer.respond_with :body => env["rack.input"].read
25
+ }
26
+ end
27
+
28
+ map "/404" do
29
+ run lambda { |env|
30
+ IntegrationServer.respond_with :code => 404, :body => env["rack.input"].read
31
+ }
32
+ end
33
+
34
+ map "/timeout" do
35
+ run lambda { |env|
36
+ sleep 2
37
+ IntegrationServer.respond_with :body => "timeout"
38
+ }
39
+ end
40
+
41
+ map "/repeat_header" do
42
+ run lambda { |env|
43
+ IntegrationServer.respond_with :body => env["HTTP_REPEAT_HEADER"]
44
+ }
45
+ end
46
+
47
+ map "/inspect_header" do
48
+ run lambda { |env|
49
+ IntegrationServer.respond_with :body => env[env["HTTP_INSPECT"]]
50
+ }
51
+ end
52
+
53
+ map "/basic_auth" do
54
+ use Rack::Auth::Basic, "basic-realm" do |username, password|
55
+ username == "admin" && password == "secret"
56
+ end
57
+
58
+ run lambda { |env|
59
+ IntegrationServer.respond_with :body => "basic-auth"
60
+ }
61
+ end
62
+
63
+ map "/digest_auth" do
64
+ unprotected_app = lambda { |env|
65
+ IntegrationServer.respond_with :body => "digest-auth"
66
+ }
67
+
68
+ realm = 'digest-realm'
69
+ app = Rack::Auth::Digest::MD5.new(unprotected_app) do |username|
70
+ username == 'admin' ? Digest::MD5.hexdigest("admin:#{realm}:secret") : nil
71
+ end
72
+ app.realm = realm
73
+ app.opaque = 'this-should-be-secret'
74
+ app.passwords_hashed = true
75
+
76
+ run app
77
+ end
78
+
79
+ end
80
+ end
@@ -0,0 +1,84 @@
1
+ require "puma"
2
+ require "puma/minissl"
3
+
4
+ require "integration/support/application"
5
+
6
+ class IntegrationServer
7
+
8
+ def self.run(options = {})
9
+ server = new(options)
10
+ server.run
11
+ server
12
+ end
13
+
14
+ def self.ssl_ca_file; integration_fixture("ca_all.pem") end
15
+ def self.ssl_key_file; integration_fixture("server.key") end
16
+ def self.ssl_cert_file; integration_fixture("server.cert") end
17
+
18
+ def self.integration_fixture(file)
19
+ file = File.expand_path("../../fixtures/#{file}", __FILE__)
20
+ raise "No such file '#{file}'" unless File.exist? file
21
+ file
22
+ end
23
+
24
+ def initialize(options = {})
25
+ @app = Application
26
+ @host = options.fetch(:host, "localhost")
27
+ @port = options.fetch(:port, 17172)
28
+ @ssl = options.fetch(:ssl, false)
29
+
30
+ @server = Puma::Server.new(app, events)
31
+
32
+ if ssl?
33
+ add_ssl_listener
34
+ else
35
+ add_tcp_listener
36
+ end
37
+ end
38
+
39
+ attr_reader :app, :host, :port, :server
40
+
41
+ def url(path = "")
42
+ protocol = ssl? ? "https" : "http"
43
+ File.join "#{protocol}://#{host}:#{port}/", path.to_s
44
+ end
45
+
46
+ def ssl?
47
+ @ssl
48
+ end
49
+
50
+ def run
51
+ server.run
52
+ end
53
+
54
+ def stop
55
+ server.stop(true)
56
+ end
57
+
58
+ private
59
+
60
+ def events
61
+ Puma::Events.new($stdout, $stderr)
62
+ end
63
+
64
+ def add_tcp_listener
65
+ server.add_tcp_listener(host, port)
66
+ rescue Errno::EADDRINUSE
67
+ raise "Panther is already running at #{url}"
68
+ end
69
+
70
+ def add_ssl_listener
71
+ server.add_ssl_listener(host, port, ssl_context)
72
+ end
73
+
74
+ def ssl_context
75
+ context = Puma::MiniSSL::Context.new
76
+
77
+ context.key = IntegrationServer.ssl_key_file
78
+ context.cert = IntegrationServer.ssl_cert_file
79
+ context.verify_mode = Puma::MiniSSL::VERIFY_PEER
80
+
81
+ context
82
+ end
83
+
84
+ end
@@ -0,0 +1,81 @@
1
+ require "spec_helper"
2
+
3
+ describe Savon::Builder do
4
+
5
+ subject(:builder) { Savon::Builder.new(:authenticate, wsdl, globals, locals) }
6
+
7
+ let(:globals) { Savon::GlobalOptions.new }
8
+ let(:locals) { Savon::LocalOptions.new }
9
+ let(:wsdl) { Wasabi::Document.new Fixture.wsdl(:authentication) }
10
+ let(:no_wsdl) { Wasabi::Document.new }
11
+
12
+ describe "#to_s" do
13
+ it "includes the global :env_namespace if it's available" do
14
+ globals[:env_namespace] = :soapenv
15
+ expect(builder.to_s).to include("<soapenv:Envelope")
16
+ end
17
+
18
+ it "defaults to include the default envelope namespace of :env" do
19
+ expect(builder.to_s).to include("<env:Envelope")
20
+ end
21
+
22
+ it "includes the target namespace from the WSDL" do
23
+ expect(builder.to_s).to include('xmlns:tns="http://v1_0.ws.auth.order.example.com/"')
24
+ end
25
+
26
+ it "includes the target namespace from the global :namespace if it's available" do
27
+ globals[:namespace] = "http://v1.example.com"
28
+ expect(builder.to_s).to include('xmlns:tns="http://v1.example.com"')
29
+ end
30
+
31
+ it "includes the local :message_tag if available" do
32
+ locals[:message_tag] = "doAuthenticate"
33
+ expect(builder.to_s).to include("<tns:doAuthenticate>")
34
+ end
35
+
36
+ it "includes the message tag from the WSDL if its available" do
37
+ expect(builder.to_s).to include("<tns:authenticate>")
38
+ end
39
+
40
+ it "includes a message tag created by Gyoku if both option and WSDL are missing" do
41
+ globals[:namespace] = "http://v1.example.com"
42
+
43
+ locals = Savon::LocalOptions.new
44
+ builder = Savon::Builder.new(:authenticate, no_wsdl, globals, locals)
45
+
46
+ expect(builder.to_s).to include("<wsdl:authenticate>")
47
+ end
48
+
49
+ it "uses the global :namespace_identifier option if it's available" do
50
+ globals[:namespace_identifier] = :v1
51
+ expect(builder.to_s).to include("<v1:authenticate>")
52
+ end
53
+
54
+ it "uses the WSDL's namespace_identifier if the global option was not specified" do
55
+ expect(builder.to_s).to include("<tns:authenticate>")
56
+ end
57
+
58
+ it "uses the default :wsdl identifier if both option and WSDL were not specified" do
59
+ globals[:namespace] = "http://v1.example.com"
60
+
61
+ builder = Savon::Builder.new(:authenticate, no_wsdl, globals, locals)
62
+ expect(builder.to_s).to include("<wsdl:authenticate>")
63
+ end
64
+
65
+ it "uses the global :element_form_default option if it's available " do
66
+ globals[:element_form_default] = :qualified
67
+ locals[:message] = { :username => "luke", :password => "secret" }
68
+
69
+ expect(builder.to_s).to include("<tns:username>luke</tns:username>")
70
+ end
71
+
72
+ it "uses the WSDL's element_form_default value if the global option was set specified" do
73
+ locals[:message] = { :username => "luke", :password => "secret" }
74
+ wsdl.element_form_default = :qualified
75
+
76
+ expect(builder.to_s).to include("<tns:username>luke</tns:username>")
77
+ end
78
+
79
+ end
80
+
81
+ end
@@ -1,556 +1,158 @@
1
1
  require "spec_helper"
2
+ require "integration/support/server"
2
3
 
3
4
  describe Savon::Client do
4
- let(:client) { Savon::Client.new { wsdl.document = Endpoint.wsdl } }
5
5
 
6
- describe ".new" do
7
- context "with a String" do
8
- it "should set the WSDL document" do
9
- wsdl = "http://example.com/UserService?wsdl"
10
- client = Savon::Client.new(wsdl)
11
- client.wsdl.instance_variable_get("@document").should == wsdl
12
- end
13
- end
14
-
15
- context "with a block expecting one argument" do
16
- it "should yield the WSDL object" do
17
- Savon::Client.new { |wsdl| wsdl.should be_a(Wasabi::Document) }
18
- end
19
- end
20
-
21
- context "with a block expecting two arguments" do
22
- it "should yield the WSDL and HTTP objects" do
23
- Savon::Client.new do |wsdl, http|
24
- wsdl.should be_an(Wasabi::Document)
25
- http.should be_an(HTTPI::Request)
26
- end
27
- end
28
- end
29
-
30
- context "with a block expecting three arguments" do
31
- it "should yield the WSDL, HTTP and WSSE objects" do
32
- Savon::Client.new do |wsdl, http, wsse|
33
- wsdl.should be_an(Wasabi::Document)
34
- http.should be_an(HTTPI::Request)
35
- wsse.should be_an(Akami::WSSE)
36
- end
37
- end
38
- end
39
-
40
- context "with a block expecting no arguments" do
41
- it "should let you access the WSDL object" do
42
- Savon::Client.new { wsdl.should be_a(Wasabi::Document) }
43
- end
44
-
45
- it "should let you access the HTTP object" do
46
- Savon::Client.new { http.should be_an(HTTPI::Request) }
47
- end
48
-
49
- it "should let you access the WSSE object" do
50
- Savon::Client.new { wsse.should be_a(Akami::WSSE) }
51
- end
52
- end
53
- end
54
-
55
- describe "#wsdl" do
56
- it "should return the Savon::Wasabi::Document" do
57
- client.wsdl.should be_a(Wasabi::Document)
58
- end
59
- end
60
-
61
- describe "#http" do
62
- it "should return the HTTPI::Request" do
63
- client.http.should be_an(HTTPI::Request)
64
- end
6
+ before :all do
7
+ @server = IntegrationServer.run
65
8
  end
66
9
 
67
- describe "#wsse" do
68
- it "should return the Akami::WSSE object" do
69
- client.wsse.should be_a(Akami::WSSE)
70
- end
10
+ after :all do
11
+ @server.stop
71
12
  end
72
13
 
73
- describe "#request" do
74
- let(:request_builder) { stub_everything('request_builder') }
75
- let(:response) { mock('response') }
76
-
77
- before do
78
- HTTPI.stubs(:get).returns(new_response(:body => Fixture.wsdl(:authentication)))
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)
84
- end
85
-
86
- context "without any arguments" do
87
- it "should raise an ArgumentError" do
88
- lambda { client.request }.should raise_error(ArgumentError)
89
- end
90
- end
91
-
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)
96
- end
97
-
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)
101
- end
102
-
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)
106
- end
107
-
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)
111
- end
112
- end
113
-
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)
118
- end
119
- end
120
-
121
- context "with a Symbol and a Hash" do
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)
125
- end
126
-
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" })
135
- end
136
- end
137
-
138
- context "with two Symbols" do
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
- end
143
-
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)
146
-
147
- namespace = 'xmlns:v1="http://v1_0.ws.auth.order.example.com/"'
148
- HTTPI::Request.any_instance.expects(:body=).with { |value| !value.include?(namespace) }
149
-
150
- client.request(:v1, :get_user) { soap.namespace = nil }
151
- end
152
- end
153
-
154
- context "with two Symbols and a Hash" do
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)
158
- end
159
-
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)
163
- end
164
-
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)
14
+ describe ".new" do
15
+ it "supports a block without arguments to create a client with global options" do
16
+ client = Savon.client do
17
+ wsdl Fixture.wsdl(:authentication)
168
18
  end
169
19
 
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" })
173
- end
20
+ expect(client.globals[:wsdl]).to eq(Fixture.wsdl(:authentication))
174
21
  end
175
22
 
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
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
203
- end
204
-
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
210
- end
211
-
212
- client.request(:authenticate) { who.should be_a Savon::SOAP::RequestBuilder }
213
- end
214
-
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 }
218
- end
219
- end
220
-
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
228
- end
229
-
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
23
+ it "supports a block with one argument to create a client with global options" do
24
+ client = Savon.client do |globals|
25
+ globals.wsdl Fixture.wsdl(:authentication)
238
26
  end
239
27
 
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
249
- end
250
-
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
267
- end
268
- end
269
-
270
- it "should not set the Cookie header for the next request" do
271
- client.request :authenticate
272
- client.http.headers["Cookie"].should be_nil
273
- end
274
- end
275
-
276
- context "#request with a Set-Cookie response header" do
277
- before do
278
- HTTPI.stubs(:get).returns(new_response(:body => Fixture.wsdl(:authentication)))
279
- HTTPI.stubs(:post).returns(new_response(:headers => { "Set-Cookie" => "some-cookie=choc-chip; Path=/; HttpOnly" }))
28
+ expect(client.globals[:wsdl]).to eq(Fixture.wsdl(:authentication))
280
29
  end
281
30
 
282
- it "sets the cookies for the next request" do
283
- client.http.expects(:set_cookies).with(kind_of(HTTPI::Response))
284
- client.request :authenticate
31
+ it "raises if not initialized with either a :wsdl or both :endpoint and :namespace options" do
32
+ expect { Savon.client(:endpoint => "http://example.com") }.
33
+ to raise_error(Savon::InitializationError, /Expected either a WSDL document or the SOAP endpoint and target namespace options/)
285
34
  end
286
35
  end
287
36
 
288
- context "with a remote WSDL document" do
289
- let(:client) { Savon::Client.new { wsdl.document = Endpoint.wsdl } }
290
- before { HTTPI.expects(:get).returns(new_response(:body => Fixture.wsdl(:authentication))) }
291
-
292
- it "adds a SOAPAction header containing the SOAP action name" do
293
- HTTPI.stubs(:post).returns(new_response)
294
-
295
- client.request :authenticate do
296
- http.headers["SOAPAction"].should == %{"authenticate"}
297
- end
298
- end
299
-
300
- it "should execute SOAP requests and return the response" do
301
- HTTPI.expects(:post).returns(new_response)
302
- response = client.request(:authenticate)
303
-
304
- response.should be_a(Savon::SOAP::Response)
305
- response.to_xml.should == Fixture.response(:authentication)
37
+ describe "#globals" do
38
+ it "returns the current set of global options" do
39
+ expect(new_client.globals).to be_an_instance_of(Savon::GlobalOptions)
306
40
  end
307
41
  end
308
42
 
309
- context "with a local WSDL document" do
310
- let(:client) { Savon::Client.new { wsdl.document = "spec/fixtures/wsdl/authentication.xml" } }
311
-
312
- before { HTTPI.expects(:get).never }
313
-
314
- it "adds a SOAPAction header containing the SOAP action name" do
315
- HTTPI.stubs(:post).returns(new_response)
316
-
317
- client.request :authenticate do
318
- http.headers["SOAPAction"].should == %{"authenticate"}
319
- end
43
+ describe "#operations" do
44
+ it "returns all operation names" do
45
+ operations = new_client.operations
46
+ expect(operations).to eq([:authenticate])
320
47
  end
321
48
 
322
- it "should execute SOAP requests and return the response" do
323
- HTTPI.expects(:post).returns(new_response)
324
- response = client.request(:authenticate)
325
-
326
- response.should be_a(Savon::SOAP::Response)
327
- response.to_xml.should == Fixture.response(:authentication)
49
+ it "raises when there is no WSDL document" do
50
+ expect { new_client_without_wsdl.operations }.to raise_error("Unable to inspect the service without a WSDL document.")
328
51
  end
329
52
  end
330
53
 
331
- context "when the WSDL specifies multiple namespaces" do
332
- before do
333
- HTTPI.stubs(:get).returns(new_response(:body => Fixture.wsdl(:multiple_namespaces)))
334
- HTTPI.stubs(:post).returns(new_response)
335
- end
336
-
337
- it "qualifies each element with the appropriate namespace" do
338
- HTTPI::Request.any_instance.expects(:body=).with do |value|
339
- xml = Nokogiri::XML(value)
340
-
341
- title = xml.at_xpath(
342
- ".//actions:Save/actions:article/article:Title/text()",
343
- "article" => "http://example.com/article",
344
- "actions" => "http://example.com/actions").to_s
345
- author = xml.at_xpath(
346
- ".//actions:Save/actions:article/article:Author/text()",
347
- "article" => "http://example.com/article",
348
- "actions" => "http://example.com/actions").to_s
349
-
350
- title == "Hamlet" && author == "Shakespeare"
351
- end
352
-
353
- client.request :save do
354
- soap.body = { :article => { "Title" => "Hamlet", "Author" => "Shakespeare" } }
355
- end
356
- end
357
-
358
- it "still sends nil as xsi:nil as in the non-namespaced case" do
359
- HTTPI::Request.any_instance.expects(:body=).with do |value|
360
- xml = Nokogiri::XML(value)
361
-
362
- attribute = xml.at_xpath(".//article:Title/@xsi:nil",
363
- "xsi" => "http://www.w3.org/2001/XMLSchema-instance",
364
- "article" => "http://example.com/article").to_s
365
-
366
- attribute == "true"
367
- end
368
-
369
- client.request(:save) { soap.body = { :article => { "Title" => nil } } }
54
+ describe "#operation" do
55
+ it "returns a new SOAP operation" do
56
+ operation = new_client.operation(:authenticate)
57
+ expect(operation).to be_a(Savon::Operation)
370
58
  end
371
59
 
372
- it "translates between symbol :save and string 'Save'" do
373
- HTTPI::Request.any_instance.expects(:body=).with do |value|
374
- xml = Nokogiri::XML(value)
375
- !!xml.at_xpath(".//actions:Save", "actions" => "http://example.com/actions")
376
- end
377
-
378
- client.request :save do
379
- soap.body = { :article => { :title => "Hamlet", :author => "Shakespeare" } }
380
- end
60
+ it "raises if there's no such SOAP operation" do
61
+ expect { new_client.operation(:does_not_exist) }.
62
+ to raise_error(ArgumentError)
381
63
  end
382
64
 
383
- it "qualifies Save with the appropriate namespace" do
384
- HTTPI::Request.any_instance.expects(:body=).with do |value|
385
- xml = Nokogiri::XML(value)
386
- !!xml.at_xpath(".//actions:Save", "actions" => "http://example.com/actions")
387
- end
388
-
389
- client.request "Save" do
390
- soap.body = { :article => { :title => "Hamlet", :author => "Shakespeare" } }
391
- end
65
+ it "does not raise when there is no WSDL document" do
66
+ new_client_without_wsdl.operation(:does_not_exist)
392
67
  end
393
68
  end
394
69
 
395
- context "when the WSDL has a lowerCamel name" do
396
- before do
397
- HTTPI.stubs(:get).returns(new_response(:body => Fixture.wsdl(:lower_camel)))
398
- HTTPI.stubs(:post).returns(new_response)
399
- end
400
-
401
- it "appends namespace when name is specified explicitly" do
402
- HTTPI::Request.any_instance.expects(:body=).with do |value|
403
- xml = Nokogiri::XML(value)
404
- !!xml.at_xpath(".//actions:Save/actions:lowerCamel", "actions" => "http://example.com/actions")
405
- end
406
-
407
- client.request("Save") { soap.body = { 'lowerCamel' => 'theValue' } }
408
- end
70
+ describe "#call" do
71
+ it "calls a new SOAP operation" do
72
+ locals = { :message => { :symbol => "AAPL" } }
73
+ soap_response = new_soap_response
409
74
 
410
- it "still appends namespace when converting from symbol" do
411
- HTTPI::Request.any_instance.expects(:body=).with do |value|
412
- xml = Nokogiri::XML(value)
413
- !!xml.at_xpath(".//actions:Save/actions:lowerCamel", "actions" => "http://example.com/actions")
414
- end
75
+ wsdl = Wasabi::Document.new('http://example.com')
76
+ operation = Savon::Operation.new(:authenticate, wsdl, Savon::GlobalOptions.new)
77
+ operation.expects(:call).with(locals).returns(soap_response)
78
+ Savon::Operation.expects(:create).returns(operation)
415
79
 
416
- client.request("Save") { soap.body = { :lower_camel => 'theValue' } }
80
+ response = new_client.call(:authenticate, locals)
81
+ expect(response).to eq(soap_response)
417
82
  end
418
- end
419
83
 
420
- context "with multiple types" do
421
- before do
422
- HTTPI.stubs(:get).returns(new_response(:body => Fixture.wsdl(:multiple_types)))
423
- HTTPI.stubs(:post).returns(new_response)
424
- end
84
+ it "sets the cookies for the next request" do
85
+ last_response = new_http_response(:headers => { "Set-Cookie" => "some-cookie=choc-chip; Path=/; HttpOnly" })
86
+ client = new_client
425
87
 
426
- it "does not blow up" do
427
- HTTPI::Request.any_instance.expects(:body=).with { |value| value.include?("Save") }
428
- client.request(:save) { soap.body = {} }
429
- end
430
- end
88
+ HTTPI.stubs(:post).returns(last_response)
431
89
 
432
- context "with an Array of namespaced items" do
433
- context "with a single namespace" do
434
- let(:client) { Savon::Client.new { wsdl.document = "spec/fixtures/wsdl/taxcloud.xml" } }
90
+ # does not try to set cookies for the first request
91
+ HTTPI::Request.any_instance.expects(:set_cookies).never
92
+ client.call(:authenticate)
435
93
 
436
- before do
437
- HTTPI.stubs(:get).returns(new_response(:body => Fixture.wsdl(:taxcloud)))
438
- HTTPI.stubs(:post).returns(new_response)
439
- end
94
+ HTTPI.stubs(:post).returns(new_http_response)
440
95
 
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
96
+ # sets cookies from the last response
97
+ HTTPI::Request.any_instance.expects(:set_cookies).with(last_response)
98
+ client.call(:authenticate)
458
99
  end
459
100
 
460
- context "with multiple namespaces" do
461
- let(:client) { Savon::Client.new { wsdl.document = "spec/fixtures/wsdl/multiple_namespaces.xml" } }
101
+ it "supports a block without arguments to call an operation with local options" do
102
+ client = new_client(:endpoint => @server.url(:repeat))
462
103
 
463
- before do
464
- HTTPI.stubs(:get).returns(new_response(:body => Fixture.wsdl(:multiple_namespaces)))
465
- HTTPI.stubs(:post).returns(new_response)
104
+ response = client.call(:authenticate) do
105
+ message(:symbol => "AAPL" )
466
106
  end
467
107
 
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
474
-
475
- article = { "Author" => "John Smith", "Title" => "Modern SOAP" }
476
- client.request :lookup, :body => {
477
- "articles" => { "Article" => [article] }
478
- }
479
- end
480
- end
481
-
482
- end
483
-
484
- context "without a WSDL document" do
485
- let(:client) do
486
- Savon::Client.new do
487
- wsdl.endpoint = Endpoint.soap
488
- wsdl.namespace = "http://v1_0.ws.auth.order.example.com/"
489
- end
108
+ expect(response.http.body).to include("<symbol>AAPL</symbol>")
490
109
  end
491
110
 
492
- before { HTTPI.expects(:get).never }
493
-
494
- it "raise an ArgumentError when trying to access the WSDL" do
495
- lambda { client.wsdl.soap_actions }.should raise_error(ArgumentError, /Wasabi/)
496
- end
111
+ it "supports a block with one argument to call an operation with local options" do
112
+ client = new_client(:endpoint => @server.url(:repeat))
497
113
 
498
- it "adds a SOAPAction header containing the SOAP action name" do
499
- HTTPI.stubs(:post).returns(new_response)
114
+ # supports instance variables!
115
+ @instance_variable = { :symbol => "AAPL" }
500
116
 
501
- client.request :authenticate do
502
- http.headers["SOAPAction"].should == %{"authenticate"}
117
+ response = client.call(:authenticate) do |locals|
118
+ locals.message(@instance_variable)
503
119
  end
504
- end
505
-
506
- it "should execute SOAP requests and return the response" do
507
- HTTPI.expects(:post).returns(new_response)
508
- response = client.request(:authenticate)
509
120
 
510
- response.should be_a(Savon::SOAP::Response)
511
- response.to_xml.should == Fixture.response(:authentication)
121
+ expect(response.http.body).to include("<symbol>AAPL</symbol>")
512
122
  end
513
- end
514
123
 
515
- context "when encountering a SOAP fault" do
516
- let(:client) do
517
- Savon::Client.new do
518
- wsdl.endpoint = Endpoint.soap
519
- wsdl.namespace = "http://v1_0.ws.auth.order.example.com/"
520
- end
124
+ it "raises when the operation name is not a symbol" do
125
+ expect { new_client.call("not a symbol") }.to raise_error(
126
+ ArgumentError,
127
+ "Expected the first parameter (the name of the operation to call) to be a symbol\n" \
128
+ "Actual: \"not a symbol\" (String)"
129
+ )
521
130
  end
131
+ end
522
132
 
523
- before { HTTPI::expects(:post).returns(new_response(:code => 500, :body => Fixture.response(:soap_fault))) }
133
+ def new_http_response(options = {})
134
+ defaults = { :code => 200, :headers => {}, :body => Fixture.response(:authentication) }
135
+ response = defaults.merge options
524
136
 
525
- it "should raise a Savon::SOAP::Fault" do
526
- lambda { client.request :authenticate }.should raise_error(Savon::SOAP::Fault)
527
- end
137
+ HTTPI::Response.new response[:code], response[:headers], response[:body]
528
138
  end
529
139
 
530
- context "when encountering an HTTP error" do
531
- let(:client) do
532
- Savon::Client.new do
533
- wsdl.endpoint = Endpoint.soap
534
- wsdl.namespace = "http://v1_0.ws.auth.order.example.com/"
535
- end
536
- end
537
-
538
- before { HTTPI::expects(:post).returns(new_response(:code => 500)) }
140
+ def new_soap_response(options = {})
141
+ http = new_http_response(options)
142
+ globals = Savon::GlobalOptions.new
143
+ locals = Savon::LocalOptions.new
539
144
 
540
- it "should raise a Savon::HTTP::Error" do
541
- lambda { client.request :authenticate }.should raise_error(Savon::HTTP::Error)
542
- end
145
+ Savon::Response.new(http, globals, locals)
543
146
  end
544
147
 
545
- def new_response(options = {})
546
- defaults = { :code => 200, :headers => {}, :body => Fixture.response(:authentication) }
547
- response = defaults.merge options
548
-
549
- HTTPI::Response.new response[:code], response[:headers], response[:body]
148
+ def new_client(globals = {})
149
+ globals = { :wsdl => Fixture.wsdl(:authentication), :log => false }.merge(globals)
150
+ Savon.client(globals)
550
151
  end
551
152
 
552
- def expect_request_builder_to_receive(operation, options = {})
553
- Savon::SOAP::RequestBuilder.expects(:new).with(operation, options).returns(request_builder)
153
+ def new_client_without_wsdl(globals = {})
154
+ globals = { :endpoint => "http://example.co", :namespace => "http://v1.example.com", :log => false }.merge(globals)
155
+ Savon.client(globals)
554
156
  end
555
157
 
556
158
  end