opensearch-transport 1.0.0

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