savon 0.3.2 → 0.5.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.
Files changed (42) hide show
  1. data/README.textile +68 -0
  2. data/Rakefile +9 -7
  3. data/VERSION +1 -1
  4. data/lib/savon.rb +33 -34
  5. data/lib/savon/client.rb +96 -0
  6. data/lib/savon/core_ext.rb +6 -0
  7. data/lib/savon/core_ext/datetime.rb +8 -0
  8. data/lib/savon/core_ext/hash.rb +65 -0
  9. data/lib/savon/core_ext/object.rb +14 -0
  10. data/lib/savon/core_ext/string.rb +41 -0
  11. data/lib/savon/core_ext/symbol.rb +8 -0
  12. data/lib/savon/core_ext/uri.rb +10 -0
  13. data/lib/savon/request.rb +103 -0
  14. data/lib/savon/soap.rb +71 -0
  15. data/lib/savon/validation.rb +57 -0
  16. data/lib/savon/wsdl.rb +39 -41
  17. data/lib/savon/wsse.rb +111 -0
  18. data/spec/fixtures/multiple_user_response.xml +22 -0
  19. data/spec/fixtures/soap_fault.xml +0 -0
  20. data/spec/fixtures/user_fixture.rb +42 -0
  21. data/spec/fixtures/user_response.xml +4 -2
  22. data/spec/fixtures/user_wsdl.xml +0 -0
  23. data/spec/http_stubs.rb +20 -0
  24. data/spec/savon/client_spec.rb +144 -0
  25. data/spec/savon/core_ext/datetime_spec.rb +12 -0
  26. data/spec/savon/core_ext/hash_spec.rb +146 -0
  27. data/spec/savon/core_ext/object_spec.rb +26 -0
  28. data/spec/savon/core_ext/string_spec.rb +52 -0
  29. data/spec/savon/core_ext/symbol_spec.rb +11 -0
  30. data/spec/savon/core_ext/uri_spec.rb +15 -0
  31. data/spec/savon/request_spec.rb +93 -0
  32. data/spec/savon/savon_spec.rb +37 -0
  33. data/spec/savon/soap_spec.rb +101 -0
  34. data/spec/savon/validation_spec.rb +88 -0
  35. data/spec/savon/wsdl_spec.rb +17 -46
  36. data/spec/savon/wsse_spec.rb +169 -0
  37. data/spec/spec_helper.rb +7 -92
  38. data/spec/spec_helper_methods.rb +29 -0
  39. metadata +68 -20
  40. data/README.rdoc +0 -62
  41. data/lib/savon/service.rb +0 -151
  42. data/spec/savon/service_spec.rb +0 -76
@@ -0,0 +1,22 @@
1
+ <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
2
+ <soap:Body>
3
+ <ns2:findUserResponse xmlns:ns2="http://v1_0.ws.user.example.com">
4
+ <return>
5
+ <id>666</id>
6
+ <email>thedude@example.com</email>
7
+ <username>thedude</username>
8
+ <firstname>The</firstname>
9
+ <lastname>Dude</lastname>
10
+ <registered>2000-01-22T22:11:21</registered>
11
+ </return>
12
+ <return>
13
+ <id>999</id>
14
+ <email>anotherdude@example.com</email>
15
+ <username>anotherdude</username>
16
+ <firstname>Another</firstname>
17
+ <lastname>Dude</lastname>
18
+ <registered>1984-05-26T11:22:12</registered>
19
+ </return>
20
+ </ns2:findUserResponse>
21
+ </soap:Body>
22
+ </soap:Envelope>
File without changes
@@ -0,0 +1,42 @@
1
+ class UserFixture
2
+
3
+ @namespace_uri = "http://v1_0.ws.user.example.com"
4
+ @soap_actions = { :find_user => "findUser" }
5
+
6
+ @datetime_string = "2010-11-22T11:22:33"
7
+ @datetime_object = DateTime.parse @datetime_string
8
+
9
+ @response_hash = { :id => "666", :active => true, :username => "thedude",
10
+ :firstname => "The", :lastname => "Dude", :email => "thedude@example.com",
11
+ :registered => @datetime_object }
12
+
13
+ class << self
14
+
15
+ attr_accessor :namespace_uri, :soap_actions,
16
+ :datetime_string, :datetime_object, :response_hash
17
+
18
+ def user_wsdl
19
+ load_fixture :user_wsdl
20
+ end
21
+
22
+ def user_response
23
+ load_fixture :user_response
24
+ end
25
+
26
+ def multiple_user_response
27
+ load_fixture :multiple_user_response
28
+ end
29
+
30
+ def soap_fault
31
+ load_fixture :soap_fault
32
+ end
33
+
34
+ private
35
+
36
+ def load_fixture(file)
37
+ file_path = File.join File.dirname(__FILE__), "#{file}.xml"
38
+ IO.readlines(file_path, "").to_s
39
+ end
40
+
41
+ end
42
+ end
@@ -2,12 +2,14 @@
2
2
  <soap:Body>
