elasticsearch-transport 6.1.0 → 6.2.0

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.
@@ -0,0 +1,929 @@
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
18
+ require 'spec_helper'
19
+
20
+ describe Elasticsearch::Transport::Client do
21
+
22
+ let(:client) do
23
+ described_class.new.tap do |_client|
24
+ allow(_client).to receive(:__build_connections)
25
+ end
26
+ end
27
+
28
+ it 'is aliased as Elasticsearch::Client' do
29
+ expect(Elasticsearch::Client.new).to be_a(described_class)
30
+ end
31
+
32
+ it 'has a default transport' do
33
+ expect(client.transport).to be_a(Elasticsearch::Transport::Client::DEFAULT_TRANSPORT_CLASS)
34
+ end
35
+
36
+ it 'uses Faraday as the default transport' do
37
+ expect(client.transport.connections.first.connection.headers['User-Agent']).to match(/Faraday/)
38
+ end
39
+
40
+ it 'sets the \'Content-Type\' header to \'application/json\' by default' do
41
+ expect(client.transport.options[:transport_options][:headers]['Content-Type']).to eq('application/json')
42
+ end
43
+
44
+ it 'uses localhost by default' do
45
+ expect(client.transport.hosts[0][:host]).to eq('localhost')
46
+ end
47
+
48
+ describe 'adapter' do
49
+
50
+ context 'when no adapter is specified' do
51
+
52
+ let(:adapter) do
53
+ client.transport.connections.all.first.connection.builder.handlers
54
+ end
55
+
56
+ it 'uses Faraday NetHttp' do
57
+ expect(adapter).to include(Faraday::Adapter::NetHttp)
58
+ end
59
+ end
60
+
61
+ context 'when the adapter is specified' do
62
+
63
+ let(:adapter) do
64
+ client.transport.connections.all.first.connection.builder.handlers
65
+ end
66
+
67
+ let(:client) do
68
+ described_class.new(adapter: :typhoeus)
69
+ end
70
+
71
+ it 'uses Faraday' do
72
+ expect(adapter).to include(Faraday::Adapter::Typhoeus)
73
+ end
74
+ end
75
+
76
+ context 'when the adapter can be detected', unless: jruby? do
77
+
78
+ around do |example|
79
+ require 'patron'; load 'patron.rb'
80
+ example.run
81
+ end
82
+
83
+ let(:adapter) do
84
+ client.transport.connections.all.first.connection.builder.handlers
85
+ end
86
+
87
+ it 'uses the detected adapter' do
88
+ expect(adapter).to include(Faraday::Adapter::Patron)
89
+ end
90
+ end
91
+
92
+ context 'when the Faraday adapter is configured' do
93
+
94
+ let(:client) do
95
+ described_class.new do |faraday|
96
+ faraday.adapter :typhoeus
97
+ faraday.response :logger
98
+ end
99
+ end
100
+
101
+ let(:handlers) do
102
+ client.transport.connections.all.first.connection.builder.handlers
103
+ end
104
+
105
+ it 'sets the adapter' do
106
+ expect(handlers).to include(Faraday::Adapter::Typhoeus)
107
+ end
108
+
109
+ it 'sets the logger' do
110
+ expect(handlers).to include(Faraday::Response::Logger)
111
+ end
112
+ end
113
+ end
114
+
115
+ shared_examples_for 'a client that extracts hosts' do
116
+
117
+ context 'when the hosts are a String' do
118
+
119
+ let(:host) do
120
+ 'myhost'
121
+ end
122
+
123
+ it 'extracts the host' do
124
+ expect(hosts[0][:host]).to eq('myhost')
125
+ expect(hosts[0][:protocol]).to eq('http')
126
+ expect(hosts[0][:port]).to be(9200)
127
+ end
128
+
129
+ context 'when a path is specified' do
130
+
131
+ let(:host) do
132
+ 'https://myhost:8080/api'
133
+ end
134
+
135
+ it 'extracts the host' do
136
+ expect(hosts[0][:host]).to eq('myhost')
137
+ expect(hosts[0][:scheme]).to eq('https')
138
+ expect(hosts[0][:path]).to eq('/api')
139
+ expect(hosts[0][:port]).to be(8080)
140
+ end
141
+ end
142
+
143
+ context 'when a scheme is specified' do
144
+
145
+ let(:host) do
146
+ 'https://myhost:8080'
147
+ end
148
+
149
+ it 'extracts the host' do
150
+ expect(hosts[0][:host]).to eq('myhost')
151
+ expect(hosts[0][:scheme]).to eq('https')
152
+ expect(hosts[0][:port]).to be(8080)
153
+ end
154
+ end
155
+
156
+ context 'when credentials are specified' do
157
+
158
+ let(:host) do
159
+ 'http://USERNAME:PASSWORD@myhost:8080'
160
+ end
161
+
162
+ it 'extracts the host' do
163
+ expect(hosts[0][:host]).to eq('myhost')
164
+ expect(hosts[0][:scheme]).to eq('http')
165
+ expect(hosts[0][:user]).to eq('USERNAME')
166
+ expect(hosts[0][:password]).to eq('PASSWORD')
167
+ expect(hosts[0][:port]).to be(8080)
168
+ end
169
+ end
170
+
171
+ context 'when there is a trailing slash' do
172
+
173
+ let(:host) do
174
+ 'http://myhost/'
175
+ end
176
+
177
+ it 'extracts the host' do
178
+ expect(hosts[0][:host]).to eq('myhost')
179
+ expect(hosts[0][:scheme]).to eq('http')
180
+ expect(hosts[0][:path]).to eq('')
181
+ end
182
+ end
183
+
184
+ context 'when there is a trailing slash with a path' do
185
+
186
+ let(:host) do
187
+ 'http://myhost/foo/bar/'
188
+ end
189
+
190
+ it 'extracts the host' do
191
+ expect(hosts[0][:host]).to eq('myhost')
192
+ expect(hosts[0][:scheme]).to eq('http')
193
+ expect(hosts[0][:path]).to eq('/foo/bar')
194
+ end
195
+ end
196
+ end
197
+
198
+ context 'when the hosts are a Hash' do
199
+
200
+ let(:host) do
201
+ { :host => 'myhost', :scheme => 'https' }
202
+ end
203
+
204
+ it 'extracts the host' do
205
+ expect(hosts[0][:host]).to eq('myhost')
206
+ expect(hosts[0][:scheme]).to eq('https')
207
+ expect(hosts[0][:port]).to be(9200)
208
+ end
209
+
210
+ context 'when the port is specified as a String' do
211
+
212
+ let(:host) do
213
+ { host: 'myhost', scheme: 'https', port: '443' }
214
+ end
215
+
216
+ it 'extracts the host' do
217
+ expect(hosts[0][:host]).to eq('myhost')
218
+ expect(hosts[0][:scheme]).to eq('https')
219
+ expect(hosts[0][:port]).to be(443)
220
+ end
221
+ end
222
+
223
+ context 'when the port is specified as an Integer' do
224
+
225
+ let(:host) do
226
+ { host: 'myhost', scheme: 'https', port: 443 }
227
+ end
228
+
229
+ it 'extracts the host' do
230
+ expect(hosts[0][:host]).to eq('myhost')
231
+ expect(hosts[0][:scheme]).to eq('https')
232
+ expect(hosts[0][:port]).to be(443)
233
+ end
234
+ end
235
+ end
236
+
237
+ context 'when the hosts are a Hashie:Mash' do
238
+
239
+ let(:host) do
240
+ Hashie::Mash.new(host: 'myhost', scheme: 'https')
241
+ end
242
+
243
+ it 'extracts the host' do
244
+ expect(hosts[0][:host]).to eq('myhost')
245
+ expect(hosts[0][:scheme]).to eq('https')
246
+ expect(hosts[0][:port]).to be(9200)
247
+ end
248
+
249
+ context 'when the port is specified as a String' do
250
+
251
+ let(:host) do
252
+ Hashie::Mash.new(host: 'myhost', scheme: 'https', port: '443')
253
+ end
254
+
255
+ it 'extracts the host' do
256
+ expect(hosts[0][:host]).to eq('myhost')
257
+ expect(hosts[0][:scheme]).to eq('https')
258
+ expect(hosts[0][:port]).to be(443)
259
+ end
260
+ end
261
+
262
+ context 'when the port is specified as an Integer' do
263
+
264
+ let(:host) do
265
+ Hashie::Mash.new(host: 'myhost', scheme: 'https', port: 443)
266
+ end
267
+
268
+ it 'extracts the host' do
269
+ expect(hosts[0][:host]).to eq('myhost')
270
+ expect(hosts[0][:scheme]).to eq('https')
271
+ expect(hosts[0][:port]).to be(443)
272
+ end
273
+ end
274
+ end
275
+
276
+ context 'when the hosts are an array' do
277
+
278
+ context 'when there is one host' do
279
+
280
+ let(:host) do
281
+ ['myhost']
282
+ end
283
+
284
+ it 'extracts the host' do
285
+ expect(hosts[0][:host]).to eq('myhost')
286
+ expect(hosts[0][:protocol]).to eq('http')
287
+ expect(hosts[0][:port]).to be(9200)
288
+ end
289
+ end
290
+
291
+ context 'when there is more than one host' do
292
+
293
+ let(:host) do
294
+ ['host1', 'host2']
295
+ end
296
+
297
+ it 'extracts the host' do
298
+ expect(hosts[0][:host]).to eq('host1')
299
+ expect(hosts[0][:protocol]).to eq('http')
300
+ expect(hosts[0][:port]).to be(9200)
301
+ expect(hosts[1][:host]).to eq('host2')
302
+ expect(hosts[1][:protocol]).to eq('http')
303
+ expect(hosts[1][:port]).to be(9200)
304
+ end
305
+ end
306
+
307
+ context 'when ports are also specified' do
308
+
309
+ let(:host) do
310
+ ['host1:1000', 'host2:2000']
311
+ end
312
+
313
+ it 'extracts the host' do
314
+ expect(hosts[0][:host]).to eq('host1')
315
+ expect(hosts[0][:protocol]).to eq('http')
316
+ expect(hosts[0][:port]).to be(1000)
317
+ expect(hosts[1][:host]).to eq('host2')
318
+ expect(hosts[1][:protocol]).to eq('http')
319
+ expect(hosts[1][:port]).to be(2000)
320
+ end
321
+ end
322
+ end
323
+
324
+ context 'when the hosts is an instance of URI' do
325
+
326
+ let(:host) do
327
+ URI.parse('https://USERNAME:PASSWORD@myhost:4430')
328
+ end
329
+
330
+ it 'extracts the host' do
331
+ expect(hosts[0][:host]).to eq('myhost')
332
+ expect(hosts[0][:scheme]).to eq('https')
333
+ expect(hosts[0][:port]).to be(4430)
334
+ expect(hosts[0][:user]).to eq('USERNAME')
335
+ expect(hosts[0][:password]).to eq('PASSWORD')
336
+ end
337
+ end
338
+
339
+ context 'when the hosts is invalid' do
340
+
341
+ let(:host) do
342
+ 123
343
+ end
344
+
345
+ it 'extracts the host' do
346
+ expect {
347
+ hosts
348
+ }.to raise_exception(ArgumentError)
349
+ end
350
+ end
351
+ end
352
+
353
+ context 'when hosts are specified with the \'host\' key' do
354
+
355
+ let(:client) do
356
+ described_class.new(hosts: ['host1', 'host2', 'host3', 'host4'], randomize_hosts: true)
357
+ end
358
+
359
+ let(:hosts) do
360
+ client.transport.hosts
361
+ end
362
+
363
+ it 'sets the hosts in random order' do
364
+ expect(hosts.all? { |host| client.transport.hosts.include?(host) }).to be(true)
365
+ end
366
+ end
367
+
368
+ context 'when hosts are specified with the \'host\' key' do
369
+
370
+ let(:client) do
371
+ described_class.new(host: host)
372
+ end
373
+
374
+ let(:hosts) do
375
+ client.transport.hosts
376
+ end
377
+
378
+ it_behaves_like 'a client that extracts hosts'
379
+ end
380
+
381
+ context 'when hosts are specified with the \'hosts\' key' do
382
+
383
+ let(:client) do
384
+ described_class.new(host: host)
385
+ end
386
+
387
+ let(:hosts) do
388
+ client.transport.hosts
389
+ end
390
+
391
+ it_behaves_like 'a client that extracts hosts'
392
+ end
393
+
394
+ context 'when hosts are specified with the \'url\' key' do
395
+
396
+ let(:client) do
397
+ described_class.new(host: host)
398
+ end
399
+
400
+ let(:hosts) do
401
+ client.transport.hosts
402
+ end
403
+
404
+ it_behaves_like 'a client that extracts hosts'
405
+ end
406
+
407
+ context 'when hosts are specified with the \'urls\' key' do
408
+
409
+ let(:client) do
410
+ described_class.new(host: host)
411
+ end
412
+
413
+ let(:hosts) do
414
+ client.transport.hosts
415
+ end
416
+
417
+ it_behaves_like 'a client that extracts hosts'
418
+ end
419
+
420
+ context 'when the URL is set in the ELASTICSEARCH_URL environment variable' do
421
+
422
+ context 'when there is only one host specified' do
423
+
424
+ around do |example|
425
+ before_url = ENV['ELASTICSEARCH_URL']
426
+ ENV['ELASTICSEARCH_URL'] = 'example.com'
427
+ example.run
428
+ ENV['ELASTICSEARCH_URL'] = before_url
429
+ end
430
+
431
+ it 'sets the host' do
432
+ expect(client.transport.hosts[0][:host]).to eq('example.com')
433
+ expect(client.transport.hosts.size).to eq(1)
434
+ end
435
+ end
436
+
437
+ context 'when mutliple hosts are specified as a comma-separated String list' do
438
+
439
+ around do |example|
440
+ before_url = ENV['ELASTICSEARCH_URL']
441
+ ENV['ELASTICSEARCH_URL'] = 'example.com, other.com'
442
+ example.run
443
+ ENV['ELASTICSEARCH_URL'] = before_url
444
+ end
445
+
446
+ it 'sets the hosts' do
447
+ expect(client.transport.hosts[0][:host]).to eq('example.com')
448
+ expect(client.transport.hosts[1][:host]).to eq('other.com')
449
+ expect(client.transport.hosts.size).to eq(2)
450
+ end
451
+ end
452
+ end
453
+
454
+ context 'when options are defined' do
455
+
456
+ context 'when scheme is specified' do
457
+
458
+ let(:client) do
459
+ described_class.new(scheme: 'https')
460
+ end
461
+
462
+ it 'sets the scheme' do
463
+ expect(client.transport.connections[0].full_url('')).to match(/https/)
464
+ end
465
+ end
466
+
467
+ context 'when user and password are specified' do
468
+
469
+ let(:client) do
470
+ described_class.new(user: 'USERNAME', password: 'PASSWORD')
471
+ end
472
+
473
+ it 'sets the user and password' do
474
+ expect(client.transport.connections[0].full_url('')).to match(/USERNAME/)
475
+ expect(client.transport.connections[0].full_url('')).to match(/PASSWORD/)
476
+ end
477
+
478
+ context 'when the connections are reloaded' do
479
+
480
+ before do
481
+ allow(client.transport.sniffer).to receive(:hosts).and_return([{ host: 'foobar', port: 4567, id: 'foobar4567' }])
482
+ client.transport.reload_connections!
483
+ end
484
+
485
+ it 'sets keeps user and password' do
486
+ expect(client.transport.connections[0].full_url('')).to match(/USERNAME/)
487
+ expect(client.transport.connections[0].full_url('')).to match(/PASSWORD/)
488
+ expect(client.transport.connections[0].full_url('')).to match(/foobar/)
489
+ end
490
+ end
491
+ end
492
+
493
+ context 'when port is specified' do
494
+
495
+ let(:client) do
496
+ described_class.new(host: 'node1', port: 1234)
497
+ end
498
+
499
+ it 'sets the port' do
500
+ expect(client.transport.connections[0].full_url('')).to match(/1234/)
501
+ end
502
+ end
503
+
504
+ context 'when the log option is true' do
505
+
506
+ let(:client) do
507
+ described_class.new(log: true)
508
+ end
509
+
510
+ it 'has a default logger for transport' do
511
+ expect(client.transport.logger.info).to eq(described_class::DEFAULT_LOGGER.call.info)
512
+ end
513
+ end
514
+
515
+ context 'when the trace option is true' do
516
+
517
+ let(:client) do
518
+ described_class.new(trace: true)
519
+ end
520
+
521
+ it 'has a default logger for transport' do
522
+ expect(client.transport.tracer.info).to eq(described_class::DEFAULT_TRACER.call.info)
523
+ end
524
+ end
525
+
526
+ context 'when a custom transport class is specified' do
527
+
528
+ let(:transport_class) do
529
+ Class.new { def initialize(*); end }
530
+ end
531
+
532
+ let(:client) do
533
+ described_class.new(transport_class: transport_class)
534
+ end
535
+
536
+ it 'allows the custom transport class to be defined' do
537
+ expect(client.transport).to be_a(transport_class)
538
+ end
539
+ end
540
+
541
+ context 'when a custom transport instance is specified' do
542
+
543
+ let(:transport_instance) do
544
+ Class.new { def initialize(*); end }.new
545
+ end
546
+
547
+ let(:client) do
548
+ described_class.new(transport: transport_instance)
549
+ end
550
+
551
+ it 'allows the custom transport class to be defined' do
552
+ expect(client.transport).to be(transport_instance)
553
+ end
554
+ end
555
+
556
+ context 'when \'transport_options\' are defined' do
557
+
558
+ let(:client) do
559
+ described_class.new(transport_options: { request: { timeout: 1 } })
560
+ end
561
+
562
+ it 'sets the options on the transport' do
563
+ expect(client.transport.options[:transport_options][:request]).to eq(timeout: 1)
564
+ end
565
+ end
566
+
567
+ context 'when \'request_timeout\' is defined' do
568
+
569
+ let(:client) do
570
+ described_class.new(request_timeout: 120)
571
+ end
572
+
573
+ it 'sets the options on the transport' do
574
+ expect(client.transport.options[:transport_options][:request]).to eq(timeout: 120)
575
+ end
576
+ end
577
+ end
578
+
579
+ describe '#perform_request' do
580
+
581
+ let(:transport_instance) do
582
+ Class.new { def initialize(*); end }.new
583
+ end
584
+
585
+ let(:client) do
586
+ described_class.new(transport: transport_instance)
587
+ end
588
+
589
+ it 'delegates performing requests to the transport' do
590
+ expect(transport_instance).to receive(:perform_request).and_return(true)
591
+ expect(client.perform_request('GET', '/')).to be(true)
592
+ end
593
+
594
+ context 'when the \'send_get_body_as\' option is specified' do
595
+
596
+ let(:client) do
597
+ described_class.new(transport: transport_instance, :send_get_body_as => 'POST')
598
+ end
599
+
600
+ before do
601
+ expect(transport_instance).to receive(:perform_request).with('POST', '/', {},
602
+ '{"foo":"bar"}',
603
+ '{"Content-Type":"application/x-ndjson"}').and_return(true)
604
+ end
605
+
606
+ let(:request) do
607
+ client.perform_request('POST', '/', {}, '{"foo":"bar"}', '{"Content-Type":"application/x-ndjson"}')
608
+ end
609
+
610
+ it 'sets the option' do
611
+ expect(request).to be(true)
612
+ end
613
+ end
614
+ end
615
+
616
+ context 'when the client connects to Elasticsearch' do
617
+
618
+ let(:logger) do
619
+ Logger.new(STDERR).tap do |logger|
620
+ logger.formatter = proc do |severity, datetime, progname, msg|
621
+ color = case severity
622
+ when /INFO/ then :green
623
+ when /ERROR|WARN|FATAL/ then :red
624
+ when /DEBUG/ then :cyan
625
+ else :white
626
+ end
627
+ ANSI.ansi(severity[0] + ' ', color, :faint) + ANSI.ansi(msg, :white, :faint) + "\n"
628
+ end
629
+ end unless ENV['QUIET']
630
+ end
631
+
632
+ let(:port) do
633
+ (ENV['TEST_CLUSTER_PORT'] || 9250).to_i
634
+ end
635
+
636
+ let(:transport_options) do
637
+ {}
638
+ end
639
+
640
+ let(:options) do
641
+ {}
642
+ end
643
+
644
+ let(:client) do
645
+ described_class.new({ host: hosts, logger: logger }.merge!(transport_options: transport_options).merge!(options))
646
+ end
647
+
648
+ context 'when a request is made' do
649
+
650
+ let!(:response) do
651
+ client.perform_request('GET', '_cluster/health')
652
+ end
653
+
654
+ it 'connects to the cluster' do
655
+ expect(response.body['number_of_nodes']).to be >(1)
656
+ end
657
+ end
658
+
659
+ describe '#initialize' do
660
+
661
+ context 'when options are specified' do
662
+
663
+ let(:transport_options) do
664
+ { headers: { accept: 'application/yaml', content_type: 'application/yaml' } }
665
+ end
666
+
667
+ let(:response) do
668
+ client.perform_request('GET', '_cluster/health')
669
+ end
670
+
671
+ it 'applies the options to the client' do
672
+ expect(response.body).to match(/---\n/)
673
+ expect(response.headers['content-type']).to eq('application/yaml')
674
+ end
675
+ end
676
+
677
+ context 'when a block is provided' do
678
+
679
+ let(:client) do
680
+ Elasticsearch::Client.new(host: ELASTICSEARCH_HOSTS.first, logger: logger) do |client|
681
+ client.headers['Accept'] = 'application/yaml'
682
+ end
683
+ end
684
+
685
+ let(:response) do
686
+ client.perform_request('GET', '_cluster/health')
687
+ end
688
+
689
+ it 'executes the block' do
690
+ expect(response.body).to match(/---\n/)
691
+ expect(response.headers['content-type']).to eq('application/yaml')
692
+ end
693
+
694
+ context 'when the Faraday adapter is set in the block' do
695
+
696
+ let(:client) do
697
+ Elasticsearch::Client.new(host: ELASTICSEARCH_HOSTS.first, logger: logger) do |client|
698
+ client.adapter(:net_http_persistent)
699
+ end
700
+ end
701
+
702
+ let(:connection_handler) do
703
+ client.transport.connections.first.connection.builder.handlers.first
704
+ end
705
+
706
+ let(:response) do
707
+ client.perform_request('GET', '_cluster/health')
708
+ end
709
+
710
+ it 'sets the adapter' do
711
+ expect(connection_handler.name).to eq('Faraday::Adapter::NetHttpPersistent')
712
+ end
713
+
714
+ it 'uses the adapter to connect' do
715
+ expect(response.status).to eq(200)
716
+ end
717
+ end
718
+ end
719
+ end
720
+
721
+ describe '#options' do
722
+
723
+ context 'when retry_on_failure is true' do
724
+
725
+ context 'when a node is unreachable' do
726
+
727
+ let(:hosts) do
728
+ [ELASTICSEARCH_HOSTS.first, "foobar1", "foobar2"]
729
+ end
730
+
731
+ let(:options) do
732
+ { retry_on_failure: true }
733
+ end
734
+
735
+ let(:responses) do
736
+ 5.times.collect do
737
+ client.perform_request('GET', '_nodes/_local')
738
+ end
739
+ end
740
+
741
+ it 'retries on failure' do
742
+ expect(responses.all? { true }).to be(true)
743
+ end
744
+ end
745
+ end
746
+
747
+ context 'when retry_on_failure is an integer' do
748
+
749
+ let(:hosts) do
750
+ [ELASTICSEARCH_HOSTS.first, 'foobar1', 'foobar2', 'foobar3']
751
+ end
752
+
753
+ let(:options) do
754
+ { retry_on_failure: 1 }
755
+ end
756
+
757
+ it 'retries only the specified number of times' do
758
+ expect(client.perform_request('GET', '_nodes/_local'))
759
+ expect {
760
+ client.perform_request('GET', '_nodes/_local')
761
+ }.to raise_exception(Faraday::Error::ConnectionFailed)
762
+ end
763
+ end
764
+
765
+ context 'when reload_on_failure is true' do
766
+
767
+ let(:hosts) do
768
+ [ELASTICSEARCH_HOSTS.first, 'foobar1', 'foobar2']
769
+ end
770
+
771
+ let(:options) do
772
+ { reload_on_failure: true }
773
+ end
774
+
775
+ let(:responses) do
776
+ 5.times.collect do
777
+ client.perform_request('GET', '_nodes/_local')
778
+ end
779
+ end
780
+
781
+ it 'reloads the connections' do
782
+ expect(client.transport.connections.size).to eq(3)
783
+ expect(responses.all? { true }).to be(true)
784
+ expect(client.transport.connections.size).to eq(2)
785
+ end
786
+ end
787
+
788
+ context 'when retry_on_status is specified' do
789
+
790
+ let(:options) do
791
+ { retry_on_status: 400 }
792
+ end
793
+
794
+ let(:logger) do
795
+ double('logger', :fatal => false)
796
+ end
797
+
798
+ before do
799
+ expect(logger).to receive(:warn).exactly(4).times
800
+ end
801
+
802
+ it 'retries when the status matches' do
803
+ expect {
804
+ client.perform_request('PUT', '_foobar')
805
+ }.to raise_exception(Elasticsearch::Transport::Transport::Errors::BadRequest)
806
+ end
807
+ end
808
+ end
809
+
810
+ describe '#perform_request' do
811
+
812
+ context 'when a request is made' do
813
+
814
+ before do
815
+ client.perform_request('DELETE', '_all')
816
+ client.perform_request('DELETE', 'myindex') rescue
817
+ client.perform_request('PUT', 'myindex', {}, { settings: { number_of_shards: 10 } })
818
+ client.perform_request('PUT', 'myindex/mydoc/1', { routing: 'XYZ', timeout: '1s' }, { foo: 'bar' })
819
+ client.perform_request('GET', '_cluster/health?wait_for_status=green', {})
820
+ end
821
+
822
+ let(:response) do
823
+ client.perform_request('GET', 'myindex/mydoc/1?routing=XYZ')
824
+ end
825
+
826
+ it 'handles paths and URL paramters' do
827
+ expect(response.status).to eq(200)
828
+ end
829
+
830
+ it 'returns response body' do
831
+ expect(response.body['_source']).to eq('foo' => 'bar')
832
+ end
833
+ end
834
+
835
+ context 'when an invalid url is specified' do
836
+
837
+ it 'raises an exception' do
838
+ expect {
839
+ client.perform_request('GET', 'myindex/mydoc/1?routing=FOOBARBAZ')
840
+ }.to raise_exception(Elasticsearch::Transport::Transport::Errors::NotFound)
841
+ end
842
+ end
843
+
844
+ context 'when the \'ignore\' parameter is specified' do
845
+
846
+ let(:response) do
847
+ client.perform_request('PUT', '_foobar', ignore: 400)
848
+ end
849
+
850
+ it 'exposes the status in the response' do
851
+ expect(response.status).to eq(400)
852
+ end
853
+
854
+ it 'exposes the body of the response' do
855
+ expect(response.body).to be_a(Hash)
856
+ expect(response.body.inspect).to match(/invalid_index_name_exception/)
857
+ end
858
+ end
859
+
860
+ context 'when request headers are specified' do
861
+
862
+ let(:response) do
863
+ client.perform_request('GET', '/', {}, nil, { 'Content-Type' => 'application/yaml' })
864
+ end
865
+
866
+ it 'passes them to the transport' do
867
+ expect(response.body).to match(/---/)
868
+ end
869
+ end
870
+
871
+ describe 'selector' do
872
+
873
+ context 'when the round-robin selector is used' do
874
+
875
+ let(:nodes) do
876
+ 3.times.collect do
877
+ client.perform_request('GET', '_nodes/_local').body['nodes'].to_a[0][1]['name']
878
+ end
879
+ end
880
+
881
+ let(:node_names) do
882
+ client.nodes.stats['nodes'].collect do |name, stats|
883
+ stats['name']
884
+ end
885
+ end
886
+
887
+ let(:expected_names) do
888
+ 3.times.collect do |i|
889
+ node_names[i % node_names.size]
890
+ end
891
+ end
892
+
893
+ # it 'rotates nodes' do
894
+ # pending 'Better way to detect rotating nodes'
895
+ # expect(nodes).to eq(expected_names)
896
+ # end
897
+ end
898
+ end
899
+
900
+ context 'when patron is used as an adapter', unless: jruby? do
901
+
902
+ before do
903
+ require 'patron'
904
+ end
905
+
906
+ let(:options) do
907
+ { adapter: :patron }
908
+ end
909
+
910
+ let(:connection_handler) do
911
+ client.transport.connections.first.connection.builder.handlers.first
912
+ end
913
+
914
+ it 'uses the patron connection handler' do
915
+ expect(connection_handler).to eq('Faraday::Adapter::Patron')
916
+ end
917
+
918
+ it 'keeps connections open' do
919
+ response = client.perform_request('GET', '_nodes/stats/http')
920
+ connections_before = response.body['nodes'].values.find { |n| n['name'] == node_names.first }['http']['total_opened']
921
+ client.transport.reload_connections!
922
+ response = client.perform_request('GET', '_nodes/stats/http')
923
+ connections_after = response.body['nodes'].values.find { |n| n['name'] == node_names.first }['http']['total_opened']
924
+ expect(connections_after).to be >= (connections_before)
925
+ end
926
+ end
927
+ end
928
+ end
929
+ end