# Тестирование Разработка ведётся по TDD: тесты пишутся до реализации. ## Уровни тестов | Уровень | Что проверяет | Где лежит | Команда | | --- | --- | --- | --- | | **API** (e2e бэкенда) | схема, миграции, правила доступа, изоляция пользователей, хуки PocketBase | `tests/api/**/*.api.spec.ts` | `pnpm test:api` | | **Unit** | доменные функции, компоненты и сервисы с фейками вместо PocketBase | рядом с кодом, `src/**/*.spec.ts` | `pnpm test` | | **Integration** | PocketBase-реализации репозиториев и сервисов против настоящего PocketBase | рядом с кодом, `src/**/*.integration.spec.ts` | `pnpm test:integration` | | **E2E** | пользовательские сценарии в браузере, SSR, доступность (axe) | `tests/e2e/**/*.e2e.ts` | `pnpm e2e` | | **E2E prod** | сценарии на production-сборке, включая изоляцию данных при SSR | `tests/e2e/` (проект `ssr-prod`) | `pnpm e2e:prod` | Команды появляются по мере того, как настраиваются соответствующие стенды. ## Контрактные тесты репозиториев У каждого репозитория две реализации: PocketBase (`data/`) и фейк в памяти (`testing/`). Чтобы фейк не расходился с реальностью, поведение описывается **один раз**: ```ts // tags/testing/tag-repository.contract.ts export function describeTagRepositoryContract( setup: () => Promise<{ repoA: TagRepository; repoB: TagRepository }>, ) { describe('контракт TagRepository', () => { it('не показывает пользователю B метки пользователя A', async () => { /* ... */ }); }); } ``` и вызывается дважды: в `in-memory-tag-repository.spec.ts` (быстро, в `pnpm test`) и в `pocketbase-tag-repository.integration.spec.ts` (на тестовом PocketBase, в `pnpm test:integration`). ## Тестовый PocketBase API-, integration- и e2e-тесты работают с **отдельным временным экземпляром** PocketBase, а не с `pocketbase/pb_data`: 1. Создаётся временная папка данных. 2. Создаётся суперпользователь (`pocketbase superuser upsert`) с данными из `.env` (`PB_SUPERUSER_EMAIL`, `PB_SUPERUSER_PASSWORD`). 3. Запускается `pocketbase serve` на свободном порту с `--dir <временная папка> --migrationsDir pocketbase/pb_migrations --hooksDir pocketbase/pb_hooks`; миграции применяются при старте. 4. Ожидание готовности по `GET /api/health`. 5. После прогона процесс останавливается, папка удаляется. Код запуска один на все уровни: `tests/support/pocketbase-server.ts`. ## Правила написания тестов - **Каждый тест создаёт своих пользователей** с уникальными email (`user-<случайная строка>@test.local`). Тесты не зависят друг от друга и от порядка, поэтому могут идти параллельно и не нуждаются в очистке. - Хелперы (`tests/support/`): `createUser()`, `asUser(user)`, `asGuest()`, `asSuperuser()` возвращают готовый экземпляр PocketBase SDK. - **Изоляцию проверяем всегда двумя пользователями**: A создаёт данные, B пытается их прочитать, найти через list/filter, изменить, удалить, сослаться на них. Плюс гость. - Тест проверяет поведение, а не реализацию: статус ответа, тело, состояние после операции (перечитать суперпользователем). - Unit-тесты компонентов используют фейки из `/testing/` и не обращаются к сети. - Компонентные тесты ищут элементы по ролям и подписям (как пользователь и скринридер), а не по CSS-классам. - E2E-тесты: каждый сценарий регистрирует своего пользователя через UI или API; на ключевых страницах выполняется проверка axe (`@axe-core/playwright`) без нарушений. - Названия тестов на русском и описывают поведение: `it('не отдаёт гостю чужую метку')`. - Пока реализации нет, тесты должны компилироваться: для этого создаются минимальные заглушки контрактов (типы, абстрактные классы, методы с `throw new Error('Not implemented')`). Новые тесты падают из-за невыполненных ожиданий, а не из-за ошибок импорта или стенда.