savon 2.2.0 → 2.12.1

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