Тестирование backend и Directus
Backend-тесты DIMETRA работают в двух слоях: быстрые API-тесты на vitest проверяют контракт permissions и бизнес-правила через REST-запросы, а Playwright-тесты запускают реальный Directus Admin UI в браузере и проходят полный user-facing сценарий менеджера.
Зачем два слоя
Заголовок раздела «Зачем два слоя»| Слой | Инструмент | Что проверяет | Скорость |
|---|---|---|---|
| API / Integration | Vitest + fetch | Permissions matrix, role isolation, preset’ы, response codes | ~2 сек/suite |
| Browser E2E | Playwright (Chromium) | Реальный Directus UI — формы, кнопки, disabled-поля, редиректы, toast’ы | ~4 сек/тест |
Почему оба нужны:
- API-тест может показать, что
POST /usersпод токеном PM возвращает 200 с правильной ролью, но не поймает, что в UI поле «Роль» случайно стало редактируемым, или что save-кнопка не активируется. - Browser-тест подтверждает, что менеджер через Admin UI, которым он реально пользуется, может создать клиента и не может обойти preset’ы.
- Менеджеры DIMETRA работают не через мобильное приложение, а через Directus Admin UI, поэтому этот UI — полноценный продукт, требующий регрессионного покрытия.
Что покрыто сейчас
Заголовок раздела «Что покрыто сейчас»| Область | API (vitest) | Browser (Playwright) |
|---|---|---|
| Login / logout / token | ✅ auth-login, auth-token | ✅ (через login helper в каждом тесте) |
| Phone-as-email | ✅ auth-phone-email | — |
| Client — isolation, permissions | ✅ auth-isolation, auth-permissions | — |
| Project Manager — CRUD matrix (projects/stages/payments/messages/etc.) | ✅ manager-permissions | — |
| Менеджер создаёт клиента | ✅ manager-create-client (7 assertion’ов) | ✅ manager-create-client.spec (2 теста) |
Пример dual coverage: «менеджер создаёт клиента»
Заголовок раздела «Пример dual coverage: «менеджер создаёт клиента»»API-тесты (backend/tests/manager-create-client.test.ts):
- PM может создать пользователя через
POST /users - Созданный юзер получает роль Client и статус active (preset из
scripts/setup-roles.sh) - PM может прочитать созданного клиента
- PM может обновить имя и описание
- Попытка переопределить роль на Administrator игнорируется — preset побеждает
- PM не может удалить пользователя (403)
- Созданный клиент реально может залогиниться с заданным паролем
Browser-тесты (backend/tests-e2e/manager-create-client.spec.ts):
- Full flow — логин в Admin UI → навигация на
/admin/users/+→ заполнение формы (Имя, Фамилия, E-mail, Пароль, Описание) → клик save → проверка в БД через admin API, что роль Client и preset сработали - Role field disabled — дропдаун «Роль» на форме создания заблокирован, менеджер физически не может выбрать другую роль
Vitest — интеграционные тесты, работают против реального Directus на http://localhost:8055. Dev-БД, seed обязателен.
Playwright — браузерные E2E на Chromium, locale ru-RU. По умолчанию headed локально (видно, что делает браузер), headless в CI (через process.env.CI). Trace и screenshot сохраняются только при падении.
backend/├── tests/ # vitest — API/integration│ ├── helpers.ts # TEST_MANAGER, TEST_CLIENT, phoneToEmail, loginRaw│ ├── auth-*.test.ts # 5 файлов: login, token, phone-email, isolation, permissions│ ├── manager-permissions.test.ts│ └── manager-create-client.test.ts├── tests-e2e/ # Playwright — browser E2E│ └── manager-create-client.spec.ts├── playwright.config.ts # headed/headless, locale, trace└── vitest.config.ts # exclude tests-e2e/**Команды
Заголовок раздела «Команды»cd backend
# API-тесты (vitest)npm test # single runnpm run test:watch # watch mode
# Browser E2E (Playwright)npm run test:e2e # headed локальноnpm run test:e2e:ui # Playwright UI mode — интерактивный debugПодготовка
Заголовок раздела «Подготовка»Оба слоя требуют поднятого Directus и выполненного seed:
docker compose up -d # Directus + Postgres + Redis на :8055cd backendnpm installnpm run seed # создаёт manager@dimetra.kz, 2 клиентов, 2 проектаSeed идемпотентен — повторный запуск выводит already exists и ничего не ломает.
Тесты используют фиксированные креды:
- Manager:
manager@dimetra.kz/manager1234 - Client A:
+77001234567/test1234 - Admin (для setup/verify):
admin@dimetra.kz/admin
Почему Directus UI тестируем через Playwright
Заголовок раздела «Почему Directus UI тестируем через Playwright»Менеджер работает исключительно через Admin UI. UI-специфичные гарантии:
- Preset’ы — поле
Рольна форме создания юзера disabled, так как setup-roles.sh выставил presetrole=Client, status=active. Permissions-тест на уровне API этого не ловит (он только проверяет, что preset применился в БД). - Редиректы — после save Directus редиректит PM на список
/admin/users(не на карточку нового юзера), потому что у PM нет прав читать весь user. - Тосты и модалки — при загрузке страницы Directus пытается подтянуть metadata-эндпоинты, на которые у PM нет прав, и показывает «Forbidden» toast. Если его не закрыть — перекрывает save-кнопку.
- Русская локаль — UI на русском (
Войти,Скрыть,Добавление пользователя), селекторы и тексты в тестах на русском.
Подводные камни (gotchas)
Заголовок раздела «Подводные камни (gotchas)»1. Directus filter brackets
Заголовок раздела «1. Directus filter brackets»Квадратные скобки [ и ] в filter-параметрах должны оставаться литералами. URLSearchParams / URL.searchParams.set их percent-кодируют (%5B / %5D), и Directus молча возвращает пустой data: [].
❌ Не делать:
const q = new URLSearchParams({ 'filter[email][_eq]': email });fetch(`${URL}/users?${q}`); // всегда []✅ Правильно:
fetch(`${URL}/users?filter[email][_eq]=${encodeURIComponent(email)}`);encodeURIComponent нужен для значения (там может быть @), но сами скобки кодировать нельзя.
2. «Forbidden» toasts перекрывают клики
Заголовок раздела «2. «Forbidden» toasts перекрывают клики»У роли PM нет прав на некоторые metadata-эндпоинты, поэтому при открытии формы создания юзера всплывает [FORBIDDEN] You don't have permission to access this. Этот toast — оверлей, перехватывающий pointer events; save-кнопка под ним не кликается.
Перед кликом save — дождаться/закрыть все видимые «Скрыть»-кнопки:
const dismissBtn = page.getByRole('button', { name: 'Скрыть' });while (await dismissBtn.first().isVisible().catch(() => false)) { await dismissBtn.first().click(); await page.waitForTimeout(100);}3. PM редиректится на список, не на карточку
Заголовок раздела «3. PM редиректится на список, не на карточку»После успешного создания юзера Directus редиректит PM на /admin/users (список), не на /admin/users/{uuid} (карточка), потому что у PM нет read-прав на весь user (только на клиентов с role=Client, а UI пытается прочитать полный объект).
Ожидать надо любой путь, начинающийся с /admin/users, кроме формы создания:
await page.waitForURL( (url) => url.pathname.startsWith('/admin/users') && !url.pathname.endsWith('/+'),);4. Cleanup — используйте admin-токен, не PM-токен
Заголовок раздела «4. Cleanup — используйте admin-токен, не PM-токен»PM не может удалять пользователей (403). Для cleanup созданных в тесте юзеров берём admin-токен:
async function deleteUserIfExists(email: string) { const token = await adminToken(); const url = `${DIRECTUS_URL}/users?filter[email][_eq]=${encodeURIComponent(email)}&fields=id&limit=1`; const { data } = await (await fetch(url, { headers: { Authorization: `Bearer ${token}` } })).json(); if (data?.[0]?.id) { await fetch(`${DIRECTUS_URL}/users/${data[0].id}`, { method: 'DELETE', headers: { Authorization: `Bearer ${token}` }, }); }}Вызывается в beforeEach и afterEach — гарантирует чистое состояние между тестами.
Изоляция состояния
Заголовок раздела «Изоляция состояния»Сейчас тесты гоняются на dev-БД (:8055). Это работает, пока никто в процессе разработки не полагается на конкретные данные, кроме seed’а — все тестовые юзеры имеют узнаваемые email (79998887766@phone.dimetra.kz, 70000000000@phone.dimetra.kz) и чистятся в cleanup.
Отдельная тест-БД на :8056 (Postgres + Directus в docker-compose.test.yml) — запланированный backlog-элемент. Он понадобится, когда появятся:
- Flow-тесты с побочными эффектами (например,
on_project_created, который создаёт 8 stages + chat) - CI, где нельзя полагаться на локальное состояние dev-БД
- Параллельные прогоны
Связь с мобильными E2E
Заголовок раздела «Связь с мобильными E2E»Мобильное приложение (Expo) тестируется отдельным стеком — см. E2E тестирование (Maestro + Expo dev build). Это другой слой:
- Backend/UI тесты (эта страница) → контракт API + Directus Admin (рабочее место менеджера)
- Mobile E2E → клиентский опыт в приложении (логин, изоляция, экраны)
Оба слоя дополняют друг друга: permissions и роли мы верим в backend-тестах, а визуальный мобильный flow — в Maestro.