savon 2.2.0 → 2.12.1

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 (60) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -0
  3. data/.travis.yml +20 -9
  4. data/CHANGELOG.md +157 -10
  5. data/CONTRIBUTING.md +1 -1
  6. data/Gemfile +10 -2
  7. data/README.md +38 -13
  8. data/donate.png +0 -0
  9. data/lib/savon/builder.rb +81 -15
  10. data/lib/savon/client.rb +6 -2
  11. data/lib/savon/core_ext/string.rb +0 -1
  12. data/lib/savon/header.rb +68 -17
  13. data/lib/savon/log_message.rb +7 -3
  14. data/lib/savon/message.rb +6 -7
  15. data/lib/savon/mock/expectation.rb +12 -2
  16. data/lib/savon/model.rb +4 -0
  17. data/lib/savon/operation.rb +45 -38
  18. data/lib/savon/options.rb +149 -22
  19. data/lib/savon/qualified_message.rb +31 -25
  20. data/lib/savon/request.rb +24 -4
  21. data/lib/savon/request_logger.rb +48 -0
  22. data/lib/savon/response.rb +35 -18
  23. data/lib/savon/soap_fault.rb +11 -11
  24. data/lib/savon/version.rb +1 -3
  25. data/savon.gemspec +12 -11
  26. data/spec/fixtures/response/empty_soap_fault.xml +13 -0
  27. data/spec/fixtures/response/f5.xml +39 -0
  28. data/spec/fixtures/response/no_body.xml +1 -0
  29. data/spec/fixtures/response/soap_fault_funky.xml +8 -0
  30. data/spec/fixtures/wsdl/brand.xml +624 -0
  31. data/spec/fixtures/wsdl/elements_in_types.xml +43 -0
  32. data/spec/fixtures/wsdl/no_message_tag.xml +1267 -0
  33. data/spec/fixtures/wsdl/vies.xml +176 -0
  34. data/spec/integration/centra_spec.rb +67 -0
  35. data/spec/integration/email_example_spec.rb +1 -1
  36. data/spec/integration/random_quote_spec.rb +23 -0
  37. data/spec/integration/stockquote_example_spec.rb +7 -1
  38. data/spec/integration/support/application.rb +1 -1
  39. data/spec/integration/zipcode_example_spec.rb +1 -1
  40. data/spec/savon/builder_spec.rb +50 -0
  41. data/spec/savon/client_spec.rb +78 -0
  42. data/spec/savon/core_ext/string_spec.rb +9 -9
  43. data/spec/savon/features/message_tag_spec.rb +5 -0
  44. data/spec/savon/http_error_spec.rb +2 -2
  45. data/spec/savon/log_message_spec.rb +18 -1
  46. data/spec/savon/message_spec.rb +70 -0
  47. data/spec/savon/mock_spec.rb +31 -0
  48. data/spec/savon/model_spec.rb +28 -0
  49. data/spec/savon/operation_spec.rb +69 -3
  50. data/spec/savon/options_spec.rb +515 -87
  51. data/spec/savon/qualified_message_spec.rb +101 -0
  52. data/spec/savon/request_logger_spec.rb +37 -0
  53. data/spec/savon/request_spec.rb +85 -10
  54. data/spec/savon/response_spec.rb +118 -27
  55. data/spec/savon/soap_fault_spec.rb +25 -5
  56. data/spec/savon/softlayer_spec.rb +27 -0
  57. data/spec/spec_helper.rb +5 -2
  58. data/spec/support/adapters.rb +48 -0
  59. data/spec/support/integration.rb +1 -1
  60. metadata +76 -93
@@ -4,33 +4,33 @@ describe String do
4
4
 
5
5
  describe "snakecase" do
6
6
  it "lowercases one word CamelCase" do
7
- "Merb".snakecase.should == "merb"
7
+ expect("Merb".snakecase).to eq("merb")
8
8
  end
9
9
 
10
10
  it "makes one underscore snakecase two word CamelCase" do
11
- "MerbCore".snakecase.should == "merb_core"
11
+ expect("MerbCore".snakecase).to eq("merb_core")
12
12
  end
13
13
 
14
14
  it "handles CamelCase with more than 2 words" do
15
- "SoYouWantContributeToMerbCore".snakecase.should == "so_you_want_contribute_to_merb_core"
15
+ expect("SoYouWantContributeToMerbCore".snakecase).to eq("so_you_want_contribute_to_merb_core")
16
16
  end
17
17
 
18
18
  it "handles CamelCase with more than 2 capital letter in a row" do
19
- "CNN".snakecase.should == "cnn"
20
- "CNNNews".snakecase.should == "cnn_news"
21
- "HeadlineCNNNews".snakecase.should == "headline_cnn_news"
19
+ expect("CNN".snakecase).to eq("cnn")
20
+ expect("CNNNews".snakecase).to eq("cnn_news")
21
+ expect("HeadlineCNNNews".snakecase).to eq("headline_cnn_news")
22
22
  end
23
23
 
24
24
  it "does NOT change one word lowercase" do
25
- "merb".snakecase.should == "merb"
25
+ expect("merb".snakecase).to eq("merb")
26
26
  end
27
27
 
28
28
  it "leaves snake_case as is" do
29
- "merb_core".snakecase.should == "merb_core"
29
+ expect("merb_core".snakecase).to eq("merb_core")
30
30
  end
31
31
 
32
32
  it "converts period characters to underscores" do
33
- "User.GetEmail".snakecase.should == "user_get_email"
33
+ expect("User.GetEmail".snakecase).to eq("user_get_email")
34
34
  end
35
35
  end
36
36
 
@@ -27,6 +27,11 @@ describe Savon do
27
27
  expect(message_tag).to eq(['http://www.betfair.com/publicapi/v5/BFExchangeService/', 'getBet'])
28
28
  end
29
29
 
30
+ it 'knows the message tag for :vies' do
31
+ message_tag = message_tag_for(:vies, :check_vat)
32
+ expect(message_tag).to eq(['urn:ec.europa.eu:taxud:vies:services:checkVat:types', 'checkVat'])
33
+ end
34
+
30
35
  it 'knows the message tag for :wasmuth' do
31
36
  message_tag = message_tag_for(:wasmuth, :get_st_tables)
32
37
  expect(message_tag).to eq(['http://ws.online.msw/', 'getStTables'])
@@ -11,11 +11,11 @@ describe Savon::HTTPError do
11
11
  describe ".present?" do
12
12
  it "returns true if there was an HTTP error" do
13
13
  http = new_response(:code => 404, :body => "Not Found")
14
- expect(Savon::HTTPError.present? http).to be_true
14
+ expect(Savon::HTTPError.present? http).to be_truthy
15
15
  end
