import { describe, expect, it } from 'vitest'; import { MAX_BODY_BYTES } from './app.js'; import { flush, setup, T0, validBody } from './testing.js'; describe('GET /api/health', () => { it('отдаёт версию', async () => { const { app } = setup({ version: 'abc123' }); const res = await app.request('/api/health'); expect(await res.json()).toEqual({ ok: true, version: 'abc123' }); }); }); describe('POST /api/lead', () => { it('201: сохраняет заявку с нормализованным телефоном, касаниями и user agent', async () => { const { db, post } = setup(); const res = await post(validBody(), { 'user-agent': 'Mozilla/5.0 test' }); expect(res.status).toBe(201); expect(await res.json()).toEqual({ ok: true, duplicate: false }); const row = db.prepare('SELECT * FROM leads').get() as Record; expect(row).toMatchObject({ id: 1, created_at: T0.toISOString(), phone: '+375291234567', name: 'Аня', interest: 'subscription', comment: 'Под эспрессо', user_agent: 'Mozilla/5.0 test', tg_status: 'pending', tg_attempts: 0, }); expect(JSON.parse(row.first_touch as string)).toMatchObject({ utm_source: 'instagram' }); expect(JSON.parse(row.last_touch as string)).toMatchObject({ utm_source: 'yandex', submitPage: '/#form' }); }); it('номер заявки клиенту не отдаёт', async () => { const { post } = setup(); const text = await (await post(validBody())).text(); expect(text).toBe('{"ok":true,"duplicate":false}'); }); it('notifier получает id заявки уже после ответа', async () => { const { post, events } = setup(); await post(validBody()); expect(events).toEqual([]); await flush(); expect(events).toEqual([{ kind: 'lead', leadId: 1 }]); }); it('упавший notifier не ломает ответ', async () => { const { post, db } = setup({ notifier: { leadSaved: () => Promise.reject(new Error('telegram down')), }, }); const res = await post(validBody()); await flush(); expect(res.status).toBe(201); expect(db.prepare('SELECT count(*) AS n FROM leads').get()).toEqual({ n: 1 }); }); it('400 validation: ошибки по полям, ничего не пишет', async () => { const { post, db, events } = setup(); const res = await post(validBody({ name: ' ', phone: '+7 999 123-45-67' })); expect(res.status).toBe(400); const body = (await res.json()) as { fieldErrors: Record }; expect(body).toMatchObject({ ok: false, error: 'validation' }); expect(Object.keys(body.fieldErrors).sort()).toEqual(['name', 'phone']); await flush(); expect(events).toEqual([]); expect(db.prepare('SELECT count(*) AS n FROM leads').get()).toEqual({ n: 0 }); }); it('400 bad_request: битый JSON', async () => { const { post } = setup(); const res = await post('{"name":'); expect(res.status).toBe(400); expect(await res.json()).toEqual({ ok: false, error: 'bad_request' }); }); it('415: не JSON', async () => { const { post } = setup(); const res = await post('name=Аня', { 'content-type': 'application/x-www-form-urlencoded' }); expect(res.status).toBe(415); expect(await res.json()).toEqual({ ok: false, error: 'bad_request' }); }); it('JSON с charset принимается', async () => { const { post } = setup(); const res = await post(validBody(), { 'content-type': 'application/json; charset=utf-8' }); expect(res.status).toBe(201); }); it('413: тело больше 16 КБ', async () => { const { post, db } = setup(); const res = await post(validBody({ comment: 'а'.repeat(MAX_BODY_BYTES) })); expect(res.status).toBe(413); expect(await res.json()).toEqual({ ok: false, error: 'bad_request' }); 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(); const res = await post(validBody()); expect(res.status).toBe(500); expect(await res.json()).toEqual({ ok: false, error: 'server' }); }); });