Files
pchelpmaster/docs/testing.md
T
kamaskandClaude Sonnet 5.5 1109f68e11 test(api): type-check harness and cover automigrate and auth isolation
- add tsconfig.tests.json and run tsc in test:api
- move vitest ProvidedContext types to provided-context.d.ts
- rename vitest.api.config.ts to .mts
- kill signal-test fixture with SIGTERM first so cleanup still runs
- add tests: no files in pb_migrations, independent user/guest clients
- docs: drop superuser env vars from tests in README, note tsc in testing.md

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EvmtStx737hu8gXe15e55W
2026-10-26 19:46:45 +03:00

6.9 KiB
Raw Blame History

Тестирование

Разработка ведётся по 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

Команды появляются по мере того, как настраиваются соответствующие стенды.

pnpm test:api сначала проверяет типы тестового кода (tsconfig.tests.json: tests/** и vitest.api.config.mts), затем запускает Vitest.

Контрактные тесты репозиториев

У каждого репозитория две реализации: PocketBase (data/) и фейк в памяти (testing/). Чтобы фейк не расходился с реальностью, поведение описывается один раз:

// 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. Создаётся временная папка данных (в системной temp-папке).
  2. Создаётся суперпользователь (pocketbase superuser upsert) со сгенерированными email и паролем: инстанс одноразовый, поэтому .env тестам не нужен.
  3. Запускается pocketbase serve на свободном порту 127.0.0.1 с --dir <временная папка> --migrationsDir pocketbase/pb_migrations --hooksDir pocketbase/pb_hooks --automigrate=false; миграции применяются при старте.
  4. Ожидание готовности по GET /api/health.
  5. После прогона процесс останавливается, папка удаляется.

Код запуска один на все уровни: tests/support/pocketbase-server.ts (startPocketBase(): Promise<{ url, dataDir, superuser, stop }>). В API-тестах URL и суперпользователь доступны через inject('pbUrl') и inject('pbSuperuser').

Стенд убирает за собой процесс и папку после успешного прогона, после падения (в том числе неудачного старта), по SIGINT/SIGTERM (Ctrl+C) и при выходе процесса. Предел: SIGKILL процесса-владельца перехватить нельзя — тогда PocketBase может продолжить работу, а папка pchelpcrm-pb-* остаться в temp.

Правила написания тестов

  • Каждый тест создаёт своих пользователей с уникальными email (user-<случайная строка>@test.local). Тесты не зависят друг от друга и от порядка, поэтому могут идти параллельно и не нуждаются в очистке.
  • Хелперы (tests/support/): createUser(), asUser(user), asGuest(), asSuperuser() возвращают готовый экземпляр PocketBase SDK.
  • Изоляцию проверяем всегда двумя пользователями: A создаёт данные, B пытается их прочитать, найти через list/filter, изменить, удалить, сослаться на них. Плюс гость.
  • Тест проверяет поведение, а не реализацию: статус ответа, тело, состояние после операции (перечитать суперпользователем).
  • Unit-тесты компонентов используют фейки из <feature>/testing/ и не обращаются к сети.
  • Компонентные тесты ищут элементы по ролям и подписям (как пользователь и скринридер), а не по CSS-классам.
  • E2E-тесты: каждый сценарий регистрирует своего пользователя через UI или API; на ключевых страницах выполняется проверка axe (@axe-core/playwright) без нарушений.
  • Названия тестов на русском и описывают поведение: it('не отдаёт гостю чужую метку').
  • Пока реализации нет, тесты должны компилироваться: для этого создаются минимальные заглушки контрактов (типы, абстрактные классы, методы с throw new Error('Not implemented')). Новые тесты падают из-за невыполненных ожиданий, а не из-за ошибок импорта или стенда.