elastic-transport 8.0.0.pre1

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 (48) hide show
  1. checksums.yaml +7 -0
  2. data/.github/check_license_headers.rb +33 -0
  3. data/.github/license-header.txt +16 -0
  4. data/.github/workflows/license.yml +13 -0
  5. data/.github/workflows/tests.yml +45 -0
  6. data/.gitignore +19 -0
  7. data/CHANGELOG.md +224 -0
  8. data/Gemfile +38 -0
  9. data/LICENSE +202 -0
  10. data/README.md +552 -0
  11. data/Rakefile +87 -0
  12. data/elastic-transport.gemspec +74 -0
  13. data/lib/elastic/transport/client.rb +276 -0
  14. data/lib/elastic/transport/meta_header.rb +135 -0
  15. data/lib/elastic/transport/redacted.rb +73 -0
  16. data/lib/elastic/transport/transport/base.rb +450 -0
  17. data/lib/elastic/transport/transport/connections/collection.rb +126 -0
  18. data/lib/elastic/transport/transport/connections/connection.rb +160 -0
  19. data/lib/elastic/transport/transport/connections/selector.rb +91 -0
  20. data/lib/elastic/transport/transport/errors.rb +91 -0
  21. data/lib/elastic/transport/transport/http/curb.rb +120 -0
  22. data/lib/elastic/transport/transport/http/faraday.rb +95 -0
  23. data/lib/elastic/transport/transport/http/manticore.rb +179 -0
  24. data/lib/elastic/transport/transport/loggable.rb +83 -0
  25. data/lib/elastic/transport/transport/response.rb +36 -0
  26. data/lib/elastic/transport/transport/serializer/multi_json.rb +52 -0
  27. data/lib/elastic/transport/transport/sniffer.rb +101 -0
  28. data/lib/elastic/transport/version.rb +22 -0
  29. data/lib/elastic/transport.rb +37 -0
  30. data/lib/elastic-transport.rb +18 -0
  31. data/spec/elasticsearch/connections/collection_spec.rb +266 -0
  32. data/spec/elasticsearch/connections/selector_spec.rb +166 -0
  33. data/spec/elasticsearch/transport/base_spec.rb +264 -0
  34. data/spec/elasticsearch/transport/client_spec.rb +1651 -0
  35. data/spec/elasticsearch/transport/meta_header_spec.rb +274 -0
  36. data/spec/elasticsearch/transport/sniffer_spec.rb +275 -0
  37. data/spec/spec_helper.rb +90 -0
  38. data/test/integration/transport_test.rb +98 -0
  39. data/test/profile/client_benchmark_test.rb +132 -0
  40. data/test/test_helper.rb +83 -0
  41. data/test/unit/connection_test.rb +135 -0
  42. data/test/unit/response_test.rb +30 -0
  43. data/test/unit/serializer_test.rb +33 -0
  44. data/test/unit/transport_base_test.rb +664 -0
  45. data/test/unit/transport_curb_test.rb +135 -0
  46. data/test/unit/transport_faraday_test.rb +228 -0
  47. data/test/unit/transport_manticore_test.rb +251 -0
  48. metadata +412 -0