3
3
  <ns2:findUserResponse xmlns:ns2="http://v1_0.ws.user.example.com">
4
4
  <return>
5
- <id>123</id>
5
+ <id>666</id>
6
6
  <email>thedude@example.com</email>
7
7
  <username>thedude</username>
8
8
  <firstname>The</firstname>
9
9
  <lastname>Dude</lastname>
10
+ <registered>2010-11-22T11:22:33</registered>
11
+ <active>true</active>
10
12
  </return>
11
13
  </ns2:findUserResponse>
12
14
  </soap:Body>
13
- </soap:Envelope>
15
+ </soap:Envelope>
File without changes
@@ -0,0 +1,20 @@
1
+ require "fakeweb"
2
+
3
+ FakeWeb.allow_net_connect = false
4
+
5
+ # Register fake WSDL and SOAP request.
6
+ FakeWeb.register_uri :get, SpecHelper.some_endpoint, :body => UserFixture.user_wsdl
7
+ FakeWeb.register_uri :post, SpecHelper.soap_call_endpoint, :body => UserFixture.user_response
8
+
9
+ # Register fake WSDL and SOAP request with multiple "//return" nodes.
10
+ FakeWeb.register_uri :get, SpecHelper.multiple_endpoint, :body => UserFixture.user_wsdl
11
+ FakeWeb.register_uri :post, SpecHelper.soap_multiple_endpoint, :body => UserFixture.multiple_user_response
12
+
13
+ # Register fake WSDL and SOAP request for a Savon::SOAPFault.
14
+ FakeWeb.register_uri :get, SpecHelper.soapfault_endpoint, :body => UserFixture.user_wsdl
15
+ FakeWeb.register_uri :post, SpecHelper.soap_soapfault_endpoint, :body => UserFixture.soap_fault
16
+
17
+ # Register fake WSDL and SOAP request for a Savon::HTTPError.
18
+ FakeWeb.register_uri :get, SpecHelper.httperror_endpoint, :body => UserFixture.user_wsdl
19
+ FakeWeb.register_uri :post, SpecHelper.soap_httperror_endpoint, :body => "",
20
+ :status => ["404", "Not Found"]
@@ -0,0 +1,144 @@
1
+ require "spec_helper"
2
+
3
+ describe Savon::Client do
4
+ before { @client = new_client_instance }
5
+
6
+ def new_client_instance
7
+ Savon::Client.new SpecHelper.some_endpoint
8
+ end
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 "initialize" do
32
+ it "expects a SOAP endpoint String" do
33
+ new_client_instance
34
+ end
35
+
36
+ it "raises an ArgumentError in case of an invalid endpoint" do
37
+ lambda { Savon::Client.new "invalid" }.should raise_error ArgumentError
38
+ end
39
+ end
40
+
41
+ describe "wsdl" do
42
+ it "returns the Savon::WSDL" do
43
+ @client.find_user
44
+
45
+ @client.wsdl.should be_a Savon::WSDL
46
+ @client.wsdl.to_s.should == UserFixture.user_wsdl
47
+ end
48
+ end
49
+
50
+ describe "response" do
51
+ it "returns the Net::HTTPResponse of the last SOAP request" do
52
+ @client.find_user
53
+
54
+ @client.response.should be_a Net::HTTPResponse
55
+ @client.response.body.should == UserFixture.user_response
56
+ end
57
+ end
58
+
59
+ describe "respond_to?" do
60
+ it "returns true for available SOAP actions" do
61
+ @client.respond_to?(UserFixture.soap_actions.keys.first).
62
+ should be_true
63
+ end
64
+
65
+ it "still behaves like usual otherwise" do
66
+ @client.respond_to?(:object_id).should be_true
67
+ @client.respond_to?(:some_missing_method).should be_false
68
+ end
69
+ end
70
+
71
+ describe "method_missing" do
72
+ it "dispatches SOAP requests for available SOAP actions" do
73
+ @client.find_user.should be_a Hash
74
+ end
75
+
76
+ it "still returns a NoMethodError for missing methods" do
77
+ lambda { @client.some_missing_method }.should raise_error NoMethodError
78
+ end
79
+
80
+ it "accepts a Hash for specifying the SOAP body" do
81
+ soap_body_hash = { :id => 666 }
82
+ @client.find_user soap_body_hash
83
+
84
+ @client.instance_variable_get("@soap").body.
85
+ should include soap_body_hash.to_soap_xml
86
+ end
87
+
88
+ it "accepts a String for specifying the SOAP body" do
89
+ soap_body_xml = "<username>dude</username>"
90
+ @client.find_user soap_body_xml
91
+
92
+ @client.instance_variable_get("@soap").body.
93
+ should include soap_body_xml
94
+ end
95
+
96
+ describe "accepts a Hash of per request options" do
97
+ it "to specify the SOAP version" do
98
+ @client.find_user nil, :soap_version => 2
99
+ @client.instance_variable_get("@soap").version.should == 2
100
+ end
101
+
102
+ it "to specify credentials for WSSE authentication" do
103
+ @client.find_user nil, :wsse =>
104
+ { :username => "gorilla", :password => "secret" }
105
+
106
+ @client.instance_variable_get("@soap").body.should include "gorilla"
107
+ @client.instance_variable_get("@soap").body.should include "secret"
108
+ end
109
+
110
+ it "to specify credentials for WSSE digestauthentication" do
111
+ @client.find_user nil, :wsse =>
112
+ { :username => "gorilla", :password => "secret", :digest => true }
113
+
114
+ @client.instance_variable_get("@soap").body.should include "gorilla"
115
+ @client.instance_variable_get("@soap").body.should_not include "secret"
116
+ end
117
+ end
118
+
119
+ it "accepts a block for specifying the response process per request" do
120
+ @client.find_user { |response| response.body }.
121
+ should == UserFixture.user_response
122
+ end
123
+
124
+ it "raises a Savon::SOAPFault in case of a SOAP fault" do
125
+ client = Savon::Client.new SpecHelper.soapfault_endpoint
126
+ lambda { client.find_user }.should raise_error Savon::SOAPFault
127
+ end
128
+
129
+ it "raises a Savon::HTTPError in case of an HTTP error" do
130
+ client = Savon::Client.new SpecHelper.httperror_endpoint
131
+ lambda { client.find_user }.should raise_error Savon::HTTPError
132
+ end
133
+ end
134
+
135
+ def http_response_mock
136
+ unless @http_response_mock
137
+ @http_response_mock = mock "Net::HTTPResponse"
138
+ @http_response_mock.stubs :code => "200", :message => "OK",
139
+ :content_type => "text/html", :body => UserFixture.user_response
140
+ end
141
+ @http_response_mock
142
+ end
143
+
144
+ end
@@ -0,0 +1,12 @@
1
+ require "spec_helper"
2
+
3
+ describe DateTime do
4
+
5
+ describe "to_soap_value" do
6
+ it "returns an xs:dateTime compliant String" do
7
+ UserFixture.datetime_object.to_soap_value.
8
+ should == UserFixture.datetime_string
9
+ end
10
+ end
11
+
12
+ end
@@ -0,0 +1,146 @@
1
+ require "spec_helper"
2
+
3
+ describe Hash do
4
+
5
+ describe "to_soap_xml" do
6
+ describe "returns SOAP request compatible XML" do
7
+ it "for a simple Hash" do
8
+ { :some => "user" }.to_soap_xml.should == "<some>user</some>"
9
+ end
10
+
11
+ it "for a nested Hash" do
12
+ { :some => { :new => "user" } }.to_soap_xml.
13
+ should == "<some><new>user</new></some>"
14
+ end
15
+
16
+ it "for a Hash with multiple keys" do
17
+ soap_xml = { :all => "users", :before => "whatever" }.to_soap_xml
18
+
19
+ soap_xml.should include "<all>users</all>"
20
+ soap_xml.should include "<before>whatever</before>"
21
+ end
22
+
23
+ it "for a Hash containing an Array" do
24
+ { :some => ["user", "gorilla"] }.to_soap_xml.
25
+ should == "<some>user</some><some>gorilla</some>"
26
+ end
27
+
28
+ it "for a Hash containing an Array of Hashes" do
29
+ { :some => [{ :new => "user" }, { :old => "gorilla" }] }.to_soap_xml.
30
+ should == "<some><new>user</new></some><some><old>gorilla</old></some>"
31
+ end
32
+ end
33
+
34
+ it "converts Hash key Symbols to lowerCamelCase" do
35
+ { :find_or_create => "user" }.to_soap_xml.
36
+ should == "<findOrCreate>user</findOrCreate>"
37
+ end
38
+
39
+ it "does not convert Hash key Strings" do
40
+ { "find_or_create" => "user" }.to_soap_xml.
41
+ should == "<find_or_create>user</find_or_create>"
42
+ end
43
+
44
+ it "converts DateTime objects to xs:dateTime compliant Strings" do
45
+ { :before => UserFixture.datetime_object }.to_soap_xml.
46
+ should == "<before>" << UserFixture.datetime_string << "</before>"
47
+ end
48
+
49
+ it "converts Objects responding to to_datetime to xs:dateTime compliant Strings" do
50
+ singleton = Object.new
51
+ def singleton.to_datetime
52
+ UserFixture.datetime_object
53
+ end
54
+
55
+ { :before => singleton }.to_soap_xml.
56
+ should == "<before>" << UserFixture.datetime_string << "</before>"
57
+ end
58
+
59
+ it "calls to_s on Strings even if they respond to to_datetime" do
60
+ singleton = "gorilla"
61
+ def singleton.to_datetime
62
+ UserFixture.datetime_object
63
+ end
64
+
65
+ { :name => singleton }.to_soap_xml.should == "<name>gorilla</name>"
66
+ end
67
+
68
+ it "call to_s on any other Object" do
69
+ [666, true, false, nil].each do |object|
70
+ { :some => object }.to_soap_xml.should == "<some>#{object}</some>"
71
+ end
72
+ end
73
+ end
74
+
75
+ describe "to_soap_fault_message" do
76
+ it "returns a SOAP fault message for SOAP version 1" do
77
+ soap_fault = soap_fault_hash(
78
+ "faultcode" => "soap:Server",
79
+ "faultstring" => "Fault occurred while processing."
80
+ )
81
+
82
+ soap_fault.to_soap_fault_message.should be_a String
83
+ soap_fault.to_soap_fault_message.should_not be_empty
84
+ end
85
+
86
+ it "returns a SOAP fault message for SOAP version 2" do
87
+ soap_fault = soap_fault_hash(
88
+ "code" => { "value" => "soap:Server" },
89
+ "reason" => { "text" => "Fault occurred while processing." }
90
+ )
91
+
92
+ soap_fault.to_soap_fault_message.should be_a String
93
+ soap_fault.to_soap_fault_message.should_not be_empty
94
+ end
95
+
96
+ it "returns nil in case the Hash does not include a SOAP fault" do
97
+ { :soap_fault => false }.to_soap_fault_message.should be_nil
98
+ end
99
+
100
+ it "returns nil for unknown SOAP faults" do
101
+ soap_fault_hash.to_soap_fault_message.should be_nil
102
+ end
103
+
104
+ def soap_fault_hash(details = {})
105
+ { "soap:Envelope" => { "soap:Body" => { "soap:Fault" => details } } }
106
+ end
107
+ end
108
+
109
+ describe "map_soap_response" do
110
+ it "converts Hash key Strings to snake_case Symbols" do
111
+ { "userResponse" => { "accountStatus" => "active" } }.map_soap_response.
112
+ should == { :user_response => { :account_status => "active" } }
113
+ end
114
+
115
+ it "strips namespaces from Hash keys" do
116
+ { "ns:userResponse" => { "ns2:id" => "666" } }.map_soap_response.
117
+ should == { :user_response => { :id => "666" } }
118
+ end
119
+
120
+ it "converts Hash keys and values in Arrays" do
121
+ { "response" => [{ "name" => "dude" }, { "name" => "gorilla" }] }.map_soap_response.
122
+ should == { :response=> [{ :name => "dude" }, { :name => "gorilla" }] }
123
+ end
124
+
125
+ it "converts xsi:nil values to nil Objects" do
126
+ { "userResponse" => { "xsi:nil" => "true" } }.map_soap_response.
127
+ should == { :user_response => nil }
128
+ end
129
+
130
+ it "converts Hash values matching the xs:dateTime format into DateTime Objects" do
131
+ { "response" => { "at" => UserFixture.datetime_string } }.map_soap_response.
132
+ should == { :response => { :at => UserFixture.datetime_object } }
133
+ end
134
+
135
+ it "converts Hash values matching 'true' to TrueClass" do
136
+ { "response" => { "active" => "false" } }.map_soap_response.
137
+ should == { :response => { :active => false } }
138
+ end
139
+
140
+ it "converts Hash values matching 'false' to FalseClass" do
141
+ { "response" => { "active" => "true" } }.map_soap_response.
142
+ should == { :response => { :active => true } }
143
+ end
144
+ end
145
+
146
+ end
@@ -0,0 +1,26 @@
1
+ require "spec_helper"
2
+
3
+ describe Object do
4
+
5
+ describe "to_soap_key" do
6
+ it "calls to_s for every Object" do
7
+ Object.to_soap_key.should == Object.to_s
8
+ end
9
+ end
10
+
11
+ describe "to_soap_value" do
12
+ it "returns an xs:dateTime compliant String for Objects responding to to_datetime" do
13
+ singleton = Object.new
14
+ def singleton.to_datetime
15
+ UserFixture.datetime_object
16
+ end
17
+
18
+ singleton.to_soap_value.should == UserFixture.datetime_string
19
+ end
20
+
21
+ it "calls to_s unless the Object responds to to_datetime" do
22
+ "value".to_soap_value.should == "value".to_s
23
+ end
24
+ end
25
+
26
+ end
@@ -0,0 +1,52 @@
1
+ require "spec_helper"
2
+
3
+ describe String do
4
+
5
+ describe "snakecase" do
6
+ it "converts a lowerCamelCase String to snakecase" do
7
+ "lowerCamelCase".snakecase.should == "lower_camel_case"
8
+ end
9
+ end
10
+
11
+ describe "lower_camelcase" do
12
+ it "converts a snakecase String to lowerCamelCase" do
13
+ "lower_camel_case".lower_camelcase.should == "lowerCamelCase"
14
+ end
15
+ end
16
+
17
+ describe "strip_namespace" do
18
+ it "strips the namespace from a namespaced String" do
19
+ "ns:customer".strip_namespace.should == "customer"
20
+ end
21
+
22
+ it "returns the original String for a String without namespace" do
23
+ "customer".strip_namespace.should == "customer"
24
+ end
25
+ end
26
+
27
+ describe "map_soap_response" do
28
+ it "returns a DateTime Object for Strings matching the xs:dateTime format" do
29
+ UserFixture.datetime_string.map_soap_response.should ==
30
+ UserFixture.datetime_object
31
+ end
32
+
33
+ it "returns true for Strings matching 'true'" do
34
+ "true".map_soap_response.should be_true
35
+ end
36
+
37
+ it "returns false for Strings matching 'false'" do
38
+ "false".map_soap_response.should be_false
39
+ end
40
+
41
+ it "defaults to return the original value" do
42
+ "whatever".map_soap_response.should == "whatever"
43
+ end
44
+ end
45
+
46
+ describe "to_soap_value" do
47
+ it "calls to_s, bypassing Rails to_datetime extension for Strings" do
48
+ "string".to_soap_value.should == "string".to_s
49
+ end
50
+ end
51
+
52
+ end