← Admin Panel

Тест-кейсы

admin panel · workzone
Принятые решения
User management API — не тестируем CRUD пользователей, invite, RBAC, audit log — эндпоинты auth-модуля, полностью покрыты в Auth & Security. Admin тестирует только свой бэкенд (PlatformSettings) и фронтенд-поведение поверх делегированных API.
Frontend → Testing Library + MSW Компоненты тестируются через React Testing Library (поведение, не реализация); API-ответы мокаются через MSW (Mock Service Worker) — единый перехват сетевых запросов.
Notifications backend — тестируем здесь Каналы, маршруты, личные настройки и диспетчер доставки — core-ORM с CRUD в Admin (как PlatformSettings), потому покрываются тут. Источники событий (sync_runs, brute-force, curation) живут и тестируются в своих модулях — здесь проверяется разводка и доставка, не детектор.
Системный prompt — тестируем здесь prompt_settings — core-ORM (singleton) с CRUD в Admin, как PlatformSettings. Дефолты обоих блоков — seed-константы в коде, NULL = «взять дефолт». Тут — хранение, кап токенов и сброс; саму подстановку prompt в запрос модели держит и тестирует Query Engine.
Стек и инфраструктура
Backend done pytest, pytest-asyncio, pytest-cov, httpx — общая инфраструктура
Backend testcontainers, factory_boy — общие из auth-security; добавляем PlatformSettingsFactory, NotificationChannelFactory, PromptSettingsFactory
Frontend Vitest + React Testing Library + MSW — перехват fetch, моделирование ответов бэкенда без реального сервера
Маркеры @pytest.mark.unit, @pytest.mark.integration — общие
Unit Pydantic-схемы — валидация без БД
test_settings_schema.py
PlatformSettings DTO — поля и ограничения
Unit
Кейсы
valid settingsполный набор валидных полей → схема проходит
partial updatePATCH-схема: только изменённые поля, остальные Optional
invalid timezoneнесуществующий IANA tz (Mars/Olympus) → reject
invalid localeне из ru | en → reject
invalid date_formatне из допустимых форматов → reject
negative TTL@parametrize access / refresh / session_absolute < 0 → reject
zero TTL@parametrize TTL = 0 → reject (CHECK > 0)
invalid accent_colorне hex-формат (красный) → reject
invalid themeне из dark | light → reject
test_notification_schema.py
Каналы, маршруты, payload — валидация без БД
Unit
Кейсы
webhook requires url+presetkind=webhook без url/preset → reject
builtin no urlkind=in_app|emailpreset/url запрещены
invalid event_typeтип вне sync|security|budget|system|discovery → reject
invalid severityне из info|warning|critical → reject
slack payloadформаттер пресета Slack → ожидаемая форма сообщения
generic payloadнейтральный JSON: event·severity·title·source·ts
test_prompt_schema.py
prompt_settings DTO — кап токенов, поля
Unit
Кейсы
valid overridesafety_text / org_text в пределах капа → схема проходит
null = defaultоба поля None допустимы → блок берёт встроенный дефолт
over token capтекст длиннее лимита → reject (кап защищает бюджет окна)
blank not nullпустая строка ''None: либо нормализуется в None (сброс), либо reject — без «пустого» override
known placeholders okтекст с {org_name} / {today} (или без них) → схема проходит: оба токена необязательны
unknown placeholder rejectнеизвестный токен {foo} → reject: whitelist закрыт, буквальный {…} в prompt не утекает
Integration · P0 PlatformSettings API → HTTP API → Conformance
test_platform_settings.py
CRUD, валидация и целостность singleton
IntegrationP0
Кейсы
GET → defaultsGET /admin/settings → 200, все поля с дефолтами из миграции
PATCH → partialorg_name + timezone → 200, остальные поля не изменились
PATCH → TTLsaccess / refresh / session_absolute TTL → значения в БД обновлены
PATCH → brandingaccent_color → 200, значение корректно
invalid timezone→ 422 VALIDATION_ERROR
invalid localelocale=fr → 422 VALIDATION_ERROR
negative TTLaccess_token_ttl=-1 → 422 VALIDATION_ERROR
singleton — no POSTPOST /admin/settings → 405 METHOD_NOT_ALLOWED
singleton — no DELETEDELETE /admin/settings → 405 METHOD_NOT_ALLOWED
updated_at triggerпосле PATCH updated_at строго больше предыдущего
curation_frequency enumdaily / weekly → 200; иное (hourly) → 422 (CHECK)
weekday only weeklyсвязанный CHECK: curation_weekday BETWEEN 0 AND 6 задан только при weekly; при daily он NULL, ненулевой → 422; weekday=7 → 422
agent_iteration_cap > 0@parametrize ≤ 0 → 422 (CHECK > 0); положительное → 200
agent_max_concurrency > 0@parametrize ≤ 0 → 422 (CHECK > 0); положительное → 200
mcp_enabled toggleбулев рубильник: PATCH true/false → 200, значение в singleton; не-булево → 422
mcp_enabled=false — эффектзапрос к /mcp отклонён до проверки ключа — закрытая дверь MCP
test_maintenance.py
Режим обслуживания — заглушка, allow-list, пауза → Maintenance
IntegrationP0
Кейсы
user gets 503режим включён → обычному Member middleware отвечает 503 + Retry-After (недоступность временная)
owner/admin allowlistOwner и Admin в обход режима → 200; заперть сам себя нельзя — оператор проверяет платформу перед снятием
sync + agents pausedпри включении режима sync и фоновая полоса агентов ставятся на паузу
toggle owner-onlyвключает только Owner (maintenance_mode в singleton); Admin/Member PATCH → 403 FORBIDDEN
off restoresснятие режима → обычные пользователи снова 200, sync и агенты возобновляются
test_notifications.py
Каналы · маршруты · диспетчер · доставка
IntegrationP0
Кейсы
seed builtin channelsпосле миграции есть in_app + email, is_builtin, неудаляемы
webhook CRUDсоздать/удалить webhook-канал; url_enc / secret_enc не возвращаются наружу
delete builtin → rejectDELETE in_app/email → 409 CONFLICT
route togglePATCH ячейки тип×канал → enabled в БД
route lockedснять маршрут in_app × security/sync (locked + CHECK enabled OR NOT locked) → 422 · прочие маршруты выключаются свободно
мутации каналов — только Ownerне-Owner (Member / Admin) POST / DELETE /admin/notification-channels → 403 FORBIDDEN на бэкенде (не только фронтовое read-only); аноним → 401 UNAUTHORIZED
правка маршрутов — только Ownerне-Owner PATCH /admin/notification-routes → 403 FORBIDDEN; аноним → 401 UNAUTHORIZED
листинг каналов скрывает секретGET /admin/notification-channels → 200; url_enc / secret_enc не отдаются ни в списке, ни в детали — только признак наличия
dispatch fan-outсобытие → deliveries на каждый включённый маршрут (канал не на паузе)
type silencedвсе маршруты типа off → событие никому не уходит; in_app security/sync под замком обнулить нельзя
channel pausedканал enabled=false → его deliveries не создаются; активные каналы доставлены
in_app channel stays onвыключить канал in_app (enabled=false) → reject (CHECK)
recipients by roleличные каналы → только Owner/Admin; новый админ включён автоматически
personal opt-outin_app_enabled / email_enabled раздельно гасят свой канал по типу
opt-out unrestrictedлюбой личный канал снимается для любого типа, включая security/sync — личных замков нет
dedup seriesсерия одного dedup_key в окне → одна строка, dedup_count растёт, last_seen_at двигается; за окном — новая строка
discovery action idempotentповторное «В scope» (контейнер уже в allow-list) → no-op; событие состояние не хранит
in_app read stateпометка прочтения → read_at, unread-счётчик уменьшается
email no SMTPSMTP не настроен → email-delivery failed, in_app доставлено
test_prompt_settings.py
Override, сброс, кап и целостность singleton
IntegrationP0
Кейсы
GET → effective defaultoverride пуст → ответ отдаёт встроенный текст обоих блоков (effective), не null
is_default flagoverride пуст → is_default=true; после PATCH блока → false; фронт по флагу метит «на дефолте» и гасит «Сбросить»
localized defaultlocale=en → effective-дефолт по-английски; ru → по-русски; override пуст в обоих
unknown placeholderoverride с {foo} → 422 VALIDATION_ERROR; {org_name}/{today} → 200
PATCH org overrideorg_text → 200, в БД сохранён; safety_text не тронут
reset → nullсброс блока → колонка NULL, GET снова отдаёт встроенный дефолт
over token capтекст длиннее лимита → 422 VALIDATION_ERROR
updated_by recordedпосле PATCH updated_by = текущий админ; updated_at строго больше
singleton — no POST/DELETEPOST / DELETE /admin/ai-prompt → 405 METHOD_NOT_ALLOWED
access — Owner/Admin editOwner и Admin PATCH → 200; Member → 403 FORBIDDEN
test_token_usage.py
Расходы AI — лимиты, по людям, по моделям
IntegrationP0
Кейсы
PATCH limitsagent_weekly_token_budget · chat_weekly_token_budget → 200, значения в singleton
budget alertPATCH ai_monthly_budget + ai_budget_alert_enabled → 200; включить флаг без суммы → 422 (CHECK); порог достигнут → уведомление типа Budget, запрос не блокируется
zero/negative limit@parametrize ≤ 0 → 422 (CHECK > 0); null → без лимита
list per-userGET /admin/usage → строка на пользователя: агенты + чат + всего за неделю
agents derivedсумма = SUM(agent_runs.tokens_used) по agents.user_id за окно недели
chat derivedсумма = SUM(messages.tokens_used) по conversations.user_id, только assistant
week windowрасход вне текущей недели (до вс 00:00 org-tz) в сумму не входит
excludes system spendembedding / RAG (model_usage) к пользователю не привязаны — в суммы не попадают
chat advisoryчат сверх chat_weekly_token_budget → индикатор «сверх», запрос не блокируется (v1)
detail breakdownGET /admin/usage/:user → агенты по агентам, чат по messages.model
by-model spendразрез по моделям из model_usage (модель · функция, вход/выход, стоимость) — org-уровень, к людям не привязан; топ-5 по стоимости, остальные за «Показать все»
company totalитого по компании = SUM(model_usage) за неделю / месяц / год (все функции, токены + $); производное, без таблицы-счётчика; @parametrize по окну
total ⊇ per-user«итого» шире суммы по людям — включает безличные embedding / RAG; месячная плитка сверяется с порогом ai_monthly_budget
sort by totalсписок по умолчанию убыванием «Всего»; поиск по имени/email
access — Owner/AdminOwner/Admin GET → 200; Member → 403; PATCH лимитов — только Owner
Integration · P1 Контроль доступа
test_admin_access.py
Доступ к admin-роутам по ролям
IntegrationP1
Кейсы
Owner GET settings→ 200
Owner PATCH settings→ 200
Admin GET settings→ 200 (read-only доступ)
Admin PATCH settings→ 403 FORBIDDEN
Member GET settings→ 403 FORBIDDEN
unauthenticated→ 401 UNAUTHORIZED
Frontend React — admin UI
AdminLayout.test.tsx
Layout, навигация, role-видимость
Frontend
Кейсы
Owner → full sidebarвсе пункты: Dashboard, Пользователи, Audit Log, Настройки
Admin → restrictedDashboard, Пользователи, Настройки (read-only); Audit Log не виден (Owner-only)
Member → redirectперенаправление на основной интерфейс, sidebar не рендерится
active stateтекущий маршрут выделен, sub-страница выделяет родителя
theme toggleклик переключает dark/light, значение в localStorage
language toggleклик переключает RU/EN, значение в localStorage
logout«Выйти» → logout, redirect /login
logout all«Выйти со всех устройств» → подтверждение → logout-all
PlatformSettings.test.tsx
Форма настроек — save, валидация, состояния
Frontend
Кейсы
form pre-fillформа загружает текущие значения из API
save → PATCHизменение + Save → PATCH только изменённых полей
validation errorsневалидные значения → inline-ошибки, запрос не уходит
success feedbackуспех → toast «Настройки сохранены»
server error422 → ошибки из errors[] у соответствующих полей
read-only for AdminAdmin видит значения, Save отсутствует / disabled
locale resolution chainпоказ берёт первое заданное: личный override → дефолт организации → locale браузера; незаданный override прозрачно падает на уровень организации → Locale
password policyсекция «Политика паролей» — текст NIST 800-63B read-only
slack write-only secretsbot-токен и signing secret — write-only, в ответе/UI не возвращаются
slack test connection«Проверить соединение» → ping Slack (auth.test) → чип статуса
slack enable toggleтумблер → slack_settings.enabled; off → бот не отвечает
slack auto-link toggleтумблер «Авто-привязка по email» → off → непривязанному всегда код, авто-матча нет
telegram write-only secretsbot-токен write-only; webhook secret генерируется при setWebhook, вручную не вводится — в ответе/UI не возвращаются
telegram test connection«Проверить соединение» → ping Telegram (getMe) → чип статуса + bot_username
telegram enable toggleтумблер → telegram_settings.enabled; off → бот не отвечает
telegram — без авто-привязкиemail нет → тумблера «Авто-привязка» нет, всегда только код-привязка
mattermost write-only secretsbot-токен — write-only, в ответе/UI не возвращается
mattermost test connection«Проверить соединение» → ping сервера (users/me) → чип статуса + хэндл бота
mattermost enable toggleтумблер → mattermost_settings.enabled; включение атомарно — живая проверка, отказ → 409 и переключатель откатывается; off → бот не отвечает
mattermost — без авто-привязкив событиях email нет → тумблера «Авто-привязка» нет, всегда только код-привязка
loading stateдо ответа API — skeleton/spinner, форма не рендерится
network error500 → сообщение + «Повторить»
UsersPage.test.tsx
Список пользователей и приглашения
Frontend
Кейсы
list rendersтаблица: имя, email, роль, статус из API
tab switchingпереключение «Список» ↔ «Приглашения»
invite form«Пригласить» → форма email + роль, валидация email
actions by roleOwner — все действия; Admin — ограниченные (RBAC-матрица)
deactivated badgeдеактивированный отмечен, доступно «Реактивировать»
empty state0 пользователей → «Пригласите команду»
revoke invite«Отозвать» → подтверждение → invite удалён из списка
resend invite«Переотправить» → POST, success toast
loading stateдо ответа API — skeleton таблицы
network error500 → сообщение об ошибке
UserDetailPage.test.tsx
Карточка пользователя — профиль и действия
Frontend
Кейсы
profile rendersимя, email, роль, дата создания, последний вход
Owner → change roledropdown ролей, сохранение → PATCH, роль обновилась
Admin → limited actionsдействия только над Member'ами, не над Owner/Admin
deactivate«Деактивировать» → модалка → статус обновлён
deleteOwner: «Удалить» → модалка подтверждения → DELETE
reset password«Сбросить пароль» → подтверждение → success toast
reactivateдеактивированный → «Реактивировать» доступна
self-viewсвой профиль → деактивация и удаление скрыты
AuditLogPage.test.tsx
Журнал аудита — таблица и фильтры
Frontend
Кейсы
table rendersзаписи: актор, действие, результат, дата, IP
filter by actionтип действия (login, role_changed…) → таблица фильтруется
filter by dateдиапазон дат → query params, таблица обновляется
filter by actorпользователь-актор → query param, только его действия
empty state0 записей → «Записей нет»
loading stateдо ответа API — skeleton таблицы
network error500 → сообщение об ошибке
NotificationsPage.test.tsx
Каналы, матрица тип×канал, адресаты
Frontend
Кейсы
channels renderin-app + email (со статусом SMTP) + список webhooks
add webhook«+ Добавить» → выбор пресета (Slack/Generic) → POST, строка в списке
test webhook«Test» → POST, success/err toast по результату
channel pause toggleтумблер email/webhook → PATCH enabled; in-app тумблер залочен (disabled)
delete webhook guard«Удалить» → подтверждение → строка убрана
matrix route toggleклик пилюли канала → PATCH маршрута
matrix in-app lockedпилюля in-app у security/sync — залочена (замок, клик не снимает); прочие пилюли переключаются
paused channel cellканал на паузе → его пилюли в матрице приглушены (маршрут цел, не стреляет)
smtp warningSMTP не настроен → чип email краснеет, предупреждение
recipients noteплашка «адресаты — Owner / Admin», ссылки на Пользователи и профиль
read-only for AdminAdmin видит, правит только Owner
loading stateдо ответа API — skeleton
AiBehaviorPage.test.tsx
Два блока — правка на месте, сброс, кап
Frontend
Кейсы
bubbles renderдва блока с текущим текстом из API; кнопки «Сбросить» / «Редактировать»
edit in place«Редактировать» → текст блока становится редактируемым на месте, кнопки → «Отмена / Сохранить»
save → PATCH«Сохранить» → PATCH только этого блока, выход из правки, success toast
cancel discards«Отмена» → правка отброшена, запрос не уходит, блок назад в режим просмотра
reset → default«Сбросить» → PATCH null, блок показывает встроенный дефолт
default stateis_default=true → блок помечен «на дефолте», «Сбросить» погашена; после правки флаг false → кнопка активна
token counter capсчётчик растёт при вводе; за капом — «Сохранить» disabled + подсветка
info tooltipиконка «i» у заголовка → подсказка о назначении блока на hover/focus
independent blocksправка/сброс одного блока не затрагивает второй — два независимых PATCH
loading stateдо ответа API — skeleton блоков
ListControls.test.tsx
Общая механика: поиск, фасеты, пагинация → List Controls
Frontend
Кейсы
search thresholdзапрос уходит при обоих условиях: пауза в наборе ≈ 300 мс (debounce) и ≥ 2 значимых символов; 1 символ или до паузы — не уходит → #threshold
query resets pageлюбое изменение запроса — новый текст, фасет, сортировка — сбрасывает на 1-ю страницу (иначе можно остаться на странице, которой в новом наборе нет) → #page-params
facet OR / ANDзначения внутри одного фасета — по ИЛИ, разные фасеты — по И (строки на пересечении) → #facet-logic
offset/limit clampзапрос за пределы диапазона → кламп к крайней странице; счётчик «X–Y из N» согласован → #pagination
AgentsPage.test.tsx
Список всех агентов — пауза, лимиты (Admin)
Frontend
Кейсы
pause agent · guarded«Пауза» → подтверждение → PATCH; агент переходит в paused, прогоны не стартуют
resume agent«Снять паузу» на paused-агенте → PATCH, статус активен
save limitsплатформенные лимиты прогона (потолок шагов + одновременных прогонов на платформу, единые на организацию) → «Сохранить» → PATCH platform_settings
open profile (read-only)«Открыть профиль» → переход на карточку агента (модель, инструкции — только чтение)
AiModelsPage.test.tsx
Провайдеры и модели — назначение эмбеддера
Frontend
Кейсы
change & reembed · guardedсмена назначенного эмбеддера → «Сменить и пересчитать» → подтверждение (векторы несовместимы) → запуск сплошного переэмбеддинга
reembed in progressпока прогон идёт — строка показывает ход (%), повторная смена заблокирована
disable model · guarded«Выключить» / «Убрать» модель → подтверждение → PATCH; назначенный эмбеддер убрать нельзя
ToolsPage.test.tsx
Инструменты AI — тумблеры и провайдеры
Frontend
Кейсы
chat / agents toggleтумблеры «В чате» / «Агентам» → PATCH раздельно; off → инструмент не подаётся в свою поверхность
configure provider«Настроить» → форма credential (write-only); секрет в ответе/UI не возвращается
test provider«Протестировать» → POST ping → чип статуса success/err
access — Owner/Admin/admin/ai-tools · Owner / Admin — правки (тумблеры, провайдеры) пишутся как админ-действие обеими ролями
UsagePage.test.tsx
Расходы AI — срезы и переход в detail
Frontend
Кейсы
sections renderсложенные секции одной страницы: «Итого по компании» · «Лимиты расхода» · «Расход по сотрудникам» · «Расход по моделям»
total tilesв «Итого» неделя / месяц / год — три статичные плитки одновременно; переключателя окна нет (селектор «30 дней ▾» есть только в срезе по моделям)
row → detailклик по строке сотрудника → переход в usage detail того сотрудника
access — Owner/Admin/admin/usage · Owner / Admin; в секции лимитов обычная «Сохранить», доступна обеим ролям
HarvesterPage.test.tsx
Harvester в Admin — прогон, guarded-действия
Frontend
Кейсы
metrics renderсписок источников из API; статус прогона показан inline в ячейке источника (idle · syncing · error)
run sync«Синхронизировать» / «Синхронизировать всё» → POST без подтверждения; строка источника переходит в syncing
pause + wipe«Поставить на паузу» → PATCH без подтверждения; «Очистить данные» — единственное guarded-действие (type-to-confirm)
access — Owner/Admin/admin/harvester · Owner / Admin — экран и действия доступны обеим ролям
KnowledgeStorePage.test.tsx
Knowledge Store в Admin — доводка, backups
Frontend
Кейсы
metrics renderсчётчики графа/векторов, расписание доводки, последние прогоны и снапшоты
run curation«Запустить» → POST без подтверждения; прогон в статусе running, повторный запуск заблокирован (кнопка disabled); «Отменить прогон» — guarded
restore guarded«Восстановить» снапшот → type-to-confirm → POST; снимки создаются только по расписанию, ручного снятия нет
reembed lockидёт переэмбеддинг (назначен через AI-модели) → запуск доводки заблокирован с пояснением
NotificationFeed.test.tsx
Лента in-app — колокольчик, чтение, действия
Frontend
Кейсы
bell unread countколокольчик со счётчиком непрочитанных (доставки read_at IS NULL); клик открывает панель хвоста событий
mark all read«Прочитать все» → всем доставкам админа read_at, счётчик обнуляется
read / unread toggle«Отметить прочитанным» гасит карточку; «Отметить непрочитанным» возвращает в счётчик
в scope · idempotentactionable discovery: «В scope» заводит контейнер в охват; повтор (уже в scope) — no-op, кнопка гаснет у всех
unread facetфасет «Непрочитанные» (частый дефолт) → только unread; снять → вся история; группировка Сегодня · Ранее
Structure Файловая структура тестов

