elasticsearch-transport 6.8.3 → 7.0.0.pre
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.
- checksums.yaml +4 -4
- data/Gemfile +17 -0
- data/LICENSE.txt +199 -10
- data/README.md +32 -86
- data/Rakefile +23 -4
- data/elasticsearch-transport.gemspec +78 -45
- data/lib/elasticsearch-transport.rb +16 -3
- data/lib/elasticsearch/transport.rb +17 -3
- data/lib/elasticsearch/transport/client.rb +70 -174
- data/lib/elasticsearch/transport/redacted.rb +5 -9
- data/lib/elasticsearch/transport/transport/base.rb +63 -55
- data/lib/elasticsearch/transport/transport/connections/collection.rb +16 -3
- data/lib/elasticsearch/transport/transport/connections/connection.rb +16 -3
- data/lib/elasticsearch/transport/transport/connections/selector.rb +16 -3
- data/lib/elasticsearch/transport/transport/errors.rb +16 -3
- data/lib/elasticsearch/transport/transport/http/curb.rb +18 -5
- data/lib/elasticsearch/transport/transport/http/faraday.rb +18 -5
- data/lib/elasticsearch/transport/transport/http/manticore.rb +17 -4
- data/lib/elasticsearch/transport/transport/loggable.rb +85 -0
- data/lib/elasticsearch/transport/transport/response.rb +16 -3
- data/lib/elasticsearch/transport/transport/serializer/multi_json.rb +16 -3
- data/lib/elasticsearch/transport/transport/sniffer.rb +17 -5
- data/lib/elasticsearch/transport/version.rb +17 -4
- data/spec/elasticsearch/transport/base_spec.rb +8 -187
- data/spec/elasticsearch/transport/client_spec.rb +29 -163
- data/spec/elasticsearch/transport/sniffer_spec.rb +19 -0
- data/spec/spec_helper.rb +2 -11
- data/test/integration/transport_test.rb +18 -5
- data/test/profile/client_benchmark_test.rb +16 -3
- data/test/test_helper.rb +63 -14
- data/test/unit/connection_collection_test.rb +17 -4
- data/test/unit/connection_selector_test.rb +17 -4
- data/test/unit/connection_test.rb +17 -4
- data/test/unit/response_test.rb +18 -5
- data/test/unit/serializer_test.rb +17 -4
- data/test/unit/transport_base_test.rb +21 -8
- data/test/unit/transport_curb_test.rb +17 -4
- data/test/unit/transport_faraday_test.rb +17 -4
- data/test/unit/transport_manticore_test.rb +24 -6
- metadata +61 -86
- data/spec/elasticsearch/transport/meta_header_spec.rb +0 -214
@@ -1,7 +1,3 @@
|
|
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
|
4
|
-
|
5
1
|
# Licensed to Elasticsearch B.V. under one or more contributor
|
6
2
|
# license agreements. See the NOTICE file distributed with
|
7
3
|
# this work for additional information regarding copyright
|
@@ -49,99 +45,31 @@ describe Elasticsearch::Transport::Client do
|
|
49
45
|
expect(client.transport.hosts[0][:host]).to eq('localhost')
|
50
46
|
end
|
51
47
|
|
52
|
-
context 'when an encoded api_key is provided' do
|
53
|
-
let(:client) do
|
54
|
-
described_class.new(api_key: 'an_api_key')
|
55
|
-
end
|
56
|
-
let(:authorization_header) do
|
57
|
-
client.transport.connections.first.connection.headers['Authorization']
|
58
|
-
end
|
59
|
-
|
60
|
-
it 'Adds the ApiKey header to the connection' do
|
61
|
-
expect(authorization_header).to eq('ApiKey an_api_key')
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
context 'when an un-encoded api_key is provided' do
|
66
|
-
let(:client) do
|
67
|
-
described_class.new(api_key: { id: 'my_id', api_key: 'my_api_key' })
|
68
|
-
end
|
69
|
-
let(:authorization_header) do
|
70
|
-
client.transport.connections.first.connection.headers['Authorization']
|
71
|
-
end
|
72
|
-
|
73
|
-
it 'Adds the ApiKey header to the connection' do
|
74
|
-
expect(authorization_header).to eq("ApiKey #{Base64.strict_encode64('my_id:my_api_key')}")
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
context 'when basic auth and api_key are provided' do
|
79
|
-
let(:client) do
|
80
|
-
described_class.new(
|
81
|
-
api_key: { id: 'my_id', api_key: 'my_api_key' },
|
82
|
-
host: 'http://elastic:password@localhost:9200'
|
83
|
-
)
|
84
|
-
end
|
85
|
-
let(:authorization_header) do
|
86
|
-
client.transport.connections.first.connection.headers['Authorization']
|
87
|
-
end
|
88
|
-
|
89
|
-
it 'removes basic auth credentials' do
|
90
|
-
expect(authorization_header).not_to match(/^Basic/)
|
91
|
-
expect(authorization_header).to match(/^ApiKey/)
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
48
|
describe 'adapter' do
|
96
|
-
context 'when no adapter is specified' do
|
97
|
-
let(:adapter) do
|
98
|
-
client.transport.connections.all.first.connection.builder.adapter
|
99
|
-
end
|
100
49
|
|
101
|
-
|
102
|
-
expect(adapter).to eq Faraday::Adapter::NetHttp
|
103
|
-
end
|
104
|
-
end
|
50
|
+
context 'when no adapter is specified' do
|
105
51
|
|
106
|
-
context 'when the adapter is patron' do
|
107
52
|
let(:adapter) do
|
108
|
-
client.transport.connections.all.first.connection.builder.
|
109
|
-
end
|
110
|
-
|
111
|
-
let(:client) do
|
112
|
-
described_class.new(adapter: :patron, enable_meta_header: false)
|
53
|
+
client.transport.connections.all.first.connection.builder.handlers
|
113
54
|
end
|
114
55
|
|
115
|
-
it 'uses Faraday
|
116
|
-
expect(adapter).to
|
56
|
+
it 'uses Faraday NetHttp' do
|
57
|
+
expect(adapter).to include(Faraday::Adapter::NetHttp)
|
117
58
|
end
|
118
59
|
end
|
119
60
|
|
120
|
-
context 'when the adapter is
|
121
|
-
let(:adapter) do
|
122
|
-
client.transport.connections.all.first.connection.builder.adapter
|
123
|
-
end
|
124
|
-
|
125
|
-
let(:client) do
|
126
|
-
described_class.new(adapter: :typhoeus, enable_meta_header: false)
|
127
|
-
end
|
61
|
+
context 'when the adapter is specified' do
|
128
62
|
|
129
|
-
it 'uses Faraday with the adapter' do
|
130
|
-
expect(adapter).to eq Faraday::Adapter::Typhoeus
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
context 'when the adapter is specified as a string key' do
|
135
63
|
let(:adapter) do
|
136
|
-
client.transport.connections.all.first.connection.builder.
|
64
|
+
client.transport.connections.all.first.connection.builder.handlers
|
137
65
|
end
|
138
66
|
|
139
67
|
let(:client) do
|
140
|
-
described_class.new(
|
68
|
+
described_class.new(adapter: :typhoeus)
|
141
69
|
end
|
142
70
|
|
143
|
-
it 'uses Faraday
|
144
|
-
expect(adapter).to
|
71
|
+
it 'uses Faraday' do
|
72
|
+
expect(adapter).to include(Faraday::Adapter::Typhoeus)
|
145
73
|
end
|
146
74
|
end
|
147
75
|
|
@@ -153,11 +81,11 @@ describe Elasticsearch::Transport::Client do
|
|
153
81
|
end
|
154
82
|
|
155
83
|
let(:adapter) do
|
156
|
-
client.transport.connections.all.first.connection.builder.
|
84
|
+
client.transport.connections.all.first.connection.builder.handlers
|
157
85
|
end
|
158
86
|
|
159
87
|
it 'uses the detected adapter' do
|
160
|
-
expect(adapter).to
|
88
|
+
expect(adapter).to include(Faraday::Adapter::Patron)
|
161
89
|
end
|
162
90
|
end
|
163
91
|
|
@@ -165,21 +93,17 @@ describe Elasticsearch::Transport::Client do
|
|
165
93
|
|
166
94
|
let(:client) do
|
167
95
|
described_class.new do |faraday|
|
168
|
-
faraday.adapter :
|
96
|
+
faraday.adapter :typhoeus
|
169
97
|
faraday.response :logger
|
170
98
|
end
|
171
99
|
end
|
172
100
|
|
173
|
-
let(:adapter) do
|
174
|
-
client.transport.connections.all.first.connection.builder.adapter
|
175
|
-
end
|
176
|
-
|
177
101
|
let(:handlers) do
|
178
102
|
client.transport.connections.all.first.connection.builder.handlers
|
179
103
|
end
|
180
104
|
|
181
105
|
it 'sets the adapter' do
|
182
|
-
expect(
|
106
|
+
expect(handlers).to include(Faraday::Adapter::Typhoeus)
|
183
107
|
end
|
184
108
|
|
185
109
|
it 'sets the logger' do
|
@@ -230,6 +154,7 @@ describe Elasticsearch::Transport::Client do
|
|
230
154
|
end
|
231
155
|
|
232
156
|
context 'when credentials are specified' do
|
157
|
+
|
233
158
|
let(:host) do
|
234
159
|
'http://USERNAME:PASSWORD@myhost:8080'
|
235
160
|
end
|
@@ -686,43 +611,10 @@ describe Elasticsearch::Transport::Client do
|
|
686
611
|
expect(request).to be(true)
|
687
612
|
end
|
688
613
|
end
|
689
|
-
|
690
|
-
context 'when x-opaque-id is set' do
|
691
|
-
let(:client) { described_class.new(host: hosts) }
|
692
|
-
|
693
|
-
it 'uses x-opaque-id on a request' do
|
694
|
-
expect(client.perform_request('GET', '/', { opaque_id: '12345' }).headers['x-opaque-id']).to eq('12345')
|
695
|
-
end
|
696
|
-
end
|
697
|
-
|
698
|
-
context 'when an x-opaque-id prefix is set on initialization' do
|
699
|
-
let(:prefix) { 'elastic_cloud' }
|
700
|
-
let(:client) do
|
701
|
-
described_class.new(host: hosts, opaque_id_prefix: prefix)
|
702
|
-
end
|
703
|
-
|
704
|
-
it 'uses x-opaque-id on a request' do
|
705
|
-
expect(client.perform_request('GET', '/', { opaque_id: '12345' }).headers['x-opaque-id']).to eq("#{prefix}12345")
|
706
|
-
end
|
707
|
-
|
708
|
-
context 'when using an API call' do
|
709
|
-
let(:client) { described_class.new(host: hosts) }
|
710
|
-
|
711
|
-
it 'doesnae raise an ArgumentError' do
|
712
|
-
expect { client.search(opaque_id: 'no_error') }.not_to raise_error
|
713
|
-
end
|
714
|
-
|
715
|
-
it 'uses X-Opaque-Id in the header' do
|
716
|
-
allow(client).to receive(:perform_request) { OpenStruct.new(body: '') }
|
717
|
-
expect { client.search(opaque_id: 'opaque_id') }.not_to raise_error
|
718
|
-
expect(client).to have_received(:perform_request)
|
719
|
-
.with('GET', '_search', { opaque_id: 'opaque_id' }, nil)
|
720
|
-
end
|
721
|
-
end
|
722
|
-
end
|
723
614
|
end
|
724
615
|
|
725
616
|
context 'when the client connects to Elasticsearch' do
|
617
|
+
|
726
618
|
let(:logger) do
|
727
619
|
Logger.new(STDERR).tap do |logger|
|
728
620
|
logger.formatter = proc do |severity, datetime, progname, msg|
|
@@ -738,7 +630,7 @@ describe Elasticsearch::Transport::Client do
|
|
738
630
|
end
|
739
631
|
|
740
632
|
let(:port) do
|
741
|
-
|
633
|
+
TEST_PORT
|
742
634
|
end
|
743
635
|
|
744
636
|
let(:transport_options) do
|
@@ -760,7 +652,7 @@ describe Elasticsearch::Transport::Client do
|
|
760
652
|
end
|
761
653
|
|
762
654
|
it 'connects to the cluster' do
|
763
|
-
expect(response.body['number_of_nodes']).to be
|
655
|
+
expect(response.body['number_of_nodes']).to be >= (1)
|
764
656
|
end
|
765
657
|
end
|
766
658
|
|
@@ -800,14 +692,15 @@ describe Elasticsearch::Transport::Client do
|
|
800
692
|
end
|
801
693
|
|
802
694
|
context 'when the Faraday adapter is set in the block' do
|
695
|
+
|
803
696
|
let(:client) do
|
804
697
|
Elasticsearch::Client.new(host: ELASTICSEARCH_HOSTS.first, logger: logger) do |client|
|
805
698
|
client.adapter(:net_http_persistent)
|
806
699
|
end
|
807
700
|
end
|
808
701
|
|
809
|
-
let(:
|
810
|
-
client.transport.connections.first.connection.builder.
|
702
|
+
let(:connection_handler) do
|
703
|
+
client.transport.connections.first.connection.builder.handlers.first
|
811
704
|
end
|
812
705
|
|
813
706
|
let(:response) do
|
@@ -815,7 +708,7 @@ describe Elasticsearch::Transport::Client do
|
|
815
708
|
end
|
816
709
|
|
817
710
|
it 'sets the adapter' do
|
818
|
-
expect(
|
711
|
+
expect(connection_handler.name).to eq('Faraday::Adapter::NetHttpPersistent')
|
819
712
|
end
|
820
713
|
|
821
714
|
it 'uses the adapter to connect' do
|
@@ -865,7 +758,7 @@ describe Elasticsearch::Transport::Client do
|
|
865
758
|
expect(client.perform_request('GET', '_nodes/_local'))
|
866
759
|
expect {
|
867
760
|
client.perform_request('GET', '_nodes/_local')
|
868
|
-
}.to raise_exception(Faraday::ConnectionFailed)
|
761
|
+
}.to raise_exception(Faraday::Error::ConnectionFailed)
|
869
762
|
end
|
870
763
|
end
|
871
764
|
|
@@ -888,7 +781,7 @@ describe Elasticsearch::Transport::Client do
|
|
888
781
|
it 'reloads the connections' do
|
889
782
|
expect(client.transport.connections.size).to eq(3)
|
890
783
|
expect(responses.all? { true }).to be(true)
|
891
|
-
expect(client.transport.connections.size).to
|
784
|
+
expect(client.transport.connections.size).to be >= (1)
|
892
785
|
end
|
893
786
|
end
|
894
787
|
|
@@ -899,7 +792,7 @@ describe Elasticsearch::Transport::Client do
|
|
899
792
|
end
|
900
793
|
|
901
794
|
let(:logger) do
|
902
|
-
double('logger', :fatal => false)
|
795
|
+
double('logger', :debug? => false, :warn? => true, :fatal? => false, :error? => false)
|
903
796
|
end
|
904
797
|
|
905
798
|
before do
|
@@ -921,7 +814,7 @@ describe Elasticsearch::Transport::Client do
|
|
921
814
|
before do
|
922
815
|
client.perform_request('DELETE', '_all')
|
923
816
|
client.perform_request('DELETE', 'myindex') rescue
|
924
|
-
|
817
|
+
client.perform_request('PUT', 'myindex', {}, { settings: { number_of_shards: 2, number_of_replicas: 0 } })
|
925
818
|
client.perform_request('PUT', 'myindex/mydoc/1', { routing: 'XYZ', timeout: '1s' }, { foo: 'bar' })
|
926
819
|
client.perform_request('GET', '_cluster/health?wait_for_status=green', {})
|
927
820
|
end
|
@@ -1014,39 +907,12 @@ describe Elasticsearch::Transport::Client do
|
|
1014
907
|
{ adapter: :patron }
|
1015
908
|
end
|
1016
909
|
|
1017
|
-
let(:
|
1018
|
-
client.transport.connections.first.connection.builder.
|
1019
|
-
end
|
1020
|
-
|
1021
|
-
it 'uses the patron connection handler' do
|
1022
|
-
expect(adapter).to eq('Faraday::Adapter::Patron')
|
1023
|
-
end
|
1024
|
-
|
1025
|
-
it 'keeps connections open' do
|
1026
|
-
response = client.perform_request('GET', '_nodes/stats/http')
|
1027
|
-
connections_before = response.body['nodes'].values.find { |n| n['name'] == node_names.first }['http']['total_opened']
|
1028
|
-
client.transport.reload_connections!
|
1029
|
-
response = client.perform_request('GET', '_nodes/stats/http')
|
1030
|
-
connections_after = response.body['nodes'].values.find { |n| n['name'] == node_names.first }['http']['total_opened']
|
1031
|
-
expect(connections_after).to be >= (connections_before)
|
1032
|
-
end
|
1033
|
-
end
|
1034
|
-
|
1035
|
-
context 'when typhoeus is used as an adapter', unless: jruby? do
|
1036
|
-
before do
|
1037
|
-
require 'typhoeus'
|
1038
|
-
end
|
1039
|
-
|
1040
|
-
let(:options) do
|
1041
|
-
{ adapter: :typhoeus }
|
1042
|
-
end
|
1043
|
-
|
1044
|
-
let(:adapter) do
|
1045
|
-
client.transport.connections.first.connection.builder.adapter
|
910
|
+
let(:connection_handler) do
|
911
|
+
client.transport.connections.first.connection.builder.handlers.first
|
1046
912
|
end
|
1047
913
|
|
1048
914
|
it 'uses the patron connection handler' do
|
1049
|
-
expect(
|
915
|
+
expect(connection_handler).to eq('Faraday::Adapter::Patron')
|
1050
916
|
end
|
1051
917
|
|
1052
918
|
it 'keeps connections open' do
|
@@ -1060,4 +926,4 @@ describe Elasticsearch::Transport::Client do
|
|
1060
926
|
end
|
1061
927
|
end
|
1062
928
|
end
|
1063
|
-
end
|
929
|
+
end
|
@@ -229,6 +229,25 @@ describe Elasticsearch::Transport::Transport::Sniffer do
|
|
229
229
|
it 'correctly parses the port' do
|
230
230
|
expect(hosts[0][:port]).to eq('9250')
|
231
231
|
end
|
232
|
+
|
233
|
+
context 'when the address is IPv6' do
|
234
|
+
|
235
|
+
let(:publish_address) do
|
236
|
+
'example.com/[::1]:9250'
|
237
|
+
end
|
238
|
+
|
239
|
+
it 'parses the response' do
|
240
|
+
expect(hosts.size).to eq(1)
|
241
|
+
end
|
242
|
+
|
243
|
+
it 'uses the hostname' do
|
244
|
+
expect(hosts[0][:host]).to eq('example.com')
|
245
|
+
end
|
246
|
+
|
247
|
+
it 'correctly parses the port' do
|
248
|
+
expect(hosts[0][:port]).to eq('9250')
|
249
|
+
end
|
250
|
+
end
|
232
251
|
end
|
233
252
|
|
234
253
|
context 'when the address is IPv6' do
|
data/spec/spec_helper.rb
CHANGED
@@ -1,20 +1,9 @@
|
|
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
|
4
|
-
|
5
1
|
require 'elasticsearch'
|
6
2
|
require 'elasticsearch-transport'
|
7
3
|
require 'logger'
|
8
4
|
require 'ansi/code'
|
9
5
|
require 'hashie/mash'
|
10
6
|
require 'pry-nav'
|
11
|
-
if defined?(JRUBY_VERSION)
|
12
|
-
require 'elasticsearch/transport/transport/http/manticore'
|
13
|
-
require 'pry-nav'
|
14
|
-
else
|
15
|
-
require 'elasticsearch/transport/transport/http/curb'
|
16
|
-
require 'curb'
|
17
|
-
end
|
18
7
|
|
19
8
|
# The hosts to use for creating a elasticsearch client.
|
20
9
|
#
|
@@ -25,6 +14,8 @@ ELASTICSEARCH_HOSTS = if hosts = ENV['TEST_ES_SERVER'] || ENV['ELASTICSEARCH_HOS
|
|
25
14
|
end
|
26
15
|
end.freeze
|
27
16
|
|
17
|
+
TEST_HOST, TEST_PORT = ELASTICSEARCH_HOSTS.first.split(':') if ELASTICSEARCH_HOSTS
|
18
|
+
|
28
19
|
# Are we testing on JRuby?
|
29
20
|
#
|
30
21
|
# @return [ true, false ] Whether JRuby is being used.
|
@@ -1,6 +1,19 @@
|
|
1
|
-
# Licensed to Elasticsearch B.V under one or more
|
2
|
-
#
|
3
|
-
#
|
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 'test_helper'
|
6
19
|
|
@@ -52,7 +65,7 @@ class Elasticsearch::Transport::ClientIntegrationTest < Elasticsearch::Test::Int
|
|
52
65
|
require 'elasticsearch/transport/transport/http/curb'
|
53
66
|
|
54
67
|
transport = Elasticsearch::Transport::Transport::HTTP::Curb.new \
|
55
|
-
:hosts => [ { host: @host, port: @port } ]
|
68
|
+
:hosts => [ { host: @host, port: @port } ] do |curl|
|
56
69
|
curl.verbose = true
|
57
70
|
end
|
58
71
|
|
@@ -65,7 +78,7 @@ class Elasticsearch::Transport::ClientIntegrationTest < Elasticsearch::Test::Int
|
|
65
78
|
require 'elasticsearch/transport/transport/http/curb'
|
66
79
|
|
67
80
|
transport = Elasticsearch::Transport::Transport::HTTP::Curb.new \
|
68
|
-
:hosts => [ { host: @host, port: @port } ]
|
81
|
+
:hosts => [ { host: @host, port: @port } ] do |curl|
|
69
82
|
curl.verbose = true
|
70
83
|
end
|
71
84
|
|
@@ -1,6 +1,19 @@
|
|
1
|
-
# Licensed to Elasticsearch B.V under one or more
|
2
|
-
#
|
3
|
-
#
|
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 'test_helper'
|
6
19
|
|