From 2eb3cf6344c14fb35f00f02d8d5ad6b7944e66d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Sun, 22 Feb 2026 23:38:14 +0100 Subject: [PATCH] use fake timers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .../catalog-backend/src/database/util.test.ts | 45 ++++++++++++------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/plugins/catalog-backend/src/database/util.test.ts b/plugins/catalog-backend/src/database/util.test.ts index c69c2b362e..b3f11b7f92 100644 --- a/plugins/catalog-backend/src/database/util.test.ts +++ b/plugins/catalog-backend/src/database/util.test.ts @@ -17,6 +17,10 @@ import { Knex } from 'knex'; import { retryOnDeadlock } from './util'; +jest.mock('node:timers/promises', () => ({ + setTimeout: jest.fn(), +})); + function mockKnex(client: string): Knex { return { client: { config: { client } } } as unknown as Knex; } @@ -28,6 +32,10 @@ function pgDeadlockError(): Error & { code: string } { } describe('retryOnDeadlock', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + it('returns the result on success', async () => { const fn = jest.fn().mockResolvedValue('ok'); const result = await retryOnDeadlock(fn, mockKnex('pg'), 3, 1); @@ -77,26 +85,33 @@ describe('retryOnDeadlock', () => { }); it('applies exponential backoff between retries', async () => { - const timestamps: number[] = []; - const fn = jest.fn().mockImplementation(async () => { - timestamps.push(Date.now()); - if (timestamps.length <= 3) { - throw pgDeadlockError(); - } - return 'done'; + const { setTimeout: sleep } = jest.requireMock<{ + setTimeout: jest.Mock; + }>('node:timers/promises'); + + const fnCallsAtSleep: number[] = []; + const fn = jest + .fn() + .mockRejectedValueOnce(pgDeadlockError()) + .mockRejectedValueOnce(pgDeadlockError()) + .mockRejectedValueOnce(pgDeadlockError()) + .mockResolvedValue('done'); + + sleep.mockImplementation(async () => { + fnCallsAtSleep.push(fn.mock.calls.length); }); const baseMs = 50; - await retryOnDeadlock(fn, mockKnex('pg'), 3, baseMs); + const result = await retryOnDeadlock(fn, mockKnex('pg'), 3, baseMs); + expect(result).toBe('done'); expect(fn).toHaveBeenCalledTimes(4); - // Verify delays increase (with some tolerance for timing) - const delay1 = timestamps[1] - timestamps[0]; - const delay2 = timestamps[2] - timestamps[1]; - const delay3 = timestamps[3] - timestamps[2]; - expect(delay1).toBeGreaterThanOrEqual(baseMs * 0.8); - expect(delay2).toBeGreaterThanOrEqual(baseMs * 2 * 0.8); - expect(delay3).toBeGreaterThanOrEqual(baseMs * 4 * 0.8); + // Each sleep happens after fn has been called N times + expect(fnCallsAtSleep).toEqual([1, 2, 3]); + expect(sleep).toHaveBeenCalledTimes(3); + expect(sleep).toHaveBeenNthCalledWith(1, 50); + expect(sleep).toHaveBeenNthCalledWith(2, 100); + expect(sleep).toHaveBeenNthCalledWith(3, 200); }); it('defaults to 3 retries when not specified', async () => {