elasticsearch-transport 7.5.0 → 7.17.10

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +30 -13
  3. data/Gemfile-faraday1.gemfile +47 -0
  4. data/README.md +159 -64
  5. data/Rakefile +63 -13
  6. data/elasticsearch-transport.gemspec +55 -63
  7. data/lib/elasticsearch/transport/client.rb +183 -58
  8. data/lib/elasticsearch/transport/meta_header.rb +135 -0
  9. data/lib/elasticsearch/transport/redacted.rb +16 -3
  10. data/lib/elasticsearch/transport/transport/base.rb +69 -30
  11. data/lib/elasticsearch/transport/transport/connections/collection.rb +18 -8
  12. data/lib/elasticsearch/transport/transport/connections/connection.rb +25 -9
  13. data/lib/elasticsearch/transport/transport/connections/selector.rb +16 -3
  14. data/lib/elasticsearch/transport/transport/errors.rb +17 -3
  15. data/lib/elasticsearch/transport/transport/http/curb.rb +60 -35
  16. data/lib/elasticsearch/transport/transport/http/faraday.rb +32 -9
  17. data/lib/elasticsearch/transport/transport/http/manticore.rb +57 -32
  18. data/lib/elasticsearch/transport/transport/loggable.rb +16 -3
  19. data/lib/elasticsearch/transport/transport/response.rb +17 -5
  20. data/lib/elasticsearch/transport/transport/serializer/multi_json.rb +16 -3
  21. data/lib/elasticsearch/transport/transport/sniffer.rb +35 -15
  22. data/lib/elasticsearch/transport/version.rb +17 -4
  23. data/lib/elasticsearch/transport.rb +35 -33
  24. data/lib/elasticsearch-transport.rb +16 -3
  25. data/spec/elasticsearch/connections/collection_spec.rb +28 -3
  26. data/spec/elasticsearch/connections/selector_spec.rb +16 -3
  27. data/spec/elasticsearch/transport/base_spec.rb +106 -43
  28. data/spec/elasticsearch/transport/client_spec.rb +734 -164
  29. data/spec/elasticsearch/transport/http/curb_spec.rb +126 -0
  30. data/spec/elasticsearch/transport/http/faraday_spec.rb +141 -0
  31. data/spec/elasticsearch/transport/http/manticore_spec.rb +161 -0
  32. data/spec/elasticsearch/transport/meta_header_spec.rb +301 -0
  33. data/spec/elasticsearch/transport/sniffer_spec.rb +16 -16
  34. data/spec/spec_helper.rb +32 -6
  35. data/test/integration/jruby_test.rb +43 -0
  36. data/test/integration/transport_test.rb +109 -46
  37. data/test/profile/client_benchmark_test.rb +16 -3
  38. data/test/test_helper.rb +26 -25
  39. data/test/unit/adapters_test.rb +88 -0
  40. data/test/unit/connection_test.rb +23 -5
  41. data/test/unit/response_test.rb +18 -5
  42. data/test/unit/serializer_test.rb +16 -3
  43. data/test/unit/transport_base_test.rb +33 -11
  44. data/test/unit/transport_curb_test.rb +16 -4
  45. data/test/unit/transport_faraday_test.rb +18 -5
  46. data/test/unit/transport_manticore_test.rb +258 -158
  47. metadata +65 -89
@@ -1,21 +1,29 @@
1
- # Licensed to Elasticsearch B.V under one or more agreements.
2
- # Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3
- # See the LICENSE file in the project root for more information
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.
4
17
 
5
18
  require 'spec_helper'
6
19
 
7
20
  describe Elasticsearch::Transport::Client do
8
-
9
21
  let(:client) do
10
22
  described_class.new.tap do |_client|
11
23
  allow(_client).to receive(:__build_connections)
12
24
  end
13
25
  end
14
26
 
15
- it 'is aliased as Elasticsearch::Client' do
16
- expect(Elasticsearch::Client.new).to be_a(described_class)
17
- end
18
-
19
27
  it 'has a default transport' do
20
28
  expect(client.transport).to be_a(Elasticsearch::Transport::Client::DEFAULT_TRANSPORT_CLASS)
21
29
  end
@@ -49,7 +57,6 @@ describe Elasticsearch::Transport::Client do
49
57
  end
50
58
 
51
59
  context 'when a User-Agent header is specified as client option' do
52
-
53
60
  let(:client) do
54
61
  described_class.new(transport_options: { headers: { 'User-Agent' => 'testing' } })
55
62
  end
@@ -59,6 +66,49 @@ describe Elasticsearch::Transport::Client do
59
66
  end
60
67
  end
61
68
 
69
+ context 'when an encoded api_key is provided' do
70
+ let(:client) do
71
+ described_class.new(api_key: 'an_api_key')
72
+ end
73
+ let(:authorization_header) do
74
+ client.transport.connections.first.connection.headers['Authorization']
75
+ end
76
+
77
+ it 'Adds the ApiKey header to the connection' do
78
+ expect(authorization_header).to eq('ApiKey an_api_key')
79
+ end
80
+ end
81
+
82
+ context 'when an un-encoded api_key is provided' do
83
+ let(:client) do
84
+ described_class.new(api_key: { id: 'my_id', api_key: 'my_api_key' })
85
+ end
86
+ let(:authorization_header) do
87
+ client.transport.connections.first.connection.headers['Authorization']
88
+ end
89
+
90
+ it 'Adds the ApiKey header to the connection' do
91
+ expect(authorization_header).to eq("ApiKey #{Base64.strict_encode64('my_id:my_api_key')}")
92
+ end
93
+ end
94
+
95
+ context 'when basic auth and api_key are provided' do
96
+ let(:client) do
97
+ described_class.new(
98
+ api_key: { id: 'my_id', api_key: 'my_api_key' },
99
+ host: 'http://elastic:password@localhost:9200'
100
+ )
101
+ end
102
+ let(:authorization_header) do
103
+ client.transport.connections.first.connection.headers['Authorization']
104
+ end
105
+
106
+ it 'removes basic auth credentials' do
107
+ expect(authorization_header).not_to match(/^Basic/)
108
+ expect(authorization_header).to match(/^ApiKey/)
109
+ end
110
+ end
111
+
62
112
  context 'when a user-agent header is specified as client option in lower-case' do
63
113
 
64
114
  let(:client) do
@@ -176,47 +226,61 @@ describe Elasticsearch::Transport::Client do
176
226
  end
177
227
 
178
228
  describe 'adapter' do
179
-
180
229
  context 'when no adapter is specified' do
230
+ fork do
231
+ let(:client) { described_class.new }
232
+ let(:adapter) { client.transport.connections.all.first.connection.builder.adapter }
181
233
 
234
+ it 'uses Faraday NetHttp' do
235
+ expect(adapter).to eq Faraday::Adapter::NetHttp
236
+ end
237
+ end
238
+ end unless jruby?
239
+
240
+ context 'when the adapter is patron' do
182
241
  let(:adapter) do
183
- client.transport.connections.all.first.connection.builder.handlers
242
+ client.transport.connections.all.first.connection.builder.adapter
184
243
  end
185
244
 
186
- it 'uses Faraday NetHttp' do
187
- expect(adapter).to include(Faraday::Adapter::NetHttp)
245
+ let(:client) do
246
+ described_class.new(adapter: :patron, enable_meta_header: false)
188
247
  end
189
- end
190
248
 
