Files
app/test/test.abuse.node.ts
T

95 lines
3.0 KiB
TypeScript

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<string, AbuseWindow>();
const originalSave = AbuseWindow.prototype.save;
const originalDelete = AbuseWindow.prototype.delete;
(AbuseWindow.prototype as AbuseWindow & { save: () => Promise<void> }).save = async function () {
store.set(this.id, this);
};
(AbuseWindow.prototype as AbuseWindow & { delete: () => Promise<void> }).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();