import { tap, expect } from '@git.zone/tstest/tapbundle'; import { AbuseProtectionManager, type IAbuseProtectionConfig, } from '../ts/reception/classes.abuseprotectionmanager.js'; import { AbuseWindow } from '../ts/reception/classes.abusewindow.js'; const createTestAbuseProtectionManager = () => { const manager = new AbuseProtectionManager({ db: { smartdataDb: {} }, } as any); const store = new Map(); const originalSave = AbuseWindow.prototype.save; const originalDelete = AbuseWindow.prototype.delete; (AbuseWindow.prototype as AbuseWindow & { save: () => Promise }).save = async function () { store.set(this.id, this); }; (AbuseWindow.prototype as AbuseWindow & { delete: () => Promise }).delete = async function () { store.delete(this.id); }; (manager as any).CAbuseWindow = { getInstance: async (queryArg) => store.get(queryArg.id) ?? null, }; const restore = () => { AbuseWindow.prototype.save = originalSave; AbuseWindow.prototype.delete = originalDelete; }; return { manager, store, restore, }; }; const testConfig: IAbuseProtectionConfig = { maxAttempts: 2, windowMillis: 1_000, blockDurationMillis: 2_000, }; tap.test('blocks after too many attempts within the active window', async () => { const { manager, restore } = createTestAbuseProtectionManager(); try { await manager.consumeAttempt('passwordLogin', 'phil@example.com', testConfig); await manager.consumeAttempt('passwordLogin', 'phil@example.com', testConfig); await expect(manager.consumeAttempt('passwordLogin', 'phil@example.com', testConfig)).rejects.toThrow(); } finally { restore(); } }); tap.test('resets attempts after the block and window have elapsed', async () => { const { manager, store, restore } = createTestAbuseProtectionManager(); try { await manager.consumeAttempt('passwordLogin', 'phil@example.com', testConfig); await manager.consumeAttempt('passwordLogin', 'phil@example.com', testConfig); await expect(manager.consumeAttempt('passwordLogin', 'phil@example.com', testConfig)).rejects.toThrow(); const abuseWindow = Array.from(store.values())[0]; abuseWindow.data.blockedUntil = Date.now() - 10; abuseWindow.data.windowStartedAt = Date.now() - testConfig.windowMillis - 10; abuseWindow.data.validUntil = Date.now() + 1_000; await manager.consumeAttempt('passwordLogin', 'phil@example.com', testConfig); expect(abuseWindow.data.attemptCount).toEqual(1); } finally { restore(); } }); tap.test('clears stored attempts after a successful action', async () => { const { manager, store, restore } = createTestAbuseProtectionManager(); try { await manager.consumeAttempt('passwordLogin', 'phil@example.com', testConfig); expect(store.size).toEqual(1); await manager.clearAttempts('passwordLogin', 'phil@example.com'); expect(store.size).toEqual(0); } finally { restore(); } }); export default tap.start();