191
- context 'when the adapter is specified' do
249
+ it 'uses Faraday with the adapter' do
250
+ require 'faraday/patron'
251
+ expect(adapter).to eq Faraday::Adapter::Patron
252
+ end
253
+ end unless jruby?
192
254
 
255
+ context 'when the adapter is typhoeus' do
193
256
  let(:adapter) do
194
- client.transport.connections.all.first.connection.builder.handlers
257
+ client.transport.connections.all.first.connection.builder.adapter
195
258
  end
196
259
 
197
260
  let(:client) do
198
- described_class.new(adapter: :typhoeus)
261
+ require 'faraday/typhoeus' if is_faraday_v2?
262
+
263
+ described_class.new(adapter: :typhoeus, enable_meta_header: false)
199
264
  end
200
265
 
201
266
  it 'uses Faraday with the adapter' do
202
- expect(adapter).to include(Faraday::Adapter::Typhoeus)
267
+ expect(adapter).to eq Faraday::Adapter::Typhoeus
203
268
  end
204
- end
269
+ end unless jruby?
205
270
 
206
271
  context 'when the adapter is specified as a string key' do
207
-
208
272
  let(:adapter) do
209
- client.transport.connections.all.first.connection.builder.handlers
273
+ client.transport.connections.all.first.connection.builder.adapter
210
274
  end
211
275
 
212
276
  let(:client) do
213
- described_class.new('adapter' => :typhoeus)
277
+ described_class.new(adapter: :patron, enable_meta_header: false)
214
278
  end
215
279
 
216
280
  it 'uses Faraday with the adapter' do
217
- expect(adapter).to include(Faraday::Adapter::Typhoeus)
281
+ expect(adapter).to eq Faraday::Adapter::Patron
218
282
  end
219
- end
283
+ end unless jruby?
220
284
 
221
285
  context 'when the adapter can be detected', unless: jruby? do
222
286
 
@@ -226,11 +290,11 @@ describe Elasticsearch::Transport::Client do
226
290
  end
227
291
 
228
292
  let(:adapter) do
229
- client.transport.connections.all.first.connection.builder.handlers
293
+ client.transport.connections.all.first.connection.builder.adapter
230
294
  end
231
295
 
232
296
  it 'uses the detected adapter' do
233
- expect(adapter).to include(Faraday::Adapter::Patron)
297
+ expect(adapter).to eq Faraday::Adapter::Patron
234
298
  end
235
299
  end
236
300
 
@@ -238,29 +302,37 @@ describe Elasticsearch::Transport::Client do
238
302
 
239
303
  let(:client) do
240
304
  described_class.new do |faraday|
241
- faraday.adapter :typhoeus
305
+ faraday.adapter :patron
242
306
  faraday.response :logger
243
307
  end
244
308
  end
245
309
 
310
+ let(:adapter) do
311
+ client.transport.connections.all.first.connection.builder.adapter
312
+ end
313
+
246
314
  let(:handlers) do
247
315
  client.transport.connections.all.first.connection.builder.handlers
248
316
  end
249
317
 
250
318
  it 'sets the adapter' do
251
- expect(handlers).to include(Faraday::Adapter::Typhoeus)
319
+ expect(adapter).to eq Faraday::Adapter::Patron
252
320
  end
253
321
 
254
322
  it 'sets the logger' do
255
323
  expect(handlers).to include(Faraday::Response::Logger)
256
324
  end
257
- end
325
+ end unless jruby?
258
326
  end
259
327
 
260
328
  context 'when cloud credentials are provided' do
261
329
 
262
330
  let(:client) do
263
- described_class.new(cloud_id: 'name:bG9jYWxob3N0JGFiY2QkZWZnaA==', user: 'elastic', password: 'changeme')
331
+ described_class.new(
332
+ cloud_id: 'name:bG9jYWxob3N0JGFiY2QkZWZnaA==',
333
+ user: 'elastic',
334
+ password: 'changeme'
335
+ )
264
336
  end
265
337
 
266
338
  let(:hosts) do
@@ -272,17 +344,19 @@ describe Elasticsearch::Transport::Client do
272
344
  expect(hosts[0][:protocol]).to eq('https')
273
345
  expect(hosts[0][:user]).to eq('elastic')
274
346
  expect(hosts[0][:password]).to eq('changeme')
275
- expect(hosts[0][:port]).to eq(9243)
347
+ expect(hosts[0][:port]).to eq(443)
276
348
  end
277
349
 
278
350
  it 'creates the correct full url' do
279
- expect(client.transport.__full_url(client.transport.hosts[0])).to eq('https://elastic:changeme@abcd.localhost:9243')
351
+ expect(
352
+ client.transport.__full_url(client.transport.hosts[0])
353
+ ).to eq('https://elastic:changeme@abcd.localhost:443')
280
354
  end
281
355
 
282
356
  context 'when a port is specified' do
283
357
 
284
358
  let(:client) do
285
- described_class.new(cloud_id: 'name:bG9jYWxob3N0JGFiY2QkZWZnaA==', user: 'elastic', password: 'changeme', port: 9200 )
359
+ described_class.new(cloud_id: 'name:bG9jYWxob3N0JGFiY2QkZWZnaA==', user: 'elastic', password: 'changeme', port: 9250)
286
360
  end
287
361
 
288
362
  it 'sets the specified port along with the cloud credentials' do
@@ -290,18 +364,22 @@ describe Elasticsearch::Transport::Client do
290
364
  expect(hosts[0][:protocol]).to eq('https')
291
365
  expect(hosts[0][:user]).to eq('elastic')
292
366
  expect(hosts[0][:password]).to eq('changeme')
293
- expect(hosts[0][:port]).to eq(9200)
367
+ expect(hosts[0][:port]).to eq(9250)
294
368
  end
295
369
 
296
370
  it 'creates the correct full url' do
297
- expect(client.transport.__full_url(client.transport.hosts[0])).to eq('https://elastic:changeme@abcd.localhost:9200')
371
+ expect(client.transport.__full_url(client.transport.hosts[0])).to eq('https://elastic:changeme@abcd.localhost:9250')
298
372
  end
299
373
  end
300
374
 
301
375
  context 'when the cluster has alternate names' do
302
376
 
303
377
  let(:client) do
304
- described_class.new(cloud_id: 'myCluster:bG9jYWxob3N0JGFiY2QkZWZnaA==', user: 'elasticfantastic', password: 'tobechanged')
378
+ described_class.new(
379
+ cloud_id: 'myCluster:bG9jYWxob3N0JGFiY2QkZWZnaA==',
380
+ user: 'elasticfantastic',
381
+ password: 'tobechanged'
382
+ )
305
383
  end
306
384
 
307
385
  let(:hosts) do
@@ -313,124 +391,334 @@ describe Elasticsearch::Transport::Client do
313
391
  expect(hosts[0][:protocol]).to eq('https')
314
392
  expect(hosts[0][:user]).to eq('elasticfantastic')
315
393
  expect(hosts[0][:password]).to eq('tobechanged')
316
- expect(hosts[0][:port]).to eq(9243)
394
+ expect(hosts[0][:port]).to eq(443)
317
395
  end
318
396
 
319
397
  it 'creates the correct full url' do
320
- expect(client.transport.__full_url(client.transport.hosts[0])).to eq('https://elasticfantastic:tobechanged@abcd.localhost:9243')
398
+ expect(
399
+ client.transport.__full_url(client.transport.hosts[0])
400
+ ).to eq('https://elasticfantastic:tobechanged@abcd.localhost:443')
321
401
  end
322
-
323
402
  end
