savon 0.3.2 → 0.5.0

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