savon 0.5.3 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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