324
- end
325
403
 
326
- shared_examples_for 'a client that extracts hosts' do
404
+ context 'when decoded cloud id has a trailing dollar sign' do
405
+ let(:client) do
406
+ described_class.new(
407
+ cloud_id: 'a_cluster:bG9jYWxob3N0JGFiY2Qk',
408
+ user: 'elasticfantastic',
409
+ password: 'changeme'
410
+ )
411
+ end
327
412
 
328
- context 'when the hosts are a String' do
413
+ let(:hosts) do
414
+ client.transport.hosts
415
+ end
329
416
 
330
- let(:host) do
331
- 'myhost'
417
+ it 'extracts the cloud credentials' do
418
+ expect(hosts[0][:host]).to eq('abcd.localhost')
419
+ expect(hosts[0][:protocol]).to eq('https')
420
+ expect(hosts[0][:user]).to eq('elasticfantastic')
421
+ expect(hosts[0][:password]).to eq('changeme')
422
+ expect(hosts[0][:port]).to eq(443)
332
423
  end
333
424
 
334
- it 'extracts the host' do
335
- expect(hosts[0][:host]).to eq('myhost')
336
- expect(hosts[0][:protocol]).to eq('http')
337
- expect(hosts[0][:port]).to be(9200)
425
+ it 'creates the correct full url' do
426
+ expect(
427
+ client.transport.__full_url(client.transport.hosts[0])
428
+ ).to eq('https://elasticfantastic:changeme@abcd.localhost:443')
338
429
  end
430
+ end
339
431
 
340
- context 'when IPv6 format is used' do
432
+ context 'when the cloud host provides a port' do
433
+ let(:client) do
434
+ described_class.new(
435
+ cloud_id: 'name:ZWxhc3RpY19zZXJ2ZXI6OTI0MyRlbGFzdGljX2lk',
436
+ user: 'elastic',
437
+ password: 'changeme'
438
+ )
439
+ end
341
440
 
342
- around do |example|
343
- original_setting = Faraday.ignore_env_proxy
344
- Faraday.ignore_env_proxy = true
345
- example.run
346
- Faraday.ignore_env_proxy = original_setting
347
- end
441
+ let(:hosts) do
442
+ client.transport.hosts
443
+ end
348
444
 
349
- let(:host) do
350
- 'https://[2090:db8:85a3:9811::1f]:8080'
351
- end
445
+ it 'creates the correct full url' do
446
+ expect(hosts[0][:host]).to eq('elastic_id.elastic_server')
447
+ expect(hosts[0][:protocol]).to eq('https')
448
+ expect(hosts[0][:user]).to eq('elastic')
449
+ expect(hosts[0][:password]).to eq('changeme')
450
+ expect(hosts[0][:port]).to eq(9243)
451
+ end
452
+ end
352
453
 
353
- it 'extracts the host' do
354
- expect(hosts[0][:host]).to eq('[2090:db8:85a3:9811::1f]')
355
- expect(hosts[0][:scheme]).to eq('https')
356
- expect(hosts[0][:port]).to be(8080)
357
- end
454
+ context 'when the cloud host provides a port and the port is also specified' do
455
+ let(:client) do
456
+ described_class.new(
457
+ cloud_id: 'name:ZWxhc3RpY19zZXJ2ZXI6OTI0MyRlbGFzdGljX2lk',
458
+ user: 'elastic',
459
+ password: 'changeme',
460
+ port: 9200
461
+ )
462
+ end
358
463
 
359
- it 'creates the correct full url' do
360
- expect(client.transport.__full_url(client.transport.hosts[0])).to eq('https://[2090:db8:85a3:9811::1f]:8080')
361
- end
464
+ let(:hosts) do
465
+ client.transport.hosts
466
+ end
467
+
468
+ it 'creates the correct full url' do
469
+ expect(hosts[0][:host]).to eq('elastic_id.elastic_server')
470
+ expect(hosts[0][:protocol]).to eq('https')
471
+ expect(hosts[0][:user]).to eq('elastic')
472
+ expect(hosts[0][:password]).to eq('changeme')
473
+ expect(hosts[0][:port]).to eq(9243)
362
474
  end
475
+ end
476
+ end
363
477
 
364
- context 'when a path is specified' do
478
+ shared_examples_for 'a client that extracts hosts' do
365
479
 
366
- let(:host) do
367
- 'https://myhost:8080/api'
368
- end
480
+ context 'when the host is a String' do
369
481
 
370
- it 'extracts the host' do
371
- expect(hosts[0][:host]).to eq('myhost')
372
- expect(hosts[0][:scheme]).to eq('https')
373
- expect(hosts[0][:path]).to eq('/api')
374
- expect(hosts[0][:port]).to be(8080)
375
- end
376
- end
482
+ context 'when there is a protocol specified' do
377
483
 
378
- context 'when a scheme is specified' do
484
+ context 'when credentials are specified \'http://USERNAME:PASSWORD@myhost:8080\'' do
379
485
 
380
- let(:host) do
381
- 'https://myhost:8080'
382
- end
486
+ let(:host) do
487
+ 'http://USERNAME:PASSWORD@myhost:8080'
488
+ end
383
489
 
384
- it 'extracts the host' do
385
- expect(hosts[0][:host]).to eq('myhost')
386
- expect(hosts[0][:scheme]).to eq('https')
387
- expect(hosts[0][:port]).to be(8080)
490
+ it 'extracts the credentials' do
491
+ expect(hosts[0][:user]).to eq('USERNAME')
492
+ expect(hosts[0][:password]).to eq('PASSWORD')
493
+ end
494
+
495
+ it 'extracts the host' do
496
+ expect(hosts[0][:host]).to eq('myhost')
497
+ end
498
+
499
+ it 'extracts the port' do
500
+ expect(hosts[0][:port]).to be(8080)
501
+ end
388
502
  end
389
- end
390
503
 
391
- context 'when credentials are specified' do
504
+ context 'when there is a trailing slash \'http://myhost/\'' do
392
505
 
393
- let(:host) do
394
- 'http://USERNAME:PASSWORD@myhost:8080'
506
+ let(:host) do
507
+ 'http://myhost/'
508
+ end
509
+
510
+ it 'extracts the host' do
511
+ expect(hosts[0][:host]).to eq('myhost')
512
+ expect(hosts[0][:scheme]).to eq('http')
513
+ expect(hosts[0][:path]).to eq('')
514
+ end
515
+
516
+ it 'extracts the scheme' do
517
+ expect(hosts[0][:scheme]).to eq('http')
518
+ end
519
+
520
+ it 'extracts the path' do
521
+ expect(hosts[0][:path]).to eq('')
522
+ end
395
523
  end
396
524
 
397
- it 'extracts the host' do
398
- expect(hosts[0][:host]).to eq('myhost')
399
- expect(hosts[0][:scheme]).to eq('http')
400
- expect(hosts[0][:user]).to eq('USERNAME')
401
- expect(hosts[0][:password]).to eq('PASSWORD')
402
- expect(hosts[0][:port]).to be(8080)
525
+ context 'when there is a trailing slash with a path \'http://myhost/foo/bar/\'' do
526
+
527
+ let(:host) do
528
+ 'http://myhost/foo/bar/'
529
+ end
530
+
531
+ it 'extracts the host' do
532
+ expect(hosts[0][:host]).to eq('myhost')
533
+ expect(hosts[0][:scheme]).to eq('http')
534
+ expect(hosts[0][:path]).to eq('/foo/bar')
535
+ end
403
536
  end
