spec/lib/idempotency_cache_spec.rb (92 lines of code) (raw):
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe IdempotencyCache, feature_category: :hosted_runners do
let(:key) { 'test_cache:1' }
let(:ttl) { 5.hours }
describe '.already_completed?', :clean_gitlab_redis_shared_state do
subject(:already_completed?) { described_class.already_completed?(key) }
context 'when cache key does not exist' do
it 'returns false' do
expect(already_completed?).to be false
end
end
context 'when cache key exists' do
before do
Gitlab::Redis::SharedState.with do |redis|
redis.set(key, 1, ex: ttl)
end
end
it 'returns true' do
expect(already_completed?).to be true
end
end
end
describe '.ensure_idempotency', :clean_gitlab_redis_shared_state do
it 'yields the block on first call' do
block_called = false
described_class.ensure_idempotency(key, ttl) do
block_called = true
end
expect(block_called).to be true
end
it 'sets the cache key after executing the block' do
described_class.ensure_idempotency(key, ttl) { 'test result' }
expect(described_class.already_completed?(key)).to be true
end
it 'does not yield the block on second call with same key' do
call_count = 0
2.times do
described_class.ensure_idempotency(key, ttl) do
call_count += 1
end
end
expect(call_count).to eq(1)
end
it 'yields the block when called with different keys' do
call_count = 0
described_class.ensure_idempotency(key, ttl) do
call_count += 1
end
described_class.ensure_idempotency('different key', ttl) do
call_count += 1
end
expect(call_count).to eq(2)
end
it 'returns the result of the block on first call' do
result = described_class.ensure_idempotency(key, ttl) { 'test result' }
expect(result).to eq('test result')
end
it 'returns nil on subsequent calls with same params' do
described_class.ensure_idempotency(key, ttl) { 'test result' }
result = described_class.ensure_idempotency(key, ttl) { 'not called' }
expect(result).to be_nil
end
end
describe '#mark_as_completed!', :clean_gitlab_redis_shared_state do
let(:cache) { described_class.new(key, ttl) }
it 'sets the cache key with the specified TTL' do
redis_double = double
expect(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis_double)
expect(redis_double).to receive(:set).with(key, 1, ex: ttl)
cache.mark_as_completed!
end
it 'makes already_completed? return true' do
expect(described_class.already_completed?(key)).to be false
cache.mark_as_completed!
expect(described_class.already_completed?(key)).to be true
end
end
context 'with expired cache entries', :clean_gitlab_redis_shared_state do
it 'treats expired entries as not completed' do
ttl = 1.second # shortest valid redis ttl
described_class.ensure_idempotency(key, ttl) { 'test result' }
expect(described_class.already_completed?(key)).to be true
sleep ttl + 0.01.seconds
expect(described_class.already_completed?(key)).to be false
call_count = 0
described_class.ensure_idempotency(key, ttl) do
call_count += 1
end
expect(call_count).to eq(1)
end
end
end