test(server): integration harness + deferred coverage (real Postgres/Redis) #115

Closed
Ghost wants to merge 0 commits from test/deferred-integration-coverage into develop

Тесты: интеграционный харнесс + отложенное покрытие против реальных Postgres/Redis

Отчёт по скиллу архитектора.

1. Задача

Из docs/backlog/feature-test-coverage-deferred.md: часть принятого тест-плана была отложена, т.к. требовала реального тестового Postgres / реального Redis / e2e-харнеса, которых в проекте не было — repo-зависимые проверки делались на моках, SQL/Lua-уровень не исполнялся. Нужно построить харнесс и закрыть отложенное.

2. Решение — изолированный интеграционный харнесс

Тесты гоняются против изолированной авто-создаваемой БД docmost_test и логического Redis DB 15 — dev-данные (docmost / Redis db 0) не трогаются.

  • apps/server/test/integration/db.tsbuildTestDb() зеркалит database.module.ts один-в-один (PostgresJSDialect + CamelCasePlugin + bigint-парсинг to:20/from:[20,1700]) + минимальные seed-хелперы; репозитории инстанцируются напрямую.
  • global-setup.ts — DROP/CREATE docmost_test, CREATE EXTENSION vector, миграции до latest через Kysely Migrator; падает громко при любой ошибочной миграции (никогда не гоняем на полу-мигрированной схеме).
  • jest-integration.json + скрипт test:int. Изоляция от unit-прогона: имя *.int-spec.ts НЕ матчит unit-testRegex \.spec\.ts$ (дефис vs точка) + они вне src. Дефолтный jest не тронут.

3. Покрытие — 5 сьютов / 16 тестов, все зелёные против живых PG+Redis

  • WorkspaceRepo.updateSetting: jsonb-merge пишет htmlEmbed:true, НЕ затирая соседние ai/sharing (write-половина kill-switch) — перечитывается из БД.
  • AiAgentRoleRepo: исключение soft-deleted; кросс-workspace tenant-изоляция; дубль (name, workspace) → Postgres 23505; имя переиспользуемо после softDelete (partial unique index WHERE deleted_at IS NULL); одинаковое имя в разных workspace разрешено.
  • page_template_references: удаление source- ИЛИ reference-страницы каскадит строку-ссылку (onDelete cascade) — реальный FK, не мок.
  • PublicShareWorkspaceLimiter vs РЕАЛЬНЫЙ Redis: настоящий ioredis EVAL sliding-window Lua — граница max (3 допущено / 4-й отклонён), re-admit после сдвига окна, same-ms различимые member'ы. Ловит баги в самой Lua (>=/PEXPIRE), которые FakeRedis не поймает.
  • AiChatRepo.findByCreator: join бейджа роли (enabled→badge; soft-deleted/disabled роль → null).

4. Найденные баги

Багов продукта не найдено (это бэкфилл покрытия). Ревью-субагент: APPROVE WITH SUGGESTIONS — критичные пункты (изоляция от unit-прогона, безопасность dev-БД/Redis, реальная инфра, точность ассертов, зеркало Kysely) подтверждены. Применил 2 его рекомендации по устойчивости: (a) бросать исключение при status==='Error' в результатах миграции даже без top-level error; (b) TEST_REDIS_URL-override + ping()-preflight для раннего понятного отказа.

5. Тестирование — статистика

Прогон (мной, независимо, после хардеринга): Test Suites: 5 passed, Tests: 16 passed против живых docmost_test + Redis db15. tsc чисто; jest --listTests | grep -c int-spec = 0 (unit-прогон их не видит).
Циклы ревью: 1 (APPROVE с предложениями; 2 применены как minor-fix).
«Живая» проверка: для тест-инфры «работает» = сам интеграционный сьют зелёный против РЕАЛЬНЫХ PG/Redis (а не моков) — это и есть доказательство; браузер тут неприменим.

НЕ вошло (осознанно отложено, причины зафиксированы здесь)

  • HTTP/e2e public-share XFF-throttle + per-workspace cap и полная интеграция AiChatService.stream (seam streamText onError/onFinish/onAbort + res.hijack) — самые тяжёлые (полный boot Nest + seam SDK), требуют отдельного e2e-слоя; харнесс этого PR — фундамент для них. Флагнул, т.к. доку удаляется этим коммитом.

🤖 Generated with Claude Code

