feat(api): rate-limit, honeypot, коды ошибок
rateLimit.ts: 5 запросов / 10 минут на IP в памяти, фиксированное окно, чистка устаревших записей по ходу запросов, 429 с Retry-After. IP из X-Real-IP только от loopback-сокета (плюс TRUSTED_PROXIES для шлюза docker), иначе адрес сокета; в базу IP не пишем. Honeypot → 201 без записи и уведомления. isPaused() → 503 на время восстановления базы. Тесты на каждый код ответа из таблицы плана.
This commit is contained in:
@@ -99,6 +99,28 @@ describe('POST /api/lead', () => {
|
||||
expect(db.prepare('SELECT count(*) AS n FROM leads').get()).toEqual({ n: 0 });
|
||||
});
|
||||
|
||||
it('honeypot: 201 как на обычную заявку, ничего не пишет, notifier молчит', async () => {
|
||||
const { post, db, events } = setup();
|
||||
const res = await post(validBody({ company: 'ООО Спам' }));
|
||||
await flush();
|
||||
expect(res.status).toBe(201);
|
||||
expect(await res.json()).toEqual({ ok: true, duplicate: false });
|
||||
expect(db.prepare('SELECT count(*) AS n FROM leads').get()).toEqual({ n: 0 });
|
||||
expect(events).toEqual([]);
|
||||
});
|
||||
|
||||
it('503 unavailable: приём на паузе (восстановление базы)', async () => {
|
||||
let paused = true;
|
||||
const { post, db } = setup({ isPaused: () => paused });
|
||||
const res = await post(validBody());
|
||||
expect(res.status).toBe(503);
|
||||
expect(await res.json()).toEqual({ ok: false, error: 'unavailable' });
|
||||
expect(db.prepare('SELECT count(*) AS n FROM leads').get()).toEqual({ n: 0 });
|
||||
|
||||
paused = false;
|
||||
expect((await post(validBody())).status).toBe(201);
|
||||
});
|
||||
|
||||
it('500 server: исключение при записи', async () => {
|
||||
const { post, db } = setup();
|
||||
db.close();
|
||||
|
||||
Reference in New Issue
Block a user