Skip to content

Commit f3c46b2

Browse files
authored
Add some test cases (#16)
1 parent 2453cb4 commit f3c46b2

File tree

7 files changed

+238
-39
lines changed

7 files changed

+238
-39
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Redis Cluster Client
77
TODO
88

99
```ruby
10-
cli = RedisClient.cluster(nodes: %w[redis://127.0.0.1:7000]).new_client
10+
cli = RedisClient.cluster(nodes: %w[redis://127.0.0.1:6379]).new_client
1111

1212
cli.call('PING')
1313
#=> PONG

Rakefile

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@ task default: :test
77
Rake::TestTask.new :test do |t|
88
t.libs << :test
99
t.libs << :lib
10-
t.test_files = Dir['test/**/test_*.rb']
10+
t.test_files = ARGV.size == 1 ? Dir['test/**/test_*.rb'] : ARGV[1..]
11+
t.options = '-v'
1112
end
1213

1314
desc 'Wait for cluster to be ready'
1415
task :wait do
1516
$LOAD_PATH.unshift(File.expand_path('test', __dir__))
1617
require 'redis_client/cluster/controller'
17-
nodes = (7000..7005).map { |port| "#{ENV.fetch('REDIS_SCHEME', 'redis')}://127.0.0.1:#{port}" }
18+
nodes = (6379..6384).map { |port| "#{ENV.fetch('REDIS_SCHEME', 'redis')}://127.0.0.1:#{port}" }
1819
ctrl = ::RedisClient::Cluster::Controller.new(nodes)
1920
ctrl.wait_for_cluster_to_be_ready
2021
end

docker-compose.yaml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,27 +16,27 @@ services:
1616
timeout: "5s"
1717
retries: 10
1818
ports:
19-
- "7000:6379"
19+
- "6379:6379"
2020
node2:
2121
<<: *node
2222
ports:
23-
- "7001:6379"
23+
- "6380:6379"
2424
node3:
2525
<<: *node
2626
ports:
27-
- "7002:6379"
27+
- "6381:6379"
2828
node4:
2929
<<: *node
3030
ports:
31-
- "7003:6379"
31+
- "6382:6379"
3232
node5:
3333
<<: *node
3434
ports:
35-
- "7004:6379"
35+
- "6383:6379"
3636
node6:
3737
<<: *node
3838
ports:
39-
- "7005:6379"
39+
- "6384:6379"
4040
clustering:
4141
image: "redis:${REDIS_VERSION:-7}"
4242
command: >

lib/redis_client/cluster_config.rb

Lines changed: 43 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,26 @@
77

88
class RedisClient
99
class ClusterConfig
10+
DEFAULT_HOST = '127.0.0.1'
11+
DEFAULT_PORT = 6379
1012
DEFAULT_SCHEME = 'redis'
1113
SECURE_SCHEME = 'rediss'
14+
DEFAULT_NODES = ["#{DEFAULT_SCHEME}://#{DEFAULT_HOST}:#{DEFAULT_PORT}"].freeze
1215
VALID_SCHEMES = [DEFAULT_SCHEME, SECURE_SCHEME].freeze
16+
VALID_NODES_KEYS = %i[ssl username password host port db].freeze
17+
MERGE_CONFIG_KEYS = %i[ssl username password].freeze
18+
1319
InvalidClientConfigError = Class.new(::RedisClient::Error)
1420

15-
def initialize(nodes:, replica: false, fixed_hostname: nil, **client_config)
16-
@replica = replica
17-
@fixed_hostname = fixed_hostname
18-
@client_config = client_config.dup
21+
def initialize(nodes: DEFAULT_NODES, replica: false, fixed_hostname: '', **client_config)
22+
@replica = true & replica
23+
@fixed_hostname = fixed_hostname.to_s
1924
@node_configs = build_node_configs(nodes.dup)
20-
add_common_node_config_if_needed(@client_config, @node_configs, :ssl)
21-
add_common_node_config_if_needed(@client_config, @node_configs, :username)
22-
add_common_node_config_if_needed(@client_config, @node_configs, :password)
25+
@client_config = merge_generic_config(client_config, @node_configs)
2326
end
2427

2528
def inspect
26-
per_node_key.to_s
29+
"#<#{self.class.name}: #{per_node_key.values}>"
2730
end
2831

2932
def new_pool(size: 5, timeout: 5, **kwargs)
@@ -38,7 +41,7 @@ def per_node_key
3841
@node_configs.to_h do |config|
3942
node_key = ::RedisClient::Cluster::NodeKey.build_from_host_port(config[:host], config[:port])
4043
config = @client_config.merge(config)
41-
config = config.merge(host: @fixed_hostname) if @fixed_hostname && !@fixed_hostname.empty?
44+
config = config.merge(host: @fixed_hostname) unless @fixed_hostname.empty?
4245
[node_key, config]
4346
end
4447
end
@@ -62,9 +65,10 @@ def dup
6265
private
6366

6467
def build_node_configs(addrs)
65-
raise InvalidClientConfigError, 'Redis option of `cluster` must be an Array' unless addrs.is_a?(Array)
68+
configs = Array[addrs].flatten.filter_map { |addr| parse_node_addr(addr) }
69+
raise InvalidClientConfigError, '`nodes` option is empty' if configs.size.zero?
6670

67-
addrs.map { |addr| parse_node_addr(addr) }
71+
configs
6872
end
6973

7074
def parse_node_addr(addr)
@@ -74,41 +78,51 @@ def parse_node_addr(addr)
7478
when Hash
7579
parse_node_option(addr)
7680
else
77-
raise InvalidClientConfigError, 'Redis option of `cluster` must includes String or Hash'
81+
raise InvalidClientConfigError, "`nodes` option includes invalid type values: #{addr}"
7882
end
7983
end
8084

81-
def parse_node_url(addr) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
85+
def parse_node_url(addr) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
86+
return if addr.empty?
87+
8288
uri = URI(addr)
83-
raise InvalidClientConfigError, "Invalid uri scheme #{addr}" unless VALID_SCHEMES.include?(uri.scheme)
89+
scheme = uri.scheme || DEFAULT_SCHEME
90+
raise InvalidClientConfigError, "`nodes` option includes a invalid uri scheme: #{addr}" unless VALID_SCHEMES.include?(scheme)
8491

85-
db = uri.path.split('/')[1]&.to_i
8692
username = uri.user ? URI.decode_www_form_component(uri.user) : nil
8793
password = uri.password ? URI.decode_www_form_component(uri.password) : nil
94+
host = uri.host || DEFAULT_HOST
95+
port = uri.port || DEFAULT_PORT
96+
db = uri.path.index('/').nil? ? uri.path : uri.path.split('/')[1]
97+
db = db.nil? || db.empty? ? db : ensure_integer(db)
8898

89-
{
90-
host: uri.host,
91-
port: uri.port,
92-
username: username,
93-
password: password,
94-
db: db,
95-
ssl: uri.scheme == SECURE_SCHEME
96-
}.reject { |_, v| v.nil? || v == '' }
99+
{ ssl: scheme == SECURE_SCHEME, username: username, password: password, host: host, port: port, db: db }
100+
.reject { |_, v| v.nil? || v == '' || v == false }
97101
rescue URI::InvalidURIError => e
98-
raise InvalidClientConfigError, e.message
102+
raise InvalidClientConfigError, "#{e.message}: #{addr}"
99103
end
100104

101105
def parse_node_option(addr)
106+
return if addr.empty?
107+
102108
addr = addr.transform_keys(&:to_sym)
103-
raise InvalidClientConfigError, 'Redis option of `cluster` must includes `:host` and `:port` keys' if addr.values_at(:host, :port).any?(&:nil?)
109+
addr[:host] ||= DEFAULT_HOST
110+
addr[:port] = ensure_integer(addr[:port] || DEFAULT_PORT)
111+
addr.select { |k, _| VALID_NODES_KEYS.include?(k) }
112+
end
104113

105-
addr
114+
def ensure_integer(value)
115+
Integer(value)
116+
rescue ArgumentError => e
117+
raise InvalidClientConfigError, e.message
106118
end
107119

108-
def add_common_node_config_if_needed(client_config, node_configs, key)
109-
return client_config if client_config[key].nil? && node_configs.first[key].nil?
120+
def merge_generic_config(client_config, node_configs)
121+
return client_config if node_configs.size.zero?
110122

111-
client_config[key] ||= node_configs.first[key]
123+
cfg = node_configs.first
124+
MERGE_CONFIG_KEYS.each { |k| client_config[k] = cfg[k] if cfg.key?(k) }
125+
client_config
112126
end
113127
end
114128
end
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
# frozen_string_literal: true
2+
3+
require 'testing_helper'
4+
require 'redis_client/cluster_config'
5+
6+
class RedisClient
7+
class TestClusterConfig < Minitest::Test
8+
def test_inspect
9+
want = '#<RedisClient::ClusterConfig: [{:host=>"127.0.0.1", :port=>6379}]>'
10+
got = ::RedisClient::ClusterConfig.new.inspect
11+
assert_equal(want, got)
12+
end
13+
14+
def test_new_pool
15+
assert_instance_of(::RedisClient::Cluster, ::RedisClient::ClusterConfig.new.new_pool)
16+
end
17+
18+
def test_new_client
19+
assert_instance_of(::RedisClient::Cluster, ::RedisClient::ClusterConfig.new.new_client)
20+
end
21+
22+
def test_per_node_key
23+
[
24+
{
25+
config: ::RedisClient::ClusterConfig.new,
26+
want: {
27+
'127.0.0.1:6379' => { host: '127.0.0.1', port: 6379 }
28+
}
29+
},
30+
{
31+
config: ::RedisClient::ClusterConfig.new(replica: true),
32+
want: {
33+
'127.0.0.1:6379' => { host: '127.0.0.1', port: 6379 }
34+
}
35+
},
36+
{
37+
config: ::RedisClient::ClusterConfig.new(fixed_hostname: 'endpoint.example.com'),
38+
want: {
39+
'127.0.0.1:6379' => { host: 'endpoint.example.com', port: 6379 }
40+
}
41+
},
42+
{
43+
config: ::RedisClient::ClusterConfig.new(timeout: 1),
44+
want: {
45+
'127.0.0.1:6379' => { host: '127.0.0.1', port: 6379, timeout: 1 }
46+
}
47+
}
48+
].each_with_index do |c, idx|
49+
assert_equal(c[:want], c[:config].per_node_key, "Case: #{idx}")
50+
end
51+
end
52+
53+
def test_use_replica?
54+
assert_predicate(::RedisClient::ClusterConfig.new(replica: true), :use_replica?)
55+
refute_predicate(::RedisClient::ClusterConfig.new(replica: false), :use_replica?)
56+
refute_predicate(::RedisClient::ClusterConfig.new, :use_replica?)
57+
end
58+
59+
def test_update_node
60+
config = ::RedisClient::ClusterConfig.new(nodes: %w[redis://127.0.0.1:6379])
61+
assert_equal([{ host: '127.0.0.1', port: 6379 }], config.instance_variable_get(:@node_configs))
62+
config.update_node(%w[redis://127.0.0.2:6380])
63+
assert_equal([{ host: '127.0.0.2', port: 6380 }], config.instance_variable_get(:@node_configs))
64+
end
65+
66+
def test_add_node
67+
config = ::RedisClient::ClusterConfig.new(nodes: %w[redis://127.0.0.1:6379])
68+
assert_equal([{ host: '127.0.0.1', port: 6379 }], config.instance_variable_get(:@node_configs))
69+
config.add_node('127.0.0.2', 6380)
70+
assert_equal([{ host: '127.0.0.1', port: 6379 }, { host: '127.0.0.2', port: 6380 }], config.instance_variable_get(:@node_configs))
71+
end
72+
73+
def test_dup
74+
orig = ::RedisClient::ClusterConfig.new
75+
copy = orig.dup
76+
refute_equal(orig.object_id, copy.object_id)
77+
end
78+
79+
def test_build_node_configs
80+
config = ::RedisClient::ClusterConfig.new
81+
[
82+
{ addrs: %w[redis://127.0.0.1], want: [{ host: '127.0.0.1', port: 6379 }] },
83+
{ addrs: %w[redis://127.0.0.1:6379], want: [{ host: '127.0.0.1', port: 6379 }] },
84+
{ addrs: %w[redis://127.0.0.1:6379/1], want: [{ host: '127.0.0.1', port: 6379, db: 1 }] },
85+
{ addrs: %w[redis://127.0.0.1:6379 redis://127.0.0.2:6380], want: [{ host: '127.0.0.1', port: 6379 }, { host: '127.0.0.2', port: 6380 }] },
86+
{ addrs: %w[rediss://foo:[email protected]:6379], want: [{ ssl: true, username: 'foo', password: 'bar', host: '127.0.0.1', port: 6379 }] },
87+
{ addrs: %w[redis://[email protected]:6379], want: [{ host: '127.0.0.1', port: 6379, username: 'foo' }] },
88+
{ addrs: %w[redis://:[email protected]:6379], want: [{ host: '127.0.0.1', port: 6379, password: 'bar' }] },
89+
{ addrs: [{ host: '127.0.0.1', port: 6379 }], want: [{ host: '127.0.0.1', port: 6379 }] },
90+
{ addrs: [{ host: '127.0.0.1', port: 6379 }, { host: '127.0.0.2', port: '6380' }], want: [{ host: '127.0.0.1', port: 6379 }, { host: '127.0.0.2', port: 6380 }] },
91+
{ addrs: [{ host: '127.0.0.1', port: 6379, username: 'foo', password: 'bar', ssl: true }], want: [{ ssl: true, username: 'foo', password: 'bar', host: '127.0.0.1', port: 6379 }] },
92+
{ addrs: [{ host: '127.0.0.1', port: 6379, db: 1 }], want: [{ host: '127.0.0.1', port: 6379, db: 1 }] },
93+
{ addrs: 'redis://127.0.0.1:6379', want: [{ host: '127.0.0.1', port: 6379 }] },
94+
{ addrs: { host: '127.0.0.1', port: 6379 }, want: [{ host: '127.0.0.1', port: 6379 }] },
95+
{ addrs: [{ host: '127.0.0.1' }], want: [{ host: '127.0.0.1', port: 6379 }] },
96+
{ addrs: %w[http://127.0.0.1:80], error: ::RedisClient::ClusterConfig::InvalidClientConfigError },
97+
{ addrs: [{ host: '127.0.0.1', port: 'foo' }], error: ::RedisClient::ClusterConfig::InvalidClientConfigError },
98+
{ addrs: %w[redis://127.0.0.1:foo], error: ::RedisClient::ClusterConfig::InvalidClientConfigError },
99+
{ addrs: [6379], error: ::RedisClient::ClusterConfig::InvalidClientConfigError },
100+
{ addrs: ['foo'], error: ::RedisClient::ClusterConfig::InvalidClientConfigError },
101+
{ addrs: [''], error: ::RedisClient::ClusterConfig::InvalidClientConfigError },
102+
{ addrs: [{}], error: ::RedisClient::ClusterConfig::InvalidClientConfigError },
103+
{ addrs: [], error: ::RedisClient::ClusterConfig::InvalidClientConfigError },
104+
{ addrs: {}, error: ::RedisClient::ClusterConfig::InvalidClientConfigError },
105+
{ addrs: '', error: ::RedisClient::ClusterConfig::InvalidClientConfigError },
106+
{ addrs: nil, error: ::RedisClient::ClusterConfig::InvalidClientConfigError }
107+
].each_with_index do |c, idx|
108+
msg = "Case: #{idx}: #{c}"
109+
got = -> { config.send(:build_node_configs, c[:addrs]) }
110+
if c.key?(:error)
111+
assert_raises(c[:error], msg, &got)
112+
else
113+
assert_equal(c.fetch(:want), got.call, msg)
114+
end
115+
end
116+
end
117+
118+
def test_merge_generic_config
119+
config = ::RedisClient::ClusterConfig.new
120+
[
121+
{
122+
params: {
123+
client_config: { ssl: false, username: 'foo', password: 'bar', timeout: 1 },
124+
node_configs: [{ ssl: true, username: 'baz', password: 'zap', host: '127.0.0.1' }]
125+
},
126+
want: { ssl: true, username: 'baz', password: 'zap', timeout: 1 }
127+
},
128+
{
129+
params: {
130+
client_config: { ssl: false, timeout: 1 },
131+
node_configs: [{ ssl: true, host: '127.0.0.1' }]
132+
},
133+
want: { ssl: true, timeout: 1 }
134+
},
135+
{
136+
params: {
137+
client_config: { timeout: 1 },
138+
node_configs: [{ ssl: true }]
139+
},
140+
want: { ssl: true, timeout: 1 }
141+
},
142+
{ params: { client_config: {}, node_configs: [], keys: [] }, want: {} }
143+
].each_with_index do |c, idx|
144+
msg = "Case: #{idx}"
145+
got = config.send(:merge_generic_config, c[:params][:client_config], c[:params][:node_configs])
146+
assert_equal(c[:want], got, msg)
147+
end
148+
end
149+
end
150+
end

test/test_redis_cluster_client.rb

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# frozen_string_literal: true
2+
3+
require 'testing_helper'
4+
require 'redis_cluster_client'
5+
require 'redis_client/cluster_config'
6+
7+
class TestRedisClient < Minitest::Test
8+
def test_cluster
9+
[
10+
{ kwargs: {}, error: nil },
11+
{ kwargs: { nodes: 'redis://127.0.0.1:6379' }, error: nil },
12+
{ kwargs: { nodes: { host: '127.0.0.1' } }, error: nil },
13+
{ kwargs: { nodes: { port: 6379 } }, error: nil },
14+
{ kwargs: { nodes: { host: '127.0.0.1', port: 6379 } }, error: nil },
15+
{ kwargs: { nodes: [{ host: '127.0.0.1', port: 6379 }] }, error: nil },
16+
{ kwargs: { nodes: %w[redis://127.0.0.1:6379] }, error: nil },
17+
{ kwargs: { nodes: %w[redis://127.0.0.1:6379], replica: true }, error: nil },
18+
{ kwargs: { nodes: %w[redis://127.0.0.1:6379], replica: true, fixed_hostname: 'endpoint.example.com' }, error: nil },
19+
{ kwargs: { nodes: %w[redis://127.0.0.1:6379], replica: 1, fixed_hostname: '' }, error: nil },
20+
{ kwargs: { nodes: %w[redis://127.0.0.1:6379], foo: 'bar' }, error: nil },
21+
{ kwargs: { nodes: 'http://127.0.0.1:80' }, error: ::RedisClient::ClusterConfig::InvalidClientConfigError },
22+
{ kwargs: { nodes: [] }, error: ::RedisClient::ClusterConfig::InvalidClientConfigError },
23+
{ kwargs: { nodes: nil }, error: ::RedisClient::ClusterConfig::InvalidClientConfigError }
24+
].each_with_index do |c, idx|
25+
msg = "Case: #{idx}"
26+
got = -> { ::RedisClient.cluster(**c[:kwargs]) }
27+
if c[:error].nil?
28+
assert_instance_of(::RedisClient::ClusterConfig, got.call, msg)
29+
else
30+
assert_raises(c[:error], msg, &got)
31+
end
32+
end
33+
end
34+
end

test/testing_helper.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
class RedisClient
77
module TestingHelper
88
REDIS_SCHEME = ENV.fetch('REDIS_SCHEME', 'redis')
9-
NODE_ADDRS = (7000..7005).map { |port| "#{REDIS_SCHEME}://127.0.0.1:#{port}" }.freeze
9+
NODE_ADDRS = (6379..6384).map { |port| "#{REDIS_SCHEME}://127.0.0.1:#{port}" }.freeze
1010

1111
def setup
1212
@raw_clients = NODE_ADDRS.map { |addr| ::RedisClient.config(url: addr).new_client }

0 commit comments

Comments
 (0)