## Тесты: интеграционный харнесс + отложенное покрытие против реальных Postgres/Redis Отчёт по скиллу архитектора. ### 1. Задача Из `docs/backlog/feature-test-coverage-deferred.md`: часть принятого тест-плана была отложена, т.к. требовала **реального тестового Postgres / реального Redis / e2e-харнеса**, которых в проекте не было — repo-зависимые проверки делались на моках, SQL/Lua-уровень не исполнялся. Нужно построить харнесс и закрыть отложенное. ### 2. Решение — изолированный интеграционный харнесс Тесты гоняются против **изолированной** авто-создаваемой БД `docmost_test` и **логического Redis DB 15** — dev-данные (`docmost` / Redis db 0) не трогаются. - `apps/server/test/integration/db.ts` — `buildTestDb()` зеркалит `database.module.ts` один-в-один (PostgresJSDialect + CamelCasePlugin + bigint-парсинг to:20/from:[20,1700]) + минимальные seed-хелперы; репозитории инстанцируются напрямую. - `global-setup.ts` — DROP/CREATE `docmost_test`, `CREATE EXTENSION vector`, миграции до latest через Kysely Migrator; **падает громко** при любой ошибочной миграции (никогда не гоняем на полу-мигрированной схеме). - `jest-integration.json` + скрипт `test:int`. Изоляция от unit-прогона: имя `*.int-spec.ts` НЕ матчит unit-`testRegex` `\.spec\.ts$` (дефис vs точка) + они вне `src`. Дефолтный `jest` не тронут. ### 3. Покрытие — 5 сьютов / 16 тестов, все зелёные против живых PG+Redis - **WorkspaceRepo.updateSetting**: jsonb-merge пишет `htmlEmbed:true`, НЕ затирая соседние `ai`/`sharing` (write-половина kill-switch) — перечитывается из БД. - **AiAgentRoleRepo**: исключение soft-deleted; кросс-workspace tenant-изоляция; дубль `(name, workspace)` → Postgres `23505`; имя переиспользуемо после softDelete (partial unique index `WHERE deleted_at IS NULL`); одинаковое имя в разных workspace разрешено. - **page_template_references**: удаление source- ИЛИ reference-страницы каскадит строку-ссылку (`onDelete cascade`) — реальный FK, не мок. - **PublicShareWorkspaceLimiter vs РЕАЛЬНЫЙ Redis**: настоящий `ioredis` EVAL sliding-window Lua — граница max (3 допущено / 4-й отклонён), re-admit после сдвига окна, same-ms различимые member'ы. Ловит баги в самой Lua (`>=`/PEXPIRE), которые FakeRedis не поймает. - **AiChatRepo.findByCreator**: join бейджа роли (enabled→badge; soft-deleted/disabled роль → null). ### 4. Найденные баги Багов продукта не найдено (это бэкфилл покрытия). **Ревью-субагент: APPROVE WITH SUGGESTIONS** — критичные пункты (изоляция от unit-прогона, безопасность dev-БД/Redis, реальная инфра, точность ассертов, зеркало Kysely) подтверждены. Применил 2 его рекомендации по устойчивости: (a) бросать исключение при `status==='Error'` в результатах миграции даже без top-level error; (b) `TEST_REDIS_URL`-override + `ping()`-preflight для раннего понятного отказа. ### 5. Тестирование — статистика **Прогон (мной, независимо, после хардеринга):** `Test Suites: 5 passed, Tests: 16 passed` против живых `docmost_test` + Redis db15. tsc чисто; `jest --listTests | grep -c int-spec` = 0 (unit-прогон их не видит). **Циклы ревью:** 1 (APPROVE с предложениями; 2 применены как minor-fix). **«Живая» проверка:** для тест-инфры «работает» = сам интеграционный сьют зелёный против РЕАЛЬНЫХ PG/Redis (а не моков) — это и есть доказательство; браузер тут неприменим. ### НЕ вошло (осознанно отложено, причины зафиксированы здесь) - **HTTP/e2e public-share XFF-throttle + per-workspace cap** и **полная интеграция `AiChatService.stream`** (seam `streamText` onError/onFinish/onAbort + `res.hijack`) — самые тяжёлые (полный boot Nest + seam SDK), требуют отдельного e2e-слоя; харнесс этого PR — фундамент для них. Флагнул, т.к. доку удаляется этим коммитом. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Ghost added 1 commit 2026-06-21 07:03:50 +03:00
Builds the deferred integration tests from docs/backlog/feature-test-coverage-
deferred.md that needed real infra (a test Postgres + real Redis) which the repo
lacked. Runs against an isolated, auto-created docmost_test database and Redis
logical DB 15 — never the dev data.

Harness (apps/server/test/integration/, run via new `pnpm --filter server test:int`
=> jest --config test/jest-integration.json; default unit `jest` is untouched and
excludes these via the *.int-spec.ts name + rootDir):
- db.ts: buildTestDb() mirrors database.module.ts exactly (PostgresJSDialect,
  CamelCasePlugin, bigint to:20/from:[20,1700] parsing) + minimal seed helpers.
- global-setup.ts: DROP/CREATE docmost_test, CREATE EXTENSION vector, migrate to
  latest via Kysely Migrator (fails loud on any errored migration).
- global-teardown.ts: closes the pool.

Coverage (5 suites, 16 tests, all green against live PG+Redis):
- WorkspaceRepo.updateSetting: jsonb-merge persists htmlEmbed without clobbering
  sibling ai/sharing namespaces (the kill-switch write half).
- AiAgentRoleRepo: soft-delete exclusion, cross-workspace tenant isolation,
  duplicate (name,workspace) -> 23505, name reusable after softDelete (partial
  unique index WHERE deleted_at IS NULL), same name across workspaces allowed.
- page_template_references: deleting either source or referenced page cascades
  the link row (onDelete cascade) — real FK, not mocked.
- PublicShareWorkspaceLimiter vs REAL Redis: real ioredis EVAL of the sliding-
  window Lua — max boundary (3 admit / 4th deny), re-admit after the window
  slides, same-ms distinct members. Catches Lua bugs a FakeRedis cannot.
- AiChatRepo.findByCreator: role-badge join (enabled->badge; soft-deleted or
  disabled role -> null).

Review: APPROVE; applied its two hardening suggestions (fail loud on errored
migration result even without a top-level error; TEST_REDIS_URL override + ping
preflight). tsc clean; unit run excludes int-spec (verified).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ghost closed this pull request 2026-06-21 14:48:40 +03:00

Pull request closed

Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: vvzvlad/gitmost#115