diff --git a/spec/components/discourse_redis_spec.rb b/spec/components/discourse_redis_spec.rb index 900e20ac981..abc9a5917a8 100644 --- a/spec/components/discourse_redis_spec.rb +++ b/spec/components/discourse_redis_spec.rb @@ -3,246 +3,244 @@ require 'rails_helper' describe DiscourseRedis do - skip "these tests are broken" do - let(:slave_host) { 'testhost' } - let(:slave_port) { 1234 } + let(:slave_host) { 'testhost' } + let(:slave_port) { 1234 } - let(:config) do - DiscourseRedis.config.dup.merge(slave_host: 'testhost', slave_port: 1234, connector: DiscourseRedis::Connector) + let(:config) do + DiscourseRedis.config.dup.merge(slave_host: 'testhost', slave_port: 1234, connector: DiscourseRedis::Connector) + end + + let(:fallback_handler) { DiscourseRedis::FallbackHandler.instance } + + 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 - let(:fallback_handler) { DiscourseRedis::FallbackHandler.instance } - - it "ignore_readonly returns nil from a pure exception" do - result = DiscourseRedis.ignore_readonly { raise Redis::CommandError.new("READONLY") } - expect(result).to eq(nil) + after do + raw_redis.flushdb end - describe 'redis commands' do - let(:raw_redis) { Redis.new(DiscourseRedis.config) } + describe 'when namespace is enabled' do + let(:redis) { DiscourseRedis.new } - before do - raw_redis.flushdb - end + it 'should append namespace to the keys' do + raw_redis.set('default:key', 1) + raw_redis.set('test:key2', 1) - after do - raw_redis.flushdb - end + expect(redis.keys).to include('key') + expect(redis.keys).to_not include('key2') + expect(redis.scan_each.to_a).to eq(['key']) - 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) - - expect(redis.keys).to include('key') - expect(redis.keys).to_not include('key2') - expect(redis.scan_each.to_a).to eq(['key']) - - redis.scan_each.each do |key| - expect(key).to eq('key') - end - - redis.del('key') - - expect(raw_redis.get('default:key')).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') - - redis.del('key') - - expect(raw_redis.get('key')).to eq(nil) - - raw_redis.set('key1', '1') - raw_redis.set('key2', '2') - - expect(redis.mget('key1', 'key2')).to eq(['1', '2']) + redis.scan_each.each do |key| + expect(key).to eq('key') end - it 'should noop a readonly redis' do - expect(Discourse.recently_readonly?).to eq(false) + redis.del('key') - redis.without_namespace - .expects(:set) - .raises(Redis::CommandError.new("READONLY")) + expect(raw_redis.get('default:key')).to eq(nil) + expect(redis.scan_each.to_a).to eq([]) - redis.set('key', 1) + raw_redis.set('default:key1', '1') + raw_redis.set('default:key2', '2') - expect(Discourse.recently_readonly?).to eq(true) - end + expect(redis.mget('key1', 'key2')).to eq(['1', '2']) + expect(redis.scan_each.to_a).to contain_exactly('key1', 'key2') end end - context '.slave_host' do - it 'should return the right config' do - slave_config = DiscourseRedis.slave_config(config) - expect(slave_config[:host]).to eq(slave_host) - expect(slave_config[:port]).to eq(slave_port) + 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') + + redis.del('key') + + expect(raw_redis.get('key')).to eq(nil) + + raw_redis.set('key1', '1') + raw_redis.set('key2', '2') + + expect(redis.mget('key1', 'key2')).to eq(['1', '2']) + 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 + end - context 'when redis connection is to a slave redis server' do - it 'should check the status of the master server' do - begin - fallback_handler.master = false - Discourse.redis.without_namespace.expects(:set).raises(Redis::CommandError.new("READONLY")) - fallback_handler.expects(:verify_master).once - Discourse.redis.set('test', '1') - ensure - fallback_handler.master = true - Discourse.redis.del('test') - end - end + context '.slave_host' do + it 'should return the right config' do + slave_config = DiscourseRedis.slave_config(config) + expect(slave_config[:host]).to eq(slave_host) + expect(slave_config[:port]).to eq(slave_port) end + end - describe DiscourseRedis::Connector do - let(:connector) { DiscourseRedis::Connector.new(config) } - - after do + context 'when redis connection is to a slave redis server' do + it 'should check the status of the master server' do + begin + fallback_handler.master = false + Discourse.redis.without_namespace.expects(:set).raises(Redis::CommandError.new("READONLY")) + fallback_handler.expects(:verify_master).once + Discourse.redis.set('test', '1') + ensure fallback_handler.master = true + Discourse.redis.del('test') + end + end + end + + describe DiscourseRedis::Connector do + let(:connector) { DiscourseRedis::Connector.new(config) } + + after do + fallback_handler.master = true + end + + it 'should return the master config when master is up' do + expect(connector.resolve).to eq(config) + end + + class BrokenRedis + def initialize(error) + @error = error end - it 'should return the master config when master is up' do - expect(connector.resolve).to eq(config) + def call(*args) + raise @error end - class BrokenRedis - def initialize(error) - @error = error - end + def disconnect + end + end - def call(*args) - raise @error - end + it 'should return the slave config when master is down' do + error = Redis::CannotConnectError - def disconnect - end + expect do + connector.resolve(BrokenRedis.new(error)) + end.to raise_error(Redis::CannotConnectError) + + config = connector.resolve + + expect(config[:host]).to eq(slave_host) + expect(config[:port]).to eq(slave_port) + end + + it "should return the slave config when master's hostname cannot be resolved" do + error = RuntimeError.new('Name or service not known') + + expect do + connector.resolve(BrokenRedis.new(error)) + end.to raise_error(error) + + expect(fallback_handler.master).to eq(false) + + config = connector.resolve + + expect(config[:host]).to eq(slave_host) + expect(config[:port]).to eq(slave_port) + expect(fallback_handler.master).to eq(false) + end + + it "should return the slave config when master is still loading data" do + Redis::Client.any_instance + .expects(:call) + .with([:info, :persistence]) + .returns(" + someconfig:haha\r + #{DiscourseRedis::FallbackHandler::MASTER_LOADING_STATUS} + ") + + config = connector.resolve + + expect(config[:host]).to eq(slave_host) + expect(config[:port]).to eq(slave_port) + end + + it "should raise the right error" do + error = RuntimeError.new('test') + + 2.times do + expect { connector.resolve(BrokenRedis.new(error)) } + .to raise_error(error) + end + end + end + + describe DiscourseRedis::FallbackHandler do + before do + @original_keepalive_interval = MessageBus.keepalive_interval + end + + after do + fallback_handler.master = true + MessageBus.keepalive_interval = @original_keepalive_interval + end + + describe '#initiate_fallback_to_master' do + it 'should return the right value if the master server is still down' do + fallback_handler.master = false + Redis::Client.any_instance.expects(:call).with([:info]).returns("Some other stuff") + + expect(fallback_handler.initiate_fallback_to_master).to eq(false) + expect(MessageBus.keepalive_interval).to eq(0) end - it 'should return the slave config when master is down' do - error = Redis::CannotConnectError + it 'should fallback to the master server once it is up' do + fallback_handler.master = false + master_conn = mock('master') + slave_conn = mock('slave') - expect do - connector.resolve(BrokenRedis.new(error)) - end.to raise_error(Redis::CannotConnectError) + Redis::Client.expects(:new) + .with(DiscourseRedis.config) + .returns(master_conn) - config = connector.resolve + Redis::Client.expects(:new) + .with(DiscourseRedis.slave_config) + .returns(slave_conn) - expect(config[:host]).to eq(slave_host) - expect(config[:port]).to eq(slave_port) - end - - it "should return the slave config when master's hostname cannot be resolved" do - error = RuntimeError.new('Name or service not known') - - expect do - connector.resolve(BrokenRedis.new(error)) - end.to raise_error(error) - - expect(fallback_handler.master).to eq(false) - - config = connector.resolve - - expect(config[:host]).to eq(slave_host) - expect(config[:port]).to eq(slave_port) - expect(fallback_handler.master).to eq(false) - end - - it "should return the slave config when master is still loading data" do - Redis::Client.any_instance - .expects(:call) - .with([:info, :persistence]) + master_conn.expects(:call) + .with([:info]) .returns(" - someconfig:haha\r - #{DiscourseRedis::FallbackHandler::MASTER_LOADING_STATUS} + #{DiscourseRedis::FallbackHandler::MASTER_ROLE_STATUS}\r\n + #{DiscourseRedis::FallbackHandler::MASTER_LOADED_STATUS} ") - config = connector.resolve - - expect(config[:host]).to eq(slave_host) - expect(config[:port]).to eq(slave_port) - end - - it "should raise the right error" do - error = RuntimeError.new('test') - - 2.times do - expect { connector.resolve(BrokenRedis.new(error)) } - .to raise_error(error) - end - end - end - - describe DiscourseRedis::FallbackHandler do - before do - @original_keepalive_interval = MessageBus.keepalive_interval - end - - after do - fallback_handler.master = true - MessageBus.keepalive_interval = @original_keepalive_interval - end - - describe '#initiate_fallback_to_master' do - it 'should return the right value if the master server is still down' do - fallback_handler.master = false - Redis::Client.any_instance.expects(:call).with([:info]).returns("Some other stuff") - - expect(fallback_handler.initiate_fallback_to_master).to eq(false) - expect(MessageBus.keepalive_interval).to eq(0) + DiscourseRedis::FallbackHandler::CONNECTION_TYPES.each do |connection_type| + slave_conn.expects(:call).with( + [:client, [:kill, 'type', connection_type]] + ) end - it 'should fallback to the master server once it is up' do - fallback_handler.master = false - master_conn = mock('master') - slave_conn = mock('slave') + master_conn.expects(:disconnect) + slave_conn.expects(:disconnect) - Redis::Client.expects(:new) - .with(DiscourseRedis.config) - .returns(master_conn) - - Redis::Client.expects(:new) - .with(DiscourseRedis.slave_config) - .returns(slave_conn) - - master_conn.expects(:call) - .with([:info]) - .returns(" - #{DiscourseRedis::FallbackHandler::MASTER_ROLE_STATUS}\r\n - #{DiscourseRedis::FallbackHandler::MASTER_LOADED_STATUS} - ") - - DiscourseRedis::FallbackHandler::CONNECTION_TYPES.each do |connection_type| - slave_conn.expects(:call).with( - [:client, [:kill, 'type', connection_type]] - ) - end - - master_conn.expects(:disconnect) - slave_conn.expects(:disconnect) - - expect(fallback_handler.initiate_fallback_to_master).to eq(true) - expect(fallback_handler.master).to eq(true) - expect(Discourse.recently_readonly?).to eq(false) - expect(MessageBus.keepalive_interval).to eq(-1) - end + expect(fallback_handler.initiate_fallback_to_master).to eq(true) + expect(fallback_handler.master).to eq(true) + expect(Discourse.recently_readonly?).to eq(false) + expect(MessageBus.keepalive_interval).to eq(-1) end end end