Captures what PR #49 intentionally left out: DB-integration tests (need a test Postgres), the public-share XFF e2e + real-Redis Lua check (need an HTTP/Redis harness), the full AiChatService.stream integration (R1-stream seam), and the related non-test findings (no server-side model allow-list, unreferenced restriction-cache invalidation, client-only embed recursion cap, missing cycle guard, and the pre-existing jest DI/lib0-ESM debt). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
9.0 KiB
Отложенные тесты по фичам с коммита 053a9c0d (хвост от PR #49)
Контекст
PR #49 («test: cover features since 053a9c0d + repair test tooling») закрыл
основную массу покрытия новых фич gitmost (+~330 тестов: server/Jest,
client/Vitest, editor-ext/Vitest, packages/mcp/node:test) и починил
тест-инструментарий (FIX-0 сломанные спеки transclusion, BUILD-0 сборка
editor-ext перед серверными тестами, INFRA-0 резолв .tsx email-шаблонов).
Часть тестов из принятого тест-плана намеренно отложена — им нужен тестовый Postgres, реальный Redis или HTTP/e2e-харнес, которых в проекте сейчас нет, либо инвазивный рефактор продакшн-кода. Ниже — что осталось и почему, чтобы не потерять.
1. Интеграционные тесты против БД (нужен тестовый Postgres)
Сейчас все repo-зависимые проверки делаются на моках; SQL-уровень не исполняется. Чтобы покрыть это честно, нужен поднимаемый в CI Postgres (testcontainers или сервис в pipeline) + хелпер миграций.
-
AiAgentRoleRepo— изоляция и индексы.apps/server/src/database/repos/ai-agent-roles/ai-agent-roles.repo.ts. Проверить против реальной БД:findById/listByWorkspaceисключают soft-deleted строки;findByIdдля roleId из ЧУЖОГО workspace → undefined (tenant-изоляция); дубль имени в одном workspace → 23505; то же имя переиспользуемо после softDelete (partial unique indexWHERE deleted_at IS NULL, миграция20260620T120000-ai-agent-roles.ts); одинаковое имя в разных workspace разрешено. Это «хребет» безопасности — сейчас только предполагается unit-моками. -
AiChatRepo.findByCreator— join role-badge.apps/server/src/database/repos/ai-chat/ai-chat.repo.ts(~:27-70). Чат с enabled-ролью → roleName/roleEmoji заполнены; с soft-deleted ролью → бейдж NULL; с DISABLED ролью → бейдж NULL (должно совпадать сresolveRoleForRequest); ORDER BY квалифицированaiChats.*(нет ambiguous column после join). Не проверяемо чистым unit-ом. -
WorkspaceService.update/WorkspaceRepo.updateSetting— jsonb-merge.apps/server/src/core/workspace/services/workspace.service.ts(:514),:275). Сейчас покрыта только форма вызова сервиса (apps/server/src/database/repos/workspace/workspace.repo.ts(workspace-html-embed.spec.ts). Не покрыто (нужна БД):htmlEmbed:trueперсистится через jsonb-merge не затирая соседние настройки (ai, sharing). Это и есть «kill-switch пишется» — критично, что write-половина тоггла не ломает остальной settings-namespace. -
FK
page_template_referencesonDelete('cascade'). Миграция20260620T131000-page-template-references.ts. Проверить, что удаление source/reference-страницы каскадит строки ссылок.
2. HTTP / e2e-харнес (его нет в apps/server)
-
Public-share ассистент: обход per-IP throttle ротацией XFF, но per-workspace cap держит. Контроллер использует стоковый
@UseGuards(ThrottlerGuard)(apps/server/src/core/ai-chat/public-share-chat.controller.ts), IP берётся из FastifytrustProxy→X-Forwarded-For. Единственный оправданный e2e (named journey «аноним спамит ассистента»): ротация XFF обходит per-IP лимит 5/min, но per-workspace cost-cap всё равно отдаёт 429. Требует поднятого HTTP-слоя Nest + trusted-proxy конфигурации. -
Достоверность Lua-окна cost-cap против реального Redis.
apps/server/src/core/ai-chat/public-share-workspace-limiter.ts(SLIDING_WINDOW_LUA). Сейчас cap тестируется против TS-реализацииFakeRedisвpublic-share-chat.spec.ts— баг в самой Lua-строке (>=vs>, неверный PEXPIRE) не поймается. Нужен интеграционный тест против реального/testcontainers Redis.
3. Полная интеграция AiChatService.stream (рефактор R1-stream)
apps/server/src/core/ai-chat/ai-chat.service.ts. В PR #49 извлечён и
покрыт только чистый buildErrorAssistantRecord. Полные интеграционные
сценарии — запись чата, упавшего на первом ходу (onError), жизненный
цикл external-MCP клиентов (закрытие при throw/onFinish), и
история восстанавливается из БД, а не из body.messages (анти-tamper) —
требуют сидирования SDK streamText (инъекция/seam колбэков onError/
onFinish/onAbort + res.hijack). Отложено, чтобы не дестабилизировать
287-строчный stream(); делать вместе с выносом testable turn-pipeline.
Сопутствующие НЕ-тестовые находки (отдельные задачи)
Всплыли во время написания тестов; чинить отдельными PR, не в тест-ветке.
-
Нет серверной валидации «допустимого набора моделей» для роли.
chatModel— свободная строкаMaxLength(200)(apps/server/src/core/ai-chat/roles/dto/agent-role.dto.ts); невалидная модель принимается и падает только в рантайме как provider-ошибка/503. Плюс клиентский enum драйверов (ai-agent-role-form.tsx) захардкожен и может разойтись с сервернымAI_DRIVERS(apps/server/src/integrations/ai/ai.types.ts) — кандидат на shared-константу или contract-тест. -
WsService.invalidateSpaceRestrictionCacheне имеет вызывающих.apps/server/src/ws/ws.service.ts(~:44-48). КэшspaceHasRestrictions(TTL 30с) ничем не инвалидируется при изменении ограничений → реальное 30-секундное окно устаревания (риск утечки заголовков/метаданных дерева). Привязать инвалидацию к ручкам restrict/grant/revoke. -
Серверный guard рекурсии page-embed. Cap глубины/циклов
PAGE_EMBED_MAX_DEPTH=5— только клиентский (page-embed-view.tsx). Серверный/pages/template/lookupограничен лишь throttle 30/60с +ArrayMaxSize(50). Оценить, нужен ли серверный guard раскрытия. -
collectPageEmbedsFromPmJsonбез cycle-guard.apps/server/src/core/page/transclusion/utils/transclusion-prosemirror.util.ts(~:108-139). На циклическом объекте —RangeError(stack overflow). Через JSON-парсинг недостижимо (реальный вход), поэтому низкий приоритет; тест закрепляет текущее поведение. -
Предсуществующий долг jest-инфраструктуры (блокирует часть интеграций). 16 серверных сьютов падают: (а) NestJS DI — стоковые
should be definedчерезTest.createTestingModule(...).compile()без провайдеров (auth, page, comment, group, space, search, user, workspace, token, storage, environment); (б) lib0 ESM —Cannot use import statement outside a moduleизlib0/decoding.jsпо цепочке@hocuspocus/server(comment.service, page.service, page.controller).lib0не входит в jesttransformIgnorePatterns. Пока это так, полноценные интеграционные тесты сервисов/контроллеров через полный DI-граф невозможны (в PR #49 такие тесты сделаны прямым конструированием с моками).