Root-level testing guide: prerequisites (Node 20+, system git, .env), the offline test suite + coverage, the §11 round-trip idempotency harness (--fixture/--corpus/ --page), and live testing against a real Docmost — read-only pull, dry-run push (plan only) vs --apply (writes), vault git inspection, what's implemented vs not, safety/recovery (Trash is reversible, conflicts never reach Docmost), and a troubleshooting table.
14 KiB
Как тестировать docmost-sync
Гайд по проверке проекта — от быстрых офлайн-тестов (без Docmost) до живого прогона против реального инстанса. Дизайн и термины — в SPEC.md, конвенции — в AGENTS.md.
TL;DR.
make install && npm test— весь набор без всякой сети. Идемпотентность конвертера —npm run roundtrip. Живой синк: заполнить.env, затемnpm run pull(read-only из Docmost) иnpm run push(dry-run — план без записи; запись только с-- --apply).
0. Пререквизиты
- Node ≥ 20 и npm.
- Системный
gitна PATH — это state store (SPEC §5); движок шеллится вgit. Без него движок честно падает с понятным сообщением (а не сырым стэком). - Установка зависимостей (монорепо npm-workspaces):
make install(илиnpm ci).
make install # npm ci: ставит корень + packages/docmost-client
npm run build # собирает либу, затем движок -> build/
Для живых прогонов (pull/push/--page) нужен .env:
make env # cp .env.example .env (если ещё нет), затем впишите значения
Переменные (.env, читаются через src/settings.ts на zod; отсутствие
обязательной — падение на старте с указанием имени):
| Переменная | Обяз. | Что это |
|---|---|---|
DOCMOST_API_URL |
да | URL API инстанса, напр. https://docs.example.com/api |
DOCMOST_EMAIL / DOCMOST_PASSWORD |
да | креды для /auth/login |
DOCMOST_SPACE_ID |
да | какое пространство зеркалить |
VAULT_PATH |
нет | git-vault, по умолчанию data/vault (gitignored) |
GIT_REMOTE |
нет | git-remote для пуша vault (пока не используется) |
POLL_INTERVAL_MS, DEBOUNCE_MS, LOG_LEVEL |
нет | тюнинг (дефолты разумны) |
.env и data/ — в .gitignore; реальные креды не коммитить (SPEC §12).
1. Автотесты (офлайн, без Docmost)
Весь набор — детерминированный, без сети, без живого инстанса.
npm test # vitest run — ~747 тестов (2 skipped — это live-e2e, см. §4.5)
make test # то же через Makefile
npm run test:watch # watch-режим
npm run coverage # v8-покрытие по src/ и packages/docmost-client/src/
Что покрыто (по слоям):
- Чистая логика (high-ROI): конвертер ProseMirror↔Markdown (golden + property),
каноникализация (
canonicalize), реконсиляция pull/push (reconcile,compute-pull-actions,compute-push-actions,classify-rename-moves), node-ops, transforms, diff, sanitize/layout, settings. - git-слой (
VaultGit) — интеграционные тесты на временных репозиториях подos.tmpdir()(init/branches/commit-провенанс/merge/ff/diffNameStatus/refs). - REST-клиент — через
axios-mock-adapter(биндинги delete/restore/trash/ move/recent и т.д.), без реальной сети. - Collab-путь записи — через настоящий
Y.Doc(без Hocuspocus-сервера). - Оркестрация pull/push — через инъецированные fakes; плюс
run-push-realgitгоняет--apply-путь против НАСТОЯЩЕГОVaultGit(ловит регрессии биндинга).
Тесты — это и есть «контракт». Падающий npm test блокирует docker-сборку (CI).
2. Идемпотентность round-trip (SPEC §11 / «Задача №0»)
git диффает побайтово: если export → import → export недетерминирован, каждый
pull рождает ложный дифф. Harness проверяет это офлайн.
npm run build
# Один фикстур (по умолчанию test/fixtures/sample-doc.json):
node build/roundtrip.js --fixture test/fixtures/sample-doc.json
# Весь синтетический корпус (заголовки, марки, списки, таблицы, callout'ы,
# код с хвостовым \n, диаграммы, mention/textStyle):
node build/roundtrip.js --corpus # default dir test/fixtures/corpus
npm run roundtrip -- --corpus # то же через npm-скрипт
Вывод: для каждого файла — md=ok canon=ok. Гарантии:
- markdown byte-stable (
md1 === md2) — свойство, нужное git; - canonically stable — семантическое равенство со снятыми block-id и нормализованными дефолтами схемы.
Код выхода: 0 — всё стабильно (CI-able), 1 — найдено расхождение (печатает
первую дивергенцию). Известные ограничения конвертера зафиксированы честно через
it.fails (блочная картинка между блоками; code-марка с другой маркой).
Живой вариант (реальная страница, нужен .env):
node build/roundtrip.js --page <pageId>
3. Живой pull — Docmost → vault (read-only к Docmost)
pull зеркалит сконфигурированное пространство в локальный git-vault. К Docmost
он строго read-only (только читает) — безопасно гонять против боевого инстанса.
# заполните .env, затем:
npm run pull # = node build/pull.js
make pull
Что происходит (SPEC §6, Docmost→ФС):
ensureRepoсоздаёт vault-репо (data/vault) с веткамиmainиdocmost.- Обход дерева пространства (
/pages/sidebar-pages) + выгрузка тел (без комментариев, только якоря, §3) → запись.mdв дерево папокSpace/…/Title.md. - Коммит на ветке
docmost→ merge вmain.
Проверить результат:
ls -R data/vault # дерево статей в Markdown
git -C data/vault log --oneline --all # коммиты docmost:/local:
git -C data/vault branch # main + docmost
git -C data/vault show refs/docmost/last-pushed # (появится после push)
cat data/vault/<Space>/<…>/<Title>.md # meta-блок + тело + якоря
Защиты: при неполном обходе дерева удаления подавляются (§8), есть порог на массовое удаление, и стартовый guard на незавершённый merge (§9/§12).
4. Живой push — vault → Docmost (запись!)
push транслирует локальные правки .md обратно в Docmost. Это единственный
путь записи в Docmost, поэтому он dry-run по умолчанию.
4.1 Dry-run (безопасно — план без записи)
npm run push # = node build/push.js — DRY-RUN: только печатает план
Dry-run ничего не пишет в Docmost и не двигает рефы. Он печатает план:
что будет create / update / delete / move / rename / noop / skipped.
Важно: чтобы посчитать дифф base..main, dry-run локально коммитит ожидающие
правки рабочего дерева на ветке main (это локальный git, в Docmost ничего не
уходит — об этом есть строка в логе).
Типичный сценарий проверки:
npm run pull # 1. зеркалим пространство в vault
# 2. правим/добавляем/переименовываем/удаляем .md в data/vault руками
npm run push # 3. смотрим план (dry-run) — что уедет в Docmost
4.2 Apply (реальная запись в Docmost)
npm run push -- --apply # ВЫПОЛНЯЕТ план: пишет в Docmost
Что делает (SPEC §6, ФС→Docmost):
- modified →
import_page_markdownчерез collab/Yjs-путь (не сырой jsonb-overwrite, §2); - added без pageId →
create_page, присвоенныйpageIdпишется обратно в meta; - deleted →
delete_page— это soft-delete в Trash Docmost (обратимо, §8); - renamed/moved →
move_page/rename_page(истина положения — путь файла, §5; чистое локальное переименование без смены родителя/title — noop, в Docmost не транслируется).
После чистого применения двигаются refs/docmost/last-pushed и ветка docmost
(loop-close, §6.3/§10) — чтобы следующий pull не утянул свою же запись обратно.
4.3 Безопасность записи
- Начинайте с dry-run и читайте план.
- Тестируйте на отдельном/тестовом пространстве, а не на боевом.
- Удаления обратимы: уходят в Docmost Trash (восстановление — Trash в UI; авто- чистка ~30 дней). Локально всё восстановимо из git-истории vault.
- Конфликт-маркеры никогда не уезжают в Docmost (§9): при незавершённом merge push прерывается с понятным сообщением.
- Рекомендуется один авторитетный прогон/демон на пространство (не пушить вдвоём).
4.4 Проверить, что dry-run действительно ничего не пишет
node build/push.js # без --apply: клиент Docmost даже не строится
(Это же гарантирует автотест: dry-run делает ноль вызовов клиента и ноль движений
рефов; путь записи достижим только с --apply + реальными кредами.)
4.5 Живой e2e-smoke
test/e2e-docmost.test.ts — 2 теста, пропущены без реальных кред (поэтому в
npm test всегда «2 skipped»). Это сквозной дым против живого инстанса; включается
при наличии .env (см. начало файла теста).
5. Что уже реализовано, а что — нет
Чтобы тестировать в границах реального:
Готово и проверяемо:
- pull (Docmost→vault) — цикл §6 с реконсиляцией и guard'ами безопасности;
- push (vault→Docmost) — полное покрытие типов изменений (create/update/delete/
move/rename/noop), runnable
npm run push(dry-run /--apply), loop-close; - идемпотентный round-trip (§11), git-слой, REST-биндинги, collab-запись.
Ещё НЕ реализовано (ручной прогон команд, не демон):
- непрерывный FS-watcher + дебаунс (§7.1) — сейчас push запускается вручную;
- поллинг-цикл pull — сейчас pull одноразовый;
git pushв remote (§7.2) — vault пока локальный;- точная fractional-index позиция при move (пока серверный дефолт);
- потребление loop-guard-записи на pull-стороне (git-native ff-
docmostуже закрывает контентную петлю).
Иными словами: обе стороны синка работают как одноразовые команды; непрерывного демона ещё нет.
6. Траблшутинг
| Симптом | Причина / что делать |
|---|---|
git binary not found … |
поставьте системный git (нужен для vault). |
Configuration error … Missing required variable(s) |
заполните .env (см. §0). |
vault has an unresolved merge … |
в vault остался конфликт: git -C data/vault merge --abort (или разрешите), затем повторите (§9/§12). |
pull: tree fetch incomplete — deletions suppressed |
часть дерева не подтянулась; удаления намеренно не применены — повторите pull (§8). |
| push «всё-или-ничего» при сбое страницы | при ошибке любой страницы рефы НЕ двигаются → повторный прогон чистый (§12). |
WARNING — the 'docmost' mirror branch DIVERGED |
кто-то писал в ветку docmost руками (нарушение §5) — она пишется только движком. |
| round-trip exit 1 | конвертер недетерминирован на этом контенте — см. вывод первой дивергенции (§11). |
Полезное:
npm run build && npm test # быстрый sanity всего
node build/roundtrip.js --corpus # идемпотентность
git -C data/vault log --oneline --all --decorate
make clean # снести build/ + node_modules + dist либы