16
16
 
17
17
  it "returns false unless there was an HTTP error" do
18
- expect(Savon::HTTPError.present? new_response).to be_false
18
+ expect(Savon::HTTPError.present? new_response).to be_falsey
19
19
  end
20
20
  end
21
21
 
@@ -21,9 +21,26 @@ describe Savon::LogMessage do
21
21
  expect(message).to include("\n <body>")
22
22
  end
23
23
 
24
- it "filters tags in a given message" do
24
+ it "filters tags in a given message without pretty printing" do
25
25
  message = log_message("<root><password>secret</password></root>", [:password], false).to_s
26
26
  expect(message).to include("<password>***FILTERED***</password>")
27
+ expect(message).to_not include("\n <password>***FILTERED***</password>") # no pretty printing
28
+ end
29
+
30
+ it "filters tags in a given message with pretty printing" do
31
+ message = log_message("<root><password>secret</password></root>", [:password], true).to_s
32
+ expect(message).to include("\n <password>***FILTERED***</password>")
33
+ end
34
+
35
+ it "properly applies Proc filter" do
36
+ filter = Proc.new do |document|
37
+ document.xpath('//password').each do |node|
38
+ node.content = "FILTERED"
39
+ end
40
+ end
41
+
42
+ message = log_message("<root><password>secret</password></root>", [filter], false).to_s
43
+ expect(message).to include("<password>FILTERED</password>")
27
44
  end
28
45
 
29
46
  def log_message(*args)
@@ -0,0 +1,70 @@
1
+ require "spec_helper"
2
+ require "integration/support/server"
3
+
4
+ describe Savon::Message do
5
+
6
+ before do
7
+ @server = IntegrationServer.run
8
+ end
9
+
10
+ after do
11
+ @server.stop
12
+ end
13
+
14
+ let(:client_config) {
15
+ {
16
+ :endpoint => @server.url(:repeat),
17
+ :namespace => 'http://example.com',
18
+ :log => false,
19
+
20
+ :element_form_default => :qualified,
21
+ :convert_request_keys_to => :camelcase,
22
+
23
+ :convert_response_tags_to => nil
24
+ }
25
+ }
26
+
27
+ let(:client) { Savon.client(client_config) }
28
+
29
+ context "with a qualified message" do
30
+ let(:message) {
31
+ {
32
+ :email_count => 3,
33
+ :user_name => 'josh',
34
+ :order! => [:user_name, :email_count]
35
+ }
36
+ }
37
+
38
+ let(:converted_keys) {
39
+ '<wsdl:UserName>josh</wsdl:UserName><wsdl:EmailCount>3</wsdl:EmailCount>'
40
+ }
41
+ it "converts request Hash keys for which there is not namespace" do
42
+ response = client.call(:something, :message => message)
43
+ expect(response.xml).to include(converted_keys)
44
+ end
45
+ end
46
+
47
+ context 'use_wsa_headers' do
48
+ let(:client_config) { super().merge(use_wsa_headers: true) }
49
+
50
+ context 'headers' do
51
+ [ 'wsa:Action', 'wsa:To', 'wsa:MessageID' ].each do |header|
52
+ it "should include #{header} header" do
53
+ response = client.call(:something, message: {})
54
+ expect(response.xml).to include(header)
55
+ end
56
+ end
57
+ end
58
+
59
+ context 'wsa:MessageID' do
60
+ let(:message_id_tag) {
61
+ '<wsa:MessageID xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing">'
62
+ }
63
+ it 'should include xmlns:wsa attribute' do
64
+ response = client.call(:something, message: {})
65
+ expect(response.xml).to include(message_id_tag)
66
+ end
67
+ end
68
+ end
69
+
70
+ end
@@ -23,6 +23,17 @@ describe "Savon's mock interface" do
23
23
  expect(response.http.body).to eq("<fixture/>")
24
24
  end
25
25
 
26
+ it "can verify a request with any parameters and return a fixture response" do
27
+ message = { :username => "luke", :password => :any }
28
+ savon.expects(:authenticate).with(:message => message).returns("<fixture/>")
29
+
30
+ response = new_client.call(:authenticate) do
31
+ message(:username => "luke", :password => "secret")
32
+ end
33
+
34
+ expect(response.http.body).to eq("<fixture/>")
35
+ end
36
+
26
37
  it "accepts a Hash to specify the response code, headers and body" do
27
38
  soap_fault = Fixture.response(:soap_fault)
28
39
  response = { :code => 500, :headers => { "X-Result" => "invalid" }, :body => soap_fault }
@@ -114,6 +125,26 @@ describe "Savon's mock interface" do
114
125
  " with this message: #{message.inspect}")
115
126
  end
116
127
 
128
+ it "does not fail when any message is expected and an actual message" do
129
+ savon.expects(:find_user).with(:message => :any).returns("<fixture/>")
130
+ message = { :username => "luke" }
131
+
132
+ expect { new_client.call(:find_user, :message => message) }.to_not raise_error
133
+ end
134
+
135
+ it "does not fail when any message is expected and no actual message" do
136
+ savon.expects(:find_user).with(:message => :any).returns("<fixture/>")
137
+
138
+ expect { new_client.call(:find_user) }.to_not raise_error
139
+ end
140
+
141
+ it "matchers can be used to specify the message" do
142
+ savon.expects(:find_user).with(:message => include(:username)).returns("<fixture/>")
143
+ message = { :username => "Han Solo", password: "querty"}
144
+
145
+ expect { new_client.call(:find_user, :message => message) }.to_not raise_error
146
+ end
147
+
117
148
  it "allows code to rescue Savon::Error and still report test failures" do
118
149
  message = { :username => "luke" }
119
150
  savon.expects(:find_user).with(:message => message).returns("<fixture/>")
@@ -151,4 +151,32 @@ describe Savon::Model do
151
151
  supermodel.authenticate(:message => { :username => "luke", :password => "secret" })
152
152
  end
153
153
 
154
+ describe ".all_operations" do
155
+ it "should call operations with all available client operations" do
156
+ model = Class.new {
157
+ extend Savon::Model
158
+
159
+ client :wsdl => Fixture.wsdl(:taxcloud)
160
+ all_operations
161
+ }
162
+
163
+ [:verify_address,
164
+ :lookup_for_date,
165
+ :lookup,
166
+ :authorized,
167
+ :authorized_with_capture,
168
+ :captured,
169
+ :returned,
170
+ :get_tic_groups,
171
+ :get_ti_cs,
172
+ :get_ti_cs_by_group,
173
+ :add_exempt_certificate,
174
+ :delete_exempt_certificate,
175
+ :get_exempt_certificates].each do |method|
176
+ expect(model).to respond_to(method)
177
+ end
178
+ end
179
+
180
+ end
181
+
154
182
  end
