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