[test][ci] e2e на каждый коммит в develop: не блокируют deploy, уведомление по почте #187

Closed
opened 2026-06-25 12:55:37 +03:00 by Ghost · 0 comments

Цель

Гонять e2e-тесты на каждый push в develop. Они не должны блокировать сборку и публикацию образа (build:develop): при падении достаточно красного статуса run'а и стандартного письма GitHub автору коммита (Telegram/Slack не нужны).

Покрываем оба существующих e2e-набора:

  • серверный — apps/server (test:e2e, NestJS + supertest);
  • MCP — packages/mcp (test:e2e, test-e2e.mjs против живого сервера).

Текущее состояние

  • .github/workflows/develop.yml на push в develop запускает reusable test.yml (job test), затем build (needs: test) собирает и пушит образ ghcr.io/vvzvlad/gitmost:develop.
  • test.yml гоняет pnpm -r test — это только юнит/spec в каждом пакете. e2e в CI не запускаются:
    • у apps/server test = jest (юнит, *.spec.ts); e2e — отдельный скрипт test:e2e (jest --config test/jest-e2e.json), который не вызывается;
    • у packages/mcp test = unit+mock; e2e — отдельный test:e2e (node test-e2e.mjs), который не вызывается.

Что нужно каждому набору

Серверный e2e (apps/server/test/app.e2e-spec.ts)

