Files
gitmost/docs/backlog/feature-test-coverage-deferred.md
claude_code bd377ca4a8 docs(backlog): record deferred tests + non-test gaps from the coverage PR
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>
2026-06-21 00:19:39 +03:00

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 index WHERE 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), apps/server/src/database/repos/workspace/workspace.repo.ts (:275). Сейчас покрыта только форма вызова сервиса (workspace-html-embed.spec.ts). Не покрыто (нужна БД): htmlEmbed:true персистится через jsonb-merge не затирая соседние настройки (ai, sharing). Это и есть «kill-switch пишется» — критично, что write-половина тоггла не ломает остальной settings-namespace.

  • FK page_template_references onDelete('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 берётся из Fastify trustProxyX-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 не входит в jest transformIgnorePatterns. Пока это так, полноценные интеграционные тесты сервисов/контроллеров через полный DI-граф невозможны (в PR #49 такие тесты сделаны прямым конструированием с моками).