elasticsearch-transport 7.4.0 → 7.17.10

Sign up to get free protection for your applications and to get access to all the features.
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 +184 -59
  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 +107 -49
  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 +64 -76
@@ -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