* fix: race condition send_message/incoming message * fix: handle update race condition as well
101 lines
4.0 KiB
Ruby
101 lines
4.0 KiB
Ruby
require 'rails_helper'
|
|
|
|
RSpec.describe BaileysHelper do
|
|
describe '#baileys_extract_message_timestamp' do
|
|
let(:timestamp_low) { 1_748_003_165 }
|
|
let(:timestamp_hash) { { 'low' => timestamp_low, 'high' => 1, 'unsigned' => true } }
|
|
|
|
it 'extracts the timestamp from a string' do
|
|
expect(baileys_extract_message_timestamp(timestamp_low.to_s)).to eq(timestamp_low)
|
|
end
|
|
|
|
it 'extracts the timestamp from an int' do
|
|
expect(baileys_extract_message_timestamp(timestamp_low)).to eq(timestamp_low)
|
|
end
|
|
|
|
it 'extracts the timestamp from a hash' do
|
|
expect(baileys_extract_message_timestamp(timestamp_hash)).to eq(6_042_970_461)
|
|
end
|
|
end
|
|
|
|
describe '#with_baileys_channel_lock_on_outgoing_message' do
|
|
let(:channel_id) { 1 }
|
|
let(:lock_key) { format(BaileysHelper::CHANNEL_LOCK_ON_OUTGOING_MESSAGE_KEY, channel_id: channel_id) }
|
|
let(:timeout) { BaileysHelper::CHANNEL_LOCK_ON_OUTGOING_MESSAGE_TIMEOUT }
|
|
|
|
before do
|
|
allow(Redis::Alfred).to receive(:set).and_return(true)
|
|
allow(Redis::Alfred).to receive(:delete)
|
|
end
|
|
|
|
context 'when a block is given' do
|
|
it 'yields to the block' do
|
|
expect { |b| with_baileys_channel_lock_on_outgoing_message(channel_id, &b) }.to yield_control
|
|
end
|
|
|
|
it 'attempts to acquire the lock' do
|
|
with_baileys_channel_lock_on_outgoing_message(channel_id) { nil }
|
|
|
|
expect(Redis::Alfred).to have_received(:set).with(lock_key, 1, nx: true, ex: timeout)
|
|
end
|
|
|
|
it 'clears the lock after the block executes' do
|
|
with_baileys_channel_lock_on_outgoing_message(channel_id) { nil }
|
|
|
|
expect(Redis::Alfred).to have_received(:delete).with(lock_key)
|
|
end
|
|
|
|
it 'clears the lock even if the block raises an error' do
|
|
expect do
|
|
with_baileys_channel_lock_on_outgoing_message(channel_id) { raise 'test error' }
|
|
end.to raise_error('test error')
|
|
|
|
expect(Redis::Alfred).to have_received(:delete).with(lock_key)
|
|
end
|
|
|
|
context 'when lock is acquired immediately' do
|
|
it 'executes the block and clears the lock' do
|
|
expect { |b| with_baileys_channel_lock_on_outgoing_message(channel_id, &b) }.to yield_control
|
|
expect(Redis::Alfred).to have_received(:set).with(lock_key, 1, nx: true, ex: timeout)
|
|
expect(Redis::Alfred).to have_received(:delete).with(lock_key)
|
|
end
|
|
end
|
|
|
|
context 'when lock is not acquired immediately but within timeout' do
|
|
it 'retries acquiring the lock, executes the block, and clears the lock' do
|
|
allow(Redis::Alfred).to receive(:set).and_return(false, true)
|
|
allow(self).to receive(:sleep)
|
|
|
|
expect { |b| with_baileys_channel_lock_on_outgoing_message(channel_id, &b) }.to yield_control
|
|
expect(Redis::Alfred).to have_received(:set).with(lock_key, 1, nx: true, ex: timeout).twice
|
|
expect(self).to have_received(:sleep).with(0.1).once
|
|
expect(Redis::Alfred).to have_received(:delete).once.with(lock_key)
|
|
end
|
|
end
|
|
|
|
context 'when lock is not acquired within timeout' do
|
|
it 'still executes the block and clears the lock' do
|
|
freeze_time
|
|
allow(Redis::Alfred).to receive(:set).and_return(false)
|
|
allow(self).to receive(:sleep) { travel_to 1.second.from_now }
|
|
|
|
expect { |b| with_baileys_channel_lock_on_outgoing_message(channel_id, &b) }.to yield_control
|
|
expect(Redis::Alfred).to have_received(:set)
|
|
.with(lock_key, 1, nx: true, ex: timeout)
|
|
.exactly(BaileysHelper::CHANNEL_LOCK_ON_OUTGOING_MESSAGE_TIMEOUT.to_i)
|
|
expect(Redis::Alfred).to have_received(:delete).once.with(lock_key)
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when no block is given' do
|
|
it 'raises an ArgumentError' do
|
|
expect do
|
|
with_baileys_channel_lock_on_outgoing_message(channel_id)
|
|
end.to raise_error(ArgumentError,
|
|
'A block is required for with_baileys_channel_lock_on_outgoing_message')
|
|
end
|
|
end
|
|
end
|
|
end
|