discourse/spec/lib/discourse_redis_spec.rb
2022-05-13 16:18:13 +08:00

249 lines
6.7 KiB
Ruby

# frozen_string_literal: true
describe DiscourseRedis do
it "ignore_readonly returns nil from a pure exception" do
result = DiscourseRedis.ignore_readonly { raise Redis::CommandError.new("READONLY") }
expect(result).to eq(nil)
end
describe 'redis commands' do
let(:raw_redis) { Redis.new(DiscourseRedis.config) }
before do
raw_redis.flushdb
end
after do
raw_redis.flushdb
end
describe 'pipelined / multi' do
let(:redis) { DiscourseRedis.new }
it 'should support multi commands' do
val = redis.multi do |transaction|
transaction.set 'foo', 'bar'
transaction.set 'bar', 'foo'
transaction.get 'bar'
end
expect(raw_redis.get('foo')).to eq(nil)
expect(raw_redis.get('bar')).to eq(nil)
expect(redis.get('foo')).to eq('bar')
expect(redis.get('bar')).to eq('foo')
expect(val).to eq(["OK", "OK", "foo"])
end
it 'should support pipelined commands' do
set, incr = nil
val = redis.pipelined do |pipeline|
set = pipeline.set "foo", "baz"
incr = pipeline.incr "baz"
end
expect(val).to eq(["OK", 1])
expect(set.value).to eq("OK")
expect(incr.value).to eq(1)
expect(raw_redis.get('foo')).to eq(nil)
expect(raw_redis.get('baz')).to eq(nil)
expect(redis.get('foo')).to eq("baz")
expect(redis.get('baz')).to eq("1")
end
it 'should noop pipelined commands against a readonly redis' do
redis.without_namespace
.expects(:pipelined)
.raises(Redis::CommandError.new("READONLY"))
set, incr = nil
val = redis.pipelined do |pipeline|
set = pipeline.set "foo", "baz"
incr = pipeline.incr "baz"
end
expect(val).to eq(nil)
expect(redis.get('foo')).to eq(nil)
expect(redis.get('baz')).to eq(nil)
end
it 'should noop multi commands against a readonly redis' do
redis.without_namespace
.expects(:multi)
.raises(Redis::CommandError.new("READONLY"))
val = redis.multi do |transaction|
transaction.set 'foo', 'bar'
transaction.set 'bar', 'foo'
transaction.get 'bar'
end
expect(val).to eq(nil)
expect(redis.get('foo')).to eq(nil)
expect(redis.get('bar')).to eq(nil)
end
end
describe 'when namespace is enabled' do
let(:redis) { DiscourseRedis.new }
it 'should append namespace to the keys' do
raw_redis.set('default:key', 1)
raw_redis.set('test:key2', 1)
raw_redis.set('default:key3', 1)
expect(redis.keys).to include('key')
expect(redis.keys).to_not include('key2')
expect(redis.scan_each.to_a).to contain_exactly('key', 'key3')
redis.del('key', 'key3')
expect(raw_redis.get('default:key')).to eq(nil)
expect(raw_redis.get('default:key3')).to eq(nil)
expect(redis.scan_each.to_a).to eq([])
raw_redis.set('default:key1', '1')
raw_redis.set('default:key2', '2')
expect(redis.mget('key1', 'key2')).to eq(['1', '2'])
expect(redis.scan_each.to_a).to contain_exactly('key1', 'key2')
end
end
describe 'when namespace is disabled' do
let(:redis) { DiscourseRedis.new(nil, namespace: false) }
it 'should not append any namespace to the keys' do
raw_redis.set('default:key', 1)
raw_redis.set('test:key2', 1)
expect(redis.keys).to include('default:key', 'test:key2')
raw_redis.set('key1', '1')
raw_redis.set('key2', '2')
expect(redis.mget('key1', 'key2')).to eq(['1', '2'])
redis.del('key1', 'key2')
expect(redis.mget('key1', 'key2')).to eq([nil, nil])
end
it 'should noop a readonly redis' do
expect(Discourse.recently_readonly?).to eq(false)
redis.without_namespace
.expects(:set)
.raises(Redis::CommandError.new("READONLY"))
redis.set('key', 1)
expect(Discourse.recently_readonly?).to eq(true)
end
end
describe "#eval" do
it "keys and arvg are passed correcty" do
keys = ["key1", "key2"]
argv = ["arg1", "arg2"]
expect(Discourse.redis.eval(
"return { KEYS, ARGV };",
keys: keys,
argv: argv,
)).to eq([keys, argv])
expect(Discourse.redis.eval(
"return { KEYS, ARGV };",
keys,
argv: argv,
)).to eq([keys, argv])
expect(Discourse.redis.eval(
"return { KEYS, ARGV };",
keys,
argv,
)).to eq([keys, argv])
end
end
describe "#evalsha" do
it "keys and arvg are passed correcty" do
keys = ["key1", "key2"]
argv = ["arg1", "arg2"]
script = "return { KEYS, ARGV };"
Discourse.redis.script(:load, script)
sha = Digest::SHA1.hexdigest(script)
expect(Discourse.redis.evalsha(
sha,
keys: keys,
argv: argv,
)).to eq([keys, argv])
expect(Discourse.redis.evalsha(
sha,
keys,
argv: argv,
)).to eq([keys, argv])
expect(Discourse.redis.evalsha(
sha,
keys,
argv,
)).to eq([keys, argv])
end
end
end
describe DiscourseRedis::EvalHelper do
it "works" do
helper = DiscourseRedis::EvalHelper.new <<~LUA
return 'hello world'
LUA
expect(helper.eval(Discourse.redis)).to eq('hello world')
end
it "works with arguments" do
helper = DiscourseRedis::EvalHelper.new <<~LUA
return ARGV[1]..ARGV[2]..KEYS[1]..KEYS[2]
LUA
expect(helper.eval(Discourse.redis, ['key1', 'key2'], ['arg1', 'arg2'])).to eq("arg1arg2key1key2")
end
it "works with arguments" do
helper = DiscourseRedis::EvalHelper.new <<~LUA
return ARGV[1]..ARGV[2]..KEYS[1]..KEYS[2]
LUA
expect(helper.eval(Discourse.redis, ['key1', 'key2'], ['arg1', 'arg2'])).to eq("arg1arg2key1key2")
end
it "uses evalsha correctly" do
redis_proxy = Class.new do
attr_reader :calls
def method_missing(meth, *args, **kwargs, &block)
@calls ||= []
@calls.push(meth)
Discourse.redis.public_send(meth, *args, **kwargs, &block)
end
end.new
Discourse.redis.call("SCRIPT", "FLUSH", "SYNC")
helper = DiscourseRedis::EvalHelper.new <<~LUA
return 'hello world'
LUA
expect(helper.eval(redis_proxy)).to eq("hello world")
expect(helper.eval(redis_proxy)).to eq("hello world")
expect(helper.eval(redis_proxy)).to eq("hello world")
expect(redis_proxy.calls).to eq([:evalsha, :eval, :evalsha, :evalsha])
end
end
end