404
- end
405
537
 
406
- context 'when there is a trailing slash' do
538
+ context 'when the protocol is http' do
407
539
 
408
- let(:host) do
409
- 'http://myhost/'
540
+ context 'when there is no port specified \'http://myhost\'' do
541
+
542
+ let(:host) do
543
+ 'http://myhost'
544
+ end
545
+
546
+ it 'extracts the host' do
547
+ expect(hosts[0][:host]).to eq('myhost')
548
+ end
549
+
550
+ it 'extracts the protocol' do
551
+ expect(hosts[0][:protocol]).to eq('http')
552
+ end
553
+
554
+ it 'defaults to port 9200' do
555
+ expect(hosts[0][:port]).to be(9200)
556
+ end
557
+ end
558
+
559
+ context 'when there is a port specified \'http://myhost:7101\'' do
560
+
561
+ let(:host) do
562
+ 'http://myhost:7101'
563
+ end
564
+
565
+ it 'extracts the host' do
566
+ expect(hosts[0][:host]).to eq('myhost')
567
+ end
568
+
569
+ it 'extracts the protocol' do
570
+ expect(hosts[0][:protocol]).to eq('http')
571
+ end
572
+
573
+ it 'extracts the port' do
574
+ expect(hosts[0][:port]).to be(7101)
575
+ end
576
+
577
+ context 'when there is a path specified \'http://myhost:7101/api\'' do
578
+
579
+ let(:host) do
580
+ 'http://myhost:7101/api'
581
+ end
582
+
583
+ it 'sets the path' do
584
+ expect(hosts[0][:host]).to eq('myhost')
585
+ expect(hosts[0][:protocol]).to eq('http')
586
+ expect(hosts[0][:path]).to eq('/api')
587
+ expect(hosts[0][:port]).to be(7101)
588
+ end
589
+
590
+ it 'extracts the host' do
591
+ expect(hosts[0][:host]).to eq('myhost')
592
+ end
593
+
594
+ it 'extracts the protocol' do
595
+ expect(hosts[0][:protocol]).to eq('http')
596
+ end
597
+
598
+ it 'extracts the port' do
599
+ expect(hosts[0][:port]).to be(7101)
600
+ end
601
+
602
+ it 'extracts the path' do
603
+ expect(hosts[0][:path]).to eq('/api')
604
+ end
605
+ end
606
+ end
410
607
  end
411
608
 
412
- it 'extracts the host' do
413
- expect(hosts[0][:host]).to eq('myhost')
414
- expect(hosts[0][:scheme]).to eq('http')
415
- expect(hosts[0][:path]).to eq('')
609
+ context 'when the protocol is https' do
610
+
611
+ context 'when there is no port specified \'https://myhost\'' do
612
+
613
+ let(:host) do
614
+ 'https://myhost'
615
+ end
616
+
617
+ it 'extracts the host' do
618
+ expect(hosts[0][:host]).to eq('myhost')
619
+ end
620
+
621
+ it 'extracts the protocol' do
622
+ expect(hosts[0][:protocol]).to eq('https')
623
+ end
624
+
625
+ it 'defaults to port 443' do
626
+ expect(hosts[0][:port]).to be(443)
627
+ end
628
+ end
629
+
630
+ context 'when there is a port specified \'https://myhost:7101\'' do
631
+
632
+ let(:host) do
633
+ 'https://myhost:7101'
634
+ end
635
+
636
+ it 'extracts the host' do
637
+ expect(hosts[0][:host]).to eq('myhost')
638
+ end
639
+
640
+ it 'extracts the protocol' do
641
+ expect(hosts[0][:protocol]).to eq('https')
642
+ end
643
+
644
+ it 'extracts the port' do
645
+ expect(hosts[0][:port]).to be(7101)
646
+ end
647
+
648
+ context 'when there is a path specified \'https://myhost:7101/api\'' do
649
+
650
+ let(:host) do
651
+ 'https://myhost:7101/api'
652
+ end
653
+
654
+ it 'extracts the host' do
655
+ expect(hosts[0][:host]).to eq('myhost')
656
+ end
657
+
658
+ it 'extracts the protocol' do
659
+ expect(hosts[0][:protocol]).to eq('https')
660
+ end
661
+
662
+ it 'extracts the port' do
663
+ expect(hosts[0][:port]).to be(7101)
664
+ end
665
+
666
+ it 'extracts the path' do
667
+ expect(hosts[0][:path]).to eq('/api')
668
+ end
669
+ end
670
+ end
671
+
672
+ context 'when IPv6 format is used' do
673
+
674
+ around do |example|
675
+ original_setting = Faraday.ignore_env_proxy
676
+ Faraday.ignore_env_proxy = true
677
+ example.run
678
+ Faraday.ignore_env_proxy = original_setting
679
+ end
680
+
681
+ let(:host) do
682
+ 'https://[2090:db8:85a3:9811::1f]:8080'
683
+ end
684
+
685
+ it 'extracts the host' do
686
+ expect(hosts[0][:host]).to eq('[2090:db8:85a3:9811::1f]')
687
+ end
688
+
689
+ it 'extracts the protocol' do
690
+ expect(hosts[0][:protocol]).to eq('https')
691
+ end
692
+
693
+ it 'extracts the port' do
694
+ expect(hosts[0][:port]).to be(8080)
695
+ end
696
+
697
+ it 'creates the correct full url' do
698
+ expect(client.transport.__full_url(client.transport.hosts[0])).to eq('https://[2090:db8:85a3:9811::1f]:8080')
699
+ end
700
+ end
416
701
  end
417
702
  end
418
703
 
419
- context 'when there is a trailing slash with a path' do
704
+ context 'when no protocol is specified \'myhost\'' do
420
705
 
421
706
  let(:host) do
422
- 'http://myhost/foo/bar/'
707
+ 'myhost'
423
708
  end
424
709
 
425
- it 'extracts the host' do
710
+ it 'defaults to http' do
426
711
  expect(hosts[0][:host]).to eq('myhost')
427
- expect(hosts[0][:scheme]).to eq('http')
428
- expect(hosts[0][:path]).to eq('/foo/bar')
712
+ expect(hosts[0][:protocol]).to eq('http')
713
+ end
714
+
715
+ it 'uses port 9200' do
716
+ expect(hosts[0][:port]).to be(9200)
429
717
  end
430
718
  end
431
719
  end
432
720
 
433
- context 'when the hosts are a Hash' do
721
+ context 'when the host is a Hash' do
434
722
 
435
723
  let(:host) do
436
724
  { :host => 'myhost', :scheme => 'https' }
@@ -438,7 +726,13 @@ describe Elasticsearch::Transport::Client do
438
726
 
439
727
  it 'extracts the host' do
440
728
  expect(hosts[0][:host]).to eq('myhost')
441
- expect(hosts[0][:scheme]).to eq('https')
729
+ end
730
+
731
+ it 'extracts the protocol' do
732
+ expect(hosts[0][:protocol]).to eq('https')
733
+ end
734
+
735
+ it 'extracts the port' do
442
736
  expect(hosts[0][:port]).to be(9200)
443
737
  end
444
738
 
@@ -497,7 +791,13 @@ describe Elasticsearch::Transport::Client do
497
791
 
498
792
  it 'extracts the host' do
499
793
  expect(hosts[0][:host]).to eq('myhost')
794
+ end
795
+
796
+ it 'extracts the protocol' do
500
797
  expect(hosts[0][:scheme]).to eq('https')
