savon 0.9.7 → 0.9.8
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/.travis.yml +2 -1
- data/.yardopts +4 -1
- data/CHANGELOG.md +34 -0
- data/Gemfile +0 -5
- data/README.md +14 -12
- data/Rakefile +2 -6
- data/lib/savon.rb +4 -3
- data/lib/savon/config.rb +103 -0
- data/lib/savon/hooks/group.rb +46 -0
- data/lib/savon/hooks/hook.rb +36 -0
- data/lib/savon/model.rb +103 -0
- data/lib/savon/soap/invalid_response_error.rb +11 -0
- data/lib/savon/soap/request.rb +19 -20
- data/lib/savon/soap/response.rb +7 -0
- data/lib/savon/soap/xml.rb +9 -1
- data/lib/savon/version.rb +1 -1
- data/savon.gemspec +3 -0
- data/spec/savon/model_spec.rb +194 -0
- data/spec/savon/savon_spec.rb +33 -11
- data/spec/savon/soap/request_spec.rb +24 -10
- data/spec/savon/soap/response_spec.rb +15 -0
- data/spec/savon/soap/xml_spec.rb +20 -3
- data/spec/spec_helper.rb +2 -5
- metadata +124 -161
- data/lib/savon/global.rb +0 -109
data/lib/savon/soap/request.rb
CHANGED
@@ -14,50 +14,49 @@ module Savon
|
|
14
14
|
|
15
15
|
# Expects an <tt>HTTPI::Request</tt> and a <tt>Savon::SOAP::XML</tt> object
|
16
16
|
# to execute a SOAP request and returns the response.
|
17
|
-
def self.execute(
|
18
|
-
new(
|
17
|
+
def self.execute(http, soap)
|
18
|
+
new(http, soap).response
|
19
19
|
end
|
20
20
|
|
21
21
|
# Expects an <tt>HTTPI::Request</tt> and a <tt>Savon::SOAP::XML</tt> object.
|
22
|
-
def initialize(
|
23
|
-
self.
|
22
|
+
def initialize(http, soap)
|
23
|
+
self.soap = soap
|
24
|
+
self.http = configure(http)
|
24
25
|
end
|
25
26
|
|
26
|
-
|
27
|
-
attr_accessor :request
|
27
|
+
attr_accessor :soap, :http
|
28
28
|
|
29
29
|
# Executes the request and returns the response.
|
30
30
|
def response
|
31
|
-
@response ||=
|
31
|
+
@response ||= SOAP::Response.new(
|
32
|
+
Savon.hooks.select(:soap_request).call(self) || with_logging { HTTPI.post(http) }
|
33
|
+
)
|
32
34
|
end
|
33
35
|
|
34
36
|
private
|
35
37
|
|
36
|
-
#
|
37
|
-
def
|
38
|
-
url
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
request.headers["Content-Length"] ||= body.length.to_s
|
44
|
-
|
45
|
-
request
|
38
|
+
# Configures a given +http+ from the +soap+ object.
|
39
|
+
def configure(http)
|
40
|
+
http.url = soap.endpoint
|
41
|
+
http.body = soap.to_xml
|
42
|
+
http.headers["Content-Type"] = ContentType[soap.version]
|
43
|
+
http.headers["Content-Length"] = soap.to_xml.bytesize.to_s
|
44
|
+
http
|
46
45
|
end
|
47
46
|
|
48
47
|
# Logs the HTTP request, yields to a given +block+ and returns a <tt>Savon::SOAP::Response</tt>.
|
49
48
|
def with_logging
|
50
|
-
log_request
|
49
|
+
log_request http.url, http.headers, http.body
|
51
50
|
response = yield
|
52
51
|
log_response response.code, response.body
|
53
|
-
|
52
|
+
response
|
54
53
|
end
|
55
54
|
|
56
55
|
# Logs the SOAP request +url+, +headers+ and +body+.
|
57
56
|
def log_request(url, headers, body)
|
58
57
|
Savon.log "SOAP request: #{url}"
|
59
58
|
Savon.log headers.map { |key, value| "#{key}: #{value}" }.join(", ")
|
60
|
-
Savon.log body
|
59
|
+
Savon.log body, :filter
|
61
60
|
end
|
62
61
|
|
63
62
|
# Logs the SOAP response +code+ and +body+.
|
data/lib/savon/soap/response.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require "savon/soap/xml"
|
2
2
|
require "savon/soap/fault"
|
3
|
+
require "savon/soap/invalid_response_error"
|
3
4
|
require "savon/http/error"
|
4
5
|
|
5
6
|
module Savon
|
@@ -50,11 +51,17 @@ module Savon
|
|
50
51
|
|
51
52
|
# Returns the SOAP response header as a Hash.
|
52
53
|
def header
|
54
|
+
if !hash.has_key? :envelope
|
55
|
+
raise Savon::SOAP::InvalidResponseError, "Unable to parse response body '#{to_xml}'"
|
56
|
+
end
|
53
57
|
hash[:envelope][:header]
|
54
58
|
end
|
55
59
|
|
56
60
|
# Returns the SOAP response body as a Hash.
|
57
61
|
def body
|
62
|
+
if !hash.has_key? :envelope
|
63
|
+
raise Savon::SOAP::InvalidResponseError, "Unable to parse response body '#{to_xml}'"
|
64
|
+
end
|
58
65
|
hash[:envelope][:body]
|
59
66
|
end
|
60
67
|
|
data/lib/savon/soap/xml.rb
CHANGED
@@ -125,6 +125,14 @@ module Savon
|
|
125
125
|
# Accessor for the <tt>Savon::WSSE</tt> object.
|
126
126
|
attr_accessor :wsse
|
127
127
|
|
128
|
+
# Returns the SOAP request encoding. Defaults to "UTF-8".
|
129
|
+
def encoding
|
130
|
+
@encoding ||= "UTF-8"
|
131
|
+
end
|
132
|
+
|
133
|
+
# Sets the SOAP request encoding.
|
134
|
+
attr_writer :encoding
|
135
|
+
|
128
136
|
# Accepts a +block+ and yields a <tt>Builder::XmlMarkup</tt> object to let you create
|
129
137
|
# custom body XML.
|
130
138
|
def body
|
@@ -161,7 +169,7 @@ module Savon
|
|
161
169
|
private
|
162
170
|
|
163
171
|
# Returns a new <tt>Builder::XmlMarkup</tt> object.
|
164
|
-
def builder(directive_tag = :xml, attrs = {})
|
172
|
+
def builder(directive_tag = :xml, attrs = { :encoding => encoding })
|
165
173
|
builder = Builder::XmlMarkup.new
|
166
174
|
builder.instruct!(directive_tag, attrs) if directive_tag
|
167
175
|
builder
|
data/lib/savon/version.rb
CHANGED
data/savon.gemspec
CHANGED
@@ -27,6 +27,9 @@ Gem::Specification.new do |s|
|
|
27
27
|
s.add_development_dependency "mocha", "~> 0.9.8"
|
28
28
|
s.add_development_dependency "timecop", "~> 0.3.5"
|
29
29
|
|
30
|
+
s.add_development_dependency "autotest"
|
31
|
+
s.add_development_dependency "ZenTest", "4.5.0"
|
32
|
+
|
30
33
|
s.files = `git ls-files`.split("\n")
|
31
34
|
s.require_path = "lib"
|
32
35
|
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Savon::Model do
|
4
|
+
|
5
|
+
let(:model) do
|
6
|
+
Class.new { extend Savon::Model }
|
7
|
+
end
|
8
|
+
|
9
|
+
describe ":model_soap_response hook" do
|
10
|
+
|
11
|
+
before(:all) do
|
12
|
+
model.actions :get_user, "GetAllUsers"
|
13
|
+
end
|
14
|
+
|
15
|
+
after do
|
16
|
+
Savon.hooks.reject! :test_hook
|
17
|
+
end
|
18
|
+
|
19
|
+
it "can be used for pre-processing SOAP responses" do
|
20
|
+
Savon.hooks.define(:test_hook, :model_soap_response) do |response|
|
21
|
+
"hello #{response}"
|
22
|
+
end
|
23
|
+
|
24
|
+
model.client.stubs(:request).returns("world") #
|
25
|
+
model.get_user.should == "hello world"
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
describe ".client" do
|
31
|
+
|
32
|
+
it "passes a given block to a new Savon::Client"
|
33
|
+
|
34
|
+
it "memoizes the Savon::Client" do
|
35
|
+
model.client.should equal(model.client)
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
describe ".endpoint" do
|
41
|
+
|
42
|
+
it "sets the SOAP endpoint" do
|
43
|
+
model.endpoint "http://example.com"
|
44
|
+
model.client.wsdl.endpoint.should == "http://example.com"
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
describe ".namespace" do
|
50
|
+
|
51
|
+
it "sets the target namespace" do
|
52
|
+
model.namespace "http://v1.example.com"
|
53
|
+
model.client.wsdl.namespace.should == "http://v1.example.com"
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
describe ".document" do
|
59
|
+
|
60
|
+
it "sets the WSDL document" do
|
61
|
+
model.document "http://example.com/?wsdl"
|
62
|
+
model.client.wsdl.document.should == "http://example.com/?wsdl"
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
describe ".headers" do
|
68
|
+
|
69
|
+
it "sets the HTTP headers" do
|
70
|
+
model.headers("Accept-Charset" => "utf-8")
|
71
|
+
model.client.http.headers.should == { "Accept-Charset" => "utf-8" }
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
describe ".basic_auth" do
|
77
|
+
|
78
|
+
it "sets HTTP Basic auth credentials" do
|
79
|
+
model.basic_auth "login", "password"
|
80
|
+
model.client.http.auth.basic.should == ["login", "password"]
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
describe ".wsse_auth" do
|
86
|
+
|
87
|
+
it "sets WSSE auth credentials" do
|
88
|
+
model.wsse_auth "login", "password", :digest
|
89
|
+
|
90
|
+
model.client.wsse.username.should == "login"
|
91
|
+
model.client.wsse.password.should == "password"
|
92
|
+
model.client.wsse.should be_digest
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
describe ".actions" do
|
98
|
+
|
99
|
+
before(:all) do
|
100
|
+
model.actions :get_user, "GetAllUsers"
|
101
|
+
end
|
102
|
+
|
103
|
+
it "defines class methods each action" do
|
104
|
+
model.should respond_to(:get_user, :get_all_users)
|
105
|
+
end
|
106
|
+
|
107
|
+
it "defines instance methods each action" do
|
108
|
+
model.new.should respond_to(:get_user, :get_all_users)
|
109
|
+
end
|
110
|
+
|
111
|
+
context "(class-level)" do
|
112
|
+
|
113
|
+
it "executes SOAP requests with a given body" do
|
114
|
+
model.client.expects(:request).with(:wsdl, :get_user, :body => { :id => 1 })
|
115
|
+
model.get_user :id => 1
|
116
|
+
end
|
117
|
+
|
118
|
+
it "accepts and passes Strings for action names" do
|
119
|
+
model.client.expects(:request).with(:wsdl, "GetAllUsers", :body => { :id => 1 })
|
120
|
+
model.get_all_users :id => 1
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
context "(instance-level)" do
|
125
|
+
|
126
|
+
it "delegates to the corresponding class method" do
|
127
|
+
model.expects(:get_all_users).with(:active => true)
|
128
|
+
model.new.get_all_users :active => true
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
describe "#client" do
|
136
|
+
|
137
|
+
it "returns the class-level Savon::Client" do
|
138
|
+
model.new.client.should == model.client
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
|
143
|
+
describe "overwriting action methods" do
|
144
|
+
|
145
|
+
context "(class-level)" do
|
146
|
+
|
147
|
+
let(:supermodel) do
|
148
|
+
supermodel = model.dup
|
149
|
+
supermodel.actions :get_user
|
150
|
+
|
151
|
+
def supermodel.get_user(body = nil, &block)
|
152
|
+
p "super"
|
153
|
+
super
|
154
|
+
end
|
155
|
+
|
156
|
+
supermodel
|
157
|
+
end
|
158
|
+
|
159
|
+
it "works" do
|
160
|
+
supermodel.client.expects(:request).with(:wsdl, :get_user, :body => { :id => 1 })
|
161
|
+
supermodel.expects(:p).with("super") # stupid, but works
|
162
|
+
|
163
|
+
supermodel.get_user :id => 1
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
context "(instance-level)" do
|
169
|
+
|
170
|
+
let(:supermodel) do
|
171
|
+
supermodel = model.dup
|
172
|
+
supermodel.actions :get_user
|
173
|
+
supermodel = supermodel.new
|
174
|
+
|
175
|
+
def supermodel.get_user(body = nil, &block)
|
176
|
+
p "super"
|
177
|
+
super
|
178
|
+
end
|
179
|
+
|
180
|
+
supermodel
|
181
|
+
end
|
182
|
+
|
183
|
+
it "works" do
|
184
|
+
supermodel.client.expects(:request).with(:wsdl, :get_user, :body => { :id => 1 })
|
185
|
+
supermodel.expects(:p).with("super") # stupid, but works
|
186
|
+
|
187
|
+
supermodel.get_user :id => 1
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|
191
|
+
|
192
|
+
end
|
193
|
+
|
194
|
+
end
|
data/spec/savon/savon_spec.rb
CHANGED
@@ -19,6 +19,39 @@ describe Savon do
|
|
19
19
|
Savon.configure { |config| config.log = false }
|
20
20
|
Savon.log?.should be_false
|
21
21
|
end
|
22
|
+
|
23
|
+
context "when instructed to filter" do
|
24
|
+
before do
|
25
|
+
Savon.log = true
|
26
|
+
end
|
27
|
+
|
28
|
+
context "and no log filter set" do
|
29
|
+
it "should not filter the message" do
|
30
|
+
Savon.logger.expects(Savon.log_level).with(Fixture.response(:authentication))
|
31
|
+
Savon.log(Fixture.response(:authentication), :filter)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context "and multiple log filters" do
|
36
|
+
before do
|
37
|
+
Savon.configure { |config| config.log_filter = ["logType", "logTime"] }
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should filter element values" do
|
41
|
+
filtered_values = /Notes Log|2010-09-21T18:22:01|2010-09-21T18:22:07/
|
42
|
+
|
43
|
+
Savon.logger.expects(Savon.log_level).with do |msg|
|
44
|
+
msg !~ filtered_values &&
|
45
|
+
msg.include?('<ns10:logTime>***FILTERED***</ns10:logTime>') &&
|
46
|
+
msg.include?('<ns10:logType>***FILTERED***</ns10:logType>') &&
|
47
|
+
msg.include?('<ns11:logTime>***FILTERED***</ns11:logTime>') &&
|
48
|
+
msg.include?('<ns11:logType>***FILTERED***</ns11:logType>')
|
49
|
+
end
|
50
|
+
|
51
|
+
Savon.log(Fixture.response(:list), :filter)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
22
55
|
end
|
23
56
|
|
24
57
|
describe "logger" do
|
@@ -69,17 +102,6 @@ describe Savon do
|
|
69
102
|
lambda { Savon.soap_version = 3 }.should raise_error(ArgumentError)
|
70
103
|
end
|
71
104
|
end
|
72
|
-
|
73
|
-
describe "strip_namespaces" do
|
74
|
-
it "should default to true" do
|
75
|
-
Savon.strip_namespaces?.should == true
|
76
|
-
end
|
77
|
-
|
78
|
-
it "should not strip namespaces when set to false" do
|
79
|
-
Savon.strip_namespaces = false
|
80
|
-
Savon.strip_namespaces?.should == false
|
81
|
-
end
|
82
|
-
end
|
83
105
|
end
|
84
106
|
|
85
107
|
end
|
@@ -20,30 +20,34 @@ describe Savon::SOAP::Request do
|
|
20
20
|
|
21
21
|
describe ".new" do
|
22
22
|
it "uses the SOAP endpoint for the request" do
|
23
|
-
soap_request.
|
23
|
+
soap_request.http.url.should == URI(soap.endpoint)
|
24
24
|
end
|
25
25
|
|
26
26
|
it "sets the SOAP body for the request" do
|
27
|
-
soap_request.
|
27
|
+
soap_request.http.body.should == soap.to_xml
|
28
28
|
end
|
29
29
|
|
30
30
|
it "sets the Content-Type header for SOAP 1.1" do
|
31
|
-
soap_request.
|
31
|
+
soap_request.http.headers["Content-Type"].should == Savon::SOAP::Request::ContentType[1]
|
32
32
|
end
|
33
33
|
|
34
34
|
it "sets the Content-Type header for SOAP 1.2" do
|
35
35
|
soap.version = 2
|
36
|
-
soap_request.
|
36
|
+
soap_request.http.headers["Content-Type"].should == Savon::SOAP::Request::ContentType[2]
|
37
37
|
end
|
38
38
|
|
39
|
-
it "
|
40
|
-
headers
|
41
|
-
soap_request = Savon::SOAP::Request.new HTTPI::Request.new(:headers => headers), soap
|
42
|
-
soap_request.request.headers["Content-Type"].should == headers["Content-Type"]
|
39
|
+
it "sets the Content-Length header" do
|
40
|
+
soap_request.http.headers["Content-Length"].should == soap.to_xml.length.to_s
|
43
41
|
end
|
44
42
|
|
45
|
-
it "sets the Content-Length header" do
|
46
|
-
|
43
|
+
it "sets the Content-Length header for every request" do
|
44
|
+
http = HTTPI::Request.new
|
45
|
+
soap_request = Savon::SOAP::Request.new(http, soap)
|
46
|
+
http.headers.should include("Content-Length" => "272")
|
47
|
+
|
48
|
+
soap = Savon::SOAP::XML.new Endpoint.soap, [nil, :create_user, {}], :id => 123
|
49
|
+
soap_request = Savon::SOAP::Request.new(http, soap)
|
50
|
+
http.headers.should include("Content-Length" => "280")
|
47
51
|
end
|
48
52
|
end
|
49
53
|
|
@@ -52,6 +56,16 @@ describe Savon::SOAP::Request do
|
|
52
56
|
HTTPI.expects(:post).returns(HTTPI::Response.new 200, {}, Fixture.response(:authentication))
|
53
57
|
soap_request.response.should be_a(Savon::SOAP::Response)
|
54
58
|
end
|
59
|
+
|
60
|
+
it "logs the filtered SOAP request body" do
|
61
|
+
HTTPI.stubs(:post).returns(HTTPI::Response.new 200, {}, "")
|
62
|
+
|
63
|
+
Savon.stubs(:log).times(2)
|
64
|
+
Savon.expects(:log).with(soap.to_xml, :filter)
|
65
|
+
Savon.stubs(:log).times(2)
|
66
|
+
|
67
|
+
soap_request.response
|
68
|
+
end
|
55
69
|
end
|
56
70
|
|
57
71
|
end
|