@@ -44,6 +44,16 @@ describe Savon::Operation do
44
44
  expect { new_operation(:no_such_operation, wsdl, globals) }.
45
45
  to raise_error(Savon::UnknownOperationError, /Unable to find SOAP operation: :no_such_operation/)
46
46
  end
47
+
48
+ it "raises if the endpoint cannot be reached" do
49
+ message = "Error!"
50
+ response = HTTPI::Response.new(500, {}, message)
51
+ error = Wasabi::Resolver::HTTPError.new(message, response)
52
+ Wasabi::Document.any_instance.stubs(:soap_actions).raises(error)
53
+
54
+ expect { new_operation(:verify_address, wsdl, globals) }.
55
+ to raise_error(Savon::HTTPError, /#{message}/)
56
+ end
47
57
  end
48
58
 
49
59
  describe ".create without a WSDL" do
@@ -77,7 +87,7 @@ describe Savon::Operation do
77
87
 
78
88
  # stub the actual request
79
89
  http_response = HTTPI::Response.new(200, {}, "")
80
- operation.expects(:call!).returns(http_response)
90
+ operation.expects(:call_with_logging).returns(http_response)
81
91
 
82
92
  operation.call
83
93
  end
@@ -90,7 +100,7 @@ describe Savon::Operation do
90
100
 
91
101
  # stub the actual request
92
102
  http_response = HTTPI::Response.new(200, {}, "")
93
- operation.expects(:call!).returns(http_response)
103
+ operation.expects(:call_with_logging).returns(http_response)
94
104
 
95
105
  operation.call
96
106
  end
@@ -154,11 +164,67 @@ describe Savon::Operation do
154
164
  actual_soap_action = inspect_request(response).soap_action
155
165
  expect(actual_soap_action).to eq(%("authenticate"))
156
166
  end
167
+
168
+ it "returns a Savon::Multipart::Response if available and requested globally" do
169
+ globals.multipart true
170
+
171
+ with_multipart_mocked do
172
+ operation = new_operation(:authenticate, no_wsdl, globals)
173
+ response = operation.call
174
+
175
+ expect(response).to be_a(Savon::Multipart::Response)
176
+ end
177
+ end
178
+
179
+ it "returns a Savon::Multipart::Response if available and requested locally" do
180
+ with_multipart_mocked do
181
+ operation = new_operation(:authenticate, no_wsdl, globals)
182
+ response = operation.call(:multipart => true)
183
+
184
+ expect(response).to be_a(Savon::Multipart::Response)
185
+ end
186
+ end
187
+
188
+ it "raises if savon-multipart is not available and it was requested globally" do
189
+ globals.multipart true
190
+
191
+ operation = new_operation(:authenticate, no_wsdl, globals)
192
+
193
+ expect { operation.call }.
194
+ to raise_error RuntimeError, /Unable to find Savon::Multipart/
195
+ end
196
+
197
+ it "raises if savon-multipart is not available and it was requested locally" do
198
+ operation = new_operation(:authenticate, no_wsdl, globals)
199
+
200
+ expect { operation.call(:multipart => true) }.
201
+ to raise_error RuntimeError, /Unable to find Savon::Multipart/
202
+ end
203
+ end
204
+
205
+ describe "#request" do
206
+ it "returns the request" do
207
+ operation = new_operation(:verify_address, wsdl, globals)
208
+ request = operation.request
209
+
210
+ expect(request.body).to include('<tns:VerifyAddress></tns:VerifyAddress>')
211
+ end
212
+ end
213
+
214
+ def with_multipart_mocked
215
+ multipart_response = Class.new { def initialize(*args); end }
216
+ multipart_mock = Module.new
217
+ multipart_mock.const_set('Response', multipart_response)
218
+
219
+ Savon.const_set('Multipart', multipart_mock)
220
+
221
+ yield
222
+ ensure
223
+ Savon.send(:remove_const, :Multipart) if Savon.const_defined? :Multipart
157
224
  end
158
225
 
159
226
  def inspect_request(response)
160
227
  hash = JSON.parse(response.http.body)
161
228
  OpenStruct.new(hash)
162
229
  end
163
-
164
230
  end
@@ -45,6 +45,35 @@ describe "Options" do
45
45
  end
46
46
  end
47
47
 
48
+ context "global: :no_message_tag" do
49
+ it "omits the 'message tag' encapsulation step" do
50
+ client = new_client(:endpoint => @server.url(:repeat), :no_message_tag => true,
51
+ :wsdl => Fixture.wsdl(:no_message_tag))
52
+ msg = {'extLoginData' => {'Login' => 'test.user', 'Password' => 'secret', 'FacilityID' => 1,
53
+ 'ThreePLKey' => '{XXXX-XXXX-XXXX-XXXX}', 'ThreePLID' => 1},
54
+ 'Items' => ['Item' => {'SKU' => '001002003A', 'CustomerID' => 1,
55
+ 'InventoryMethod' => 'FIFO', 'UPC' => '001002003A'}]}
56
+ response = client.call(:create_items, :message => msg)
57
+
58
+ expect(response.http.body.scan(/<tns:extLoginData>/).count).to eq(1)
59
+ end
60
+
61
+ it "includes the 'message tag' encapsulation step" do
62
+ # This test is probably just exposing a bug while the previous
63
+ # test is using a workaround fix.
64
+ # That is just a guess though. I don't really have to properly debug the WSDL parser.
65
+ client = new_client(:endpoint => @server.url(:repeat), :no_message_tag => false,
66
+ :wsdl => Fixture.wsdl(:no_message_tag))
67
+ msg = {'extLoginData' => {'Login' => 'test.user', 'Password' => 'secret', 'FacilityID' => 1,
68
+ 'ThreePLKey' => '{XXXX-XXXX-XXXX-XXXX}', 'ThreePLID' => 1},
69
+ 'Items' => ['Item' => {'SKU' => '001002003A', 'CustomerID' => 1,
70
+ 'InventoryMethod' => 'FIFO', 'UPC' => '001002003A'}]}
71
+ response = client.call(:create_items, :message => msg)
72
+
73
+ expect(response.http.body.scan(/<tns:extLoginData>/).count).to eq(2)
74
+ end
75
+ end
76
+
48
77
  context "global :namespaces" do
49
78
  it "adds additional namespaces to the SOAP envelope" do
50
79
  namespaces = { "xmlns:whatever" => "http://whatever.example.com" }
@@ -55,6 +84,24 @@ describe "Options" do
55
84
  end
56
85
  end
57
86
 
87
+ context 'global :follow_redirects' do
88
+ it 'sets whether or not request should follow redirects' do
89
+ client = new_client(:endpoint => @server.url, :follow_redirects => true)
90
+
91
+ HTTPI::Request.any_instance.expects(:follow_redirect=).with(true)
92
+
93
+ response = client.call(:authenticate)
94
+ end
95
+
96
+ it 'defaults to false' do
97
+ client = new_client(:endpoint => @server.url)
98
+
99
+ HTTPI::Request.any_instance.expects(:follow_redirect=).with(false)
100
+
101
+ response = client.call(:authenticate)
102
+ end
103
+ end
104
+
58
105
  context "global :proxy" do
59
106
  it "sets the proxy server to use" do
60
107
  proxy_url = "http://example.com"
@@ -67,6 +114,15 @@ describe "Options" do
67
114
  end
68
115
  end
69
116
 
117
+ context "global :host" do
118
+ it "overrides the WSDL endpoint host" do
119
+ client = new_client(:wsdl => Fixture.wsdl(:no_message_tag), host: "https://example.com:8080")
120
+
121
+ request = client.build_request(:update_orders)
122
+ expect(request.url.to_s).to eq "https://example.com:8080/webserviceexternal/contracts.asmx"
123
+ end
124
+ end
125
+
70
126
  context "global :headers" do
71
127
  it "sets the HTTP headers for the next request" do
72
128
  client = new_client(:endpoint => @server.url(:inspect_request), :headers => { "X-Token" => "secret" })
@@ -80,11 +136,14 @@ describe "Options" do
80
136
 
81
137
  context "global :open_timeout" do
82
138
  it "makes the client timeout after n seconds" do
83
- non_routable_ip = "http://10.255.255.1"
139
+ non_routable_ip = "http://192.0.2.0"
84
140
  client = new_client(:endpoint => non_routable_ip, :open_timeout => 0.1)
85
141
 
86
142
  expect { client.call(:authenticate) }.to raise_error { |error|
87
- if error.kind_of? Errno::EHOSTUNREACH
143
+ host_unreachable = error.kind_of? Errno::EHOSTUNREACH
144
+ net_unreachable = error.kind_of? Errno::ENETUNREACH
145
+ socket_err = error.kind_of? SocketError
146
+ if host_unreachable || net_unreachable || socket_err
88
147
  warn "Warning: looks like your network may be down?!\n" +
89
148
  "-> skipping spec at #{__FILE__}:#{__LINE__}"
90
149
  else
@@ -125,10 +184,23 @@ describe "Options" do
125
184
  context "global :soap_header" do
126
185
  it "accepts a Hash of SOAP header information" do
127
186
  client = new_client(:endpoint => @server.url(:repeat), :soap_header => { :auth_token => "secret" })
128
-
129
187
  response = client.call(:authenticate)
188
+
130
189
  expect(response.http.body).to include("<env:Header><authToken>secret</authToken></env:Header>")
131
190
  end
191
+
192
+ it "accepts anything other than a String and calls #to_s on it" do
193
+ to_s_header = Class.new {
194
+ def to_s
195
+ "to_s_header"
196
+ end
197
+ }.new
198
+
199
+ client = new_client(:endpoint => @server.url(:repeat), :soap_header => to_s_header)
200
+ response = client.call(:authenticate)
201
+
202
+ expect(response.http.body).to include("<env:Header>to_s_header</env:Header>")
203
+ end
132
204
  end
133
205
 
134
206
  context "global :element_form_default" do
@@ -147,6 +219,17 @@ describe "Options" do
147
219
  expect(response.http.body).to include("<user>lea</user>")
148
220
  expect(response.http.body).to include("<password>top-secret</password>")
149
221
  end
222
+
223
+ it "qualifies elements embedded in complex types" do
224
+ client = new_client(:endpoint => @server.url(:repeat),
225
+ :wsdl => Fixture.wsdl(:elements_in_types))
226
+ msg = {":TopLevelTransaction"=>{":Qualified"=>"A Value"}}
227
+
228
+ response = client.call(:top_level_transaction, :message => msg)
229
+
230
+ expect(response.http.body.scan(/<tns:Qualified>/).count).to eq(1)
231
+ end
232
+
150
233
  end
151
234
 
152
235
  context "global :env_namespace" do
@@ -262,6 +345,15 @@ describe "Options" do
262
345
 
263
346
  expect(logger).to eq(custom_logger)
264
347
  end
348
+
349
+ it "sets the logger of HTTPI as well" do
350
+ custom_logger = Logger.new($stdout)
351
+
352
+ client = new_client(:logger => custom_logger, :log => true)
353
+
354
+ expect(HTTPI.logger).to be custom_logger
355
+ end
356
+
265
357
  end
266
358
 
267
359
  context "global :log_level" do
@@ -308,18 +400,27 @@ describe "Options" do
308
400
 
309
401
  context "global :ssl_version" do
310
402
  it "sets the SSL version to use" do
311
- HTTPI::Auth::SSL.any_instance.expects(:ssl_version=).with(:SSLv3).twice
403
+ HTTPI::Auth::SSL.any_instance.expects(:ssl_version=).with(:TLSv1).twice
312
404
 
313
- client = new_client(:endpoint => @server.url, :ssl_version => :SSLv3)
405
+ client = new_client(:endpoint => @server.url, :ssl_version => :TLSv1)
314
406
  client.call(:authenticate)
315
407
  end
316
408
  end
317
409
 
318
410
  context "global :ssl_verify_mode" do
319
411
  it "sets the verify mode to use" do
320
- HTTPI::Auth::SSL.any_instance.expects(:verify_mode=).with(:none).twice
412
+ HTTPI::Auth::SSL.any_instance.expects(:verify_mode=).with(:peer).twice
321
413
 
322
- client = new_client(:endpoint => @server.url, :ssl_verify_mode => :none)
414
+ client = new_client(:endpoint => @server.url, :ssl_verify_mode => :peer)
415
+ client.call(:authenticate)
416
+ end
417
+ end
418
+
419
+ context "global :ssl_ciphers" do
420
+ it "sets the ciphers to use" do
421
+ HTTPI::Auth::SSL.any_instance.expects(:ciphers=).with(:none).twice
422
+
423
+ client = new_client(:endpoint => @server.url, :ssl_ciphers => :none)
323
424
  client.call(:authenticate)
324
425
  end
325
426
  end
@@ -334,6 +435,17 @@ describe "Options" do
334
435
  end
335
436
  end
336
437
 
438
+ context "global :ssl_cert_key" do
439
+ it "sets the cert key to use" do
440
+ cert_key = File.open(File.expand_path("../../fixtures/ssl/client_key.pem", __FILE__)).read
441
+ HTTPI::Auth::SSL.any_instance.expects(:cert_key=).with(cert_key).twice
442
+
443
+ client = new_client(:endpoint => @server.url, :ssl_cert_key => cert_key)
444
+ client.call(:authenticate)
445
+ end
446
+ end
447
+
448
+
337
449
  context "global :ssl_cert_key_password" do
338
450
  it "sets the encrypted cert key file password to use" do
339
451
  cert_key = File.expand_path("../../fixtures/ssl/client_encrypted_key.pem", __FILE__)
@@ -357,6 +469,16 @@ describe "Options" do
357
469
  end
358
470
  end
359
471
 
472
+ context "global :ssl_cert" do
473
+ it "sets the cert to use" do
474
+ cert = File.open(File.expand_path("../../fixtures/ssl/client_cert.pem", __FILE__)).read
475
+ HTTPI::Auth::SSL.any_instance.expects(:cert=).with(cert).twice
476
+
477
+ client = new_client(:endpoint => @server.url, :ssl_cert => cert)
478
+ client.call(:authenticate)
479
+ end
480
+ end
481
+
360
482
  context "global :ssl_ca_cert_file" do
361
483
  it "sets the ca cert file to use" do
362
484
  ca_cert = File.expand_path("../../fixtures/ssl/client_cert.pem", __FILE__)
@@ -367,6 +489,37 @@ describe "Options" do
367
489
  end
368
490
  end
369
491
 
492
+ context "global :ssl_ca_cert_path" do
493
+ it "sets the ca cert path to use" do
494
+ ca_cert_path = "../../fixtures/ssl"
495
+ HTTPI::Auth::SSL.any_instance.expects(:ca_cert_path=).with(ca_cert_path).twice
496
+
497
+ client = new_client(:endpoint => @server.url, :ssl_ca_cert_path => ca_cert_path)
498
+ client.call(:authenticate)
499
+ end
500
+ end
501
+
502
+ context "global :ssl_ca_cert_store" do
503
+ it "sets the cert store to use" do
504
+ cert_store = OpenSSL::X509::Store.new
505
+ HTTPI::Auth::SSL.any_instance.expects(:cert_store=).with(cert_store).twice
506
+
507
+ client = new_client(:endpoint => @server.url, :ssl_cert_store => cert_store)
508
+ client.call(:authenticate)
509
+ end
510
+ end
511
+
512
+ context "global :ssl_ca_cert" do
513
+ it "sets the ca cert file to use" do
514
+ ca_cert = File.open(File.expand_path("../../fixtures/ssl/client_cert.pem", __FILE__)).read
515
+ HTTPI::Auth::SSL.any_instance.expects(:ca_cert=).with(ca_cert).twice
516
+
517
+ client = new_client(:endpoint => @server.url, :ssl_ca_cert => ca_cert)
518
+ client.call(:authenticate)
519
+ end
520
+ end
521
+
522
+
370
523
  context "global :basic_auth" do
371
524
  it "sets the basic auth credentials" do
372
525
  client = new_client(:endpoint => @server.url(:basic_auth), :basic_auth => ["admin", "secret"])
@@ -385,133 +538,278 @@ describe "Options" do
385
538
  end
386
539
  end
387
540
 
541
+ context "global :ntlm" do
542
+ it "sets the ntlm credentials to use" do
543
+ credentials = ["admin", "secret"]
544
+ client = new_client(:endpoint => @server.url, :ntlm => credentials)
545
+
546
+ # TODO: find a way to integration test this. including an entire ntlm
547
+ # server implementation seems a bit over the top though.
548
+ HTTPI::Auth::Config.any_instance.expects(:ntlm).with(*credentials)
549
+
550
+ response = client.call(:authenticate)
551
+ end
552
+ end
553
+
388
554
  context "global :filters" do
389
555
  it "filters a list of XML tags from logged SOAP messages" do
390
- silence_stdout do
556
+ captured = mock_stdout do
391
557
  client = new_client(:endpoint => @server.url(:repeat), :log => true)
392
-
393
558
  client.globals[:filters] << :password
394
559
 
395
- # filter out logs we're not interested in
396
- client.globals[:logger].expects(:info).at_least_once
397
-
398
- # check whether the password is filtered
399
- client.globals[:logger].expects(:debug).with { |message|
400
- message.include? "<password>***FILTERED***</password>"
401
- }.twice
402
-
403
560
  message = { :username => "luke", :password => "secret" }
404
561
  client.call(:authenticate, :message => message)
405
562
  end
563
+
564
+ captured.rewind
565
+ messages = captured.readlines.join("\n")
566
+
567
+ expect(messages).to include("<password>***FILTERED***</password>")
406
568
  end
407
569
  end
408
570
 
409
571
  context "global :pretty_print_xml" do
410
572
  it "is a nice but expensive way to debug XML messages" do
411
- silence_stdout do
412
- client = new_client(:endpoint => @server.url(:repeat), :pretty_print_xml => true, :log => true)
573
+ captured = mock_stdout do
574
+ client = new_client(
575
+ :endpoint => @server.url(:repeat),
576
+ :pretty_print_xml => true,
577
+ :log => true)
578
+ client.globals[:logger].formatter = proc { |*, msg| "#{msg}\n" }
413
579
 
414
- # filter out logs we're not interested in
415
- client.globals[:logger].expects(:info).at_least_once
580
+ client.call(:authenticate)
581
+ end
416
582
 
417
- # check whether the message is pretty printed
418
- client.globals[:logger].expects(:debug).with { |message|
419
- envelope = message =~ /\n<env:Envelope/
420
- body = message =~ /\n <env:Body>/
421
- message_tag = message =~ /\n <tns:authenticate\/>/
583
+ captured.rewind
584
+ messages = captured.readlines.join("\n")
422
585
 
423
- envelope && body && message_tag
424
- }.twice
586
+ expect(messages).to match(/\n<env:Envelope/)
587
+ expect(messages).to match(/\n <env:Body/)
588
+ expect(messages).to match(/\n <tns:authenticate/)
589
+ end
590
+ end
425
591
 
426
- client.call(:authenticate)
592
+ context ":wsse_auth" do
593
+ let(:username) { "luke" }
594
+ let(:password) { "secret" }
595
+ let(:request) { response.http.body }
596
+
597
+ shared_examples "WSSE basic auth" do
598
+ it "adds WSSE basic auth information to the request" do
599
+ # the header and wsse security node
600
+ wsse_namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
601
+ expect(request).to include("<env:Header><wsse:Security xmlns:wsse=\"#{wsse_namespace}\">")
602
+
603
+ # split up to prevent problems with unordered Hash attributes in 1.8 [dh, 2012-12-13]
604
+ expect(request).to include("<wsse:UsernameToken")
605
+ expect(request).to include("wsu:Id=\"UsernameToken-1\"")
606
+ expect(request).to include("xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\"")
607
+
608
+ # the username and password node with type attribute
609
+ expect(request).to include("<wsse:Username>#{username}</wsse:Username>")
610
+ password_text = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText"
611
+ expect(request).to include("<wsse:Password Type=\"#{password_text}\">#{password}</wsse:Password>")
427
612
  end
428
613
  end
429
- end
430
614
 
431
- context "global :wsse_auth" do
432
- it "adds WSSE basic auth information to the request" do
433
- client = new_client(:endpoint => @server.url(:repeat), :wsse_auth => ["luke", "secret"])
434
- response = client.call(:authenticate)
615
+ shared_examples "WSSE digest auth" do
616
+ it "adds WSSE digest auth information to the request" do
617
+ # the header and wsse security node
618
+ wsse_namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
619
+ expect(request).to include("<env:Header><wsse:Security xmlns:wsse=\"#{wsse_namespace}\">")
435
620
 
436
- request = response.http.body
621
+ # split up to prevent problems with unordered Hash attributes in 1.8 [dh, 2012-12-13]
622
+ expect(request).to include("<wsse:UsernameToken")
623
+ expect(request).to include("wsu:Id=\"UsernameToken-1\"")
624
+ expect(request).to include("xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\"")
437
625
 
438
- # the header and wsse security node
439
- wsse_namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
440
- expect(request).to include("<env:Header><wsse:Security xmlns:wsse=\"#{wsse_namespace}\">")
626
+ # the username node
627
+ expect(request).to include("<wsse:Username>#{username}</wsse:Username>")
441
628
 
442
- # split up to prevent problems with unordered Hash attributes in 1.8 [dh, 2012-12-13]
443
- expect(request).to include("<wsse:UsernameToken")
444
- expect(request).to include("wsu:Id=\"UsernameToken-1\"")
445
- expect(request).to include("xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\"")
629
+ # the nonce node
630
+ expect(request).to match(/<wsse:Nonce.*>.+\n?<\/wsse:Nonce>/)
446
631
 
447
- # the username and password node with type attribute
448
- expect(request).to include("<wsse:Username>luke</wsse:Username>")
449
- password_text = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText"
450
- expect(request).to include("<wsse:Password Type=\"#{password_text}\">secret</wsse:Password>")
632
+ # the created node with a timestamp
633
+ expect(request).to match(/<wsu:Created>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.*<\/wsu:Created>/)
634
+
635
+ # the password node contains the encrypted value
636
+ password_digest = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest"
637
+ expect(request).to match(/<wsse:Password Type=\"#{password_digest}\">.+<\/wsse:Password>/)
638
+ expect(request).to_not include(password)
639
+ end
451
640
  end
452
641
 
453
- it "adds WSSE digest auth information to the request" do
454
- client = new_client(:endpoint => @server.url(:repeat), :wsse_auth => ["lea", "top-secret", :digest])
455
- response = client.call(:authenticate)
642
+ shared_examples "no WSSE auth" do
643
+ it "does not add WSSE auth to the request" do
644
+ expect(request).not_to include("<wsse:UsernameToken")
645
+ end
646
+ end
456
647
 
457
- request = response.http.body
648
+ describe "global" do
649
+ context "enabled" do
650
+ context "without digest" do
651
+ let(:client) { new_client(:endpoint => @server.url(:repeat), :wsse_auth => [username, password]) }
652
+ let(:response) { client.call(:authenticate) }
653
+ include_examples "WSSE basic auth"
654
+ end
655
+
656
+ context "with digest" do
657
+ let(:client) { new_client(:endpoint => @server.url(:repeat), :wsse_auth => [username, password, :digest]) }
658
+ let(:response) { client.call(:authenticate) }
659
+ include_examples "WSSE digest auth"
660
+ end
458
661
 
459
- # the header and wsse security node
460
- wsse_namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
461
- expect(request).to include("<env:Header><wsse:Security xmlns:wsse=\"#{wsse_namespace}\">")
662
+ context "local override" do
663
+ let(:client) { new_client(:endpoint => @server.url(:repeat), :wsse_auth => ["luke", "secret"]) }
664
+
665
+ context "enabled" do
666
+ let(:username) { "lea" }
667
+ let(:password) { "top-secret" }
668
+
669
+ context "without digest" do
670
+ let(:response) { client.call(:authenticate) {|locals| locals.wsse_auth(username, password)} }
671
+ include_examples "WSSE basic auth"
672
+ end
673
+
674
+ context "with digest" do
675
+ let(:response) { client.call(:authenticate) {|locals| locals.wsse_auth(username, password, :digest)} }
676
+ include_examples "WSSE digest auth"
677
+ end
678
+ end
679
+
680
+ context "disabled" do
681
+ let(:response) { client.call(:authenticate) {|locals| locals.wsse_auth(false)} }
682
+ include_examples "no WSSE auth"
683
+ end
684
+
685
+ context "set to nil" do
686
+ let(:response) { client.call(:authenticate) {|locals| locals.wsse_auth(nil)} }
687
+ include_examples "WSSE basic auth"
688
+ end
689
+ end
462
690
 
463
- # split up to prevent problems with unordered Hash attributes in 1.8 [dh, 2012-12-13]
464
- expect(request).to include("<wsse:UsernameToken")
465
- expect(request).to include("wsu:Id=\"UsernameToken-1\"")
466
- expect(request).to include("xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\"")
691
+ context "global" do
692
+ let(:client) { new_client(:endpoint => @server.url(:repeat), :wsse_auth => [username, password, :digest]) }
693
+ let(:response) { client.call(:authenticate) }
694
+ include_examples "WSSE digest auth"
695
+ end
696
+ end
467
697
 
468
- # the username node
469
- expect(request).to include("<wsse:Username>lea</wsse:Username>")
698
+ context "not enabled" do
699
+ let(:client) { new_client(:endpoint => @server.url(:repeat)) }
470
700
 
471
- # the nonce node
472
- expect(request).to match(/<wsse:Nonce>.+<\/wsse:Nonce>/)
701
+ describe "local" do
702
+ context "enabled" do
703
+ let(:response) { client.call(:authenticate) {|locals| locals.wsse_auth(username, password, :digest)} }
704
+ include_examples "WSSE digest auth"
705
+ end
473
706
 
474
- # the created node with a timestamp
475
- expect(request).to match(/<wsu:Created>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.*<\/wsu:Created>/)
707
+ context "disabled" do
708
+ let(:response) { client.call(:authenticate) { |locals| locals.wsse_auth(false)} }
709
+ include_examples "no WSSE auth"
710
+ end
476
711
 
477
- # the password node contains the encrypted value
478
- password_digest = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest"
479
- expect(request).to match(/<wsse:Password Type=\"#{password_digest}\">.+<\/wsse:Password>/)
480
- expect(request).to_not include("top-secret")
712
+ context "set to nil" do
713
+ let(:response) { client.call(:authenticate) { |locals| locals.wsse_auth(nil)} }
714
+ include_examples "no WSSE auth"
715
+ end
716
+ end
717
+ end
481
718
  end
482
719
  end
483
720
 
484
- context "global :wsse_timestamp" do
485
- it "adds WSSE timestamp auth information to the request" do
486
- client = new_client(:endpoint => @server.url(:repeat), :wsse_timestamp => true)
487
- response = client.call(:authenticate)
721
+ context ":wsse_timestamp" do
722
+ let(:request) { response.http.body }
488
723
 
489
- request = response.http.body
724
+ shared_examples "WSSE timestamp" do
725
+ it "adds WSSE timestamp auth information to the request" do
726
+ # the header and wsse security node
727
+ wsse_namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
728
+ expect(request).to include("<env:Header><wsse:Security xmlns:wsse=\"#{wsse_namespace}\">")
490
729
 
491
- # the header and wsse security node
492
- wsse_namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
493
- expect(request).to include("<env:Header><wsse:Security xmlns:wsse=\"#{wsse_namespace}\">")
730
+ # split up to prevent problems with unordered Hash attributes in 1.8 [dh, 2012-12-13]
731
+ expect(request).to include("<wsu:Timestamp")
732
+ expect(request).to include("wsu:Id=\"Timestamp-1\"")
733
+ expect(request).to include("xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\"")
494
734
 
495
- # split up to prevent problems with unordered Hash attributes in 1.8 [dh, 2012-12-13]
496
- expect(request).to include("<wsu:Timestamp")
497
- expect(request).to include("wsu:Id=\"Timestamp-1\"")
498
- expect(request).to include("xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\"")
735
+ # the created node with a timestamp
736
+ expect(request).to match(/<wsu:Created>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.*<\/wsu:Created>/)
499
737
 
500
- # the created node with a timestamp
501
- expect(request).to match(/<wsu:Created>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.*<\/wsu:Created>/)
738
+ # the expires node with a timestamp
739
+ expect(request).to match(/<wsu:Expires>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.*<\/wsu:Expires>/)
740
+ end
741
+ end
502
742
 
503
- # the expires node with a timestamp
504
- expect(request).to match(/<wsu:Expires>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.*<\/wsu:Expires>/)
743
+ shared_examples "no WSSE timestamp" do
744
+ it "does not add WSSE timestamp to the request" do
745
+ expect(request).not_to include("<wsu:Timestamp")
746
+ end
747
+ end
748
+
749
+ describe "global" do
750
+ context "enabled" do
751
+ context "through block without arguments" do
752
+ let(:client) do
753
+ new_client(:endpoint => @server.url(:repeat)) do |globals|
754
+ globals.wsse_timestamp
755
+ end
756
+ end
757
+ let(:response) { client.call(:authenticate) }
758
+ include_examples "WSSE timestamp"
759
+ end
760
+
761
+ context "through initializer options" do
762
+ let(:client) { new_client(:endpoint => @server.url(:repeat), :wsse_timestamp => true) }
763
+ let(:response) { client.call(:authenticate) }
764
+ include_examples "WSSE timestamp"
765
+ end
766
+
767
+ context "with local override" do
768
+ let(:client) { new_client(:endpoint => @server.url(:repeat), :wsse_timestamp => true) }
769
+ context "enabled" do
770
+ let(:response) { client.call(:authenticate) {|locals| locals.wsse_timestamp} }
771
+ include_examples "WSSE timestamp"
772
+ end
773
+ context "disabled" do
774
+ let(:response) { client.call(:authenticate) {|locals| locals.wsse_timestamp(false) } }
775
+ include_examples "no WSSE timestamp"
776
+ end
777
+ context "set to nil" do
778
+ let(:response) { client.call(:authenticate) {|locals| locals.wsse_timestamp(nil) } }
779
+ include_examples "WSSE timestamp"
780
+ end
781
+ end
782
+ end
783
+
784
+ context "not enabled" do
785
+ let(:client) { new_client(:endpoint => @server.url(:repeat)) }
786
+ describe "local" do
787
+ context "enabled" do
788
+ let(:response) { client.call(:authenticate) {|locals| locals.wsse_timestamp} }
789
+ include_examples "WSSE timestamp"
790
+ end
791
+ context "disabled" do
792
+ let(:response) { client.call(:authenticate) {|locals| locals.wsse_timestamp(false) } }
793
+ include_examples "no WSSE timestamp"
794
+ end
795
+ context "set to nil" do
796
+ let(:response) { client.call(:authenticate) {|locals| locals.wsse_timestamp(nil) } }
797
+ include_examples "no WSSE timestamp"
798
+ end
799
+ end
800
+ end
505
801
  end
506
802
  end
507
803
 
508
804
  context "global :strip_namespaces" do
509
805
  it "can be changed to not strip any namespaces" do
510
- client = new_client(:endpoint => @server.url(:repeat), :convert_response_tags_to => lambda { |tag| tag.snakecase }, :strip_namespaces => false)
511
- response = client.call(:authenticate, :xml => Fixture.response(:authentication))
806
+ client = new_client(
807
+ :endpoint => @server.url(:repeat),
808
+ :convert_response_tags_to => lambda { |tag| tag.snakecase },
809
+ :strip_namespaces => false
810
+ )
512
811
 
513
- # the header/body convenience methods fails when conventions are not met. [dh, 2012-12-12]
514
- expect { response.body }.to raise_error(Savon::InvalidResponseError)
812
+ response = client.call(:authenticate, :xml => Fixture.response(:authentication))
515
813
 
516
814
  expect(response.hash["soap:envelope"]["soap:body"]).to include("ns2:authenticate_response")
517
815
  end
@@ -562,6 +860,125 @@ describe "Options" do
562
860
  end
563
861
  end
564
862
 
863
+ context "global :convert_attributes_to" do
864
+ it "changes how XML tag attributes from the SOAP response are translated into Hash keys" do
865
+ client = new_client(:endpoint => @server.url(:repeat), :convert_attributes_to => lambda {|k,v| [k,v]})
866
+ response = client.call(:authenticate, :xml => Fixture.response(:f5))
867
+ expect(response.body[:get_agent_listen_address_response][:return][:item].first[:ipport][:address]).to eq({:"@s:type"=>"y:string"})
868
+ end
869
+
870
+ it "strips the attributes if an appropriate lambda is set" do
871
+ client = new_client(:endpoint => @server.url(:repeat), :convert_attributes_to => lambda {|k,v| []})
872
+ response = client.call(:authenticate, :xml => Fixture.response(:f5))
873
+ expect(response.body[:get_agent_listen_address_response][:return][:item].first[:ipport][:address]).to eq(nil)
874
+ end
875
+
876
+ it "accepts a block in the block-based interface" do
877
+ client = Savon.client do |globals|
878
+ globals.log false
879
+ globals.wsdl Fixture.wsdl(:authentication)
880
+ globals.endpoint @server.url(:repeat)
881
+ globals.convert_attributes_to {|k,v| [k,v]}
882
+ end
883
+
884
+ response = client.call(:authenticate) do |locals|
885
+ locals.xml Fixture.response(:f5)
886
+ end
887
+
888
+ expect(response.body[:get_agent_listen_address_response][:return][:item].first[:ipport][:address]).to eq({:"@s:type"=>"y:string"})
889
+ end
890
+ end
891
+
892
+ context 'global: :adapter' do
893
+ it 'passes option to Wasabi initializer for WSDL fetching' do
894
+ ## I want to use there something similar to the next mock expectation, but I can't
895
+ ## as due to how Savon sets up Wasabi::Document and Wasabi::Document initialize itself
896
+ ## adapter= method is called first time with nil and second time with adapter. [Envek, 2014-05-03]
897
+ # Wasabi::Document.any_instance.expects(:adapter=).with(:fake_adapter_for_test)
898
+ client = Savon.client(
899
+ :log => false,
900
+ :wsdl => @server.url(:authentication),
901
+ :adapter => :fake_adapter_for_test,
902
+ )
903
+ operations = client.operations
904
+ expect(operations).to eq([:authenticate])
905
+ expect(FakeAdapterForTest.class_variable_get(:@@requests).size).to eq(1)
906
+ expect(FakeAdapterForTest.class_variable_get(:@@requests).first.url).to eq(URI.parse(@server.url(:authentication)))
907
+ expect(FakeAdapterForTest.class_variable_get(:@@methods)).to eq([:get])
908
+ end
909
+
910
+ it 'instructs HTTPI to use provided adapter for performing SOAP requests' do
911
+ client = new_client_without_wsdl(
912
+ :endpoint => @server.url(:repeat),
913
+ :namespace => "http://v1.example.com",
914
+ :adapter => :adapter_for_test,
915
+ )
916
+ response = client.call(:authenticate)
917
+ expect(response.http.body).to include('xmlns:wsdl="http://v1.example.com"')
918
+ expect(response.http.body).to include('<wsdl:authenticate>')
919
+ expect(AdapterForTest.class_variable_get(:@@requests).size).to eq(1)
920
+ expect(AdapterForTest.class_variable_get(:@@requests).first.url).to eq(URI.parse(@server.url(:repeat)))
921
+ expect(AdapterForTest.class_variable_get(:@@methods)).to eq([:post])
922
+ end
923
+ end
924
+
925
+ context "global and request :soap_header" do
926
+ it "merges the headers if both were provided as Hashes" do
927
+ global_soap_header = {
928
+ :global_header => { :auth_token => "secret" },
929
+ :merged => { :global => true }
930
+ }
931
+
932
+ request_soap_header = {
933
+ :request_header => { :auth_token => "secret" },
934
+ :merged => { :request => true }
935
+ }
936
+
937
+ client = new_client(:endpoint => @server.url(:repeat), :soap_header => global_soap_header)
938
+
939
+ response = client.call(:authenticate, :soap_header => request_soap_header)
940
+ request_body = response.http.body
941
+
942
+ expect(request_body).to include("<globalHeader><authToken>secret</authToken></globalHeader>")
943
+ expect(request_body).to include("<requestHeader><authToken>secret</authToken></requestHeader>")
944
+ expect(request_body).to include("<merged><request>true</request></merged>")
945
+ end
946
+
947
+ it "prefers the request over the global option if at least one of them is not a Hash" do
948
+ global_soap_header = "<global>header</global>"
949
+ request_soap_header = "<request>header</request>"
950
+
951
+ client = new_client(:endpoint => @server.url(:repeat), :soap_header => global_soap_header)
952
+
953
+ response = client.call(:authenticate, :soap_header => request_soap_header)
954
+ request_body = response.http.body
955
+
956
+ expect(request_body).to include("<env:Header><request>header</request></env:Header>")
957
+ end
958
+ end
959
+
960
+ context "request :soap_header" do
961
+ it "accepts a Hash of SOAP header information" do
962
+ client = new_client(:endpoint => @server.url(:repeat))
963
+
964
+ response = client.call(:authenticate, :soap_header => { :auth_token => "secret" })
965
+ expect(response.http.body).to include("<env:Header><authToken>secret</authToken></env:Header>")
966
+ end
967
+
968
+ it "accepts anything other than a String and calls #to_s on it" do
969
+ to_s_header = Class.new {
970
+ def to_s
971
+ "to_s_header"
972
+ end
973
+ }.new
974
+
975
+ client = new_client(:endpoint => @server.url(:repeat))
976
+
977
+ response = client.call(:authenticate, :soap_header => to_s_header)
978
+ expect(response.http.body).to include("<env:Header>to_s_header</env:Header>")
979
+ end
980
+ end
981
+
565
982
  context "request: message_tag" do
566
983
  it "when set, changes the SOAP message tag" do
567
984
  response = new_client(:endpoint => @server.url(:repeat)).call(:authenticate, :message_tag => :doAuthenticate)
@@ -669,6 +1086,17 @@ describe "Options" do
669
1086
  end
670
1087
  end
671
1088
 
1089
+ context "request :headers" do
1090
+ it "sets headers" do
1091
+ client = new_client(:endpoint => @server.url(:inspect_request))
1092
+
1093
+ response = client.call(:authenticate, :headers => { "X-Token" => "secret" })
1094
+ x_token = inspect_request(response).x_token
1095
+
1096
+ expect(x_token).to eq("secret")
1097
+ end
1098
+ end
1099
+
672
1100
  def new_client(globals = {}, &block)
673
1101
  globals = { :wsdl => Fixture.wsdl(:authentication), :log => false }.merge(globals)
674
1102
  Savon.client(globals, &block)