798
+ end
799
+
800
+ it 'converts the port to an integer' do
501
801
  expect(hosts[0][:port]).to be(443)
502
802
  end
503
803
  end
@@ -510,7 +810,13 @@ describe Elasticsearch::Transport::Client do
510
810
 
511
811
  it 'extracts the host' do
512
812
  expect(hosts[0][:host]).to eq('myhost')
813
+ end
814
+
815
+ it 'extracts the protocol' do
513
816
  expect(hosts[0][:scheme]).to eq('https')
817
+ end
818
+
819
+ it 'extracts port as an integer' do
514
820
  expect(hosts[0][:port]).to be(443)
515
821
  end
516
822
  end
@@ -524,7 +830,13 @@ describe Elasticsearch::Transport::Client do
524
830
 
525
831
  it 'extracts the host' do
526
832
  expect(hosts[0][:host]).to eq('myhost')
833
+ end
834
+
835
+ it 'extracts the protocol' do
527
836
  expect(hosts[0][:scheme]).to eq('https')
837
+ end
838
+
839
+ it 'converts the port to an integer' do
528
840
  expect(hosts[0][:port]).to be(9200)
529
841
  end
530
842
 
@@ -536,7 +848,13 @@ describe Elasticsearch::Transport::Client do
536
848
 
537
849
  it 'extracts the host' do
538
850
  expect(hosts[0][:host]).to eq('myhost')
851
+ end
852
+
853
+ it 'extracts the protocol' do
539
854
  expect(hosts[0][:scheme]).to eq('https')
855
+ end
856
+
857
+ it 'converts the port to an integer' do
540
858
  expect(hosts[0][:port]).to be(443)
541
859
  end
542
860
  end
@@ -549,7 +867,13 @@ describe Elasticsearch::Transport::Client do
549
867
 
550
868
  it 'extracts the host' do
551
869
  expect(hosts[0][:host]).to eq('myhost')
870
+ end
871
+
872
+ it 'extracts the protocol' do
552
873
  expect(hosts[0][:scheme]).to eq('https')
874
+ end
875
+
876
+ it 'extracts port as an integer' do
553
877
  expect(hosts[0][:port]).to be(443)
554
878
  end
555
879
  end
@@ -565,7 +889,13 @@ describe Elasticsearch::Transport::Client do
565
889
 
566
890
  it 'extracts the host' do
567
891
  expect(hosts[0][:host]).to eq('myhost')
892
+ end
893
+
894
+ it 'extracts the protocol' do
568
895
  expect(hosts[0][:protocol]).to eq('http')
896
+ end
897
+
898
+ it 'defaults to port 9200' do
569
899
  expect(hosts[0][:port]).to be(9200)
570
900
  end
571
901
  end
@@ -578,20 +908,13 @@ describe Elasticsearch::Transport::Client do
578
908
 
579
909
  it 'extracts the host' do
580
910
  expect(hosts[0][:host]).to eq('myhost')
581
- expect(hosts[0][:protocol]).to eq('http')
582
- expect(hosts[0][:port]).to be(9200)
583
911
  end
584
- end
585
-
586
- context 'when there is one host with a protocol and no port' do
587
912
 
588
- let(:host) do
589
- ['http://myhost']
913
+ it 'extracts the protocol' do
914
+ expect(hosts[0][:scheme]).to eq('http')
590
915
  end
591
916
 
592
- it 'extracts the host' do
593
- expect(hosts[0][:host]).to eq('myhost')
594
- expect(hosts[0][:protocol]).to eq('http')
917
+ it 'defaults to port 9200' do
595
918
  expect(hosts[0][:port]).to be(9200)
596
919
  end
597
920
  end
@@ -616,7 +939,7 @@ describe Elasticsearch::Transport::Client do
616
939
  end
617
940
  end
618
941
 
619
- context 'when there is one host with a scheme, protocol and no port' do
942
+ context 'when there is one host with a protocol and no port' do
620
943
 
621
944
  let(:host) do
622
945
  ['https://myhost']
@@ -624,12 +947,18 @@ describe Elasticsearch::Transport::Client do
624
947
 
625
948
  it 'extracts the host' do
626
949
  expect(hosts[0][:host]).to eq('myhost')
627
- expect(hosts[0][:protocol]).to eq('https')
628
- expect(hosts[0][:port]).to be(9200)
950
+ end
951
+
952
+ it 'extracts the protocol' do
953
+ expect(hosts[0][:scheme]).to eq('https')
954
+ end
955
+
956
+ it 'defaults to port 443' do
957
+ expect(hosts[0][:port]).to be(443)
629
958
  end
630
959
  end
631
960
 
632
- context 'when there is one host with a scheme, protocol, path, and no port' do
961
+ context 'when there is one host with a protocol, path, and no port' do
633
962
 
634
963
  let(:host) do
635
964
  ['http://myhost/foo/bar']
@@ -637,9 +966,18 @@ describe Elasticsearch::Transport::Client do
637
966
 
638
967
  it 'extracts the host' do
639
968
  expect(hosts[0][:host]).to eq('myhost')
640
- expect(hosts[0][:protocol]).to eq('http')
969
+ end
970
+
971
+ it 'extracts the protocol' do
972
+ expect(hosts[0][:scheme]).to eq('http')
973
+ end
974
+
975
+ it 'defaults to port 9200' do
641
976
  expect(hosts[0][:port]).to be(9200)
642
- expect(hosts[0][:path]).to eq("/foo/bar")
977
+ end
978
+
979
+ it 'extracts the path' do
980
+ expect(hosts[0][:path]).to eq('/foo/bar')
643
981
  end
644
982
  end
645
983
 
@@ -649,7 +987,7 @@ describe Elasticsearch::Transport::Client do
649
987
  ['host1', 'host2']
650
988
  end
651
989
 
652
- it 'extracts the host' do
990
+ it 'extracts the hosts' do
653
991
  expect(hosts[0][:host]).to eq('host1')
654
992
  expect(hosts[0][:protocol]).to eq('http')
655
993
  expect(hosts[0][:port]).to be(9200)
@@ -665,7 +1003,7 @@ describe Elasticsearch::Transport::Client do
665
1003
  ['host1:1000', 'host2:2000']
666
1004
  end
667
1005
 
668
- it 'extracts the host' do
1006
+ it 'extracts the hosts' do
669
1007
  expect(hosts[0][:host]).to eq('host1')
670
1008
  expect(hosts[0][:protocol]).to eq('http')
671
1009
  expect(hosts[0][:port]).to be(1000)
@@ -1055,10 +1393,130 @@ describe Elasticsearch::Transport::Client do
1055
1393
  expect(request).to be(true)
1056
1394
  end
1057
1395
  end
