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/lib/savon/wsdl.rb CHANGED
@@ -2,10 +2,8 @@ module Savon
2
2
 
3
3
  # Savon::WSDL
4
4
  #
5
- # Savon::WSDL represents a WSDL document. A WSDL document serves as a more
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 an Array of available SOAP actions from the WSDL.
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
- mapped_soap_actions.keys
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 a Hash of available SOAP actions and their original names.
26
- def mapped_soap_actions
27
- @mapped_soap_actions ||= parse_soap_actions.inject Hash.new do |hash, soap_action|
28
- hash.merge soap_action.snakecase.to_sym => soap_action
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 or +nil+ in case the WSDL could not be retrieved.
32
+ # Returns the WSDL document.
33
33
  def to_s
34
- wsdl_response ? wsdl_response.body : nil
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
- invalid! :wsdl, @request.endpoint unless soap_actions && !soap_actions.empty?
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
- def parse_soap_actions
62
- document.elements.collect "//[@soapAction]" do |element|
63
- element.parent.attributes["name"]
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
- # Includes support methods for adding WSSE authentication to a SOAP request.
6
- module WSSE
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 = nil
15
+ @username = ""
16
16
 
17
17
  # Default WSSE password.
18
- @password = nil
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
- # Accessor for the default WSSE username.
26
- attr_accessor :username
25
+ # Returns the default WSSE username.
26
+ attr_reader :username
27
27
 
28
- # Accessor for the default WSSE password.
29
- attr_accessor :password
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
- # Returns whether WSSE authentication was set via options.
42
- def wsse?
43
- options[:wsse] = {} unless options[:wsse].kind_of? Hash
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
- # Takes a Builder::XmlMarkup instance and appends a WSSE header.
48
- def wsse_header(xml)
49
- options[:wsse] = {} unless options[:wsse].kind_of? Hash
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
- private
59
-
60
- # Returns the WSSE username or false in case no username was found.
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 or false in case no password was found.
67
- def wsse_password
68
- password = options[:wsse][:password] || WSSE.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
- # Returns whether to use WSSE digest authentication based on options.
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 = options[:wsse][:digest] || WSSE.digest?
75
- digest ? true : false
76
+ @digest || self.class.digest?
76
77
  end
77
78
 
78
- # Takes a Builder::XmlMarkup instance and appends the credentials for
79
- # WSSE authentication.
80
- def wsse_nodes(xml)
81
- xml.wsse :Username, wsse_username
82
- xml.wsse :Nonce, wsse_nonce
83
- xml.wsu :Created, wsse_timestamp
84
- xml.wsse :Password, wsse_password_node
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 wsse_password_node
89
- return wsse_password unless digest?
98
+ def password_node
99
+ return password unless digest?
90
100
 
91
- token = wsse_nonce + wsse_timestamp + wsse_password
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 wsse_nonce
97
- @wsse_nonce ||= Digest::SHA1.hexdigest random_string + wsse_timestamp
106
+ def nonce
107
+ @nonce ||= Digest::SHA1.hexdigest String.random + timestamp
98
108
  end
99
109
 
100
110
  # Returns a WSSE timestamp.
101
- def wsse_timestamp
102
- @wsse_timestamp ||= Time.now.strftime Savon::SOAPDateTimeFormat
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 = { :user_find_by_id => "User.FindById", :find_user => "findUser" }
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 = { :id => "666", :active => true, :username => "thedude",
10
- :firstname => "The", :lastname => "Dude", :email => "thedude@example.com",
11
- :registered => @datetime_object }
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
 
@@ -1,72 +1,15 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe Savon::Client do
4
- before { @client = new_client_instance }
4
+ before { @client = some_client_instance }
5
5
 
6
- def new_client_instance
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
- new_client_instance
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?(:some_missing_method).should be_false
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 Hash
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
- def http_response_mock(code = 200)
172
- http_response_mock = mock "Net::HTTPResponse"
173
- http_response_mock.stubs :code => code.to_s, :message => "OK",
174
- :content_type => "text/html", :body => UserFixture.user_response
175
- http_response_mock
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