savon 1.2.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +119 -104
- data/README.md +12 -11
- data/Rakefile +0 -6
- data/lib/savon.rb +16 -14
- data/lib/savon/block_interface.rb +26 -0
- data/lib/savon/builder.rb +142 -0
- data/lib/savon/client.rb +36 -135
- data/lib/savon/header.rb +42 -0
- data/lib/savon/http_error.rb +27 -0
- data/lib/savon/log_message.rb +23 -25
- data/lib/savon/message.rb +35 -0
- data/lib/savon/mock.rb +5 -0
- data/lib/savon/mock/expectation.rb +70 -0
- data/lib/savon/mock/spec_helper.rb +62 -0
- data/lib/savon/model.rb +39 -61
- data/lib/savon/operation.rb +62 -0
- data/lib/savon/options.rb +265 -0
- data/lib/savon/qualified_message.rb +49 -0
- data/lib/savon/request.rb +92 -0
- data/lib/savon/response.rb +97 -0
- data/lib/savon/soap_fault.rb +40 -0
- data/lib/savon/version.rb +1 -1
- data/savon.gemspec +10 -8
- data/spec/integration/options_spec.rb +536 -0
- data/spec/integration/request_spec.rb +31 -16
- data/spec/integration/support/application.rb +80 -0
- data/spec/integration/support/server.rb +84 -0
- data/spec/savon/builder_spec.rb +81 -0
- data/spec/savon/client_spec.rb +90 -488
- data/spec/savon/http_error_spec.rb +49 -0
- data/spec/savon/log_message_spec.rb +33 -0
- data/spec/savon/mock_spec.rb +127 -0
- data/spec/savon/model_spec.rb +110 -99
- data/spec/savon/observers_spec.rb +92 -0
- data/spec/savon/operation_spec.rb +49 -0
- data/spec/savon/request_spec.rb +145 -0
- data/spec/savon/{soap/response_spec.rb → response_spec.rb} +22 -59
- data/spec/savon/soap_fault_spec.rb +94 -0
- data/spec/spec_helper.rb +5 -3
- data/spec/support/fixture.rb +5 -1
- metadata +202 -197
- data/lib/savon/config.rb +0 -46
- data/lib/savon/error.rb +0 -6
- data/lib/savon/hooks/group.rb +0 -68
- data/lib/savon/hooks/hook.rb +0 -61
- data/lib/savon/http/error.rb +0 -42
- data/lib/savon/logger.rb +0 -39
- data/lib/savon/null_logger.rb +0 -10
- data/lib/savon/soap.rb +0 -21
- data/lib/savon/soap/fault.rb +0 -59
- data/lib/savon/soap/invalid_response_error.rb +0 -13
- data/lib/savon/soap/request.rb +0 -86
- data/lib/savon/soap/request_builder.rb +0 -205
- data/lib/savon/soap/response.rb +0 -117
- data/lib/savon/soap/xml.rb +0 -257
- data/spec/savon/config_spec.rb +0 -38
- data/spec/savon/hooks/group_spec.rb +0 -71
- data/spec/savon/hooks/hook_spec.rb +0 -16
- data/spec/savon/http/error_spec.rb +0 -52
- data/spec/savon/logger_spec.rb +0 -51
- data/spec/savon/savon_spec.rb +0 -33
- data/spec/savon/soap/fault_spec.rb +0 -89
- data/spec/savon/soap/request_builder_spec.rb +0 -207
- data/spec/savon/soap/request_spec.rb +0 -112
- data/spec/savon/soap/xml_spec.rb +0 -357
- data/spec/savon/soap_spec.rb +0 -16
@@ -1,22 +1,23 @@
|
|
1
|
-
require "spec_helper"
|
1
|
+
require "spec_helper"
|
2
2
|
|
3
|
-
describe "
|
3
|
+
describe "Requests" do
|
4
4
|
|
5
5
|
subject(:client) {
|
6
|
-
|
7
|
-
|
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.
|
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.
|
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
|
-
|
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.
|
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
|
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
|
data/spec/savon/client_spec.rb
CHANGED
@@ -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
|
-
|
7
|
-
|
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
|
-
|
68
|
-
|
69
|
-
client.wsse.should be_a(Akami::WSSE)
|
70
|
-
end
|
10
|
+
after :all do
|
11
|
+
@server.stop
|
71
12
|
end
|
72
13
|
|
73
|
-
describe "
|
74
|
-
|
75
|
-
|
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
|
-
|
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
|
-
|
177
|
-
|
178
|
-
|
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
|
-
|
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 "
|
283
|
-
client
|
284
|
-
|
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
|
-
|
289
|
-
|
290
|
-
|
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
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
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 "
|
323
|
-
|
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
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
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 "
|
373
|
-
|
374
|
-
|
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 "
|
384
|
-
|
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
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
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
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
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
|
-
|
80
|
+
response = new_client.call(:authenticate, locals)
|
81
|
+
expect(response).to eq(soap_response)
|
417
82
|
end
|
418
|
-
end
|
419
83
|
|
420
|
-
|
421
|
-
|
422
|
-
|
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
|
-
|
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
|
-
|
433
|
-
|
434
|
-
|
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
|
-
|
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
|
-
|
442
|
-
|
443
|
-
|
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
|
-
|
461
|
-
|
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
|
-
|
464
|
-
|
465
|
-
HTTPI.stubs(:post).returns(new_response)
|
104
|
+
response = client.call(:authenticate) do
|
105
|
+
message(:symbol => "AAPL" )
|
466
106
|
end
|
467
107
|
|
468
|
-
|
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
|
-
|
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
|
-
|
499
|
-
|
114
|
+
# supports instance variables!
|
115
|
+
@instance_variable = { :symbol => "AAPL" }
|
500
116
|
|
501
|
-
client.
|
502
|
-
|
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.
|
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
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
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
|
-
|
133
|
+
def new_http_response(options = {})
|
134
|
+
defaults = { :code => 200, :headers => {}, :body => Fixture.response(:authentication) }
|
135
|
+
response = defaults.merge options
|
524
136
|
|
525
|
-
|
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
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
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
|
-
|
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
|
546
|
-
|
547
|
-
|
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
|
553
|
-
|
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
|