Перейти к содержимому

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

Backend-тесты DIMETRA работают в двух слоях: быстрые API-тесты на vitest проверяют контракт permissions и бизнес-правила через REST-запросы, а Playwright-тесты запускают реальный Directus Admin UI в браузере и проходят полный user-facing сценарий менеджера.

СлойИнструментЧто проверяетСкорость
API / IntegrationVitest + fetchPermissions matrix, role isolation, preset’ы, response codes~2 сек/suite
Browser E2EPlaywright (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 / tokenauth-login, auth-token✅ (через login helper в каждом тесте)
Phone-as-emailauth-phone-email
Client — isolation, permissionsauth-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):

  1. PM может создать пользователя через POST /users
  2. Созданный юзер получает роль Client и статус active (preset из scripts/setup-roles.sh)
  3. PM может прочитать созданного клиента
  4. PM может обновить имя и описание
  5. Попытка переопределить роль на Administrator игнорируется — preset побеждает
  6. PM не может удалить пользователя (403)
  7. Созданный клиент реально может залогиниться с заданным паролем

Browser-тесты (backend/tests-e2e/manager-create-client.spec.ts):

  1. Full flow — логин в Admin UI → навигация на /admin/users/+ → заполнение формы (Имя, Фамилия, E-mail, Пароль, Описание) → клик save → проверка в БД через admin API, что роль Client и preset сработали
  2. 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 run
npm 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 на :8055
cd backend
npm install
npm 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

Менеджер работает исключительно через Admin UI. UI-специфичные гарантии:

  • Preset’ы — поле Роль на форме создания юзера disabled, так как setup-roles.sh выставил preset role=Client, status=active. Permissions-тест на уровне API этого не ловит (он только проверяет, что preset применился в БД).
  • Редиректы — после save Directus редиректит PM на список /admin/users (не на карточку нового юзера), потому что у PM нет прав читать весь user.
  • Тосты и модалки — при загрузке страницы Directus пытается подтянуть metadata-эндпоинты, на которые у PM нет прав, и показывает «Forbidden» toast. Если его не закрыть — перекрывает save-кнопку.
  • Русская локаль — UI на русском (Войти, Скрыть, Добавление пользователя), селекторы и тексты в тестах на русском.

Квадратные скобки [ и ] в 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 нужен для значения (там может быть @), но сами скобки кодировать нельзя.

У роли 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('/+'),
);

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-БД
  • Параллельные прогоны

Мобильное приложение (Expo) тестируется отдельным стеком — см. E2E тестирование (Maestro + Expo dev build). Это другой слой:

  • Backend/UI тесты (эта страница) → контракт API + Directus Admin (рабочее место менеджера)
  • Mobile E2E → клиентский опыт в приложении (логин, изоляция, экраны)

Оба слоя дополняют друг друга: permissions и роли мы верим в backend-тестах, а визуальный мобильный flow — в Maestro.