import { Logger } from '@nestjs/common'; import { Client } from 'pg'; import { SyncLockDatabaseRepository } from './syncLock.database.repository'; jest.mock('pg', () => ({ Client: jest.fn() })); describe('SyncLockDatabaseRepository', () => { const client = { on: jest.fn(), connect: jest.fn(), query: jest.fn(), end: jest.fn() }; const configService = { get: jest.fn().mockReturnValue('postgresql://user:pass@localhost:5432/db') }; let repository: SyncLockDatabaseRepository; beforeAll(() => { jest.spyOn(Logger.prototype, 'warn').mockImplementation(() => undefined); jest.spyOn(Logger.prototype, 'error').mockImplementation(() => undefined); }); beforeEach(() => { jest.clearAllMocks(); (Client as unknown as jest.Mock).mockImplementation(() => client); client.connect.mockResolvedValue(undefined); client.end.mockResolvedValue(undefined); client.query.mockResolvedValue({ rows: [{ locked: true }] }); repository = new SyncLockDatabaseRepository(configService as never); }); it('takes the advisory lock on a dedicated connection and keeps it open', async () => { const release = await repository.tryAcquire(); expect(release).toEqual(expect.any(Function)); expect(Client).toHaveBeenCalledWith({ connectionString: 'postgresql://user:pass@localhost:5432/db' }); expect(client.query).toHaveBeenCalledWith('SELECT pg_try_advisory_lock($1) AS locked', [expect.any(Number)]); expect(client.end).not.toHaveBeenCalled(); }); it('returns null and closes the connection when the lock is already held', async () => { client.query.mockResolvedValue({ rows: [{ locked: false }] }); await expect(repository.tryAcquire()).resolves.toBeNull(); expect(client.end).toHaveBeenCalledTimes(1); }); it('unlocks then closes the connection on release', async () => { const release = await repository.tryAcquire(); await release!(); expect(client.query).toHaveBeenLastCalledWith('SELECT pg_advisory_unlock($1)', [expect.any(Number)]); expect(client.end).toHaveBeenCalledTimes(1); }); it('still closes the connection, and does not throw, when the explicit unlock fails', async () => { const release = await repository.tryAcquire(); client.query.mockRejectedValueOnce(new Error('connection lost')); await expect(release!()).resolves.toBeUndefined(); expect(client.end).toHaveBeenCalledTimes(1); }); it('closes the connection and rethrows when the lock query fails', async () => { client.query.mockRejectedValue(new Error('boom')); await expect(repository.tryAcquire()).rejects.toThrow('boom'); expect(client.end).toHaveBeenCalledTimes(1); }); it('logs connection errors instead of leaving them unhandled', async () => { await repository.tryAcquire(); expect(client.on).toHaveBeenCalledWith('error', expect.any(Function)); }); });