1396
+
1397
+ context 'when x-opaque-id is set' do
1398
+ let(:client) { described_class.new(host: hosts) }
1399
+
1400
+ it 'uses x-opaque-id on a request' do
1401
+ expect(client.perform_request('GET', '/', { opaque_id: '12345' }).headers['x-opaque-id']).to eq('12345')
1402
+ end
1403
+ end
1404
+
1405
+ context 'when an x-opaque-id prefix is set on initialization' do
1406
+ let(:prefix) { 'elastic_cloud' }
1407
+ let(:client) do
1408
+ described_class.new(host: hosts, opaque_id_prefix: prefix)
1409
+ end
1410
+
1411
+ it 'uses x-opaque-id on a request' do
1412
+ expect(client.perform_request('GET', '/', { opaque_id: '12345' }).headers['x-opaque-id']).to eq("#{prefix}12345")
1413
+ end
1414
+
1415
+ context 'when using an API call' do
1416
+ let(:client) { described_class.new(host: hosts) }
1417
+
1418
+ it 'doesnae raise an ArgumentError' do
1419
+ expect { client.perform_request('GET', '_search', opaque_id: 'no_error') }.not_to raise_error
1420
+ end
1421
+
1422
+ it 'uses X-Opaque-Id in the header' do
1423
+ allow(client).to receive(:perform_request) { OpenStruct.new(body: '') }
1424
+ expect { client.perform_request('GET', '_search', {}, nil, opaque_id: 'opaque_id') }.not_to raise_error
1425
+ expect(client).to have_received(:perform_request)
1426
+ .with('GET', '_search', {}, nil, { opaque_id: 'opaque_id' })
1427
+ end
1428
+ end
1429
+ end
1430
+
1431
+ context 'when using the API Compatibility Header' do
1432
+ it 'sets the API compatibility headers' do
1433
+ ENV['ELASTIC_CLIENT_APIVERSIONING'] = 'true'
1434
+ client = described_class.new(host: hosts)
1435
+ headers = client.transport.connections.first.connection.headers
1436
+
1437
+ expect(headers['Content-Type']).to eq('application/vnd.elasticsearch+json; compatible-with=7')
1438
+ expect(headers['Accept']).to eq('application/vnd.elasticsearch+json; compatible-with=7')
1439
+
1440
+ ENV.delete('ELASTIC_CLIENT_APIVERSIONING')
1441
+ end
1442
+
1443
+ it 'does not use API compatibility headers' do
1444
+ val = ENV.delete('ELASTIC_CLIENT_APIVERSIONING')
1445
+ client = described_class.new(host: hosts)
1446
+ expect(client.transport.connections.first.connection.headers['Content-Type']).to eq('application/json')
1447
+ ENV['ELASTIC_CLIENT_APIVERSIONING'] = val
1448
+ end
1449
+
1450
+ it 'does not use API compatibility headers when it is set to unsupported values' do
1451
+ val = ENV.delete('ELASTIC_CLIENT_APIVERSIONING')
1452
+
1453
+ ENV['ELASTIC_CLIENT_APIVERSIONING'] = 'test'
1454
+ client = described_class.new(host: hosts)
1455
+ expect(client.transport.connections.first.connection.headers['Content-Type']).to eq('application/json')
1456
+
1457
+ ENV['ELASTIC_CLIENT_APIVERSIONING'] = 'false'
1458
+ client = described_class.new(host: hosts)
1459
+ expect(client.transport.connections.first.connection.headers['Content-Type']).to eq('application/json')
1460
+
1461
+ ENV['ELASTIC_CLIENT_APIVERSIONING'] = '3'
1462
+ client = described_class.new(host: hosts)
1463
+ expect(client.transport.connections.first.connection.headers['Content-Type']).to eq('application/json')
1464
+ ENV['ELASTIC_CLIENT_APIVERSIONING'] = val
1465
+ end
1466
+ end
1467
+
1468
+ context 'when Elasticsearch response includes a warning header' do
1469
+ let(:logger) { double('logger', warn: '', warn?: '', info?: '', info: '', debug?: '', debug: '') }
1470
+ let(:client) do
1471
+ Elasticsearch::Transport::Client.new(hosts: hosts, logger: logger)
1472
+ end
1473
+
1474
+ let(:warning) { 'Elasticsearch warning: "deprecation warning"' }
1475
+
1476
+ it 'prints a warning' do
1477
+ expect_any_instance_of(Faraday::Connection).to receive(:run_request) do
1478
+ Elasticsearch::Transport::Transport::Response.new(200, {}, { 'warning' => warning })
1479
+ end
1480
+ client.perform_request('GET', '/')
1481
+ expect(logger).to have_received(:warn).with(warning)
1482
+ end
1483
+ end
1484
+
1485
+ context 'when a header is set on an endpoint request' do
1486
+ let(:client) { described_class.new(host: hosts) }
1487
+ let(:headers) { { 'user-agent' => 'my ruby app' } }
1488
+
1489
+ it 'performs the request with the header' do
1490
+ allow(client).to receive(:perform_request) { OpenStruct.new(body: '') }
1491
+ expect { client.perform_request('GET', '_search', {}, nil, headers) }.not_to raise_error
1492
+ expect(client).to have_received(:perform_request)
1493
+ .with('GET', '_search', {}, nil, headers)
1494
+ end
1495
+ end
1496
+
1497
+ context 'when a header is set on an endpoint request and on initialization' do
1498
+ let!(:client) do
1499
+ described_class.new(
1500
+ host: hosts,
1501
+ transport_options: { headers: instance_headers }
1502
+ )
1503
+ end
1504
+ let(:instance_headers) { { set_in_instantiation: 'header value' } }
1505
+ let(:param_headers) { {'user-agent' => 'My Ruby Tests', 'set-on-method-call' => 'header value'} }
1506
+
1507
+ it 'performs the request with the header' do
1508
+ expected_headers = client.transport.connections.connections.first.connection.headers.merge(param_headers)
1509
+
1510
+ expect_any_instance_of(Faraday::Connection)
1511
+ .to receive(:run_request)
1512
+ .with(:get, "http://#{hosts[0]}/_search", nil, expected_headers) { OpenStruct.new(body: '')}
1513
+
1514
+ client.perform_request('GET', '_search', {}, nil, param_headers)
1515
+ end
1516
+ end
1058
1517
  end
1059
1518
 
1060
1519
  context 'when the client connects to Elasticsearch' do
1061
-
1062
1520
  let(:logger) do
1063
1521
  Logger.new(STDERR).tap do |logger|
1064
1522
  logger.formatter = proc do |severity, datetime, progname, msg|
@@ -1090,7 +1548,6 @@ describe Elasticsearch::Transport::Client do
1090
1548
  end
1091
1549
 
1092
1550
  context 'when a request is made' do
1093
-
1094
1551
  let!(:response) do
1095
1552
  client.perform_request('GET', '_cluster/health')
1096
1553
  end
@@ -1101,9 +1558,7 @@ describe Elasticsearch::Transport::Client do
1101
1558
  end
1102
1559
 
1103
1560
  describe '#initialize' do
1104
-
1105
1561
  context 'when options are specified' do
1106
-
1107
1562
  let(:transport_options) do
1108
1563
  { headers: { accept: 'application/yaml', content_type: 'application/yaml' } }
1109
1564
  end
@@ -1119,9 +1574,8 @@ describe Elasticsearch::Transport::Client do
1119
1574
  end
1120
1575
 
1121
1576
  context 'when a block is provided' do
1122
-
1123
1577
  let(:client) do
1124
- Elasticsearch::Client.new(host: ELASTICSEARCH_HOSTS.first, logger: logger) do |client|
1578
+ described_class.new(host: ELASTICSEARCH_HOSTS.first, logger: logger) do |client|
1125
1579
  client.headers['Accept'] = 'application/yaml'
1126
1580
  end
1127
1581
  end
@@ -1136,15 +1590,16 @@ describe Elasticsearch::Transport::Client do
1136
1590
  end
1137
1591
 
1138
1592
  context 'when the Faraday adapter is set in the block' do
1593
+ require 'faraday/net_http_persistent' if is_faraday_v2?
1139
1594
 
1140
1595
  let(:client) do
