elasticsearch-transport 7.5.0 → 7.17.7

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