Тест-стратегия: аудит покрытия и план тестов (10 модулей) #204
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Отчёт по тест-стратегии — gitmost — 2026-06-26
1. Исполнительное резюме
packages/git-syncисключён из тест-плана: в репозитории отслеживается толькоbuild/(транспилированный JS), исходников нет, сервер его не импортирует — тестируется в своём репозитории.@vitest/coverage-v8не установлен, а istanbul-инструментирование Jest падает на ESM-импорте@docmost/editor-ext; и то, и другое требует изменения зависимостей/конфигов (вне мандата «только.md»). Верифицировано фактически — 2312 существующих тестов проходят (server 1336 + client 529 + editor-ext 134 + mcp 313). Качественно: чистая логика и критичные по безопасности пути покрыты хорошо; пробелы — точечные (см. ниже).2. Рекомендации по модулям
server/core/ai-chat (gitmost AI-бэкенд) — покрытие отличное (28 spec)
mcp-clients.service.ts:153(решениеrelease/refcount →decideClose(entry)); экспортироватьguardedFetch/lookup(приватны, SSRF-логика недостижима тестом).mcp-clients.service.ts:146/220/413) — highest-value gap, ловит утечку/преждевременное закрытие живого клиента и double-close;decryptHeaders(:391, fail-open без auth при ротацииAPP_SECRET);guardedFetchизвлечение host + IP-literal vs hostname (SSRF-обход через форму Request);testServercleanup вfinally(утечка клиента + утечка секрета в текст ошибки).ai-transcription.service.ts(passthrough),*.module.ts(DI),*.dto.ts, undici/SDK-обвязка — уже покрыто или сторонне.server/integrations/ai + integrations/mcp — покрытие хорошее, кластер пробелов в STT/settings
transcribeJsonBase64(ai.service.ts:305) — нормализация URL (двойной слэш), секрет только в заголовке, пустойlanguageне отправляется;embedTextsклассификация таймаута (:350) — реальный провайдерский error не маскируется под timeout (дуальное условие, комментарий :370);testConnection(:441) — маппинг ошибок + неутечка ключа; весьAiSettingsService(spec отсутствует):resolve(фоллбэк ключей/URL),update(write-only семантика: undefined=оставить, ''=очистить, значение=записать),getMasked(никогда не отдаёт ключ),resolvePublicShareAssistantName(disabled-персона не утекает);mcpStreamTimeoutMs/mcpCallTimeoutMsenv-парсинг.AiSettingsService.reindex(:83) — порядок dedup BullMQ (remove purge → remove → add со стабильным jobId),.catchне прерывает enqueue.buildJsonTranscriptionRequest(...), общийpositiveEnvдляembeddingTimeoutMs.McpService.handle/hijack (фреймворк, ядро покрыто вmcp.service.spec.ts),*.exception.ts,silentWavProbe.server/core (Docmost-ядро, без ai-chat) — покрытие ~75–80 %, остаток в recursive-CTE
isSharingDisabled(wsSettings, spaceSettings)(share.service.ts:490);applyUserSettingsUpdate(user,dto)(user.service.ts:48);computeNextPosition(last|null)(page.service.ts).validateAllowedEmailmalformed-email guard (auth.util.ts:42) — подтверждённый latent-баг, припаркован какit.todo(1 todo в прогоне);UserService.updateсмена email (account-takeover без confirmPassword/дубликат);PageService.nextPagePosition(порядок сайдбара);movePagephantom-broadcast guard (numUpdatedRows===0n→ нетPAGE_MOVED).getShareForPageнаследование share (includeSubPages-гейт, deletedAt) — наивысший blast-radius;isSharingAllowed(toggle disabled → утечка);getShareTreerestricted-ancestor;lookupTransclusionForShareaccess-граф (эксфильтрация);acceptInvitationатомарность + токен-гейт.*.controller.ts(есть supertest-спеки),favorite/watcher/session/notification/label(тонкая Kysely-делегация),attachment.serviceS3/queue (механика), tiptap/prosemirror-обёртки.server/integrations (import/export/storage/crypto/security/env) — покрытие ~40 %, богатый pure-слой
getInternalLinkPageName(export/utils.ts:108) — баг: extensionless путь → пустой текст ссылки;replaceInternalLinks,updateAttachmentUrlsToLocalPaths(битые ссылки/вложения в экспорте);analyzeAttachments(import-attachment.service.ts:672, нужен R1) — Draw.io-пары при Confluence-импорте;notionFormatterветки callout/bookmark/toggle/inline-math;encodeFilePath/cleanUrlString;resolveTrustProxy(IP-spoofing — сначала проверить, не покрыто ли уже вtrust-proxy.util.spec.ts);environment.validation.validate(нужен R2 — снятьprocess.exit).storageDriverConfigProvider/createStorageDriver(выбор драйвера local/s3/azure, без сети).analyzeAttachments→ экспортируемая чистая функция (R1);collectValidationErrors(config)(R2); builder имени/пути вложения изuploadOnce(R3).queue/*,mail/drivers/*,redis,telemetry,audit, S3/Azure SDK-тела,*.module.ts/DTO, статический CSS. Антипаттерн: тавтологичные спекиstorage.service.spec.ts:15иenvironment.service.spec.ts:14(«should be defined») — заменить на проверки делегирования.server/common + collaboration + ws — покрытие отличное; один реальный пробел
yjs.util.ts(setYjsMark/removeYjsMarkByAttribute/updateYjsMarkAttribute) — 0 тестов, mark по комментариям в live-collab: off-by-one позиций, утечка mark в codeBlock, порча соседнего commentId (тестируется на in-memoryY.Doc);extractInternalLinkSlugIds(backlink-граф);AuthProvenance-декоратор (фабрика, дрейф сresolveProvenance);withCachetry/catch-ветки;InternalLogFilter.log(low prio).html-escaper.ts(сторонний verbatim), interceptors/guards/middlewares (фреймворк), tiptap/hocuspocus-адаптеры, redis-sync обвязка. Известный gap (не тест):ws.service.ts spaceHasRestrictionsотдаёт устаревшийfalseдо TTL,invalidateSpaceRestrictionCacheбез продакшен-вызова — нужна integration-проверка, когда приедет эндпоинт-мутатор.client/features/ai-chat (фронт AI-chat, #198) — pure-слой эталонный; пробел в orchestration
decideTurnEnd({isAbort,isDisconnect,isError,intentionalInterrupt})и решениеsendNowизchat-thread.tsx:277/353(риск: очередь молча теряется на Stop / авто-ретрай после ошибки / «Send now» не флашит); экспортироватьrowToUiMessage(:88, дрейф с сервером); инжектировать clock в throttle-эмиттер (:402, R2).decideTurnEnd/sendNow(после R1);MessageItemпустой reasoning-блок (message-item.tsx:99).@ai-sdk/react): поток «Send now»-прерывания (chat-thread.tsx:353/277) — one-shotinterrupted-флаг потребляется ровно один раз; defuse устаревшего флага (:380) — abort не пришёл → не портит будущий тёрн.ConversationList/MessageListscroll (layout в jsdom), презентационныеTypingIndicator/ToolCallCard/ChatInput(passthrough), query/service/atom/types. Покрытое (адекватно): весьutils/*,useChatSession(15 кейсов).client/features/editor (+dictation/page-embed/transclusion) — 152 файла, ценность в pure-utils
normalizeTableColumnWidths(markdown-clipboard.ts:197) — squash столбцов вставленной таблицы;sort-cells.ts(sortItems/weaveItems/isHeaderCell) — порядок/стабильность сортировки, header не уезжает в тело;decideInternalLinkPaste/collectReuploadCandidates(нужен R2) — внутренняя ссылка не превращается в plain link, вложения той же страницы не перезаливаются; ordered-emitter диктовки (use-streaming-dictation.ts:149, нужен R3) — out-of-order seq, stale-epoch, счётчик in-flight;sortFrequentlyUsedEmoji+ баг: незащищённыйJSON.parselocalStorage (emoji-menu/utils.ts:35/64);useLinkEditorState(классификация URL/поиск);transclusion-lookup-context(0 тестов, ручной дубликатpage-embed-lookup-context); partial-response веткаpage-embed-lookup-context.tsx:83.handleFileDrop(drag-reorder ≠ file-drop).parsePixelWidth/deriveColumnWidths; R2 paste-хелперы; R3 ordered-emitter из god-хука (474 LOC).*-view.tsxnode-views, plugin-регистрация, DOM-зависимыйtable/dnd/calc-drag-over. Покрытое: html-embed-sandbox, decide-embed-state, slash-menu gating/suggestions, subpages-utils, encode-wav.client/features (auth/comment/share/space/page/...) — покрытие ~80 %, ценность в page-tree
applyUpdateOne(tree-socket-reducers.ts:20— единственная непокрытая ветка сокет-редьюсера);appendNodeChildren/deleteTreeNode/findBreadcrumbPath/updateTreeNodeName/updateTreeNodeIcon(page/tree/utils/utils.ts— потеря lazy-loaded поддерева, over-deletion);formatRelativeTime(notification.utils.ts:4, сvi.setSystemTime);sortPositionKeys(стабильность компаратора).resolveShareState({share,workspace,space})изshare-modal.tsx:45(приоритет workspace>space disable, inherited vs own); экспорт zod-схем auth-форм (login-form.tsx:25и 4 близнеца) для unit-валидации.findBreadcrumbPathмутирует вход (utils.ts:53,node.name="Untitled") — зафиксировать тестом и завести issue.queries/*,services/*,atoms/*(обвязка),use-space-ability(@casl-обёртка), презентационные компоненты.packages/mcp — покрытие высокое (313 тестов); пробелы в markdown-экспорте медиа
markdown-converter.tsdefault-ветка (~601): атом без case сериализуется в "" и блок исчезает из экспорта (зафиксировать намеренный плейсхолдер ИЛИ round-trip); round-tripvideo/youtube/embed/excalidraw/audio/pdf(schema.test.mjsпроверяет только Yjs-путь, не markdown);footnoteMarkerslegacy[N]+ notesHeading split (diff.ts);performLoginколлизия имени cookie (auth-utils.ts:59,authTokenRefreshпередauthToken);applyAnchorInDocfirst-match при двух одинаковых блоках.SHARED_TOOL_SPECSс реальными потребителями (in-app ai-chat + MCP-регистрация) — текущийtool-specs.test.mjsсамореферентен и не ловит дрейф, ради которого создан.stdio.ts/index.ts(обвязка),filters/page-lock/tree/parse-node-arg/text-normalize(покрыто), live image-serving (вtest-e2e.mjs). Антипаттерн:schema.test.mjs:75маскирует покрытие («survives Yjs» ≠ markdown-путь).packages/editor-ext — покрытие pure-surface ~35 %; высокий blast-radius
transpose(инверсия индексов на не-квадратных таблицах);moveRowInArrayOfRows(off-by-one на чётных блоках — самая ветвистая непокрытая функция);convertTableNodeToArrayOfRows/convertArrayOfRowsToTableNode(round-trip merged-cell → null-плейсхолдер);getBasename; токенайзерыcallout.marked/math-block.marked/math-inline.marked(false-positive math из цен$5 and $6); ordered-liststartвmarked.utils/turndown.utils(нумерация сбрасывается на 1 при экспорте); video-rule turndown (escape имени файла);getReplaceStepoverlap-ветка;simplifyTransform.recreateTransform— инвариант: применённый diff восстанавливает целевой doc (иначе data-loss в page-history и mcp git-sync);moveRow/moveColumn;getSelectionRangeInColumn(merged-cell off-by-one);addUniqueIdsToDoc(дубликаты/пропуски block-id ломают адресацию MCP).footnoteDefinitionnewline-collapse +fillEmptyFootnoteRefs.table/dnd/*(DOM-layout), plugin-обвязка footnote-numbering/sync, node/extension-определения, барреля. Антипаттерн:marked.utils.ts:11мутирует глобальный singletonmarkedна импорте — тестировать только черезmarkdownToHtml.3. Сквозные аспекты
SHARED_TOOL_SPECS↔ in-app/MCP-потребители; (2) дрейфrecreate-transform— vendored-копияeditor-extvs npm-пакет вmcp/diff.ts(один алгоритм, две копии); (3) уже естьai-agent-role-form.drivers.test.tsиmcp-login-gate-coupling.contract.spec.ts— образец для (1)/(2).transpose∘transpose=id, round-trip matrix↔node), markdown↔prosemirror round-trip,recreateTransform(apply(diff)=target) — естественные кандидаты после извлечения чистых функций.getSchema(extensions)+PMNode.fromJSON(паттернfootnote.test.ts); фабрики строк chat-сообщений (drift сервер↔клиентrowToUiMessage); фикстуры share-графа для DB-интеграций.transclusion-lookup-context⇄page-embed-lookup-context(Gitea #94).4. Обнаруженные антипаттерны
AiChatService.stream(~550 строк,ai-chat.service.ts:258, + временный «Safari»-логгинг к удалению);use-streaming-dictation.ts(474 LOC); orchestration через 5 ref-ов вchat-thread.tsx(проверяемо только чтением комментариев).ShareService(581 LOC, 7 зависимостей) — спеки 3× используютjest.spyOn(service, 'getShareForPage')(запах: recursive-CTE не изолированы) → извлечьShareGraphRepo.storage.service.spec.ts,environment.service.spec.ts(«should be defined»);tool-specs.test.mjs(самореферентный);schema.test.mjs(Yjs вместо markdown).findBreadcrumbPathиbuildTreeWithChildrenмутируют вход (client tree-utils);marked.use(...)на импорте (editor-ext); незащищённыйJSON.parseв emoji-menu.ai-streaming-fetch.spec.ts(реальный loopback-сервер, sub-second margins) — наблюдать в CI.5. Необходимые рефакторинги перед написанием тестов
guardedFetch/lookup, извлечениеdecideClose— блокирует SSRF- и lease-тесты.computeNextPosition+ShareGraphRepo; DB-харнесс (Postgres) — блокирует 5 share/invitation integration-тестов (recursive-CTE/транзакции).analyzeAttachments→pure (R1);collectValidationErrorsбезprocess.exit(R2); builder имени вложения (R3).decideTurnEnd/sendNowextract (R1), inject clock (R2), exportrowToUiMessage(R3).parsePixelWidth/deriveColumnWidths(R1), paste-хелперы (R2), ordered-emitter (R3).resolveShareState(R1), экспорт zod-схем auth (R2).6. План внедрения (по фазам)
#198(ROI: активная фича, наибольший непокрытый orchestration-риск): извлечь+покрытьdecideTurnEnd/sendNow(chat-thread.tsx), integration «Send now»-прерывания и defuse флага; экспорт+тест SSRFguardedFetch; жизненный цикл lease/refcount MCP-клиента. Закрывает потерю очереди, утечку клиента и SSRF-обход.recreateTransform-инвариант, markdown-токенайзеры); mcp (htmlEmbed + media round-trip, footnote-diff, cookie-парсинг); client (sort-cells,normalizeTableColumnWidths, ordered-emitter диктовки, баг emoji JSON.parse); server import/export (getInternalLinkPageNameи др. баги). Фиксирует подтверждённые баги и потери данных.isSharingAllowed/transclusion-access/acceptInvitation-атомарность;AiSettingsService(секреты write-only, неутечка ключей);environment.validation(после R2). Параллельно — unit-пробелы ai-chat/ai-integration/common (yjs.util).applyUpdateOne, tree-utils,formatRelativeTime),transclusion-lookup-context; contract-тестыSHARED_TOOL_SPECSиrecreate-transform-паритет; замена тавтологичных спеков на проверки делегирования; завести issue наfindBreadcrumbPath-мутацию.7. Источники
build/).node:test/ 313 pass. Итого 2312 проходящих тестов.@vitest/coverage-v8не установлен; istanbul-инструментирование Jest падает на ESM-импорте@docmost/editor-ext. Оба требуют изменения зависимостей/конфигурации (вне мандата «только.md»); подтверждённый1 todoсовпал с заявленным latent-багомvalidateAllowedEmail.recreate-transformоставлен на нижнем слое вeditor-ext, вmcp— только wrapperdiffDocs;normalizeTableColumnWidthsклиента (clipboard) и сервера (import) — разные функции, обе сохранены. Шаг 2 (skip-лист) — отброшены DTO/DI/презентация/сторонние/тавтологии по всем модулям. Шаг 3 (пирамида) — соблюдена без урезаний. Шаг 4 (refactor-blocking) — 5 integration-тестов core помечены зависимостью от DB-харнесса. Шаг 6 (adversarial) — каждый предложенный тест назван классом дефекта, который провалится при «реалистичной тихой поломке».Ghost referenced this issue2026-06-27 18:48:39 +03:00
Ghost referenced this issue2026-06-27 21:03:47 +03:00
Ghost referenced this issue2026-06-27 21:55:17 +03:00
Ghost referenced this issue2026-06-27 22:12:37 +03:00