1141
- Elasticsearch::Client.new(host: ELASTICSEARCH_HOSTS.first, logger: logger) do |client|
1596
+ described_class.new(host: ELASTICSEARCH_HOSTS.first, logger: logger) do |client|
1142
1597
  client.adapter(:net_http_persistent)
1143
1598
  end
1144
1599
  end
1145
1600
 
1146
- let(:connection_handler) do
1147
- client.transport.connections.first.connection.builder.handlers.first
1601
+ let(:handler_name) do
1602
+ client.transport.connections.first.connection.builder.adapter.name
1148
1603
  end
1149
1604
 
1150
1605
  let(:response) do
@@ -1152,7 +1607,7 @@ describe Elasticsearch::Transport::Client do
1152
1607
  end
1153
1608
 
1154
1609
  it 'sets the adapter' do
1155
- expect(connection_handler.name).to eq('Faraday::Adapter::NetHttpPersistent')
1610
+ expect(handler_name).to eq('Faraday::Adapter::NetHttpPersistent')
1156
1611
  end
1157
1612
 
1158
1613
  it 'uses the adapter to connect' do
@@ -1202,7 +1657,30 @@ describe Elasticsearch::Transport::Client do
1202
1657
  expect(client.perform_request('GET', '_nodes/_local'))
1203
1658
  expect {
1204
1659
  client.perform_request('GET', '_nodes/_local')
1205
- }.to raise_exception(Faraday::Error::ConnectionFailed)
1660
+ }.to raise_exception(Faraday::ConnectionFailed)
1661
+ end
1662
+ end
1663
+
1664
+ context 'when retry_on_failure is true and delay_on_retry is specified' do
1665
+ context 'when a node is unreachable' do
1666
+ let(:hosts) do
1667
+ [ELASTICSEARCH_HOSTS.first, "foobar1", "foobar2"]
1668
+ end
1669
+
1670
+ let(:options) do
1671
+ { retry_on_failure: true, delay_on_retry: 3000 }
1672
+ end
1673
+
1674
+ let(:responses) do
1675
+ 5.times.collect do
1676
+ client.perform_request('GET', '_nodes/_local')
1677
+ end
1678
+ end
1679
+
1680
+ it 'retries on failure' do
1681
+ allow_any_instance_of(Object).to receive(:sleep).with(3000 / 1000)
1682
+ expect(responses.all? { true }).to be(true)
1683
+ end
1206
1684
  end
1207
1685
  end
1208
1686
 
@@ -1265,7 +1743,7 @@ describe Elasticsearch::Transport::Client do
1265
1743
  end
1266
1744
 
1267
1745
  it 'sets the Accept-Encoding header' do
1268
- expect(client.transport.connections[0].connection.headers['Accept-Encoding'])
1746
+ expect(client.transport.connections[0].connection.headers['Accept-Encoding']).to eq 'gzip'
1269
1747
  end
1270
1748
 
1271
1749
  it 'preserves the other headers' do
@@ -1274,9 +1752,10 @@ describe Elasticsearch::Transport::Client do
1274
1752
  end
1275
1753
 
1276
1754
  context 'when using the HTTPClient adapter' do
1755
+ require 'faraday/httpclient'
1277
1756
 
1278
1757
  let(:client) do
1279
- described_class.new(hosts: ELASTICSEARCH_HOSTS, compression: true, adapter: :httpclient)
1758
+ described_class.new(hosts: ELASTICSEARCH_HOSTS, compression: true, adapter: :httpclient, enable_meta_header: false)
1280
1759
  end
1281
1760
 
1282
1761
  it 'compresses the request and decompresses the response' do
@@ -1284,7 +1763,7 @@ describe Elasticsearch::Transport::Client do
1284
1763
  end
1285
1764
 
1286
1765
  it 'sets the Accept-Encoding header' do
1287
- expect(client.transport.connections[0].connection.headers['Accept-Encoding'])
1766
+ expect(client.transport.connections[0].connection.headers['Accept-Encoding']).to eq 'gzip'
1288
1767
  end
1289
1768
 
1290
1769
  it 'preserves the other headers' do
@@ -1303,7 +1782,7 @@ describe Elasticsearch::Transport::Client do
1303
1782
  end
1304
1783
 
1305
1784
  it 'sets the Accept-Encoding header' do
1306
- expect(client.transport.connections[0].connection.headers['Accept-Encoding'])
1785
+ expect(client.transport.connections[0].connection.headers['Accept-Encoding']).to eq 'gzip'
1307
1786
  end
1308
1787
 
1309
1788
  it 'preserves the other headers' do
@@ -1322,7 +1801,7 @@ describe Elasticsearch::Transport::Client do
1322
1801
  end
1323
1802
 
1324
1803
  it 'sets the Accept-Encoding header' do
1325
- expect(client.transport.connections[0].connection.headers['Accept-Encoding'])
1804
+ expect(client.transport.connections[0].connection.headers['Accept-Encoding']).to eq 'gzip'
1326
1805
  end
1327
1806
 
1328
1807
  it 'preserves the other headers' do
@@ -1341,22 +1820,23 @@ describe Elasticsearch::Transport::Client do
1341
1820
  end
1342
1821
 
1343
1822
  it 'sets the Accept-Encoding header' do
1344
- expect(client.transport.connections[0].connection.headers['Accept-Encoding'])
1823
+ expect(client.transport.connections[0].connection.headers['Accept-Encoding']).to eq 'gzip'
1345
1824
  end
1346
1825
 
1347
1826
  it 'preserves the other headers' do
1348
1827
  expect(client.transport.connections[0].connection.headers['User-Agent'])
1349
1828
  end
1350
- end
1829
+ end unless jruby?
1351
1830
  end
1352
1831
  end
1353
1832
 
1354
1833
  context 'when using Curb as the transport', unless: jruby? do
1355
-
1356
1834
  let(:client) do
1357
- described_class.new(hosts: ELASTICSEARCH_HOSTS,
1358
- compression: true,
1359
- transport_class: Elasticsearch::Transport::Transport::HTTP::Curb)
1835
+ described_class.new(
1836
+ hosts: ELASTICSEARCH_HOSTS,
1837
+ compression: true,
1838
+ transport_class: Elasticsearch::Transport::Transport::HTTP::Curb
1839
+ )
1360
1840
  end
1361
1841
 
1362
1842
  it 'compresses the request and decompresses the response' do
@@ -1364,7 +1844,7 @@ describe Elasticsearch::Transport::Client do
1364
1844
  end
1365
1845
 
1366
1846
  it 'sets the Accept-Encoding header' do
1367
- expect(client.transport.connections[0].connection.headers['Accept-Encoding'])
1847
+ expect(client.transport.connections[0].connection.headers['Accept-Encoding']).to eq 'gzip'
1368
1848
  end
1369
1849
 
1370
1850
  it 'preserves the other headers' do
@@ -1373,7 +1853,6 @@ describe Elasticsearch::Transport::Client do
1373
1853
  end
1374
1854
 
1375
1855
  context 'when using Manticore as the transport', if: jruby? do
1376
-
1377
1856
  let(:client) do
