savon 1.2.0 → 2.0.0

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