savon-ng-1.6 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.travis.yml +15 -0
  4. data/.yardopts +6 -0
  5. data/CHANGELOG.md +1024 -0
  6. data/CONTRIBUTING.md +46 -0
  7. data/Gemfile +8 -0
  8. data/LICENSE +20 -0
  9. data/README.md +81 -0
  10. data/Rakefile +14 -0
  11. data/donate.png +0 -0
  12. data/lib/savon/block_interface.rb +26 -0
  13. data/lib/savon/builder.rb +166 -0
  14. data/lib/savon/client.rb +88 -0
  15. data/lib/savon/core_ext/string.rb +29 -0
  16. data/lib/savon/header.rb +70 -0
  17. data/lib/savon/http_error.rb +27 -0
  18. data/lib/savon/log_message.rb +48 -0
  19. data/lib/savon/message.rb +36 -0
  20. data/lib/savon/mock/expectation.rb +71 -0
  21. data/lib/savon/mock/spec_helper.rb +62 -0
  22. data/lib/savon/mock.rb +5 -0
  23. data/lib/savon/model.rb +80 -0
  24. data/lib/savon/operation.rb +127 -0
  25. data/lib/savon/options.rb +330 -0
  26. data/lib/savon/qualified_message.rb +49 -0
  27. data/lib/savon/request.rb +89 -0
  28. data/lib/savon/request_logger.rb +48 -0
  29. data/lib/savon/response.rb +112 -0
  30. data/lib/savon/soap_fault.rb +48 -0
  31. data/lib/savon/version.rb +5 -0
  32. data/lib/savon.rb +27 -0
  33. data/savon.gemspec +47 -0
  34. data/spec/fixtures/gzip/message.gz +0 -0
  35. data/spec/fixtures/response/another_soap_fault.xml +14 -0
  36. data/spec/fixtures/response/authentication.xml +14 -0
  37. data/spec/fixtures/response/header.xml +13 -0
  38. data/spec/fixtures/response/list.xml +18 -0
  39. data/spec/fixtures/response/multi_ref.xml +39 -0
  40. data/spec/fixtures/response/soap_fault.xml +8 -0
  41. data/spec/fixtures/response/soap_fault12.xml +18 -0
  42. data/spec/fixtures/response/taxcloud.xml +1 -0
  43. data/spec/fixtures/ssl/client_cert.pem +16 -0
  44. data/spec/fixtures/ssl/client_encrypted_key.pem +30 -0
  45. data/spec/fixtures/ssl/client_encrypted_key_cert.pem +24 -0
  46. data/spec/fixtures/ssl/client_key.pem +15 -0
  47. data/spec/fixtures/wsdl/authentication.xml +63 -0
  48. data/spec/fixtures/wsdl/betfair.xml +2981 -0
  49. data/spec/fixtures/wsdl/edialog.xml +15416 -0
  50. data/spec/fixtures/wsdl/interhome.xml +2137 -0
  51. data/spec/fixtures/wsdl/lower_camel.xml +52 -0
  52. data/spec/fixtures/wsdl/multiple_namespaces.xml +92 -0
  53. data/spec/fixtures/wsdl/multiple_types.xml +60 -0
  54. data/spec/fixtures/wsdl/taxcloud.xml +934 -0
  55. data/spec/fixtures/wsdl/team_software.xml +1 -0
  56. data/spec/fixtures/wsdl/vies.xml +176 -0
  57. data/spec/fixtures/wsdl/wasmuth.xml +153 -0
  58. data/spec/integration/email_example_spec.rb +32 -0
  59. data/spec/integration/ratp_example_spec.rb +28 -0
  60. data/spec/integration/stockquote_example_spec.rb +28 -0
  61. data/spec/integration/support/application.rb +82 -0
  62. data/spec/integration/support/server.rb +84 -0
  63. data/spec/integration/temperature_example_spec.rb +46 -0
  64. data/spec/integration/zipcode_example_spec.rb +42 -0
  65. data/spec/savon/builder_spec.rb +86 -0
  66. data/spec/savon/client_spec.rb +193 -0
  67. data/spec/savon/core_ext/string_spec.rb +37 -0
  68. data/spec/savon/features/message_tag_spec.rb +61 -0
  69. data/spec/savon/http_error_spec.rb +49 -0
  70. data/spec/savon/log_message_spec.rb +33 -0
  71. data/spec/savon/message_spec.rb +40 -0
  72. data/spec/savon/mock_spec.rb +157 -0
  73. data/spec/savon/model_spec.rb +154 -0
  74. data/spec/savon/observers_spec.rb +92 -0
  75. data/spec/savon/operation_spec.rb +211 -0
  76. data/spec/savon/options_spec.rb +772 -0
  77. data/spec/savon/request_spec.rb +493 -0
  78. data/spec/savon/response_spec.rb +258 -0
  79. data/spec/savon/soap_fault_spec.rb +126 -0
  80. data/spec/spec_helper.rb +30 -0
  81. data/spec/support/endpoint.rb +25 -0
  82. data/spec/support/fixture.rb +39 -0
  83. data/spec/support/integration.rb +9 -0
  84. data/spec/support/stdout.rb +25 -0
  85. metadata +308 -0
