import { Logger } from '@nestjs/common'; import { ThrottlerDatabaseStorage } from './throttlerStorage.database.repository'; // The SQL itself (window, block, reset) was checked on a real Postgres engine; these tests cover the TypeScript side. describe('ThrottlerDatabaseStorage', () => { const record = { totalHits: 2, timeToExpire: 40, isBlocked: false, timeToBlockExpire: 0 }; const prisma = { $queryRaw: jest.fn(), throttlerRecord: { deleteMany: jest.fn() } }; let logError: jest.SpyInstance; let random: jest.Mock; let storage: ThrottlerDatabaseStorage; beforeEach(() => { jest.clearAllMocks(); logError = jest.spyOn(Logger.prototype, 'error').mockImplementation(() => undefined); prisma.$queryRaw.mockResolvedValue([record]); prisma.throttlerRecord.deleteMany.mockResolvedValue({ count: 0 }); random = jest.fn().mockReturnValue(0.5); storage = new ThrottlerDatabaseStorage(prisma as never, random); }); it('returns the record computed by the database, with parameters bound (never interpolated)', async () => { await expect(storage.increment('key-hash', 60_000, 5, 30_000)).resolves.toEqual(record); const [strings, ...values] = prisma.$queryRaw.mock.calls[0] as [TemplateStringsArray, ...unknown[]]; expect(values).toEqual(['key-hash', 60_000, 5, 30_000]); expect(strings.join('?')).not.toContain('key-hash'); }); it('purges the finished counters on about one request in 100', async () => { await storage.increment('k', 60_000, 5, 60_000); expect(prisma.throttlerRecord.deleteMany).not.toHaveBeenCalled(); random.mockReturnValue(0.001); await storage.increment('k', 60_000, 5, 60_000); expect(prisma.throttlerRecord.deleteMany).toHaveBeenCalledTimes(1); }); it('lets the request through when the database fails, logging at most once a minute', async () => { prisma.$queryRaw.mockRejectedValue(new Error('connection refused')); await expect(storage.increment('k', 60_000, 5, 60_000)).resolves.toMatchObject({ isBlocked: false }); await storage.increment('k', 60_000, 5, 60_000); expect(logError).toHaveBeenCalledTimes(1); expect(logError).toHaveBeenCalledWith(expect.stringContaining('connection refused')); }); it('does not fail the request when the purge fails', async () => { random.mockReturnValue(0); prisma.throttlerRecord.deleteMany.mockRejectedValue(new Error('boom')); await expect(storage.increment('k', 60_000, 5, 60_000)).resolves.toEqual(record); await new Promise((resolve) => setImmediate(resolve)); expect(logError).toHaveBeenCalledWith(expect.stringContaining('boom')); }); });