discourse/spec/lib/discourse_redis_spec.rb
2022-12-08 06:48:44 +08:00

321 lines
8.2 KiB
Ruby

# frozen_string_literal: true
RSpec.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 '#sadd?' do
it "should send the right command with the right key prefix to redis" do
redis = DiscourseRedis.new
redis.without_namespace.expects(:sadd?).with("default:testset", "1", anything)
redis.sadd?("testset", "1")
end
end
describe '#srem?' do
it "should send the right command with the right key prefix to redis" do
redis = DiscourseRedis.new
redis.without_namespace.expects(:srem?).with("default:testset", "1", anything)
redis.srem?("testset", "1")
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
describe ".new_redis_store" do
let(:cache) { Cache.new(namespace: 'foo') }
let(:store) { DiscourseRedis.new_redis_store }
before do
cache.redis.del("key")
store.delete("key")
end
it "can store stuff" do
store.fetch("key") do
"key in store"
end
r = store.read("key")
expect(r).to eq("key in store")
end
it "doesn't collide with our Cache" do
store.fetch("key") do
"key in store"
end
cache.fetch("key") do
"key in cache"
end
r = store.read("key")
expect(r).to eq("key in store")
end
it "can be cleared without clearing our cache" do
cache.clear
store.clear
store.fetch("key") do
"key in store"
end
cache.fetch("key") do
"key in cache"
end
store.clear
expect(store.read("key")).to eq(nil)
expect(cache.fetch("key")).to eq("key in cache")
end
end
end