1378
1857
  described_class.new(hosts: ELASTICSEARCH_HOSTS,
1379
1858
  compression: true,
@@ -1387,9 +1866,7 @@ describe Elasticsearch::Transport::Client do
1387
1866
  end
1388
1867
 
1389
1868
  describe '#perform_request' do
1390
-
1391
1869
  context 'when a request is made' do
1392
-
1393
1870
  before do
1394
1871
  client.perform_request('DELETE', '_all')
1395
1872
  client.perform_request('DELETE', 'myindex') rescue
@@ -1412,7 +1889,6 @@ describe Elasticsearch::Transport::Client do
1412
1889
  end
1413
1890
 
1414
1891
  context 'when an invalid url is specified' do
1415
-
1416
1892
  it 'raises an exception' do
1417
1893
  expect {
1418
1894
  client.perform_request('GET', 'myindex/mydoc/1?routing=FOOBARBAZ')
@@ -1421,7 +1897,6 @@ describe Elasticsearch::Transport::Client do
1421
1897
  end
1422
1898
 
1423
1899
  context 'when the \'ignore\' parameter is specified' do
1424
-
1425
1900
  let(:response) do
1426
1901
  client.perform_request('PUT', '_foobar', ignore: 400)
1427
1902
  end
@@ -1437,7 +1912,6 @@ describe Elasticsearch::Transport::Client do
1437
1912
  end
1438
1913
 
1439
1914
  context 'when request headers are specified' do
1440
-
1441
1915
  let(:response) do
1442
1916
  client.perform_request('GET', '/', {}, nil, { 'Content-Type' => 'application/yaml' })
1443
1917
  end
@@ -1448,9 +1922,7 @@ describe Elasticsearch::Transport::Client do
1448
1922
  end
1449
1923
 
1450
1924
  describe 'selector' do
1451
-
1452
1925
  context 'when the round-robin selector is used' do
1453
-
1454
1926
  let(:nodes) do
1455
1927
  3.times.collect do
1456
1928
  client.perform_request('GET', '_nodes/_local').body['nodes'].to_a[0][1]['name']
@@ -1458,7 +1930,7 @@ describe Elasticsearch::Transport::Client do
1458
1930
  end
1459
1931
 
1460
1932
  let(:node_names) do
1461
- client.nodes.stats['nodes'].collect do |name, stats|
1933
+ client.perform_request('GET', '_nodes/stats').body('nodes').collect do |name, stats|
1462
1934
  stats['name']
1463
1935
  end
1464
1936
  end
@@ -1477,7 +1949,6 @@ describe Elasticsearch::Transport::Client do
1477
1949
  end
1478
1950
 
1479
1951
  context 'when patron is used as an adapter', unless: jruby? do
1480
-
1481
1952
  before do
1482
1953
  require 'patron'
1483
1954
  end
@@ -1486,12 +1957,39 @@ describe Elasticsearch::Transport::Client do
1486
1957
  { adapter: :patron }
1487
1958
  end
1488
1959
 
1489
- let(:connection_handler) do
1490
- client.transport.connections.first.connection.builder.handlers.first
1960
+ let(:adapter) do
1961
+ client.transport.connections.first.connection.builder.adapter
1962
+ end
1963
+
1964
+ it 'uses the patron connection handler' do
1965
+ expect(adapter).to eq('Faraday::Adapter::Patron')
1966
+ end
1967
+
1968
+ it 'keeps connections open' do
1969
+ response = client.perform_request('GET', '_nodes/stats/http')
1970
+ connections_before = response.body['nodes'].values.find { |n| n['name'] == node_names.first }['http']['total_opened']
1971
+ client.transport.reload_connections!
1972
+ response = client.perform_request('GET', '_nodes/stats/http')
1973
+ connections_after = response.body['nodes'].values.find { |n| n['name'] == node_names.first }['http']['total_opened']
1974
+ expect(connections_after).to be >= (connections_before)
1975
+ end
1976
+ end
1977
+
1978
+ context 'when typhoeus is used as an adapter', unless: jruby? do
1979
+ before do
1980
+ require 'typhoeus'
1981
+ end
1982
+
1983
+ let(:options) do
1984
+ { adapter: :typhoeus }
1985
+ end
1986
+
1987
+ let(:adapter) do
1988
+ client.transport.connections.first.connection.builder.adapter
1491
1989
  end
1492
1990
 
1493
1991
  it 'uses the patron connection handler' do
1494
- expect(connection_handler).to eq('Faraday::Adapter::Patron')
1992
+ expect(adapter).to eq('Faraday::Adapter::Typhoeus')
1495
1993
  end
1496
1994
 
1497
1995
  it 'keeps connections open' do
@@ -1505,4 +2003,76 @@ describe Elasticsearch::Transport::Client do
1505
2003
  end
1506
2004
  end
1507
2005
  end
2006
+
2007
+ context 'CA Fingerprinting' do
2008
+ context 'when setting a ca_fingerprint' do
2009
+ after do
2010
+ File.delete('./certificate.crt')
2011
+ File.delete('./certificate.key')
2012
+ end
2013
+
2014
+ let(:certificate) do
2015
+ system(
2016
+ 'openssl req -new -newkey rsa:4096 -days 3650 -nodes -x509 -subj "/C=BE/O=Test/CN=Test"' \
2017
+ ' -keyout certificate.key -out certificate.crt',
2018
+ err: File::NULL
2019
+ )
2020
+ OpenSSL::X509::Certificate.new File.read('./certificate.crt')
2021
+ end
2022
+
2023
+ let(:client) do
2024
+ Elasticsearch::Transport::Client.new(
2025
+ host: 'https://elastic:changeme@localhost:9200',
2026
+ ca_fingerprint: OpenSSL::Digest::SHA256.hexdigest(certificate.to_der)
2027
+ )
2028
+ end
2029
+
2030
+ it 'validates CA fingerprints on perform request' do
2031
+ expect(client.transport.connections.connections.map(&:verified).uniq).to eq [false]
2032
+ allow(client.transport).to receive(:perform_request) { 'Hello' }
2033
+
2034
+ server = double('server').as_null_object
2035
+ allow(TCPSocket).to receive(:new) { server }
2036
+ socket = double('socket')
2037
+ allow(OpenSSL::SSL::SSLSocket).to receive(:new) { socket }
2038
+ allow(socket).to receive(:connect) { nil }
2039
+ allow(socket).to receive(:peer_cert_chain) { [certificate] }
2040
+
2041
+ response = client.perform_request('GET', '/')
2042
+ expect(client.transport.connections.connections.map(&:verified).uniq).to eq [true]
2043
+ expect(response).to eq 'Hello'
2044
+ end
2045
+ end
2046
+
2047
+ context 'when using an http host' do
2048
+ let(:client) do
2049
+ Elasticsearch::Transport::Client.new(
2050
+ host: 'http://elastic:changeme@localhost:9200',
2051
+ ca_fingerprint: 'test'
2052
+ )
2053
+ end
2054
+
2055
+ it 'raises an error' do
2056
+ expect do
2057
+ client.perform_request('GET', '/')
2058
+ end.to raise_exception(Elasticsearch::Transport::Transport::Error)
2059
+ end
2060
+ end
2061
+
2062
+ context 'when not setting a ca_fingerprint' do
2063
+ let(:client) do
2064
+ Elasticsearch::Transport::Client.new(
2065
+ host: 'http://elastic:changeme@localhost:9200'
2066
+ )
2067
+ end
2068
+
2069
+ it 'has unvalidated connections' do
2070
+ allow(client).to receive(:validate_ca_fingerprints) { nil }
2071
+ allow(client.transport).to receive(:perform_request) { nil }
2072
+
2073
+ client.perform_request('GET', '/')
2074
+ expect(client).to_not have_received(:validate_ca_fingerprints)
2075
+ end
2076
+ end
2077
+ end
1508
2078
  end