Бэкенд тонкий (PlatformSettings CRUD), основная логика — фронтенд. User management и audit log API покрыты в auth-security — здесь только UI поверх них. Экраны данных/AI (Агенты, AI-модели, Инструменты, Расходы, Harvester, Knowledge Store) и лента уведомлений покрыты FE-карточками здесь — только React-компонент и его guarded-действия; доменную бэкенд-логику держат и тестируют сами модули. Приоритет (P0–P1) ортогонален каталогам и задаётся маркерами (pytest -m p0), а не папками.

  • tests/backend — общий каркас
    • conftest.pyБД, сессия, app client (уже есть)
    • factories/фабрики данных
      • admin.pyPlatformSettingsFactory · NotificationChannelFactory · PromptSettingsFactory
    • admin/каталог модуля
      • conftest.pyadmin fixtures, admin_client
      • unit/без БД
        • test_settings_schema.pyPydantic DTO
        • test_notification_schema.pyканалы · маршруты · payload
        • test_prompt_schema.pyprompt_settings DTO · кап токенов
      • integration/с БД
        • test_platform_settings.pyCRUD · validation · singleton CHECK
        • test_maintenance.py503 · allow-list · пауза sync/агентов
        • test_admin_access.pyrole-based access
        • test_notifications.pychannels · routes · dispatch · delivery
        • test_prompt_settings.pyoverride · reset · cap · singleton
  • frontend/…/admin/__tests__/рядом с кодом
    • AdminLayout.test.tsxsidebar · role · toggles · logout
    • PlatformSettings.test.tsxform · save · validation · states
    • UsersPage.test.tsxlist · tabs · invite · actions
    • UserDetailPage.test.tsxprofile · role · actions · RBAC
    • AuditLogPage.test.tsxtable · filters · states
    • NotificationsPage.test.tsxchannels · matrix · recipients
    • AiBehaviorPage.test.tsxbubbles · edit · reset · cap · tooltip
    • ListControls.test.tsxshared: search threshold · reset page · facets · clamp
    • AgentsPage.test.tsxpause · resume · limits (guarded)
    • AiModelsPage.test.tsxchange & reembed · disable (guarded)
    • ToolsPage.test.tsxchat/agents toggles · configure · test
    • UsagePage.test.tsxslices · window · → detail
    • HarvesterPage.test.tsxrun · pause source (guarded)
    • KnowledgeStorePage.test.tsxcuration · backups (guarded)
    • NotificationFeed.test.tsxbell · read/unread · в scope · unread-facet