2017-08-17 22:00:37 +05:30
|
|
|
require 'spec_helper'
|
|
|
|
|
2017-09-10 17:25:29 +05:30
|
|
|
describe ReactiveCaching, :use_clean_rails_memory_store_caching do
|
2018-11-08 19:23:39 +05:30
|
|
|
include ExclusiveLeaseHelpers
|
2017-08-17 22:00:37 +05:30
|
|
|
include ReactiveCachingHelpers
|
|
|
|
|
|
|
|
class CacheTest
|
|
|
|
include ReactiveCaching
|
|
|
|
|
|
|
|
self.reactive_cache_key = ->(thing) { ["foo", thing.id] }
|
|
|
|
|
|
|
|
self.reactive_cache_lifetime = 5.minutes
|
|
|
|
self.reactive_cache_refresh_interval = 15.seconds
|
|
|
|
|
|
|
|
attr_reader :id
|
|
|
|
|
|
|
|
def initialize(id, &blk)
|
|
|
|
@id = id
|
|
|
|
@calculator = blk
|
|
|
|
end
|
|
|
|
|
|
|
|
def calculate_reactive_cache
|
|
|
|
@calculator.call
|
|
|
|
end
|
|
|
|
|
|
|
|
def result
|
|
|
|
with_reactive_cache do |data|
|
|
|
|
data / 2
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
let(:calculation) { -> { 2 + 2 } }
|
|
|
|
let(:cache_key) { "foo:666" }
|
|
|
|
let(:instance) { CacheTest.new(666, &calculation) }
|
|
|
|
|
|
|
|
describe '#with_reactive_cache' do
|
2017-09-10 17:25:29 +05:30
|
|
|
before do
|
|
|
|
stub_reactive_cache
|
|
|
|
end
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
subject(:go!) { instance.result }
|
|
|
|
|
|
|
|
context 'when cache is empty' do
|
|
|
|
it { is_expected.to be_nil }
|
|
|
|
|
2018-11-08 19:23:39 +05:30
|
|
|
it 'enqueues a background worker to bootstrap the cache' do
|
2017-08-17 22:00:37 +05:30
|
|
|
expect(ReactiveCachingWorker).to receive(:perform_async).with(CacheTest, 666)
|
|
|
|
|
|
|
|
go!
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'updates the cache lifespan' do
|
2018-11-08 19:23:39 +05:30
|
|
|
expect(reactive_cache_alive?(instance)).to be_falsy
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
go!
|
|
|
|
|
|
|
|
expect(reactive_cache_alive?(instance)).to be_truthy
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the cache is full' do
|
2017-09-10 17:25:29 +05:30
|
|
|
before do
|
|
|
|
stub_reactive_cache(instance, 4)
|
|
|
|
end
|
2017-08-17 22:00:37 +05:30
|
|
|
|
|
|
|
it { is_expected.to eq(2) }
|
|
|
|
|
2018-11-08 19:23:39 +05:30
|
|
|
it 'does not enqueue a background worker' do
|
|
|
|
expect(ReactiveCachingWorker).not_to receive(:perform_async)
|
|
|
|
|
|
|
|
go!
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'updates the cache lifespan' do
|
|
|
|
expect(Rails.cache).to receive(:write).with(alive_reactive_cache_key(instance), true, expires_in: anything)
|
|
|
|
|
|
|
|
go!
|
|
|
|
end
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
context 'and expired' do
|
2017-09-10 17:25:29 +05:30
|
|
|
before do
|
|
|
|
invalidate_reactive_cache(instance)
|
|
|
|
end
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
it { is_expected.to be_nil }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#clear_reactive_cache!' do
|
|
|
|
before do
|
|
|
|
stub_reactive_cache(instance, 4)
|
|
|
|
instance.clear_reactive_cache!
|
|
|
|
end
|
|
|
|
|
|
|
|
it { expect(instance.result).to be_nil }
|
2018-11-08 19:23:39 +05:30
|
|
|
it { expect(reactive_cache_alive?(instance)).to be_falsy }
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
describe '#exclusively_update_reactive_cache!' do
|
|
|
|
subject(:go!) { instance.exclusively_update_reactive_cache! }
|
|
|
|
|
|
|
|
context 'when the lease is free and lifetime is not exceeded' do
|
2017-09-10 17:25:29 +05:30
|
|
|
before do
|
|
|
|
stub_reactive_cache(instance, "preexisting")
|
|
|
|
end
|
2017-08-17 22:00:37 +05:30
|
|
|
|
|
|
|
it 'takes and releases the lease' do
|
2018-11-08 19:23:39 +05:30
|
|
|
expect_to_obtain_exclusive_lease(cache_key, 'uuid')
|
|
|
|
expect_to_cancel_exclusive_lease(cache_key, 'uuid')
|
2017-08-17 22:00:37 +05:30
|
|
|
|
|
|
|
go!
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'caches the result of #calculate_reactive_cache' do
|
|
|
|
go!
|
|
|
|
|
|
|
|
expect(read_reactive_cache(instance)).to eq(calculation.call)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "enqueues a repeat worker" do
|
|
|
|
expect_reactive_cache_update_queued(instance)
|
|
|
|
|
|
|
|
go!
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'and #calculate_reactive_cache raises an exception' do
|
2017-09-10 17:25:29 +05:30
|
|
|
before do
|
|
|
|
stub_reactive_cache(instance, "preexisting")
|
|
|
|
end
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
let(:calculation) { -> { raise "foo"} }
|
|
|
|
|
|
|
|
it 'leaves the cache untouched' do
|
|
|
|
expect { go! }.to raise_error("foo")
|
|
|
|
expect(read_reactive_cache(instance)).to eq("preexisting")
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'enqueues a repeat worker' do
|
|
|
|
expect_reactive_cache_update_queued(instance)
|
|
|
|
|
|
|
|
expect { go! }.to raise_error("foo")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when lifetime is exceeded' do
|
|
|
|
it 'skips the calculation' do
|
|
|
|
expect(instance).to receive(:calculate_reactive_cache).never
|
|
|
|
|
|
|
|
go!
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the lease is already taken' do
|
|
|
|
it 'skips the calculation' do
|
2018-11-08 19:23:39 +05:30
|
|
|
stub_exclusive_lease_taken(cache_key)
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
expect(instance).to receive(:calculate_reactive_cache).never
|
|
|
|
|
|
|
|
go!
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|