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.
256 lines
14 KiB
Markdown
256 lines
14 KiB
Markdown
# Как тестировать docmost-sync
|
|
|
|
Гайд по проверке проекта — от быстрых офлайн-тестов (без Docmost) до живого
|
|
прогона против реального инстанса. Дизайн и термины — в [SPEC.md](SPEC.md),
|
|
конвенции — в [AGENTS.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`).
|
|
|
|
```bash
|
|
make install # npm ci: ставит корень + packages/docmost-client
|
|
npm run build # собирает либу, затем движок -> build/
|
|
```
|
|
|
|
Для **живых** прогонов (pull/push/`--page`) нужен `.env`:
|
|
|
|
```bash
|
|
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)
|
|
|
|
Весь набор — детерминированный, без сети, без живого инстанса.
|
|
|
|
```bash
|
|
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 проверяет это **офлайн**.
|
|
|
|
```bash
|
|
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`):
|
|
|
|
```bash
|
|
node build/roundtrip.js --page <pageId>
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Живой pull — Docmost → vault (read-only к Docmost)
|
|
|
|
`pull` зеркалит сконфигурированное пространство в локальный git-vault. **К Docmost
|
|
он строго read-only** (только читает) — безопасно гонять против боевого инстанса.
|
|
|
|
```bash
|
|
# заполните .env, затем:
|
|
npm run pull # = node build/pull.js
|
|
make pull
|
|
```
|
|
|
|
Что происходит (SPEC §6, Docmost→ФС):
|
|
1. `ensureRepo` создаёт vault-репо (`data/vault`) с ветками `main` и `docmost`.
|
|
2. Обход дерева пространства (`/pages/sidebar-pages`) + выгрузка тел (без
|
|
комментариев, только якоря, §3) → запись `.md` в дерево папок `Space/…/Title.md`.
|
|
3. Коммит на ветке `docmost` → merge в `main`.
|
|
|
|
Проверить результат:
|
|
|
|
```bash
|
|
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 (безопасно — план без записи)
|
|
|
|
```bash
|
|
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 ничего не
|
|
уходит — об этом есть строка в логе).
|
|
|
|
Типичный сценарий проверки:
|
|
|
|
```bash
|
|
npm run pull # 1. зеркалим пространство в vault
|
|
# 2. правим/добавляем/переименовываем/удаляем .md в data/vault руками
|
|
npm run push # 3. смотрим план (dry-run) — что уедет в Docmost
|
|
```
|
|
|
|
### 4.2 Apply (реальная запись в Docmost)
|
|
|
|
```bash
|
|
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 действительно ничего не пишет
|
|
|
|
```bash
|
|
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). |
|
|
|
|
Полезное:
|
|
|
|
```bash
|
|
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 либы
|
|
```
|