savon 0.5.3 → 0.6.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/VERSION +1 -1
- data/lib/savon.rb +12 -22
- data/lib/savon/client.rb +26 -63
- data/lib/savon/core_ext.rb +3 -6
- data/lib/savon/core_ext/hash.rb +0 -12
- data/lib/savon/core_ext/string.rb +5 -0
- data/lib/savon/request.rb +17 -15
- data/lib/savon/response.rb +109 -0
- data/lib/savon/soap.rb +67 -33
- data/lib/savon/wsdl.rb +25 -18
- data/lib/savon/wsse.rb +59 -54
- data/spec/fixtures/user_fixture.rb +16 -4
- data/spec/savon/client_spec.rb +20 -129
- data/spec/savon/core_ext/hash_spec.rb +0 -26
- data/spec/savon/core_ext/string_spec.rb +12 -0
- data/spec/savon/request_spec.rb +9 -10
- data/spec/savon/response_spec.rb +133 -0
- data/spec/savon/savon_spec.rb +1 -8
- data/spec/savon/soap_spec.rb +86 -47
- data/spec/savon/wsdl_spec.rb +19 -8
- data/spec/savon/wsse_spec.rb +73 -84
- metadata +5 -5
- data/lib/savon/validation.rb +0 -57
- data/spec/savon/validation_spec.rb +0 -88
data/lib/savon/wsdl.rb
CHANGED
@@ -2,10 +2,8 @@ module Savon
|
|
2
2
|
|
3
3
|
# Savon::WSDL
|
4
4
|
#
|
5
|
-
#
|
6
|
-
# or less qualitative API documentation.
|
5
|
+
# Represents a WSDL document.
|
7
6
|
class WSDL
|
8
|
-
include Validation
|
9
7
|
|
10
8
|
# Expects a Savon::Request object.
|
11
9
|
def initialize(request)
|
@@ -17,21 +15,23 @@ module Savon
|
|
17
15
|
@namespace_uri ||= parse_namespace_uri
|
18
16
|
end
|
19
17
|
|
20
|
-
# Returns
|
18
|
+
# Returns a Hash of available SOAP actions mapped to snake_case (keys)
|
19
|
+
# and their original names and inputs in another Hash (values).
|
21
20
|
def soap_actions
|
22
|
-
|
21
|
+
@soap_actions ||= parse_soap_operations.inject({}) do |hash, (input, action)|
|
22
|
+
hash.merge input.snakecase.to_sym => { :name => action, :input => input }
|
23
|
+
end
|
23
24
|
end
|
24
25
|
|
25
|
-
# Returns
|
26
|
-
def
|
27
|
-
|
28
|
-
|
29
|
-
end
|
26
|
+
# Returns +true+ for available methods and SOAP actions.
|
27
|
+
def respond_to?(method)
|
28
|
+
return true if soap_actions.keys.include? method
|
29
|
+
super
|
30
30
|
end
|
31
31
|
|
32
|
-
# Returns the WSDL
|
32
|
+
# Returns the WSDL document.
|
33
33
|
def to_s
|
34
|
-
wsdl_response
|
34
|
+
wsdl_response.body
|
35
35
|
end
|
36
36
|
|
37
37
|
private
|
@@ -41,7 +41,7 @@ module Savon
|
|
41
41
|
def wsdl_response
|
42
42
|
unless @wsdl_response
|
43
43
|
@wsdl_response ||= @request.wsdl
|
44
|
-
|
44
|
+
raise ArgumentError, "Invalid WSDL: #{@request.endpoint}" if soap_actions.empty?
|
45
45
|
end
|
46
46
|
@wsdl_response
|
47
47
|
end
|
@@ -57,12 +57,19 @@ module Savon
|
|
57
57
|
definitions.attributes["targetNamespace"] if definitions
|
58
58
|
end
|
59
59
|
|
60
|
-
# Parses the WSDL for available SOAP actions.
|
61
|
-
|
62
|
-
|
63
|
-
|
60
|
+
# Parses the WSDL for available SOAP actions and inputs. Returns a Hash
|
61
|
+
# containing the SOAP action inputs and corresponding SOAP actions.
|
62
|
+
def parse_soap_operations
|
63
|
+
wsdl_binding = document.elements["//wsdl:binding"]
|
64
|
+
return {} unless wsdl_binding
|
65
|
+
|
66
|
+
wsdl_binding.elements.inject("//wsdl:operation", {}) do |hash, operation|
|
67
|
+
action = operation.elements["*:operation"].attributes["soapAction"] || ""
|
68
|
+
action = operation.attributes["name"] if action.empty?
|
69
|
+
|
70
|
+
hash.merge action.split("/").last => action
|
64
71
|
end
|
65
72
|
end
|
66
73
|
|
67
74
|
end
|
68
|
-
end
|
75
|
+
end
|
data/lib/savon/wsse.rb
CHANGED
@@ -2,8 +2,8 @@ module Savon
|
|
2
2
|
|
3
3
|
# Savon::WSSE
|
4
4
|
#
|
5
|
-
#
|
6
|
-
|
5
|
+
# Represents parameters for WSSE authentication.
|
6
|
+
class WSSE
|
7
7
|
|
8
8
|
# Namespace for WS Security Secext.
|
9
9
|
WSENamespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
|
@@ -12,99 +12,104 @@ module Savon
|
|
12
12
|
WSUNamespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
|
13
13
|
|
14
14
|
# Default WSSE username.
|
15
|
-
@username =
|
15
|
+
@username = ""
|
16
16
|
|
17
17
|
# Default WSSE password.
|
18
|
-
@password =
|
18
|
+
@password = ""
|
19
19
|
|
20
20
|
# Default for whether to use WSSE digest.
|
21
21
|
@digest = false
|
22
22
|
|
23
23
|
class << self
|
24
24
|
|
25
|
-
#
|
26
|
-
|
25
|
+
# Returns the default WSSE username.
|
26
|
+
attr_reader :username
|
27
27
|
|
28
|
-
#
|
29
|
-
|
28
|
+
# Sets the default WSSE username.
|
29
|
+
def username=(username)
|
30
|
+
@username = username.to_s if username.respond_to? :to_s
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns the default WSSE password.
|
34
|
+
attr_reader :password
|
35
|
+
|
36
|
+
# Sets the default WSSE password.
|
37
|
+
def password=(password)
|
38
|
+
@password = password.to_s if password.respond_to? :to_s
|
39
|
+
end
|
30
40
|
|
31
41
|
# Sets whether to use WSSE digest by default.
|
32
42
|
attr_writer :digest
|
33
43
|
|
34
|
-
# Returns whether to use WSSE digest by default.
|
44
|
+
# Returns whether to use WSSE digest by default.
|
35
45
|
def digest?
|
36
46
|
@digest
|
37
47
|
end
|
38
48
|
|
39
49
|
end
|
40
50
|
|
41
|
-
#
|
42
|
-
def
|
43
|
-
|
44
|
-
wsse_username && wsse_password
|
51
|
+
# Sets the WSSE username.
|
52
|
+
def username=(username)
|
53
|
+
@username = username.to_s if username.respond_to? :to_s
|
45
54
|
end
|
46
55
|
|
47
|
-
#
|
48
|
-
def
|
49
|
-
|
50
|
-
|
51
|
-
xml.wsse :Security, "xmlns:wsse" => WSENamespace do
|
52
|
-
xml.wsse :UsernameToken, "xmlns:wsu" => WSUNamespace do
|
53
|
-
wsse_nodes xml
|
54
|
-
end
|
55
|
-
end
|
56
|
+
# Returns the WSSE username. Defaults to the global default.
|
57
|
+
def username
|
58
|
+
@username || self.class.username
|
56
59
|
end
|
57
60
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
def wsse_username
|
62
|
-
username = options[:wsse][:username] || WSSE.username
|
63
|
-
username ? username : false
|
61
|
+
# Sets the WSSE password.
|
62
|
+
def password=(password)
|
63
|
+
@password = password.to_s if password.respond_to? :to_s
|
64
64
|
end
|
65
65
|
|
66
|
-
# Returns the WSSE password
|
67
|
-
def
|
68
|
-
password
|
69
|
-
password ? password : false
|
66
|
+
# Returns the WSSE password. Defaults to the global default.
|
67
|
+
def password
|
68
|
+
@password || self.class.password
|
70
69
|
end
|
71
70
|
|
72
|
-
#
|
71
|
+
# Sets whether to use WSSE digest.
|
72
|
+
attr_writer :digest
|
73
|
+
|
74
|
+
# Returns whether to use WSSE digest. Defaults to the global default.
|
73
75
|
def digest?
|
74
|
-
digest
|
75
|
-
digest ? true : false
|
76
|
+
@digest || self.class.digest?
|
76
77
|
end
|
77
78
|
|
78
|
-
#
|
79
|
-
#
|
80
|
-
def
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
79
|
+
# Returns the XML for a WSSE header or an empty String unless username
|
80
|
+
# and password are specified.
|
81
|
+
def header
|
82
|
+
return "" unless username && password
|
83
|
+
|
84
|
+
builder = Builder::XmlMarkup.new
|
85
|
+
builder.wsse :Security, "xmlns:wsse" => WSENamespace do |xml|
|
86
|
+
xml.wsse :UsernameToken, "xmlns:wsu" => WSUNamespace do
|
87
|
+
xml.wsse :Username, username
|
88
|
+
xml.wsse :Nonce, nonce
|
89
|
+
xml.wsu :Created, timestamp
|
90
|
+
xml.wsse :Password, password_node
|
91
|
+
end
|
92
|
+
end
|
85
93
|
end
|
86
94
|
|
95
|
+
private
|
96
|
+
|
87
97
|
# Returns the WSSE password. Encrypts the password for digest authentication.
|
88
|
-
def
|
89
|
-
return
|
98
|
+
def password_node
|
99
|
+
return password unless digest?
|
90
100
|
|
91
|
-
token =
|
101
|
+
token = nonce + timestamp + password
|
92
102
|
Base64.encode64(Digest::SHA1.hexdigest(token)).chomp!
|
93
103
|
end
|
94
104
|
|
95
105
|
# Returns a WSSE nonce.
|
96
|
-
def
|
97
|
-
@
|
106
|
+
def nonce
|
107
|
+
@nonce ||= Digest::SHA1.hexdigest String.random + timestamp
|
98
108
|
end
|
99
109
|
|
100
110
|
# Returns a WSSE timestamp.
|
101
|
-
def
|
102
|
-
@
|
103
|
-
end
|
104
|
-
|
105
|
-
# Returns a random String of a given +length+.
|
106
|
-
def random_string(length = 100)
|
107
|
-
(0...length).map { ("a".."z").to_a[rand(26)] }.join
|
111
|
+
def timestamp
|
112
|
+
@timestamp ||= Time.now.strftime Savon::SOAPDateTimeFormat
|
108
113
|
end
|
109
114
|
|
110
115
|
end
|
@@ -1,14 +1,26 @@
|
|
1
1
|
class UserFixture
|
2
2
|
|
3
3
|
@namespace_uri = "http://v1_0.ws.user.example.com"
|
4
|
-
@soap_actions = {
|
4
|
+
@soap_actions = {
|
5
|
+
:user_find_by_id => { :name => "User.FindById", :input => "User.FindById" },
|
6
|
+
:find_user => { :name => "findUser", :input => "findUser" }
|
7
|
+
}
|
5
8
|
|
6
9
|
@datetime_string = "2010-11-22T11:22:33"
|
7
10
|
@datetime_object = DateTime.parse @datetime_string
|
8
11
|
|
9
|
-
@response_hash = {
|
10
|
-
:
|
11
|
-
:
|
12
|
+
@response_hash = {
|
13
|
+
:ns2 => "http://v1_0.ws.user.example.com",
|
14
|
+
:return => {
|
15
|
+
:active => true,
|
16
|
+
:firstname => "The",
|
17
|
+
:lastname => "Dude",
|
18
|
+
:email => "thedude@example.com",
|
19
|
+
:id => "666",
|
20
|
+
:registered => @datetime_object,
|
21
|
+
:username => "thedude"
|
22
|
+
}
|
23
|
+
}
|
12
24
|
|
13
25
|
class << self
|
14
26
|
|
data/spec/savon/client_spec.rb
CHANGED
@@ -1,72 +1,15 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
3
|
describe Savon::Client do
|
4
|
-
before { @client =
|
4
|
+
before { @client = some_client_instance }
|
5
5
|
|
6
|
-
def
|
6
|
+
def some_client_instance
|
7
7
|
Savon::Client.new SpecHelper.some_endpoint
|
8
8
|
end
|
9
9
|
|
10
|
-
describe "@response_process" do
|
11
|
-
it "expects a Net::HTTPResponse, translates the response" <<
|
12
|
-
"into a Hash and returns the SOAP response body" do
|
13
|
-
response = Savon::Client.response_process.call(http_response_mock)
|
14
|
-
|
15
|
-
response.should be_a Hash
|
16
|
-
UserFixture.response_hash.each do |key, value|
|
17
|
-
response[key].should == value
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
it "has accessor methods" do
|
22
|
-
response_process = Savon::Client.response_process
|
23
|
-
|
24
|
-
Savon::Client.response_process = "process"
|
25
|
-
Savon::Client.response_process.should == "process"
|
26
|
-
Savon::Client.response_process = response_process
|
27
|
-
Savon::Client.response_process.should == response_process
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
describe "@error_handling" do
|
32
|
-
before { @error_handling = Savon::Client.error_handling }
|
33
|
-
|
34
|
-
it "raises a Savon::SOAPFault in case it finds a SOAP fault" do
|
35
|
-
body = { "soapenv:Fault" => {
|
36
|
-
"faultcode" => "soap:Server",
|
37
|
-
"faultstring" => "Fault occurred while processing."
|
38
|
-
} }
|
39
|
-
|
40
|
-
lambda { @error_handling.call(http_response_mock, body) }.
|
41
|
-
should raise_error Savon::SOAPFault
|
42
|
-
end
|
43
|
-
|
44
|
-
it "raises a Savon::SOAPFault in case it finds both HTTPError and SOAP fault" do
|
45
|
-
body = { "soapenv:Fault" => {
|
46
|
-
"faultcode" => "soap:Server",
|
47
|
-
"faultstring" => "Fault occurred while processing."
|
48
|
-
} }
|
49
|
-
|
50
|
-
lambda { @error_handling.call(http_response_mock(500), body) }.
|
51
|
-
should raise_error Savon::SOAPFault
|
52
|
-
end
|
53
|
-
|
54
|
-
it "raises a Savon::HTTPError in case it finds an HTTP error" do
|
55
|
-
body = { "someResponse" => "whatever" }
|
56
|
-
|
57
|
-
lambda { @error_handling.call(http_response_mock(404), body) }.
|
58
|
-
should raise_error Savon::HTTPError
|
59
|
-
end
|
60
|
-
|
61
|
-
it "passes without raising anything otherwise" do
|
62
|
-
body = { "someResponse" => "whatever" }
|
63
|
-
@error_handling.call(http_response_mock, body).should be_nil
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
10
|
describe "initialize" do
|
68
11
|
it "expects a SOAP endpoint String" do
|
69
|
-
|
12
|
+
some_client_instance
|
70
13
|
end
|
71
14
|
|
72
15
|
it "raises an ArgumentError in case of an invalid endpoint" do
|
@@ -76,19 +19,7 @@ describe Savon::Client do
|
|
76
19
|
|
77
20
|
describe "wsdl" do
|
78
21
|
it "returns the Savon::WSDL" do
|
79
|
-
@client.find_user
|
80
|
-
|
81
22
|
@client.wsdl.should be_a Savon::WSDL
|
82
|
-
@client.wsdl.to_s.should == UserFixture.user_wsdl
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
describe "response" do
|
87
|
-
it "returns the Net::HTTPResponse of the last SOAP request" do
|
88
|
-
@client.find_user
|
89
|
-
|
90
|
-
@client.response.should be_a Net::HTTPResponse
|
91
|
-
@client.response.body.should == UserFixture.user_response
|
92
23
|
end
|
93
24
|
end
|
94
25
|
|
@@ -100,61 +31,13 @@ describe Savon::Client do
|
|
100
31
|
|
101
32
|
it "still behaves like usual otherwise" do
|
102
33
|
@client.respond_to?(:object_id).should be_true
|
103
|
-
@client.respond_to?(:
|
34
|
+
@client.respond_to?(:some_undefined_method).should be_false
|
104
35
|
end
|
105
36
|
end
|
106
37
|
|
107
38
|
describe "method_missing" do
|
108
39
|
it "dispatches SOAP requests for available SOAP actions" do
|
109
|
-
@client.find_user.should be_a
|
110
|
-
end
|
111
|
-
|
112
|
-
it "still returns a NoMethodError for missing methods" do
|
113
|
-
lambda { @client.some_missing_method }.should raise_error NoMethodError
|
114
|
-
end
|
115
|
-
|
116
|
-
it "accepts a Hash for specifying the SOAP body" do
|
117
|
-
soap_body_hash = { :id => 666 }
|
118
|
-
@client.find_user soap_body_hash
|
119
|
-
|
120
|
-
@client.instance_variable_get("@soap").body.
|
121
|
-
should include soap_body_hash.to_soap_xml
|
122
|
-
end
|
123
|
-
|
124
|
-
it "accepts a String for specifying the SOAP body" do
|
125
|
-
soap_body_xml = "<username>dude</username>"
|
126
|
-
@client.find_user soap_body_xml
|
127
|
-
|
128
|
-
@client.instance_variable_get("@soap").body.
|
129
|
-
should include soap_body_xml
|
130
|
-
end
|
131
|
-
|
132
|
-
describe "accepts a Hash of per request options" do
|
133
|
-
it "to specify the SOAP version" do
|
134
|
-
@client.find_user nil, :soap_version => 2
|
135
|
-
@client.instance_variable_get("@soap").version.should == 2
|
136
|
-
end
|
137
|
-
|
138
|
-
it "to specify credentials for WSSE authentication" do
|
139
|
-
@client.find_user nil, :wsse =>
|
140
|
-
{ :username => "gorilla", :password => "secret" }
|
141
|
-
|
142
|
-
@client.instance_variable_get("@soap").body.should include "gorilla"
|
143
|
-
@client.instance_variable_get("@soap").body.should include "secret"
|
144
|
-
end
|
145
|
-
|
146
|
-
it "to specify credentials for WSSE digestauthentication" do
|
147
|
-
@client.find_user nil, :wsse =>
|
148
|
-
{ :username => "gorilla", :password => "secret", :digest => true }
|
149
|
-
|
150
|
-
@client.instance_variable_get("@soap").body.should include "gorilla"
|
151
|
-
@client.instance_variable_get("@soap").body.should_not include "secret"
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
it "accepts a block for specifying the response process per request" do
|
156
|
-
@client.find_user { |response| response.body }.
|
157
|
-
should == UserFixture.user_response
|
40
|
+
@client.find_user.should be_a Savon::Response
|
158
41
|
end
|
159
42
|
|
160
43
|
it "raises a Savon::SOAPFault in case of a SOAP fault" do
|
@@ -166,13 +49,21 @@ describe Savon::Client do
|
|
166
49
|
client = Savon::Client.new SpecHelper.httperror_endpoint
|
167
50
|
lambda { client.find_user }.should raise_error Savon::HTTPError
|
168
51
|
end
|
169
|
-
end
|
170
52
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
53
|
+
it "yields the SOAP object to a block that expects one argument" do
|
54
|
+
@client.find_user { |soap| soap.should be_a Savon::SOAP }
|
55
|
+
end
|
56
|
+
|
57
|
+
it "yields the SOAP and WSSE object to a block that expects two argument" do
|
58
|
+
@client.find_user do |soap, wsse|
|
59
|
+
soap.should be_a Savon::SOAP
|
60
|
+
wsse.should be_a Savon::WSSE
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
it "still raises a NoMethodError for undefined methods" do
|
65
|
+
lambda { @client.some_undefined_method }.should raise_error NoMethodError
|
66
|
+
end
|
176
67
|
end
|
177
68
|
|
178
|
-
end
|
69
|
+
end
|