@@ -0,0 +1,166 @@
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
18
+ require 'spec_helper'
19
+
20
+ describe Elastic::Transport::Transport::Connections::Selector do
21
+ before do
22
+ class BackupStrategySelector
23
+ include Elastic::Transport::Transport::Connections::Selector::Base
24
+
25
+ def select(options={})
26
+ connections.reject do |c|
27
+ c.host[:attributes] && c.host[:attributes][:backup]
28
+ end.sample
29
+ end
30
+ end
31
+ end
32
+
33
+ after do
34
+ Object.send(:remove_const, :BackupStrategySelector)
35
+ end
36
+
37
+ let(:backup_strategy_selector) do
38
+ BackupStrategySelector.new
39
+ end
40
+
41
+ describe 'the Random selector' do
42
+ let(:connections) do
43
+ [1, 2]
44
+ end
45
+
46
+ let(:selector) do
47
+ described_class::Random.new(connections: connections)
48
+ end
49
+
50
+ it 'is initialized with connections' do
51
+ expect(selector.connections).to eq(connections)
52
+ end
53
+
54
+ describe '#select' do
55
+ let(:connections) do
56
+ (0..19).to_a
57
+ end
58
+
59
+ it 'returns a connection' do
60
+ expect(selector.select).to be_a(Integer)
61
+ end
62
+
63
+ context 'when multiple threads are used' do
64
+ it 'allows threads to select connections in parallel' do
65
+ expect(10.times.collect do
66
+ threads = []
67
+ 20.times do
68
+ threads << Thread.new do
69
+ selector.select
70
+ end
71
+ end
72
+ threads.map { |t| t.join }
73
+ selector.select
74
+ end).to all(be_a(Integer))
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ describe 'the RoundRobin selector' do
81
+ let(:connections) do
82
+ ['A', 'B', 'C']
83
+ end
84
+
85
+ let(:selector) do
86
+ described_class::RoundRobin.new(connections: connections)
87
+ end
88
+
89
+ it 'is initialized with connections' do
90
+ expect(selector.connections).to eq(connections)
91
+ end
92
+
93
+ describe '#select' do
94
+ it 'rotates over the connections' do
95
+ expect(selector.select).to eq('A')
96
+ expect(selector.select).to eq('B')
97
+ expect(selector.select).to eq('C')
98
+ expect(selector.select).to eq('A')
99
+ end
100
+
101
+ context 'when multiple threads are used' do
102
+ let(:connections) do
103
+ (0..19).to_a
104
+ end
105
+
106
+ it 'returns a connection' do
107
+ expect(selector.select).to be_a(Integer)
108
+ end
109
+
110
+ it 'allows threads to select connections in parallel' do
111
+ expect(10.times.collect do
112
+ threads = []
113
+ 20.times do
114
+ threads << Thread.new do
115
+ selector.select
116
+ end
117
+ end
118
+ threads.map { |t| t.join }
119
+ selector.select
120
+ end).to eq((0..9).to_a)
121
+ end
122
+ end
123
+ end
124
+ end
125
+
126
+ describe 'a custom selector' do
127
+ let(:connections) do
128
+ [
129
+ double(host: { hostname: 'host1' }),
130
+ double(host: { hostname: 'host2', attributes: { backup: true } })
131
+ ]
132
+ end
133
+
134
+ let(:selector) do
135
+ BackupStrategySelector.new(connections: connections)
136
+ end
137
+
138
+ it 'is initialized with connections' do
139
+ expect(selector.connections).to eq(connections)
140
+ end
141
+
142
+ describe '#select' do
143
+ it 'applies the custom strategy' do
144
+ 10.times { expect(selector.select.host[:hostname]).to eq('host1') }
145
+ end
146
+ end
147
+ end
148
+
149
+ context 'when the Base module is included in a class' do
150
+ before do
151
+ class ExampleSelector
152
+ include Elastic::Transport::Transport::Connections::Selector::Base
153
+ end
154
+ end
155
+
156
+ after do
157
+ Object.send(:remove_const, :ExampleSelector)
158
+ end
159
+
160
+ it 'requires the #select method to be redefined' do
161
+ expect do
162
+ ExampleSelector.new.select
163
+ end.to raise_exception(NoMethodError)
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,264 @@
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
18
+ require 'spec_helper'
19
+
20
+ describe Elastic::Transport::Transport::Base do
21
+ context 'when a host is printed in a logged message' do
22
+ shared_examples_for 'a redacted string' do
23
+ let(:client) do
24
+ Elastic::Transport::Client.new(arguments)
25
+ end
26
+
27
+ let(:logger) do
28
+ double('logger', error?: true, error: '')
29
+ end
30
+
31
+ it 'does not include the password in the logged string' do
32
+ expect(logger).not_to receive(:error).with(/secret_password/)
33
+ expect {
34
+ client.perform_request('GET', '/_cluster/stats')
35
+ }.to raise_exception(Faraday::ConnectionFailed)
36
+ end
37
+
38
+ it 'replaces the password with the string \'REDACTED\'' do
39
+ expect(logger).to receive(:error).with(/REDACTED/)
40
+ expect {
41
+ client.perform_request('GET', '/_cluster/stats')
42
+ }.to raise_exception(Faraday::ConnectionFailed)
43
+ end
44
+ end
45
+
46
+ context 'when the user and password are provided as separate arguments' do
47
+ let(:arguments) do
48
+ {
49
+ hosts: 'fake',
50
+ logger: logger,
51
+ password: 'secret_password',
52
+ user: 'test'
53
+ }
54
+ end
55
+
56
+ it_behaves_like 'a redacted string'
57
+ end
58
+
59
+ context 'when the user and password are provided in the string URI' do
60
+ let(:arguments) do
61
+ {
62
+ hosts: 'http://test:secret_password@fake',
63
+ logger: logger
64
+ }
65
+ end
66
+
67
+ it_behaves_like 'a redacted string'
68
+ end
69
+
70
+ context 'when the user and password are provided in the URI object' do
71
+ let(:arguments) do
72
+ {
73
+ hosts: URI.parse('http://test:secret_password@fake'),
74
+ logger: logger
75
+ }
76
+ end
77
+
78
+ it_behaves_like 'a redacted string'
79
+ end
80
+ end
81
+
82
+ context 'when reload_on_failure is true and and hosts are unreachable' do
83
+ let(:client) do
84
+ Elastic::Transport::Client.new(arguments)
85
+ end
86
+
87
+ let(:arguments) do
88
+ {
89
+ hosts: ['http://unavailable:9200', 'http://unavailable:9201'],
90
+ reload_on_failure: true,
91
+ sniffer_timeout: 5
92
+ }
93
+ end
94
+
95
+ it 'raises an exception' do
96
+ expect { client.perform_request('GET', '/info') }.to raise_exception(Faraday::ConnectionFailed)
97
+ end
98
+ end
99
+
100
+ context 'when the client has `retry_on_failure` set to an integer' do
101
+ let(:client) do
102
+ Elastic::Transport::Client.new(arguments)
103
+ end
104
+
105
+ let(:arguments) do
106
+ {
107
+ hosts: ['http://unavailable:9200', 'http://unavailable:9201'],
108
+ retry_on_failure: 2
109
+ }
110
+ end
111
+
112
+ context 'when `perform_request` is called without a `retry_on_failure` option value' do
113
+ before do
114
+ expect(client.transport).to receive(:get_connection).exactly(3).times.and_call_original
115
+ end
116
+
117
+ it 'uses the client `retry_on_failure` value' do
118
+ expect {
119
+ client.transport.perform_request('GET', '/info')
120
+ }.to raise_exception(Faraday::ConnectionFailed)
121
+ end
122
+ end
123
+
124
+ context 'when `perform_request` is called with a `retry_on_status` option value' do
125
+ before do
126
+ expect(client.transport).to receive(:__raise_transport_error).exactly(6).times.and_call_original
127
+ end
128
+
129
+ let(:arguments) do
130
+ {
131
+ hosts: ELASTICSEARCH_HOSTS,
132
+ retry_on_status: ['404']
133
+ }
134
+ end
135
+
136
+ it 'retries on 404 status the specified number of max_retries' do
137
+ expect do
138
+ client.transport.perform_request('GET', 'myindex/_doc/1?routing=FOOBARBAZ', {}, nil, nil, retry_on_failure: 5)
139
+ end.to raise_exception(Elastic::Transport::Transport::Errors::NotFound)
140
+ end
141
+ end
142
+
143
+ context 'when `perform_request` is called with a `retry_on_failure` option value' do
144
+ before do
145
+ expect(client.transport).to receive(:get_connection).exactly(6).times.and_call_original
146
+ end
147
+
148
+ it 'uses the option `retry_on_failure` value' do
149
+ expect do
150
+ client.transport.perform_request('GET', '/info', {}, nil, nil, retry_on_failure: 5)
151
+ end.to raise_exception(Faraday::ConnectionFailed)
152
+ end
153
+ end
154
+ end
155
+
156
+ context 'when the client has `retry_on_failure` set to true' do
157
+ let(:client) do
158
+ Elastic::Transport::Client.new(arguments)
159
+ end
160
+
161
+ let(:arguments) do
162
+ {
163
+ hosts: ['http://unavailable:9200', 'http://unavailable:9201'],
164
+ retry_on_failure: true
165
+ }
166
+ end
167
+
168
+ context 'when `perform_request` is called without a `retry_on_failure` option value' do
169
+ before do
170
+ expect(client.transport).to receive(:get_connection).exactly(4).times.and_call_original
171
+ end
172
+
173
+ it 'uses the default `MAX_RETRIES` value' do
174
+ expect {
175
+ client.transport.perform_request('GET', '/info')
176
+ }.to raise_exception(Faraday::ConnectionFailed)
177
+ end
178
+ end
179
+
180
+ context 'when `perform_request` is called with a `retry_on_failure` option value' do
181
+ before do
182
+ expect(client.transport).to receive(:get_connection).exactly(6).times.and_call_original
183
+ end
184
+
185
+ it 'uses the option `retry_on_failure` value' do
186
+ expect {
187
+ client.transport.perform_request('GET', '/info', {}, nil, nil, retry_on_failure: 5)
188
+ }.to raise_exception(Faraday::ConnectionFailed)
189
+ end
190
+ end
191
+ end
192
+
193
+ context 'when the client has `retry_on_failure` set to false' do
194
+ let(:client) do
195
+ Elastic::Transport::Client.new(arguments)
196
+ end
197
+
198
+ let(:arguments) do
199
+ {
200
+ hosts: ['http://unavailable:9200', 'http://unavailable:9201'],
201
+ retry_on_failure: false
202
+ }
203
+ end
204
+
205
+ context 'when `perform_request` is called without a `retry_on_failure` option value' do
206
+ before do
207
+ expect(client.transport).to receive(:get_connection).once.and_call_original
208
+ end
209
+
210
+ it 'does not retry' do
211
+ expect {
212
+ client.transport.perform_request('GET', '/info')
213
+ }.to raise_exception(Faraday::ConnectionFailed)
214
+ end
215
+ end
216
+
217
+ context 'when `perform_request` is called with a `retry_on_failure` option value' do
218
+
219
+ before do
220
+ expect(client.transport).to receive(:get_connection).exactly(6).times.and_call_original
221
+ end
222
+
223
+ it 'uses the option `retry_on_failure` value' do
224
+ expect {
225
+ client.transport.perform_request('GET', '/info', {}, nil, nil, retry_on_failure: 5)
226
+ }.to raise_exception(Faraday::ConnectionFailed)
227
+ end
228
+ end
229
+ end
230
+
231
+ context 'when the client has no `retry_on_failure` set' do
232
+ let(:client) do
233
+ Elastic::Transport::Client.new(arguments)
234
+ end
235
+
236
+ let(:arguments) do
237
+ { hosts: ['http://unavailable:9200', 'http://unavailable:9201'] }
238
+ end
239
+
240
+ context 'when `perform_request` is called without a `retry_on_failure` option value' do
241
+ before do
242
+ expect(client.transport).to receive(:get_connection).exactly(1).times.and_call_original
243
+ end
244
+
245
+ it 'does not retry' do
246
+ expect do
247
+ client.transport.perform_request('GET', '/info')
248
+ end.to raise_exception(Faraday::ConnectionFailed)
249
+ end
250
+ end
251
+
252
+ context 'when `perform_request` is called with a `retry_on_failure` option value' do
253
+ before do
254
+ expect(client.transport).to receive(:get_connection).exactly(6).times.and_call_original
255
+ end
256
+
257
+ it 'uses the option `retry_on_failure` value' do
258
+ expect do
259
+ client.transport.perform_request('GET', '/info', {}, nil, nil, retry_on_failure: 5)
260
+ end.to raise_exception(Faraday::ConnectionFailed)
261
+ end
262
+ end
263
+ end
264
+ end
@@ -0,0 +1,1651 @@
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
18
+ require 'spec_helper'
19
+
20
+ describe Elastic::Transport::Client do
21
+ let(:client) do
22
+ described_class.new.tap do |_client|
23
+ allow(_client).to receive(:__build_connections)
24
+ end
25
+ end
26
+
27
+ it 'has a default transport' do
28
+ expect(client.transport).to be_a(Elastic::Transport::Client::DEFAULT_TRANSPORT_CLASS)
29
+ end
30
+
31
+ it 'preserves the Faraday default user agent header' do
32
+ expect(client.transport.connections.first.connection.headers['User-Agent']).to match(/Faraday/)
33
+ end
34
+
35
+ it 'identifies the Ruby client in the User-Agent header' do
36
+ expect(client.transport.connections.first.connection.headers['User-Agent']).to match(/elastic-transport-ruby\/#{Elastic::Transport::VERSION}/)
37
+ end
38
+
39
+ it 'identifies the Ruby version in the User-Agent header' do
40
+ expect(client.transport.connections.first.connection.headers['User-Agent']).to match(/#{RUBY_VERSION}/)
41
+ end
42
+
43
+ it 'identifies the host_os in the User-Agent header' do
44
+ expect(client.transport.connections.first.connection.headers['User-Agent']).to match(/#{RbConfig::CONFIG['host_os'].split('_').first[/[a-z]+/i].downcase}/)
45
+ end
46
+
47
+ it 'identifies the target_cpu in the User-Agent header' do
48
+ expect(client.transport.connections.first.connection.headers['User-Agent']).to match(/#{RbConfig::CONFIG['target_cpu']}/)
49
+ end
50
+
51
+ it 'sets the \'Content-Type\' header to \'application/json\' by default' do
52
+ expect(client.transport.connections.first.connection.headers['Content-Type']).to eq('application/json')
53
+ end
54
+
55
+ it 'uses localhost by default' do
56
+ expect(client.transport.hosts[0][:host]).to eq('localhost')
57
+ end
58
+
59
+ context 'when a User-Agent header is specified as client option' do
60
+ let(:client) do
61
+ described_class.new(transport_options: { headers: { 'User-Agent' => 'testing' } })
62
+ end
63
+
64
+ it 'sets the specified User-Agent header' do
65
+ expect(client.transport.connections.first.connection.headers['User-Agent']).to eq('testing')
66
+ end
67
+ end
68
+
69
+ context 'when a user-agent header is specified as client option in lower-case' do
70
+
71
+ let(:client) do
72
+ described_class.new(transport_options: { headers: { 'user-agent' => 'testing' } })
73
+ end
74
+
75
+ it 'sets the specified User-Agent header' do
76
+ expect(client.transport.connections.first.connection.headers['User-Agent']).to eq('testing')
77
+ end
78
+ end
79
+
80
+ context 'when a Content-Type header is specified as client option' do
81
+
82
+ let(:client) do
83
+ described_class.new(transport_options: { headers: { 'Content-Type' => 'testing' } })
84
+ end
85
+
86
+ it 'sets the specified Content-Type header' do
87
+ expect(client.transport.connections.first.connection.headers['Content-Type']).to eq('testing')
88
+ end
89
+ end
90
+
91
+ context 'when a content-type header is specified as client option in lower-case' do
92
+
93
+ let(:client) do
94
+ described_class.new(transport_options: { headers: { 'content-type' => 'testing' } })
95
+ end
96
+
97
+ it 'sets the specified Content-Type header' do
98
+ expect(client.transport.connections.first.connection.headers['Content-Type']).to eq('testing')
99
+ end
100
+ end
101
+
102
+ context 'when the Curb transport class is used', unless: jruby? do
103
+
104
+ let(:client) do
105
+ described_class.new(transport_class: Elastic::Transport::Transport::HTTP::Curb)
106
+ end
107
+
108
+ it 'preserves the Curb default user agent header' do
109
+ expect(client.transport.connections.first.connection.headers['User-Agent']).to match(/Curb/)
110
+ end
111
+
112
+ it 'identifies the Ruby client in the User-Agent header' do
113
+ expect(client.transport.connections.first.connection.headers['User-Agent']).to match(/elastic-transport-ruby\/#{Elastic::Transport::VERSION}/)
114
+ end
115
+
116
+ it 'identifies the Ruby version in the User-Agent header' do
117
+ expect(client.transport.connections.first.connection.headers['User-Agent']).to match(/#{RUBY_VERSION}/)
118
+ end
119
+
120
+ it 'identifies the host_os in the User-Agent header' do
121
+ expect(client.transport.connections.first.connection.headers['User-Agent']).to match(/#{RbConfig::CONFIG['host_os'].split('_').first[/[a-z]+/i].downcase}/)
122
+ end
123
+
124
+ it 'identifies the target_cpu in the User-Agent header' do
125
+ expect(client.transport.connections.first.connection.headers['User-Agent']).to match(/#{RbConfig::CONFIG['target_cpu']}/)
126
+ end
127
+
128
+ it 'sets the \'Content-Type\' header to \'application/json\' by default' do
129
+ expect(client.transport.connections.first.connection.headers['Content-Type']).to eq('application/json')
130
+ end
131
+
132
+ it 'uses localhost by default' do
133
+ expect(client.transport.hosts[0][:host]).to eq('localhost')
134
+ end
135
+
136
+ context 'when a User-Agent header is specified as a client option' do
137
+
138
+ let(:client) do
139
+ described_class.new(transport_class: Elastic::Transport::Transport::HTTP::Curb,
140
+ transport_options: { headers: { 'User-Agent' => 'testing' } })
141
+ end
142
+
143
+ it 'sets the specified User-Agent header' do
144
+ expect(client.transport.connections.first.connection.headers['User-Agent']).to eq('testing')
145
+ end
146
+ end
147
+
148
+ context 'when a user-agent header is specified as a client option as lower-case' do
149
+
150
+ let(:client) do
151
+ described_class.new(transport_class: Elastic::Transport::Transport::HTTP::Curb,
152
+ transport_options: { headers: { 'user-agent' => 'testing' } })
153
+ end
154
+
155
+ it 'sets the specified User-Agent header' do
156
+ expect(client.transport.connections.first.connection.headers['User-Agent']).to eq('testing')
157
+ end
158
+ end
159
+
160
+ context 'when a Content-Type header is specified as client option' do
161
+
162
+ let(:client) do
163
+ described_class.new(transport_class: Elastic::Transport::Transport::HTTP::Curb,
164
+ transport_options: { headers: { 'Content-Type' => 'testing' } })
165
+ end
166
+
167
+ it 'sets the specified Content-Type header' do
168
+ expect(client.transport.connections.first.connection.headers['Content-Type']).to eq('testing')
169
+ end
170
+ end
171
+
172
+ context 'when a content-type header is specified as client option in lower-case' do
173
+
174
+ let(:client) do
175
+ described_class.new(transport_class: Elastic::Transport::Transport::HTTP::Curb,
176
+ transport_options: { headers: { 'content-type' => 'testing' } })
177
+ end
178
+
179
+ it 'sets the specified Content-Type header' do
180
+ expect(client.transport.connections.first.connection.headers['Content-Type']).to eq('testing')
181
+ end
182
+ end
183
+ end
184
+
185
+ describe 'adapter' do
186
+ context 'when no adapter is specified' do
187
+ fork do
188
+ let(:client) { described_class.new }
189
+ let(:adapter) { client.transport.connections.all.first.connection.builder.adapter }
190
+
191
+ it 'uses Faraday NetHttp' do
192
+ expect(adapter).to eq Faraday::Adapter::NetHttp
193
+ end
194
+ end unless jruby?
195
+ end
196
+
197
+ context 'when the adapter is patron' do
198
+ let(:adapter) do
199
+ client.transport.connections.all.first.connection.builder.adapter
200
+ end
201
+
202
+ let(:client) do
203
+ described_class.new(adapter: :patron, enable_meta_header: false)
204
+ end
205
+
206
+ it 'uses Faraday with the adapter' do
207
+ expect(adapter).to eq Faraday::Adapter::Patron
208
+ end
209
+ end
210
+
211
+ context 'when the adapter is typhoeus' do
212
+ let(:adapter) do
213
+ client.transport.connections.all.first.connection.builder.adapter
214
+ end
215
+
216
+ let(:client) do
217
+ described_class.new(adapter: :typhoeus, enable_meta_header: false)
218
+ end
219
+
220
+ it 'uses Faraday with the adapter' do
221
+ expect(adapter).to eq Faraday::Adapter::Typhoeus
222
+ end
223
+ end unless jruby?
224
+
225
+ context 'when the adapter is specified as a string key' do
226
+ let(:adapter) do
227
+ client.transport.connections.all.first.connection.builder.adapter
228
+ end
229
+
230
+ let(:client) do
231
+ described_class.new(adapter: :patron, enable_meta_header: false)
232
+ end
233
+
234
+ it 'uses Faraday with the adapter' do
235
+ expect(adapter).to eq Faraday::Adapter::Patron
236
+ end
237
+ end
238
+
239
+ context 'when the adapter can be detected', unless: jruby? do
240
+ around do |example|
241
+ require 'patron'; load 'patron.rb'
242
+ example.run
243
+ end
244
+
245
+ let(:adapter) do
246
+ client.transport.connections.all.first.connection.builder.adapter
247
+ end
248
+
249
+ it 'uses the detected adapter' do
250
+ expect(adapter).to eq Faraday::Adapter::Patron
251
+ end
252
+ end
253
+
254
+ context 'when the Faraday adapter is configured' do
255
+ let(:client) do
256
+ described_class.new do |faraday|
257
+ faraday.adapter :patron
258
+ faraday.response :logger
259
+ end
260
+ end
261
+
262
+ let(:adapter) do
263
+ client.transport.connections.all.first.connection.builder.adapter
264
+ end
265
+
266
+ let(:handlers) do
267
+ client.transport.connections.all.first.connection.builder.handlers
268
+ end
269
+
270
+ it 'sets the adapter' do
271
+ expect(adapter).to eq Faraday::Adapter::Patron
272
+ end
273
+
274
+ it 'sets the logger' do
275
+ expect(handlers).to include(Faraday::Response::Logger)
276
+ end
277
+ end
278
+ end
279
+
280
+ shared_examples_for 'a client that extracts hosts' do
281
+ context 'when the host is a String' do
282
+ context 'when there is a protocol specified' do
283
+ context 'when credentials are specified \'http://USERNAME:PASSWORD@myhost:8080\'' do
284
+ let(:host) do
285
+ 'http://USERNAME:PASSWORD@myhost:8080'
286
+ end
287
+
288
+ it 'extracts the credentials' do
289
+ expect(hosts[0][:user]).to eq('USERNAME')
290
+ expect(hosts[0][:password]).to eq('PASSWORD')
291
+ end
292
+
293
+ it 'extracts the host' do
294
+ expect(hosts[0][:host]).to eq('myhost')
295
+ end
296
+
297
+ it 'extracts the port' do
298
+ expect(hosts[0][:port]).to be(8080)
299
+ end
300
+ end
301
+
302
+ context 'when there is a trailing slash \'http://myhost/\'' do
303
+ let(:host) do
304
+ 'http://myhost/'
305
+ end
306
+
307
+ it 'extracts the host' do
308
+ expect(hosts[0][:host]).to eq('myhost')
309
+ expect(hosts[0][:scheme]).to eq('http')
310
+ expect(hosts[0][:path]).to eq('')
311
+ end
312
+
313
+ it 'extracts the scheme' do
314
+ expect(hosts[0][:scheme]).to eq('http')
315
+ end
316
+
317
+ it 'extracts the path' do
318
+ expect(hosts[0][:path]).to eq('')
319
+ end
320
+ end
321
+
322
+ context 'when there is a trailing slash with a path \'http://myhost/foo/bar/\'' do
323
+ let(:host) do
324
+ 'http://myhost/foo/bar/'
325
+ end
326
+
327
+ it 'extracts the host' do
328
+ expect(hosts[0][:host]).to eq('myhost')
329
+ expect(hosts[0][:scheme]).to eq('http')
330
+ expect(hosts[0][:path]).to eq('/foo/bar')
331
+ end
332
+ end
333
+
334
+ context 'when the protocol is http' do
335
+ context 'when there is no port specified \'http://myhost\'' do
336
+ let(:host) do
337
+ 'http://myhost'
338
+ end
339
+
340
+ it 'extracts the host' do
341
+ expect(hosts[0][:host]).to eq('myhost')
342
+ end
343
+
344
+ it 'extracts the protocol' do
345
+ expect(hosts[0][:protocol]).to eq('http')
346
+ end
347
+
348
+ it 'defaults to port 9200' do
349
+ expect(hosts[0][:port]).to be(9200)
350
+ end
351
+ end
352
+
353
+ context 'when there is a port specified \'http://myhost:7101\'' do
354
+
355
+ let(:host) do
356
+ 'http://myhost:7101'
357
+ end
358
+
359
+ it 'extracts the host' do
360
+ expect(hosts[0][:host]).to eq('myhost')
361
+ end
362
+
363
+ it 'extracts the protocol' do
364
+ expect(hosts[0][:protocol]).to eq('http')
365
+ end
366
+
367
+ it 'extracts the port' do
368
+ expect(hosts[0][:port]).to be(7101)
369
+ end
370
+
371
+ context 'when there is a path specified \'http://myhost:7101/api\'' do
372
+
373
+ let(:host) do
374
+ 'http://myhost:7101/api'
375
+ end
376
+
377
+ it 'sets the path' do
378
+ expect(hosts[0][:host]).to eq('myhost')
379
+ expect(hosts[0][:protocol]).to eq('http')
380
+ expect(hosts[0][:path]).to eq('/api')
381
+ expect(hosts[0][:port]).to be(7101)
382
+ end
383
+
384
+ it 'extracts the host' do
385
+ expect(hosts[0][:host]).to eq('myhost')
386
+ end
387
+
388
+ it 'extracts the protocol' do
389
+ expect(hosts[0][:protocol]).to eq('http')
390
+ end
391
+
392
+ it 'extracts the port' do
393
+ expect(hosts[0][:port]).to be(7101)
394
+ end
395
+
396
+ it 'extracts the path' do
397
+ expect(hosts[0][:path]).to eq('/api')
398
+ end
399
+ end
400
+ end
401
+ end
402
+
403
+ context 'when the protocol is https' do
404
+
405
+ context 'when there is no port specified \'https://myhost\'' do
406
+
407
+ let(:host) do
408
+ 'https://myhost'
409
+ end
410
+
411
+ it 'extracts the host' do
412
+ expect(hosts[0][:host]).to eq('myhost')
413
+ end
414
+
415
+ it 'extracts the protocol' do
416
+ expect(hosts[0][:protocol]).to eq('https')
417
+ end
418
+
419
+ it 'defaults to port 443' do
420
+ expect(hosts[0][:port]).to be(443)
421
+ end
422
+ end
423
+
424
+ context 'when there is a port specified \'https://myhost:7101\'' do
425
+
426
+ let(:host) do
427
+ 'https://myhost:7101'
428
+ end
429
+
430
+ it 'extracts the host' do
431
+ expect(hosts[0][:host]).to eq('myhost')
432
+ end
433
+
434
+ it 'extracts the protocol' do
435
+ expect(hosts[0][:protocol]).to eq('https')
436
+ end
437
+
438
+ it 'extracts the port' do
439
+ expect(hosts[0][:port]).to be(7101)
440
+ end
441
+
442
+ context 'when there is a path specified \'https://myhost:7101/api\'' do
443
+
444
+ let(:host) do
445
+ 'https://myhost:7101/api'
446
+ end
447
+
448
+ it 'extracts the host' do
449
+ expect(hosts[0][:host]).to eq('myhost')
450
+ end
451
+
452
+ it 'extracts the protocol' do
453
+ expect(hosts[0][:protocol]).to eq('https')
454
+ end
455
+
456
+ it 'extracts the port' do
457
+ expect(hosts[0][:port]).to be(7101)
458
+ end
459
+
460
+ it 'extracts the path' do
461
+ expect(hosts[0][:path]).to eq('/api')
462
+ end
463
+ end
464
+ end
465
+
466
+ context 'when IPv6 format is used' do
467
+
468
+ around do |example|
469
+ original_setting = Faraday.ignore_env_proxy
470
+ Faraday.ignore_env_proxy = true
471
+ example.run
472
+ Faraday.ignore_env_proxy = original_setting
473
+ end
474
+
475
+ let(:host) do
476
+ 'https://[2090:db8:85a3:9811::1f]:8080'
477
+ end
478
+
479
+ it 'extracts the host' do
480
+ expect(hosts[0][:host]).to eq('[2090:db8:85a3:9811::1f]')
481
+ end
482
+
483
+ it 'extracts the protocol' do
484
+ expect(hosts[0][:protocol]).to eq('https')
485
+ end
486
+
487
+ it 'extracts the port' do
488
+ expect(hosts[0][:port]).to be(8080)
489
+ end
490
+
491
+ it 'creates the correct full url' do
492
+ expect(client.transport.__full_url(client.transport.hosts[0])).to eq('https://[2090:db8:85a3:9811::1f]:8080')
493
+ end
494
+ end
495
+ end
496
+ end
497
+
498
+ context 'when no protocol is specified \'myhost\'' do
499
+
500
+ let(:host) do
501
+ 'myhost'
502
+ end
503
+
504
+ it 'defaults to http' do
505
+ expect(hosts[0][:host]).to eq('myhost')
506
+ expect(hosts[0][:protocol]).to eq('http')
507
+ end
508
+
509
+ it 'uses port 9200' do
510
+ expect(hosts[0][:port]).to be(9200)
511
+ end
512
+ end
513
+ end
514
+
515
+ context 'when the host is a Hash' do
516
+
517
+ let(:host) do
518
+ { :host => 'myhost', :scheme => 'https' }
519
+ end
520
+
521
+ it 'extracts the host' do
522
+ expect(hosts[0][:host]).to eq('myhost')
523
+ end
524
+
525
+ it 'extracts the protocol' do
526
+ expect(hosts[0][:protocol]).to eq('https')
527
+ end
528
+
529
+ it 'extracts the port' do
530
+ expect(hosts[0][:port]).to be(9200)
531
+ end
532
+
533
+ context 'when IPv6 format is used' do
534
+
535
+ around do |example|
536
+ original_setting = Faraday.ignore_env_proxy
537
+ Faraday.ignore_env_proxy = true
538
+ example.run
539
+ Faraday.ignore_env_proxy = original_setting
540
+ end
541
+
542
+ let(:host) do
543
+ { host: '[2090:db8:85a3:9811::1f]', scheme: 'https', port: '443' }
544
+ end
545
+
546
+ it 'extracts the host' do
547
+ expect(hosts[0][:host]).to eq('[2090:db8:85a3:9811::1f]')
548
+ expect(hosts[0][:scheme]).to eq('https')
549
+ expect(hosts[0][:port]).to be(443)
550
+ end
551
+
552
+ it 'creates the correct full url' do
553
+ expect(client.transport.__full_url(client.transport.hosts[0])).to eq('https://[2090:db8:85a3:9811::1f]:443')
554
+ end
555
+ end
556
+
557
+ context 'when the host is localhost as a IPv6 address' do
558
+
559
+ around do |example|
560
+ original_setting = Faraday.ignore_env_proxy
561
+ Faraday.ignore_env_proxy = true
562
+ example.run
563
+ Faraday.ignore_env_proxy = original_setting
564
+ end
565
+
566
+ let(:host) do
567
+ { host: '[::1]' }
568
+ end
569
+
570
+ it 'extracts the host' do
571
+ expect(hosts[0][:host]).to eq('[::1]')
572
+ expect(hosts[0][:port]).to be(9200)
573
+ end
574
+
575
+ it 'creates the correct full url' do
576
+ expect(client.transport.__full_url(client.transport.hosts[0])).to eq('http://[::1]:9200')
577
+ end
578
+ end
579
+
580
+ context 'when the port is specified as a String' do
581
+
582
+ let(:host) do
583
+ { host: 'myhost', scheme: 'https', port: '443' }
584
+ end
585
+
586
+ it 'extracts the host' do
587
+ expect(hosts[0][:host]).to eq('myhost')
588
+ end
589
+
590
+ it 'extracts the protocol' do
591
+ expect(hosts[0][:scheme]).to eq('https')
592
+ end
593
+
594
+ it 'converts the port to an integer' do
595
+ expect(hosts[0][:port]).to be(443)
596
+ end
597
+ end
598
+
599
+ context 'when the port is specified as an Integer' do
600
+
601
+ let(:host) do
602
+ { host: 'myhost', scheme: 'https', port: 443 }
603
+ end
604
+
605
+ it 'extracts the host' do
606
+ expect(hosts[0][:host]).to eq('myhost')
607
+ end
608
+
609
+ it 'extracts the protocol' do
610
+ expect(hosts[0][:scheme]).to eq('https')
611
+ end
612
+
613
+ it 'extracts port as an integer' do
614
+ expect(hosts[0][:port]).to be(443)
615
+ end
616
+ end
617
+ end
618
+
619
+ context 'when the hosts are a Hashie:Mash' do
620
+
621
+ let(:host) do
622
+ Hashie::Mash.new(host: 'myhost', scheme: 'https')
623
+ end
624
+
625
+ it 'extracts the host' do
626
+ expect(hosts[0][:host]).to eq('myhost')
627
+ end
628
+
629
+ it 'extracts the protocol' do
630
+ expect(hosts[0][:scheme]).to eq('https')
631
+ end
632
+
633
+ it 'converts the port to an integer' do
634
+ expect(hosts[0][:port]).to be(9200)
635
+ end
636
+
637
+ context 'when the port is specified as a String' do
638
+
639
+ let(:host) do
640
+ Hashie::Mash.new(host: 'myhost', scheme: 'https', port: '443')
641
+ end
642
+
643
+ it 'extracts the host' do
644
+ expect(hosts[0][:host]).to eq('myhost')
645
+ end
646
+
647
+ it 'extracts the protocol' do
648
+ expect(hosts[0][:scheme]).to eq('https')
649
+ end
650
+
651
+ it 'converts the port to an integer' do
652
+ expect(hosts[0][:port]).to be(443)
653
+ end
654
+ end
655
+
656
+ context 'when the port is specified as an Integer' do
657
+
658
+ let(:host) do
659
+ Hashie::Mash.new(host: 'myhost', scheme: 'https', port: 443)
660
+ end
661
+
662
+ it 'extracts the host' do
663
+ expect(hosts[0][:host]).to eq('myhost')
664
+ end
665
+
666
+ it 'extracts the protocol' do
667
+ expect(hosts[0][:scheme]).to eq('https')
668
+ end
669
+
670
+ it 'extracts port as an integer' do
671
+ expect(hosts[0][:port]).to be(443)
672
+ end
673
+ end
674
+ end
675
+
676
+ context 'when the hosts are an array' do
677
+
678
+ context 'when there is one host' do
679
+
680
+ let(:host) do
681
+ ['myhost']
682
+ end
683
+
684
+ it 'extracts the host' do
685
+ expect(hosts[0][:host]).to eq('myhost')
686
+ end
687
+
688
+ it 'extracts the protocol' do
689
+ expect(hosts[0][:protocol]).to eq('http')
690
+ end
691
+
692
+ it 'defaults to port 9200' do
693
+ expect(hosts[0][:port]).to be(9200)
694
+ end
695
+ end
696
+
697
+ context 'when there is one host with a protocol and no port' do
698
+
699
+ let(:host) do
700
+ ['http://myhost']
701
+ end
702
+
703
+ it 'extracts the host' do
704
+ expect(hosts[0][:host]).to eq('myhost')
705
+ end
706
+
707
+ it 'extracts the protocol' do
708
+ expect(hosts[0][:scheme]).to eq('http')
709
+ end
710
+
711
+ it 'defaults to port 9200' do
712
+ expect(hosts[0][:port]).to be(9200)
713
+ end
714
+ end
715
+
716
+ context 'when there is one host with a protocol and the default http port explicitly provided' do
717
+ let(:host) do
718
+ ['http://myhost:80']
719
+ end
720
+
721
+ it 'respects the explicit port' do
722
+ expect(hosts[0][:port]).to be(80)
723
+ end
724
+ end
725
+
726
+ context 'when there is one host with a protocol and the default https port explicitly provided' do
727
+ let(:host) do
728
+ ['https://myhost:443']
729
+ end
730
+
731
+ it 'respects the explicit port' do
732
+ expect(hosts[0][:port]).to be(443)
733
+ end
734
+ end
735
+
736
+ context 'when there is one host with a protocol and no port' do
737
+ let(:host) do
738
+ ['https://myhost']
739
+ end
740
+
741
+ it 'extracts the host' do
742
+ expect(hosts[0][:host]).to eq('myhost')
743
+ end
744
+
745
+ it 'extracts the protocol' do
746
+ expect(hosts[0][:scheme]).to eq('https')
747
+ end
748
+
749
+ it 'defaults to port 443' do
750
+ expect(hosts[0][:port]).to be(443)
751
+ end
752
+ end
753
+
754
+ context 'when there is one host with a protocol, path, and no port' do
755
+ let(:host) do
756
+ ['http://myhost/foo/bar']
757
+ end
758
+
759
+ it 'extracts the host' do
760
+ expect(hosts[0][:host]).to eq('myhost')
761
+ end
762
+
763
+ it 'extracts the protocol' do
764
+ expect(hosts[0][:scheme]).to eq('http')
765
+ end
766
+
767
+ it 'defaults to port 9200' do
768
+ expect(hosts[0][:port]).to be(9200)
769
+ end
770
+
771
+ it 'extracts the path' do
772
+ expect(hosts[0][:path]).to eq('/foo/bar')
773
+ end
774
+ end
775
+
776
+ context 'when there is more than one host' do
777
+ let(:host) do
778
+ ['host1', 'host2']
779
+ end
780
+
781
+ it 'extracts the hosts' do
782
+ expect(hosts[0][:host]).to eq('host1')
783
+ expect(hosts[0][:protocol]).to eq('http')
784
+ expect(hosts[0][:port]).to be(9200)
785
+ expect(hosts[1][:host]).to eq('host2')
786
+ expect(hosts[1][:protocol]).to eq('http')
787
+ expect(hosts[1][:port]).to be(9200)
788
+ end
789
+ end
790
+
791
+ context 'when ports are also specified' do
792
+ let(:host) do
793
+ ['host1:1000', 'host2:2000']
794
+ end
795
+
796
+ it 'extracts the hosts' do
797
+ expect(hosts[0][:host]).to eq('host1')
798
+ expect(hosts[0][:protocol]).to eq('http')
799
+ expect(hosts[0][:port]).to be(1000)
800
+ expect(hosts[1][:host]).to eq('host2')
801
+ expect(hosts[1][:protocol]).to eq('http')
802
+ expect(hosts[1][:port]).to be(2000)
803
+ end
804
+ end
805
+ end
806
+
807
+ context 'when the hosts is an instance of URI' do
808
+ let(:host) do
809
+ URI.parse('https://USERNAME:PASSWORD@myhost:4430')
810
+ end
811
+
812
+ it 'extracts the host' do
813
+ expect(hosts[0][:host]).to eq('myhost')
814
+ expect(hosts[0][:scheme]).to eq('https')
815
+ expect(hosts[0][:port]).to be(4430)
816
+ expect(hosts[0][:user]).to eq('USERNAME')
817
+ expect(hosts[0][:password]).to eq('PASSWORD')
818
+ end
819
+ end
820
+
821
+ context 'when the hosts is invalid' do
822
+ let(:host) do
823
+ 123
824
+ end
825
+
826
+ it 'extracts the host' do
827
+ expect {
828
+ hosts
829
+ }.to raise_exception(ArgumentError)
830
+ end
831
+ end
832
+ end
833
+
834
+ context 'when hosts are specified with the \'host\' key' do
835
+ let(:client) do
836
+ described_class.new(host: ['host1', 'host2', 'host3', 'host4'], randomize_hosts: true)
837
+ end
838
+
839
+ let(:hosts) do
840
+ client.transport.hosts
841
+ end
842
+
843
+ it 'sets the hosts in random order' do
844
+ expect(hosts.all? { |host| client.transport.hosts.include?(host) }).to be(true)
845
+ end
846
+ end
847
+
848
+ context 'when hosts are specified with the \'host\' key as a String' do
849
+ let(:client) do
850
+ described_class.new('host' => ['host1', 'host2', 'host3', 'host4'], 'randomize_hosts' => true)
851
+ end
852
+
853
+ let(:hosts) do
854
+ client.transport.hosts
855
+ end
856
+
857
+ it 'sets the hosts in random order' do
858
+ expect(hosts.all? { |host| client.transport.hosts.include?(host) }).to be(true)
859
+ end
860
+ end
861
+
862
+ context 'when hosts are specified with the \'hosts\' key' do
863
+ let(:client) do
864
+ described_class.new(hosts: host)
865
+ end
866
+
867
+ let(:hosts) do
868
+ client.transport.hosts
869
+ end
870
+
871
+ it_behaves_like 'a client that extracts hosts'
872
+ end
873
+
874
+ context 'when hosts are specified with the \'hosts\' key as a String' do
875
+ let(:client) do
876
+ described_class.new('hosts' => host)
877
+ end
878
+
879
+ let(:hosts) do
880
+ client.transport.hosts
881
+ end
882
+
883
+ it_behaves_like 'a client that extracts hosts'
884
+ end
885
+
886
+ context 'when hosts are specified with the \'url\' key' do
887
+ let(:client) do
888
+ described_class.new(url: host)
889
+ end
890
+
891
+ let(:hosts) do
892
+ client.transport.hosts
893
+ end
894
+
895
+ it_behaves_like 'a client that extracts hosts'
896
+ end
897
+
898
+ context 'when hosts are specified with the \'url\' key as a String' do
899
+ let(:client) do
900
+ described_class.new('url' => host)
901
+ end
902
+
903
+ let(:hosts) do
904
+ client.transport.hosts
905
+ end
906
+
907
+ it_behaves_like 'a client that extracts hosts'
908
+ end
909
+
910
+ context 'when hosts are specified with the \'urls\' key' do
911
+ let(:client) do
912
+ described_class.new(urls: host)
913
+ end
914
+
915
+ let(:hosts) do
916
+ client.transport.hosts
917
+ end
918
+
919
+ it_behaves_like 'a client that extracts hosts'
920
+ end
921
+
922
+ context 'when hosts are specified with the \'urls\' key as a String' do
923
+ let(:client) do
924
+ described_class.new('urls' => host)
925
+ end
926
+
927
+ let(:hosts) do
928
+ client.transport.hosts
929
+ end
930
+
931
+ it_behaves_like 'a client that extracts hosts'
932
+ end
933
+
934
+ context 'when the URL is set in the ELASTICSEARCH_URL environment variable' do
935
+ context 'when there is only one host specified' do
936
+ around do |example|
937
+ before_url = ENV['ELASTICSEARCH_URL']
938
+ ENV['ELASTICSEARCH_URL'] = 'example.com'
939
+ example.run
940
+ ENV['ELASTICSEARCH_URL'] = before_url
941
+ end
942
+
943
+ it 'sets the host' do
944
+ expect(client.transport.hosts[0][:host]).to eq('example.com')
945
+ expect(client.transport.hosts.size).to eq(1)
946
+ end
947
+ end
948
+
949
+ context 'when mutliple hosts are specified as a comma-separated String list' do
950
+ around do |example|
951
+ before_url = ENV['ELASTICSEARCH_URL']
952
+ ENV['ELASTICSEARCH_URL'] = 'example.com, other.com'
953
+ example.run
954
+ ENV['ELASTICSEARCH_URL'] = before_url
955
+ end
956
+
957
+ it 'sets the hosts' do
958
+ expect(client.transport.hosts[0][:host]).to eq('example.com')
959
+ expect(client.transport.hosts[1][:host]).to eq('other.com')
960
+ expect(client.transport.hosts.size).to eq(2)
961
+ end
962
+ end
963
+ end
964
+
965
+ context 'when options are defined' do
966
+
967
+ context 'when scheme is specified' do
968
+
969
+ let(:client) do
970
+ described_class.new(scheme: 'https')
971
+ end
972
+
973
+ it 'sets the scheme' do
974
+ expect(client.transport.connections[0].full_url('')).to match(/https/)
975
+ end
976
+ end
977
+
978
+ context 'when scheme is specified as a String key' do
979
+
980
+ let(:client) do
981
+ described_class.new('scheme' => 'https')
982
+ end
983
+
984
+ it 'sets the scheme' do
985
+ expect(client.transport.connections[0].full_url('')).to match(/https/)
986
+ end
987
+ end
988
+
989
+ context 'when user and password are specified' do
990
+
991
+ let(:client) do
992
+ described_class.new(user: 'USERNAME', password: 'PASSWORD')
993
+ end
994
+
995
+ it 'sets the user and password' do
996
+ expect(client.transport.connections[0].full_url('')).to match(/USERNAME/)
997
+ expect(client.transport.connections[0].full_url('')).to match(/PASSWORD/)
998
+ end
999
+
1000
+ context 'when the connections are reloaded' do
1001
+
1002
+ before do
1003
+ allow(client.transport.sniffer).to receive(:hosts).and_return([{ host: 'foobar', port: 4567, id: 'foobar4567' }])
1004
+ client.transport.reload_connections!
1005
+ end
1006
+
1007
+ it 'sets keeps user and password' do
1008
+ expect(client.transport.connections[0].full_url('')).to match(/USERNAME/)
1009
+ expect(client.transport.connections[0].full_url('')).to match(/PASSWORD/)
1010
+ expect(client.transport.connections[0].full_url('')).to match(/foobar/)
1011
+ end
1012
+ end
1013
+ end
1014
+
1015
+ context 'when user and password are specified as String keys' do
1016
+
1017
+ let(:client) do
1018
+ described_class.new('user' => 'USERNAME', 'password' => 'PASSWORD')
1019
+ end
1020
+
1021
+ it 'sets the user and password' do
1022
+ expect(client.transport.connections[0].full_url('')).to match(/USERNAME/)
1023
+ expect(client.transport.connections[0].full_url('')).to match(/PASSWORD/)
1024
+ end
1025
+
1026
+ context 'when the connections are reloaded' do
1027
+
1028
+ before do
1029
+ allow(client.transport.sniffer).to receive(:hosts).and_return([{ host: 'foobar', port: 4567, id: 'foobar4567' }])
1030
+ client.transport.reload_connections!
1031
+ end
1032
+
1033
+ it 'sets keeps user and password' do
1034
+ expect(client.transport.connections[0].full_url('')).to match(/USERNAME/)
1035
+ expect(client.transport.connections[0].full_url('')).to match(/PASSWORD/)
1036
+ expect(client.transport.connections[0].full_url('')).to match(/foobar/)
1037
+ end
1038
+ end
1039
+ end
1040
+
1041
+ context 'when port is specified' do
1042
+
1043
+ let(:client) do
1044
+ described_class.new(host: 'node1', port: 1234)
1045
+ end
1046
+
1047
+ it 'sets the port' do
1048
+ expect(client.transport.connections[0].full_url('')).to match(/1234/)
1049
+ end
1050
+ end
1051
+
1052
+ context 'when the log option is true' do
1053
+
1054
+ let(:client) do
1055
+ described_class.new(log: true)
1056
+ end
1057
+
1058
+ it 'has a default logger for transport' do
1059
+ expect(client.transport.logger.info).to eq(described_class::DEFAULT_LOGGER.call.info)
1060
+ end
1061
+ end
1062
+
1063
+ context 'when the trace option is true' do
1064
+
1065
+ let(:client) do
1066
+ described_class.new(trace: true)
1067
+ end
1068
+
1069
+ it 'has a default logger for transport' do
1070
+ expect(client.transport.tracer.info).to eq(described_class::DEFAULT_TRACER.call.info)
1071
+ end
1072
+ end
1073
+
1074
+ context 'when a custom transport class is specified' do
1075
+
1076
+ let(:transport_class) do
1077
+ Class.new { def initialize(*); end }
1078
+ end
1079
+
1080
+ let(:client) do
1081
+ described_class.new(transport_class: transport_class)
1082
+ end
1083
+
1084
+ it 'allows the custom transport class to be defined' do
1085
+ expect(client.transport).to be_a(transport_class)
1086
+ end
1087
+ end
1088
+
1089
+ context 'when a custom transport instance is specified' do
1090
+
1091
+ let(:transport_instance) do
1092
+ Class.new { def initialize(*); end }.new
1093
+ end
1094
+
1095
+ let(:client) do
1096
+ described_class.new(transport: transport_instance)
1097
+ end
1098
+
1099
+ it 'allows the custom transport class to be defined' do
1100
+ expect(client.transport).to be(transport_instance)
1101
+ end
1102
+ end
1103
+
1104
+ context 'when \'transport_options\' are defined' do
1105
+
1106
+ let(:client) do
1107
+ described_class.new(transport_options: { request: { timeout: 1 } })
1108
+ end
1109
+
1110
+ it 'sets the options on the transport' do
1111
+ expect(client.transport.options[:transport_options][:request]).to eq(timeout: 1)
1112
+ end
1113
+ end
1114
+
1115
+ context 'when \'request_timeout\' is defined' do
1116
+
1117
+ let(:client) do
1118
+ described_class.new(request_timeout: 120)
1119
+ end
1120
+
1121
+ it 'sets the options on the transport' do
1122
+ expect(client.transport.options[:transport_options][:request]).to eq(timeout: 120)
1123
+ end
1124
+ end
1125
+
1126
+ context 'when \'request_timeout\' is defined as a String key' do
1127
+
1128
+ let(:client) do
1129
+ described_class.new('request_timeout' => 120)
1130
+ end
1131
+
1132
+ it 'sets the options on the transport' do
1133
+ expect(client.transport.options[:transport_options][:request]).to eq(timeout: 120)
1134
+ end
1135
+ end
1136
+ end
1137
+
1138
+ describe '#perform_request' do
1139
+
1140
+ let(:transport_instance) do
1141
+ Class.new { def initialize(*); end }.new
1142
+ end
1143
+
1144
+ let(:client) do
1145
+ described_class.new(transport: transport_instance)
1146
+ end
1147
+
1148
+ it 'delegates performing requests to the transport' do
1149
+ expect(transport_instance).to receive(:perform_request).and_return(true)
1150
+ expect(client.perform_request('GET', '/')).to be(true)
1151
+ end
1152
+
1153
+ context 'when the \'send_get_body_as\' option is specified' do
1154
+
1155
+ let(:client) do
1156
+ described_class.new(transport: transport_instance, :send_get_body_as => 'POST')
1157
+ end
1158
+
1159
+ before do
1160
+ expect(transport_instance).to receive(:perform_request).with('POST', '/', {},
1161
+ '{"foo":"bar"}',
1162
+ '{"Content-Type":"application/x-ndjson"}').and_return(true)
1163
+ end
1164
+
1165
+ let(:request) do
1166
+ client.perform_request('POST', '/', {}, '{"foo":"bar"}', '{"Content-Type":"application/x-ndjson"}')
1167
+ end
1168
+
1169
+ it 'sets the option' do
1170
+ expect(request).to be(true)
1171
+ end
1172
+ end
1173
+
1174
+ context 'when Elasticsearch response includes a warning header' do
1175
+ let(:client) do
1176
+ Elastic::Transport::Client.new(hosts: hosts)
1177
+ end
1178
+
1179
+ let(:warning) { 'Elasticsearch warning: "deprecation warning"' }
1180
+
1181
+ it 'prints a warning' do
1182
+ allow_any_instance_of(Elastic::Transport::Transport::Response).to receive(:headers) do
1183
+ { 'warning' => warning }
1184
+ end
1185
+
1186
+ begin
1187
+ stderr = $stderr
1188
+ fake_stderr = StringIO.new
1189
+ $stderr = fake_stderr
1190
+
1191
+ client.perform_request('GET', '/')
1192
+ fake_stderr.rewind
1193
+ expect(fake_stderr.string).to eq("warning: #{warning}\n")
1194
+ ensure
1195
+ $stderr = stderr
1196
+ end
1197
+ end
1198
+ end
1199
+ end
1200
+
1201
+ context 'when the client connects to Elasticsearch' do
1202
+ let(:logger) do
1203
+ Logger.new(STDERR).tap do |logger|
1204
+ logger.formatter = proc do |severity, datetime, progname, msg|
1205
+ color = case severity
1206
+ when /INFO/ then :green
1207
+ when /ERROR|WARN|FATAL/ then :red
1208
+ when /DEBUG/ then :cyan
1209
+ else :white
1210
+ end
1211
+ ANSI.ansi(severity[0] + ' ', color, :faint) + ANSI.ansi(msg, :white, :faint) + "\n"
1212
+ end
1213
+ end unless ENV['QUIET']
1214
+ end
1215
+
1216
+ let(:port) do
1217
+ TEST_PORT
1218
+ end
1219
+
1220
+ let(:transport_options) do
1221
+ {}
1222
+ end
1223
+
1224
+ let(:options) do
1225
+ {}
1226
+ end
1227
+
1228
+ let(:client) do
1229
+ described_class.new({ host: hosts, logger: logger }.merge!(transport_options: transport_options).merge!(options))
1230
+ end
1231
+
1232
+ context 'when a request is made' do
1233
+ let!(:response) do
1234
+ client.perform_request('GET', '_cluster/health')
1235
+ end
1236
+
1237
+ it 'connects to the cluster' do
1238
+ expect(response.body['number_of_nodes']).to be >= (1)
1239
+ end
1240
+ end
1241
+
1242
+ describe '#initialize' do
1243
+ context 'when options are specified' do
1244
+ let(:transport_options) do
1245
+ { headers: { accept: 'application/yaml', content_type: 'application/yaml' } }
1246
+ end
1247
+
1248
+ let(:response) do
1249
+ client.perform_request('GET', '_cluster/health')
1250
+ end
1251
+
1252
+ it 'applies the options to the client' do
1253
+ expect(response.body).to match(/---\n/)
1254
+ expect(response.headers['content-type']).to eq('application/yaml')
1255
+ end
1256
+ end
1257
+
1258
+ context 'when a block is provided' do
1259
+ let(:client) do
1260
+ Elastic::Transport::Client.new(host: ELASTICSEARCH_HOSTS.first, logger: logger) do |client|
1261
+ client.headers['Accept'] = 'application/yaml'
1262
+ end
1263
+ end
1264
+
1265
+ let(:response) do
1266
+ client.perform_request('GET', '_cluster/health')
1267
+ end
1268
+
1269
+ it 'executes the block' do
1270
+ expect(response.body).to match(/---\n/)
1271
+ expect(response.headers['content-type']).to eq('application/yaml')
1272
+ end
1273
+
1274
+ context 'when the Faraday adapter is set in the block' do
1275
+ let(:client) do
1276
+ Elastic::Transport::Client.new(host: ELASTICSEARCH_HOSTS.first, logger: logger) do |client|
1277
+ client.adapter(:net_http_persistent)
1278
+ end
1279
+ end
1280
+
1281
+ let(:handler_name) do
1282
+ client.transport.connections.first.connection.builder.adapter.name
1283
+ end
1284
+
1285
+ let(:response) do
1286
+ client.perform_request('GET', '_cluster/health')
1287
+ end
1288
+
1289
+ it 'sets the adapter' do
1290
+ expect(handler_name).to eq('Faraday::Adapter::NetHttpPersistent')
1291
+ end
1292
+
1293
+ it 'uses the adapter to connect' do
1294
+ expect(response.status).to eq(200)
1295
+ end
1296
+ end
1297
+ end
1298
+ end
1299
+
1300
+ describe '#options' do
1301
+ context 'when retry_on_failure is true' do
1302
+ context 'when a node is unreachable' do
1303
+ let(:hosts) do
1304
+ [ELASTICSEARCH_HOSTS.first, "foobar1", "foobar2"]
1305
+ end
1306
+
1307
+ let(:options) do
1308
+ { retry_on_failure: true }
1309
+ end
1310
+
1311
+ let(:responses) do
1312
+ 5.times.collect do
1313
+ client.perform_request('GET', '_nodes/_local')
1314
+ end
1315
+ end
1316
+
1317
+ it 'retries on failure' do
1318
+ expect(responses.all? { true }).to be(true)
1319
+ end
1320
+ end
1321
+ end
1322
+
1323
+ context 'when retry_on_failure is an integer' do
1324
+ let(:hosts) do
1325
+ [ELASTICSEARCH_HOSTS.first, 'foobar1', 'foobar2', 'foobar3']
1326
+ end
1327
+
1328
+ let(:options) do
1329
+ { retry_on_failure: 1 }
1330
+ end
1331
+
1332
+ it 'retries only the specified number of times' do
1333
+ expect(client.perform_request('GET', '_nodes/_local'))
1334
+ expect {
1335
+ client.perform_request('GET', '_nodes/_local')
1336
+ }.to raise_exception(Faraday::ConnectionFailed)
1337
+ end
1338
+ end
1339
+
1340
+ context 'when reload_on_failure is true' do
1341
+ let(:hosts) do
1342
+ [ELASTICSEARCH_HOSTS.first, 'foobar1', 'foobar2']
1343
+ end
1344
+
1345
+ let(:options) do
1346
+ { reload_on_failure: true }
1347
+ end
1348
+
1349
+ let(:responses) do
1350
+ 5.times.collect do
1351
+ client.perform_request('GET', '_nodes/_local')
1352
+ end
1353
+ end
1354
+
1355
+ it 'reloads the connections' do
1356
+ expect(client.transport.connections.size).to eq(3)
1357
+ expect(responses.all? { true }).to be(true)
1358
+ expect(client.transport.connections.size).to be >= (1)
1359
+ end
1360
+ end
1361
+
1362
+ context 'when retry_on_status is specified' do
1363
+ let(:options) do
1364
+ { retry_on_status: 400 }
1365
+ end
1366
+
1367
+ let(:logger) do
1368
+ double('logger', :debug? => false, :warn? => true, :fatal? => false, :error? => false)
1369
+ end
1370
+
1371
+ before do
1372
+ expect(logger).to receive(:warn).exactly(4).times
1373
+ end
1374
+
1375
+ it 'retries when the status matches' do
1376
+ expect {
1377
+ client.perform_request('PUT', '_foobar')
1378
+ }.to raise_exception(Elastic::Transport::Transport::Errors::BadRequest)
1379
+ end
1380
+ end
1381
+
1382
+ context 'when the \'compression\' option is set to true' do
1383
+ context 'when using Faraday as the transport' do
1384
+ context 'when using the Net::HTTP adapter' do
1385
+ let(:client) do
1386
+ described_class.new(hosts: ELASTICSEARCH_HOSTS, compression: true, adapter: :net_http)
1387
+ end
1388
+
1389
+ it 'compresses the request and decompresses the response' do
1390
+ expect(client.perform_request('GET', '/').body).to be_a(Hash)
1391
+ end
1392
+
1393
+ it 'sets the Accept-Encoding header' do
1394
+ expect(client.transport.connections[0].connection.headers['Accept-Encoding'])
1395
+ end
1396
+
1397
+ it 'preserves the other headers' do
1398
+ expect(client.transport.connections[0].connection.headers['User-Agent'])
1399
+ end
1400
+ end
1401
+
1402
+ context 'when using the HTTPClient adapter' do
1403
+ let(:client) do
1404
+ described_class.new(hosts: ELASTICSEARCH_HOSTS, compression: true, adapter: :httpclient, enable_meta_header: false)
1405
+ end
1406
+
1407
+ it 'compresses the request and decompresses the response' do
1408
+ expect(client.perform_request('GET', '/').body).to be_a(Hash)
1409
+ end
1410
+
1411
+ it 'sets the Accept-Encoding header' do
1412
+ expect(client.transport.connections[0].connection.headers['Accept-Encoding'])
1413
+ end
1414
+
1415
+ it 'preserves the other headers' do
1416
+ expect(client.transport.connections[0].connection.headers['User-Agent'])
1417
+ end
1418
+ end
1419
+
1420
+ context 'when using the Patron adapter', unless: jruby? do
1421
+ let(:client) do
1422
+ described_class.new(hosts: ELASTICSEARCH_HOSTS, compression: true, adapter: :patron)
1423
+ end
1424
+
1425
+ it 'compresses the request and decompresses the response' do
1426
+ expect(client.perform_request('GET', '/').body).to be_a(Hash)
1427
+ end
1428
+
1429
+ it 'sets the Accept-Encoding header' do
1430
+ expect(client.transport.connections[0].connection.headers['Accept-Encoding'])
1431
+ end
1432
+
1433
+ it 'preserves the other headers' do
1434
+ expect(client.transport.connections[0].connection.headers['User-Agent'])
1435
+ end
1436
+ end
1437
+
1438
+ context 'when using the Net::HTTP::Persistent adapter' do
1439
+ let(:client) do
1440
+ described_class.new(hosts: ELASTICSEARCH_HOSTS, compression: true, adapter: :net_http_persistent)
1441
+ end
1442
+
1443
+ it 'compresses the request and decompresses the response' do
1444
+ expect(client.perform_request('GET', '/').body).to be_a(Hash)
1445
+ end
1446
+
1447
+ it 'sets the Accept-Encoding header' do
1448
+ expect(client.transport.connections[0].connection.headers['Accept-Encoding'])
1449
+ end
1450
+
1451
+ it 'preserves the other headers' do
1452
+ expect(client.transport.connections[0].connection.headers['User-Agent'])
1453
+ end
1454
+ end
1455
+
1456
+ context 'when using the Typhoeus adapter' do
1457
+ let(:client) do
1458
+ described_class.new(hosts: ELASTICSEARCH_HOSTS, compression: true, adapter: :typhoeus)
1459
+ end
1460
+
1461
+ it 'compresses the request and decompresses the response' do
1462
+ expect(client.perform_request('GET', '/').body).to be_a(Hash)
1463
+ end
1464
+
1465
+ it 'sets the Accept-Encoding header' do
1466
+ expect(client.transport.connections[0].connection.headers['Accept-Encoding'])
1467
+ end
1468
+
1469
+ it 'preserves the other headers' do
1470
+ expect(client.transport.connections[0].connection.headers['User-Agent'])
1471
+ end
1472
+ end unless jruby?
1473
+ end
1474
+ end
1475
+
1476
+ context 'when using Curb as the transport', unless: jruby? do
1477
+ let(:client) do
1478
+ described_class.new(hosts: ELASTICSEARCH_HOSTS,
1479
+ compression: true,
1480
+ transport_class: Elastic::Transport::Transport::HTTP::Curb)
1481
+ end
1482
+
1483
+ it 'compresses the request and decompresses the response' do
1484
+ expect(client.perform_request('GET', '/').body).to be_a(Hash)
1485
+ end
1486
+
1487
+ it 'sets the Accept-Encoding header' do
1488
+ expect(client.transport.connections[0].connection.headers['Accept-Encoding'])
1489
+ end
1490
+
1491
+ it 'preserves the other headers' do
1492
+ expect(client.transport.connections[0].connection.headers['User-Agent'])
1493
+ end
1494
+ end
1495
+
1496
+ context 'when using Manticore as the transport', if: jruby? do
1497
+ let(:client) do
1498
+ described_class.new(hosts: ELASTICSEARCH_HOSTS,
1499
+ compression: true,
1500
+ transport_class: Elastic::Transport::Transport::HTTP::Manticore)
1501
+ end
1502
+
1503
+ it 'compresses the request and decompresses the response' do
1504
+ expect(client.perform_request('GET', '/').body).to be_a(Hash)
1505
+ end
1506
+ end
1507
+ end
1508
+
1509
+ describe '#perform_request' do
1510
+ context 'when a request is made' do
1511
+ before do
1512
+ client.perform_request('DELETE', '_all')
1513
+ client.perform_request('DELETE', 'myindex') rescue
1514
+ client.perform_request('PUT', 'myindex', {}, { settings: { number_of_shards: 2, number_of_replicas: 0 } })
1515
+ client.perform_request('PUT', 'myindex/_doc/1', { routing: 'XYZ', timeout: '1s' }, { foo: 'bar' })
1516
+ client.perform_request('GET', '_cluster/health?wait_for_status=green&timeout=2s', {})
1517
+ end
1518
+
1519
+ let(:response) do
1520
+ client.perform_request('GET', 'myindex/_doc/1?routing=XYZ')
1521
+ end
1522
+
1523
+ it 'handles paths and URL paramters' do
1524
+ expect(response.status).to eq(200)
1525
+ end
1526
+
1527
+ it 'returns response body' do
1528
+ expect(response.body['_source']).to eq('foo' => 'bar')
1529
+ end
1530
+ end
1531
+
1532
+ context 'when an invalid url is specified' do
1533
+ it 'raises an exception' do
1534
+ expect {
1535
+ client.perform_request('GET', 'myindex/_doc/1?routing=FOOBARBAZ')
1536
+ }.to raise_exception(Elastic::Transport::Transport::Errors::NotFound)
1537
+ end
1538
+ end
1539
+
1540
+ context 'when the \'ignore\' parameter is specified' do
1541
+
1542
+ let(:response) do
1543
+ client.perform_request('PUT', '_foobar', ignore: 400)
1544
+ end
1545
+
1546
+ it 'exposes the status in the response' do
1547
+ expect(response.status).to eq(400)
1548
+ end
1549
+
1550
+ it 'exposes the body of the response' do
1551
+ expect(response.body).to be_a(Hash)
1552
+ expect(response.body.inspect).to match(/invalid_index_name_exception/)
1553
+ end
1554
+ end
1555
+
1556
+ context 'when request headers are specified' do
1557
+
1558
+ let(:response) do
1559
+ client.perform_request('GET', '/', {}, nil, { 'Content-Type' => 'application/yaml' })
1560
+ end
1561
+
1562
+ it 'passes them to the transport' do
1563
+ expect(response.body).to match(/---/)
1564
+ end
1565
+ end
1566
+
1567
+ describe 'selector' do
1568
+
1569
+ context 'when the round-robin selector is used' do
1570
+
1571
+ let(:nodes) do
1572
+ 3.times.collect do
1573
+ client.perform_request('GET', '_nodes/_local').body['nodes'].to_a[0][1]['name']
1574
+ end
1575
+ end
1576
+
1577
+ let(:node_names) do
1578
+ client.nodes.stats['nodes'].collect do |name, stats|
1579
+ stats['name']
1580
+ end
1581
+ end
1582
+
1583
+ let(:expected_names) do
1584
+ 3.times.collect do |i|
1585
+ node_names[i % node_names.size]
1586
+ end
1587
+ end
1588
+
1589
+ # it 'rotates nodes' do
1590
+ # pending 'Better way to detect rotating nodes'
1591
+ # expect(nodes).to eq(expected_names)
1592
+ # end
1593
+ end
1594
+ end
1595
+
1596
+ context 'when patron is used as an adapter', unless: jruby? do
1597
+ before do
1598
+ require 'patron'
1599
+ end
1600
+
1601
+ let(:options) do
1602
+ { adapter: :patron }
1603
+ end
1604
+
1605
+ let(:adapter) do
1606
+ client.transport.connections.first.connection.builder.adapter
1607
+ end
1608
+
1609
+ it 'uses the patron connection handler' do
1610
+ expect(adapter).to eq('Faraday::Adapter::Patron')
1611
+ end
1612
+
1613
+ it 'keeps connections open' do
1614
+ response = client.perform_request('GET', '_nodes/stats/http')
1615
+ connections_before = response.body['nodes'].values.find { |n| n['name'] == node_names.first }['http']['total_opened']
1616
+ client.transport.reload_connections!
1617
+ response = client.perform_request('GET', '_nodes/stats/http')
1618
+ connections_after = response.body['nodes'].values.find { |n| n['name'] == node_names.first }['http']['total_opened']
1619
+ expect(connections_after).to be >= (connections_before)
1620
+ end
1621
+ end
1622
+
1623
+ context 'when typhoeus is used as an adapter', unless: jruby? do
1624
+ before do
1625
+ require 'typhoeus'
1626
+ end
1627
+
1628
+ let(:options) do
1629
+ { adapter: :typhoeus }
1630
+ end
1631
+
1632
+ let(:adapter) do
1633
+ client.transport.connections.first.connection.builder.adapter
1634
+ end
1635
+
1636
+ it 'uses the patron connection handler' do
1637
+ expect(adapter).to eq('Faraday::Adapter::Typhoeus')
1638
+ end
1639
+
1640
+ it 'keeps connections open' do
1641
+ response = client.perform_request('GET', '_nodes/stats/http')
1642
+ connections_before = response.body['nodes'].values.find { |n| n['name'] == node_names.first }['http']['total_opened']
1643
+ client.transport.reload_connections!
1644
+ response = client.perform_request('GET', '_nodes/stats/http')
1645
+ connections_after = response.body['nodes'].values.find { |n| n['name'] == node_names.first }['http']['total_opened']
1646
+ expect(connections_after).to be >= (connections_before)
1647
+ end
1648
+ end
1649
+ end
1650
+ end
1651
+ end