@@ -0,0 +1,258 @@
1
+ require "spec_helper"
2
+
3
+ describe Savon::Response do
4
+
5
+ let(:globals) { Savon::GlobalOptions.new }
6
+ let(:locals) { Savon::LocalOptions.new }
7
+
8
+ describe ".new" do
9
+ it "should raise a Savon::Fault in case of a SOAP fault" do
10
+ lambda { soap_fault_response }.should raise_error(Savon::SOAPFault)
11
+ end
12
+
13
+ it "should not raise a Savon::Fault in case the default is turned off" do
14
+ globals[:raise_errors] = false
15
+ lambda { soap_fault_response }.should_not raise_error
16
+ end
17
+
18
+ it "should raise a Savon::HTTP::Error in case of an HTTP error" do
19
+ lambda { soap_response :code => 500 }.should raise_error(Savon::HTTPError)
20
+ end
21
+
22
+ it "should not raise a Savon::HTTP::Error in case the default is turned off" do
23
+ globals[:raise_errors] = false
24
+ soap_response :code => 500
25
+ end
26
+ end
27
+
28
+ describe "#success?" do
29
+ before { globals[:raise_errors] = false }
30
+
31
+ it "should return true if the request was successful" do
32
+ soap_response.should be_a_success
33
+ end
34
+
35
+ it "should return false if there was a SOAP fault" do
36
+ soap_fault_response.should_not be_a_success
37
+ end
38
+
39
+ it "should return false if there was an HTTP error" do
40
+ http_error_response.should_not be_a_success
41
+ end
42
+ end
43
+
44
+ describe "#soap_fault?" do
45
+ before { globals[:raise_errors] = false }
46
+
47
+ it "should not return true in case the response seems to be ok" do
48
+ soap_response.soap_fault?.should be_false
49
+ end
50
+
51
+ it "should return true in case of a SOAP fault" do
52
+ soap_fault_response.soap_fault?.should be_true
53
+ end
54
+ end
55
+
56
+ describe "#soap_fault" do
57
+ before { globals[:raise_errors] = false }
58
+
59
+ it "should return nil in case the response seems to be ok" do
60
+ soap_response.soap_fault.should be_nil
61
+ end
62
+
63
+ it "should return a SOAPFault in case of a SOAP fault" do
64
+ soap_fault_response.soap_fault.should be_a(Savon::SOAPFault)
65
+ end
66
+ end
67
+
68
+ describe "#http_error?" do
69
+ before { globals[:raise_errors] = false }
70
+
71
+ it "should not return true in case the response seems to be ok" do
72
+ soap_response.http_error?.should_not be_true
73
+ end
74
+
75
+ it "should return true in case of an HTTP error" do
76
+ soap_response(:code => 500).http_error?.should be_true
77
+ end
78
+ end
79
+
80
+ describe "#http_error" do
81
+ before { globals[:raise_errors] = false }
82
+
83
+ it "should return nil in case the response seems to be ok" do
84
+ soap_response.http_error.should be_nil
85
+ end
86
+
87
+ it "should return a HTTPError in case of an HTTP error" do
88
+ soap_response(:code => 500).http_error.should be_a(Savon::HTTPError)
89
+ end
90
+ end
91
+
92
+ describe "#header" do
93
+ it "should return the SOAP response header as a Hash" do
94
+ response = soap_response :body => Fixture.response(:header)
95
+ response.header.should include(:session_number => "ABCD1234")
96
+ end
97
+
98
+ it 'respects the global :strip_namespaces option' do
99
+ globals[:strip_namespaces] = false
100
+
101
+ response_with_header = soap_response(:body => Fixture.response(:header))
102
+ header = response_with_header.header
103
+
104
+ expect(header).to be_a(Hash)
105
+
106
+ # notice: :session_number is a snake_case Symbol without namespaces,
107
+ # but the Envelope and Header elements are qualified.
108
+ expect(header.keys).to include(:session_number)
109
+ end
110
+
111
+ it 'respects the global :convert_response_tags_to option' do
112
+ globals[:convert_response_tags_to] = lambda { |key| key.upcase }
113
+
114
+ response_with_header = soap_response(:body => Fixture.response(:header))
115
+ header = response_with_header.header
116
+
117
+ expect(header).to be_a(Hash)
118
+ expect(header.keys).to include('SESSIONNUMBER')
119
+ end
120
+
121
+ it "should throw an exception when the response header isn't parsable" do
122
+ lambda { invalid_soap_response.header }.should raise_error Savon::InvalidResponseError
123
+ end
124
+ end
125
+
126
+ %w(body to_hash).each do |method|
127
+ describe "##{method}" do
128
+ it "should return the SOAP response body as a Hash" do
129
+ soap_response.send(method)[:authenticate_response][:return].should ==
130
+ Fixture.response_hash(:authentication)[:authenticate_response][:return]
131
+ end
132
+
133
+ it "should return a Hash for a SOAP multiRef response" do
134
+ hash = soap_response(:body => Fixture.response(:multi_ref)).send(method)
135
+
136
+ hash[:list_response].should be_a(Hash)
137
+ hash[:multi_ref].should be_an(Array)
138
+ end
139
+
140
+ it "should add existing namespaced elements as an array" do
141
+ hash = soap_response(:body => Fixture.response(:list)).send(method)
142
+
143
+ hash[:multi_namespaced_entry_response][:history].should be_a(Hash)
144
+ hash[:multi_namespaced_entry_response][:history][:case].should be_an(Array)
145
+ end
146
+
147
+ it 'respects the global :strip_namespaces option' do
148
+ globals[:strip_namespaces] = false
149
+
150
+ body = soap_response.body
151
+
152
+ expect(body).to be_a(Hash)
153
+ expect(body.keys).to include(:"ns2:authenticate_response")
154
+ end
155
+
156
+ it 'respects the global :convert_response_tags_to option' do
157
+ globals[:convert_response_tags_to] = lambda { |key| key.upcase }
158
+
159
+ body = soap_response.body
160
+
161
+ expect(body).to be_a(Hash)
162
+ expect(body.keys).to include('AUTHENTICATERESPONSE')
163
+ end
164
+ end
165
+ end
166
+
167
+ describe "#to_array" do
168
+ context "when the given path exists" do
169
+ it "should return an Array containing the path value" do
170
+ soap_response.to_array(:authenticate_response, :return).should ==
171
+ [Fixture.response_hash(:authentication)[:authenticate_response][:return]]
172
+ end
173
+
174
+ it "should properly return FalseClass values [#327]" do
175
+ body = Gyoku.xml(:envelope => { :body => { :return => { :success => false } } })
176
+ soap_response(:body => body).to_array(:return, :success).should == [false]
177
+ end
178
+ end
179
+
180
+ context "when the given path returns nil" do
181
+ it "should return an empty Array" do
182
+ soap_response.to_array(:authenticate_response, :undefined).should == []
183
+ end
184
+ end
185
+
186
+ context "when the given path does not exist at all" do
187
+ it "should return an empty Array" do
188
+ soap_response.to_array(:authenticate_response, :some, :undefined, :path).should == []
189
+ end
190
+ end
191
+ end
192
+
193
+ describe "#hash" do
194
+ it "should return the complete SOAP response XML as a Hash" do
195
+ response = soap_response :body => Fixture.response(:header)
196
+ response.hash[:envelope][:header][:session_number].should == "ABCD1234"
197
+ end
198
+ end
199
+
200
+ describe "#to_xml" do
201
+ it "should return the raw SOAP response body" do
202
+ soap_response.to_xml.should == Fixture.response(:authentication)
203
+ end
204
+ end
205
+
206
+ describe "#doc" do
207
+ it "returns a Nokogiri::XML::Document for the SOAP response XML" do
208
+ soap_response.doc.should be_a(Nokogiri::XML::Document)
209
+ end
210
+ end
211
+
212
+ describe "#xpath" do
213
+ it "permits XPath access to elements in the request" do
214
+ soap_response.xpath("//client").first.inner_text.should == "radclient"
215
+ soap_response.xpath("//ns2:authenticateResponse/return/success").first.inner_text.should == "true"
216
+ end
217
+ end
218
+
219
+ describe '#find' do
220
+ it 'delegates to Nori#find to find child elements inside the Envelope' do
221
+ result = soap_response.find('Body', 'authenticateResponse', 'return')
222
+
223
+ expect(result).to be_a(Hash)
224
+ expect(result.keys).to include(:authentication_value)
225
+ end
226
+ end
227
+
228
+ describe "#http" do
229
+ it "should return the HTTPI::Response" do
230
+ soap_response.http.should be_an(HTTPI::Response)
231
+ end
232
+ end
233
+
234
+ def soap_response(options = {})
235
+ defaults = { :code => 200, :headers => {}, :body => Fixture.response(:authentication) }
236
+ response = defaults.merge options
237
+ http_response = HTTPI::Response.new(response[:code], response[:headers], response[:body])
238
+
239
+ Savon::Response.new(http_response, globals, locals)
240
+ end
241
+
242
+ def soap_fault_response
243
+ soap_response :code => 500, :body => Fixture.response(:soap_fault)
244
+ end
245
+
246
+ def http_error_response
247
+ soap_response :code => 404, :body => "Not found"
248
+ end
249
+
250
+ def invalid_soap_response(options = {})
251
+ defaults = { :code => 200, :headers => {}, :body => "I'm not SOAP" }
252
+ response = defaults.merge options
253
+ http_response = HTTPI::Response.new(response[:code], response[:headers], response[:body])
254
+
255
+ Savon::Response.new(http_response, globals, locals)
256
+ end
257
+
258
+ end
@@ -0,0 +1,126 @@
1
+ require "spec_helper"
2
+
3
+ describe Savon::SOAPFault do
4
+ let(:soap_fault) { Savon::SOAPFault.new new_response(:body => Fixture.response(:soap_fault)), nori }
5
+ let(:soap_fault2) { Savon::SOAPFault.new new_response(:body => Fixture.response(:soap_fault12)), nori }
6
+ let(:soap_fault_nc) { Savon::SOAPFault.new new_response(:body => Fixture.response(:soap_fault)), nori_no_convert }
7
+ let(:soap_fault_nc2) { Savon::SOAPFault.new new_response(:body => Fixture.response(:soap_fault12)), nori_no_convert }
8
+ let(:another_soap_fault) { Savon::SOAPFault.new new_response(:body => Fixture.response(:another_soap_fault)), nori }
9
+ let(:no_fault) { Savon::SOAPFault.new new_response, nori }
10
+
11
+ let(:nori) { Nori.new(:strip_namespaces => true, :convert_tags_to => lambda { |tag| tag.snakecase.to_sym }) }
12
+ let(:nori_no_convert) { Nori.new(:strip_namespaces => true, :convert_tags_to => nil) }
13
+
14
+ it "inherits from Savon::Error" do
15
+ expect(Savon::SOAPFault.ancestors).to include(Savon::Error)
16
+ end
17
+
18
+ describe "#http" do
19
+ it "returns the HTTPI::Response" do
20
+ expect(soap_fault.http).to be_an(HTTPI::Response)
21
+ end
22
+ end
23
+
24
+ describe ".present?" do
25
+ it "returns true if the HTTP response contains a SOAP 1.1 fault" do
26
+ http = new_response(:body => Fixture.response(:soap_fault))
27
+ expect(Savon::SOAPFault.present? http).to be_true
28
+ end
29
+
30
+ it "returns true if the HTTP response contains a SOAP 1.2 fault" do
31
+ http = new_response(:body => Fixture.response(:soap_fault12))
32
+ expect(Savon::SOAPFault.present? http).to be_true
33
+ end
34
+
35
+ it "returns true if the HTTP response contains a SOAP fault with different namespaces" do
36
+ http = new_response(:body => Fixture.response(:another_soap_fault))
37
+ expect(Savon::SOAPFault.present? http).to be_true
38
+ end
39
+
40
+ it "returns false unless the HTTP response contains a SOAP fault" do
41
+ expect(Savon::SOAPFault.present? new_response).to be_false
42
+ end
43
+ end
44
+
45
+ [:message, :to_s].each do |method|
46
+ describe "##{method}" do
47
+ it "returns a SOAP 1.1 fault message" do
48
+ expect(soap_fault.send method).to eq("(soap:Server) Fault occurred while processing.")
49
+ end
50
+
51
+ it "returns a SOAP 1.2 fault message" do
52
+ expect(soap_fault2.send method).to eq("(soap:Sender) Sender Timeout")
53
+ end
54
+
55
+ it "returns a SOAP fault message (with different namespaces)" do
56
+ expect(another_soap_fault.send method).to eq("(ERR_NO_SESSION) Wrong session message")
57
+ end
58
+
59
+ it "works even if the keys are different in a SOAP 1.1 fault message" do
60
+ expect(soap_fault_nc.send method).to eq("(soap:Server) Fault occurred while processing.")
61
+ end
62
+
63
+ it "works even if the keys are different in a SOAP 1.2 fault message" do
64
+ expect(soap_fault_nc2.send method).to eq("(soap:Sender) Sender Timeout")
65
+ end
66
+ end
67
+ end
68
+
69
+ describe "#to_hash" do
70
+ it "returns the SOAP response as a Hash unless a SOAP fault is present" do
71
+ expect(no_fault.to_hash[:authenticate_response][:return][:success]).to be_true
72
+ end
73
+
74
+ it "returns a SOAP 1.1 fault as a Hash" do
75
+ expected = {
76
+ :fault => {
77
+ :faultstring => "Fault occurred while processing.",
78
+ :faultcode => "soap:Server"
79
+ }
80
+ }
81
+
82
+ expect(soap_fault.to_hash).to eq(expected)
83
+ end
84
+
85
+ it "returns a SOAP 1.2 fault as a Hash" do
86
+ expected = {
87
+ :fault => {
88
+ :detail => { :max_time => "P5M" },
89
+ :reason => { :text => "Sender Timeout" },
90
+ :code => { :value => "soap:Sender", :subcode => { :value => "m:MessageTimeout" } }
91
+ }
92
+ }
93
+
94
+ expect(soap_fault2.to_hash).to eq(expected)
95
+ end
96
+
97
+ it "works even if the keys are different" do
98
+ expected = {
99
+ "Fault" => {
100
+ "Code" => {
101
+ "Value" => "soap:Sender",
102
+ "Subcode"=> {
103
+ "Value" => "m:MessageTimeout"
104
+ }
105
+ },
106
+ "Reason" => {
107
+ "Text" => "Sender Timeout"
108
+ },
109
+ "Detail" => {
110
+ "MaxTime" => "P5M"
111
+ }
112
+ }
113
+ }
114
+
115
+ expect(soap_fault_nc2.to_hash).to eq(expected)
116
+ end
117
+ end
118
+
119
+ def new_response(options = {})
120
+ defaults = { :code => 500, :headers => {}, :body => Fixture.response(:authentication) }
121
+ response = defaults.merge options
122
+
123
+ HTTPI::Response.new response[:code], response[:headers], response[:body]
124
+ end
125
+
126
+ end
@@ -0,0 +1,30 @@
1
+ require "bundler"
2
+ Bundler.setup(:default, :development)
3
+
4
+ unless RUBY_PLATFORM =~ /java/
5
+ require "simplecov"
6
+ require "coveralls"
7
+
8
+ SimpleCov.formatter = Coveralls::SimpleCov::Formatter
9
+ SimpleCov.start do
10
+ add_filter "spec"
11
+ end
12
+ end
13
+
14
+ require "savon"
15
+ require "rspec"
16
+
17
+ # don't have HTTPI lazy-load HTTPClient, because then
18
+ # it can't actually be refered to inside the specs.
19
+ require "httpclient"
20
+
21
+ support_files = File.expand_path("spec/support/**/*.rb")
22
+ Dir[support_files].each { |file| require file }
23
+
24
+ RSpec.configure do |config|
25
+ config.include SpecSupport
26
+ config.mock_with :mocha
27
+ config.order = "random"
28
+ end
29
+
30
+ HTTPI.log = false
@@ -0,0 +1,25 @@
1
+ class Endpoint
2
+ class << self
3
+
4
+ # Returns the WSDL endpoint for a given +type+ of request.
5
+ def wsdl(type = nil)
6
+ case type
7
+ when :no_namespace then "http://nons.example.com/Service?wsdl"
8
+ when :namespaced_actions then "http://nsactions.example.com/Service?wsdl"
9
+ when :geotrust then "https://test-api.geotrust.com/webtrust/query.jws?WSDL"
10
+ else soap(type)
11
+ end
12
+ end
13
+
14
+ # Returns the SOAP endpoint for a given +type+ of request.
15
+ def soap(type = nil)
16
+ case type
17
+ when :soap_fault then "http://soapfault.example.com/Service?wsdl"
18
+ when :http_error then "http://httperror.example.com/Service?wsdl"
19
+ when :invalid then "http://invalid.example.com/Service?wsdl"
20
+ else "http://example.com/validation/1.0/AuthenticationService"
21
+ end
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,39 @@
1
+ class Fixture
2
+
3
+ TYPES = { :gzip => "gz", :response => "xml", :wsdl => "xml" }
4
+
5
+ class << self
6
+
7
+ def [](type, fixture)
8
+ fixtures(type)[fixture] ||= read_file type, fixture
9
+ end
10
+
11
+ def response_hash(fixture)
12
+ @response_hash ||= {}
13
+ @response_hash[fixture] ||= nori.parse(response(fixture))[:envelope][:body]
14
+ end
15
+
16
+ TYPES.each do |type, ext|
17
+ define_method(type) { |fixture| self[type, fixture] }
18
+ end
19
+
20
+ private
21
+
22
+ def nori
23
+ Nori.new(:strip_namespaces => true, :convert_tags_to => lambda { |tag| tag.snakecase.to_sym })
24
+ end
25
+
26
+ def fixtures(type)
27
+ @fixtures ||= {}
28
+ @fixtures[type] ||= {}
29
+ end
30
+
31
+ def read_file(type, fixture)
32
+ path = File.expand_path "../../fixtures/#{type}/#{fixture}.#{TYPES[type]}", __FILE__
33
+ raise ArgumentError, "Unable to load: #{path}" unless File.exist? path
34
+
35
+ File.read path
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,9 @@
1
+ module SpecSupport
2
+
3
+ def call_and_fail_gracefully(client, *args, &block)
4
+ client.call(*args, &block)
5
+ rescue Savon::SOAPFault => e
6
+ pending e.message
7
+ end
8
+
9
+ end
@@ -0,0 +1,25 @@
1
+ module SpecSupport
2
+
3
+ def mock_stdout
4
+ original_stdout = $stdout
5
+
6
+ stdout = StringIO.new
7
+ $stdout = stdout
8
+
9
+ yield
10
+
11
+ $stdout = original_stdout
12
+ stdout
13
+ end
14
+
15
+ def silence_stdout
16
+ original_stdout = $stdout
17
+ $stdout = StringIO.new
18
+
19
+ result = yield
20
+
21
+ $stdout = original_stdout
22
+ result
23
+ end
24
+
25
+ end