Поднимает полный AppModule через Test.createTestingModule + app.init(). Требуется инфраструктура и env (см. apps/server/src/integrations/environment/environment.validation.ts):

  • Postgres c pgvector (миграции делают CREATE EXTENSION vector) — образ pgvector/pgvector:pg18, как в docker-compose.yml;
  • Redis;
  • env: DATABASE_URL (postgres://…), REDIS_URL (redis://…), APP_SECRET (≥ 32 символов и не REPLACE_WITH_LONG_SECRET); APP_URL опционально;
  • перед запуском — миграции: pnpm --filter ./apps/server migration:latest;
  • pretest сервера сам собирает @docmost/editor-ext (учтено).

Сейчас сам тест тривиальный (GET /Hello World!), но как каркас уже валиден — добавляем инфраструктуру, тесты дорастут позже.

MCP e2e (packages/mcp/test-e2e.mjs)

Бьёт по живому Docmost и использует не только REST /api, но и collaboration websocket (HocuspocusProvider, путь /collab — см. packages/mcp/src/lib/collaboration.ts: http→ws, срезается /api, домонтируется /collab). Главный процесс монтирует CollaborationModule в том же сервере (Dockerfile: CMD ["pnpm","start"]node dist/main), поэтому одного серверного процесса хватает для REST + ws.

Нужно:

  • весь стек поднят: db (pgvector) + redis + собранный сервер;
  • сборка пакета mcp перед запуском: test-e2e.mjs импортирует ./build/client.js, а pre-хук есть только у test (pretest = tsc), не у test:e2e. Значит явный шаг pnpm --filter @docmost/mcp build;
  • seed первого пользователя/воркспейсаPOST /api/auth/setup (гард SetupGuard: работает только при CLOUD != true и когда воркспейсов 0). DTO CreateAdminUserDto: name, email, password, опц. workspaceName;
  • env для теста: DOCMOST_API_URL (напр. http://localhost:3000/api), DOCMOST_EMAIL, DOCMOST_PASSWORD.

Предлагаемая реализация

Добавить в develop.yml два независимых job (e2e-server, e2e-mcp):

  • запускаются параллельно с test/build на push в develop;
  • build НЕ зависит от них (остаётся needs: test) → e2e не блокируют сборку/публикацию :develop;
  • при падении job весь run помечается красным (видно на коммите) и GitHub шлёт письмо автору пуша — этого достаточно как уведомление.

Про «красный, но не блокирует»: deploy не блокируется, потому что build от e2e не зависит. Сам run при этом честно красный. Если позже захочется, чтобы галка коммита оставалась зелёной — навесить continue-on-error: true; но по текущему решению оставляем красный.

Скетч: job серверного e2e

  e2e-server:
    runs-on: ubuntu-latest
    services:
      db:
        image: pgvector/pgvector:pg18
        env:
          POSTGRES_DB: docmost
          POSTGRES_USER: docmost
          POSTGRES_PASSWORD: docmost
        ports: ['5432:5432']
        options: >-
          --health-cmd "pg_isready -U docmost"
          --health-interval 5s --health-timeout 5s --health-retries 20
      redis:
        image: redis:7
        ports: ['6379:6379']
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 5s --health-timeout 5s --health-retries 20
    env:
      DATABASE_URL: postgresql://docmost:docmost@localhost:5432/docmost
      REDIS_URL: redis://localhost:6379
      APP_SECRET: ci-e2e-secret-change-me-min-32-characters
      APP_URL: http://localhost:3000
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with: { node-version: 22, cache: pnpm }
      - run: pnpm install --frozen-lockfile
      - run: pnpm --filter @docmost/editor-ext build
      - run: pnpm --filter ./apps/server migration:latest
      - run: pnpm --filter ./apps/server test:e2e

Скетч: job MCP e2e

  e2e-mcp:
    runs-on: ubuntu-latest
    services:
      db:    # ← тот же pgvector/pgvector:pg18, что выше
        image: pgvector/pgvector:pg18
        env: { POSTGRES_DB: docmost, POSTGRES_USER: docmost, POSTGRES_PASSWORD: docmost }
        ports: ['5432:5432']
        options: >-
          --health-cmd "pg_isready -U docmost"
          --health-interval 5s --health-timeout 5s --health-retries 20
      redis:
        image: redis:7
        ports: ['6379:6379']
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 5s --health-timeout 5s --health-retries 20
    env:
      DATABASE_URL: postgresql://docmost:docmost@localhost:5432/docmost
      REDIS_URL: redis://localhost:6379
      APP_SECRET: ci-e2e-secret-change-me-min-32-characters
      APP_URL: http://localhost:3000
      NODE_ENV: production
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with: { node-version: 22, cache: pnpm }
      - run: pnpm install --frozen-lockfile
      - run: pnpm --filter @docmost/editor-ext build
      - run: pnpm server:build                       # nx run server:build -> apps/server/dist
      - run: pnpm --filter @docmost/mcp build         # tsc -> packages/mcp/build (нужно test-e2e.mjs)
      - run: pnpm --filter ./apps/server migration:latest
      - name: Start server (REST + /collab in one process)
        run: pnpm --filter ./apps/server start:prod &
      - name: Wait for health
        run: |
          for i in $(seq 1 60); do
            curl -fsS http://localhost:3000/api/health && exit 0
            sleep 2
          done
          echo "server did not become healthy"; exit 1
      - name: Seed admin user / workspace
        run: |
          curl -fsS -X POST http://localhost:3000/api/auth/setup \
            -H 'Content-Type: application/json' \
            -d '{"name":"E2E","email":"e2e@example.com","password":"E2ePassword123","workspaceName":"E2E"}'
      - name: Run MCP e2e
        env:
          DOCMOST_API_URL: http://localhost:3000/api
          DOCMOST_EMAIL: e2e@example.com
          DOCMOST_PASSWORD: E2ePassword123
        run: pnpm --filter @docmost/mcp test:e2e

Healthcheck: /api/health проверяет БД+Redis (контроллер health, глобальный префикс api); есть и лёгкий /api/health/liveok.

Подводные камни / на что обратить внимание

  1. pgvector обязателен — миграции делают CREATE EXTENSION vector; обычный postgres:18 не подойдёт, нужен pgvector/pgvector:pg18.
  2. APP_SECRET ≥ 32 символов и не плейсхолдер, иначе валидация env завершает процесс (process.exit(1) в environment.validation.ts).
  3. MCP e2e зависит от collab ws — нужен поднятый сервер с примонтированным /collab; node dist/main это обеспечивает. При раздельных процессах не забыть collab.
  4. Сборка mcp обязательна до test:e2e (./build/client.js); pretest на test:e2e не срабатывает.
  5. CLOUD не должен быть true, иначе SetupGuard запретит /auth/setup.
  6. Seed идемпотентен только на чистой БДPOST /auth/setup сработает при 0 воркспейсов. В CI БД одноразовая (service-контейнер на job), так что ок.
  7. MCP e2e создаёт временные страницы и сам убирает за собой; при падении часть может остаться — для одноразовой БД некритично.
  8. Стоимость/время: e2e тяжелее юнитов (БД/Redis/сборка/старт сервера), но это отдельные job'ы и на деплой не влияют.
  9. Альтернатива для MCP-стека: вместо сборки из исходников поднять стек через готовый образ / docker compose. Минус — тестируется образ, а не текущий коммит; поэтому рекомендуется сборка из исходников.

Definition of Done

  • В develop.yml добавлены job e2e-server и e2e-mcp, запускаются на push в develop.
  • build по-прежнему needs: test и не зависит от e2e (deploy не блокируется).
  • Серверный e2e: pgvector+redis service, миграции, apps/server test:e2e — зелёный.
  • MCP e2e: стек поднят, mcp собран, seed через /auth/setup, @docmost/mcp test:e2e — зелёный.
  • Падение любого e2e-job → красный run + письмо GitHub автору; deploy при этом проходит.
## Цель Гонять e2e-тесты на **каждый push в `develop`**. Они **не должны блокировать** сборку и публикацию образа (`build` → `:develop`): при падении достаточно красного статуса run'а и стандартного письма GitHub автору коммита (Telegram/Slack не нужны). Покрываем **оба** существующих e2e-набора: - серверный — `apps/server` (`test:e2e`, NestJS + supertest); - MCP — `packages/mcp` (`test:e2e`, `test-e2e.mjs` против живого сервера). ## Текущее состояние - `.github/workflows/develop.yml` на push в `develop` запускает reusable `test.yml` (job `test`), затем `build` (`needs: test`) собирает и пушит образ `ghcr.io/vvzvlad/gitmost:develop`. - `test.yml` гоняет `pnpm -r test` — это **только юнит/spec** в каждом пакете. e2e в CI **не запускаются**: - у `apps/server` `test` = `jest` (юнит, `*.spec.ts`); e2e — отдельный скрипт `test:e2e` (`jest --config test/jest-e2e.json`), который не вызывается; - у `packages/mcp` `test` = unit+mock; e2e — отдельный `test:e2e` (`node test-e2e.mjs`), который не вызывается. ## Что нужно каждому набору ### Серверный e2e (`apps/server/test/app.e2e-spec.ts`) Поднимает полный `AppModule` через `Test.createTestingModule` + `app.init()`. Требуется инфраструктура и env (см. `apps/server/src/integrations/environment/environment.validation.ts`): - **Postgres c pgvector** (миграции делают `CREATE EXTENSION vector`) — образ `pgvector/pgvector:pg18`, как в `docker-compose.yml`; - **Redis**; - env: `DATABASE_URL` (postgres://…), `REDIS_URL` (redis://…), `APP_SECRET` (≥ 32 символов и не `REPLACE_WITH_LONG_SECRET`); `APP_URL` опционально; - перед запуском — **миграции**: `pnpm --filter ./apps/server migration:latest`; - `pretest` сервера сам собирает `@docmost/editor-ext` (учтено). Сейчас сам тест тривиальный (`GET /` → `Hello World!`), но как каркас уже валиден — добавляем инфраструктуру, тесты дорастут позже. ### MCP e2e (`packages/mcp/test-e2e.mjs`) Бьёт по **живому Docmost** и использует не только REST `/api`, но и **collaboration websocket** (`HocuspocusProvider`, путь `/collab` — см. `packages/mcp/src/lib/collaboration.ts`: http→ws, срезается `/api`, домонтируется `/collab`). Главный процесс монтирует `CollaborationModule` в том же сервере (`Dockerfile`: `CMD ["pnpm","start"]` → `node dist/main`), поэтому **одного серверного процесса** хватает для REST + ws. Нужно: - весь стек поднят: db (pgvector) + redis + собранный сервер; - **сборка пакета mcp** перед запуском: `test-e2e.mjs` импортирует `./build/client.js`, а `pre`-хук есть только у `test` (`pretest` = `tsc`), не у `test:e2e`. Значит явный шаг `pnpm --filter @docmost/mcp build`; - **seed первого пользователя/воркспейса** — `POST /api/auth/setup` (гард `SetupGuard`: работает только при `CLOUD != true` и когда воркспейсов 0). DTO `CreateAdminUserDto`: `name`, `email`, `password`, опц. `workspaceName`; - env для теста: `DOCMOST_API_URL` (напр. `http://localhost:3000/api`), `DOCMOST_EMAIL`, `DOCMOST_PASSWORD`. ## Предлагаемая реализация Добавить в `develop.yml` **два независимых job** (`e2e-server`, `e2e-mcp`): - запускаются параллельно с `test`/`build` на push в `develop`; - **`build` НЕ зависит от них** (остаётся `needs: test`) → e2e не блокируют сборку/публикацию `:develop`; - при падении job весь run помечается красным (видно на коммите) и GitHub шлёт письмо автору пуша — этого достаточно как уведомление. > Про «красный, но не блокирует»: deploy не блокируется, потому что `build` от e2e не зависит. Сам run при этом честно красный. Если позже захочется, чтобы галка коммита оставалась зелёной — навесить `continue-on-error: true`; но по текущему решению оставляем красный. ### Скетч: job серверного e2e ```yaml e2e-server: runs-on: ubuntu-latest services: db: image: pgvector/pgvector:pg18 env: POSTGRES_DB: docmost POSTGRES_USER: docmost POSTGRES_PASSWORD: docmost ports: ['5432:5432'] options: >- --health-cmd "pg_isready -U docmost" --health-interval 5s --health-timeout 5s --health-retries 20 redis: image: redis:7 ports: ['6379:6379'] options: >- --health-cmd "redis-cli ping" --health-interval 5s --health-timeout 5s --health-retries 20 env: DATABASE_URL: postgresql://docmost:docmost@localhost:5432/docmost REDIS_URL: redis://localhost:6379 APP_SECRET: ci-e2e-secret-change-me-min-32-characters APP_URL: http://localhost:3000 steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 with: { node-version: 22, cache: pnpm } - run: pnpm install --frozen-lockfile - run: pnpm --filter @docmost/editor-ext build - run: pnpm --filter ./apps/server migration:latest - run: pnpm --filter ./apps/server test:e2e ``` ### Скетч: job MCP e2e ```yaml e2e-mcp: runs-on: ubuntu-latest services: db: # ← тот же pgvector/pgvector:pg18, что выше image: pgvector/pgvector:pg18 env: { POSTGRES_DB: docmost, POSTGRES_USER: docmost, POSTGRES_PASSWORD: docmost } ports: ['5432:5432'] options: >- --health-cmd "pg_isready -U docmost" --health-interval 5s --health-timeout 5s --health-retries 20 redis: image: redis:7 ports: ['6379:6379'] options: >- --health-cmd "redis-cli ping" --health-interval 5s --health-timeout 5s --health-retries 20 env: DATABASE_URL: postgresql://docmost:docmost@localhost:5432/docmost REDIS_URL: redis://localhost:6379 APP_SECRET: ci-e2e-secret-change-me-min-32-characters APP_URL: http://localhost:3000 NODE_ENV: production steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 with: { node-version: 22, cache: pnpm } - run: pnpm install --frozen-lockfile - run: pnpm --filter @docmost/editor-ext build - run: pnpm server:build # nx run server:build -> apps/server/dist - run: pnpm --filter @docmost/mcp build # tsc -> packages/mcp/build (нужно test-e2e.mjs) - run: pnpm --filter ./apps/server migration:latest - name: Start server (REST + /collab in one process) run: pnpm --filter ./apps/server start:prod & - name: Wait for health run: | for i in $(seq 1 60); do curl -fsS http://localhost:3000/api/health && exit 0 sleep 2 done echo "server did not become healthy"; exit 1 - name: Seed admin user / workspace run: | curl -fsS -X POST http://localhost:3000/api/auth/setup \ -H 'Content-Type: application/json' \ -d '{"name":"E2E","email":"e2e@example.com","password":"E2ePassword123","workspaceName":"E2E"}' - name: Run MCP e2e env: DOCMOST_API_URL: http://localhost:3000/api DOCMOST_EMAIL: e2e@example.com DOCMOST_PASSWORD: E2ePassword123 run: pnpm --filter @docmost/mcp test:e2e ``` Healthcheck: `/api/health` проверяет БД+Redis (контроллер `health`, глобальный префикс `api`); есть и лёгкий `/api/health/live` → `ok`. ## Подводные камни / на что обратить внимание 1. **pgvector обязателен** — миграции делают `CREATE EXTENSION vector`; обычный `postgres:18` не подойдёт, нужен `pgvector/pgvector:pg18`. 2. **APP_SECRET** ≥ 32 символов и не плейсхолдер, иначе валидация env завершает процесс (`process.exit(1)` в `environment.validation.ts`). 3. **MCP e2e зависит от collab ws** — нужен поднятый сервер с примонтированным `/collab`; `node dist/main` это обеспечивает. При раздельных процессах не забыть collab. 4. **Сборка mcp обязательна** до `test:e2e` (`./build/client.js`); `pretest` на `test:e2e` не срабатывает. 5. **CLOUD не должен быть `true`**, иначе `SetupGuard` запретит `/auth/setup`. 6. **Seed идемпотентен только на чистой БД** — `POST /auth/setup` сработает при 0 воркспейсов. В CI БД одноразовая (service-контейнер на job), так что ок. 7. **MCP e2e создаёт временные страницы и сам убирает за собой**; при падении часть может остаться — для одноразовой БД некритично. 8. **Стоимость/время**: e2e тяжелее юнитов (БД/Redis/сборка/старт сервера), но это отдельные job'ы и на деплой не влияют. 9. **Альтернатива для MCP-стека**: вместо сборки из исходников поднять стек через готовый образ / `docker compose`. Минус — тестируется образ, а не текущий коммит; поэтому рекомендуется сборка из исходников. ## Definition of Done - [ ] В `develop.yml` добавлены job `e2e-server` и `e2e-mcp`, запускаются на push в `develop`. - [ ] `build` по-прежнему `needs: test` и **не** зависит от e2e (deploy не блокируется). - [ ] Серверный e2e: pgvector+redis service, миграции, `apps/server test:e2e` — зелёный. - [ ] MCP e2e: стек поднят, mcp собран, seed через `/auth/setup`, `@docmost/mcp test:e2e` — зелёный. - [ ] Падение любого e2e-job → красный run + письмо GitHub автору; deploy при этом проходит.
Ghost added the test label 2026-06-25 12:55:37 +03:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: vvzvlad/gitmost#187