name: Test on: pull_request: workflow_call: workflow_dispatch: concurrency: group: test-${{ github.ref }} cancel-in-progress: true permissions: contents: read jobs: # Guard against a long-lived branch adding a migration whose timestamped # filename sorts BEFORE migrations already applied on the target branch (and # thus in prod). The Kysely startup migrator rejects that as "corrupted # migrations" and crash-loops the app on boot (incident #361). This gate fails # the PR so the migration is renamed to a current timestamp before merge. Only # runs for pull_request events (needs a base branch to diff against). migration-order: if: github.event_name == 'pull_request' runs-on: ubuntu-latest timeout-minutes: 5 steps: - name: Checkout (full history for the base-branch diff) uses: actions/checkout@v4 with: fetch-depth: 0 - name: Added migrations must sort after the newest on the base branch env: TARGET_BRANCH: ${{ github.base_ref }} run: | set -euo pipefail MIG_DIR="apps/server/src/database/migrations" # checkout above already did fetch-depth:0 (full history). Fetch the base # WITHOUT --depth (a shallow graft would truncate the base history and # break the merge-base when the base has moved ahead of the PR merge — # exactly the long-branch-vs-moving-base case this gate guards, #361). git fetch --no-tags origin "$TARGET_BRANCH" newest_on_target=$(git ls-tree -r --name-only "origin/${TARGET_BRANCH}" "$MIG_DIR" | sort | tail -1) # NO `|| true`: a diff failure (e.g. an unresolved merge-base) must fail # the job CLOSED — a gate whose job is to BLOCK must never pass on error. # `set -e` above already aborts on a non-zero diff exit. added=$(git diff --diff-filter=A --name-only "origin/${TARGET_BRANCH}...HEAD" -- "$MIG_DIR") bad=0 for f in $added; do if [[ "$f" < "$newest_on_target" || "$f" == "$newest_on_target" ]]; then echo "::error::Migration $f sorts at or before the newest on ${TARGET_BRANCH} ($newest_on_target) — rename it with a CURRENT timestamp before merge (do not change its contents). See incident #361." bad=1 fi done if [ "$bad" -eq 0 ]; then echo "Migration order OK (added migrations all sort after $newest_on_target)." fi exit $bad test: runs-on: ubuntu-latest timeout-minutes: 20 # Real Postgres + Redis so the server integration suite (`*.int-spec.ts`, # behind `pnpm --filter server test:int`) runs in CI (red-team finding #7). # Without it, cost-cap / FK-cascade / jsonb-round-trip / real-apply tests # only ran locally, so regressions in those paths stayed green in CI. # Postgres uses the pgvector image because migrations create vector columns # and global-setup runs `CREATE EXTENSION vector`. Credentials/db match the # defaults in apps/server/test/integration/db.ts + global-setup.ts # (docmost / docmost_dev_pw, maintenance db `docmost`, redis on 6379), so no # TEST_*_URL overrides are needed. services: postgres: # via mirror.gcr.io (Docker Hub pull-through cache; avoids Hub anonymous # pull rate-limit that randomly fails on shared GitHub runner IPs). image: mirror.gcr.io/pgvector/pgvector:pg18 env: POSTGRES_USER: docmost POSTGRES_PASSWORD: docmost_dev_pw POSTGRES_DB: docmost ports: - 5432:5432 options: >- --health-cmd "pg_isready -U docmost" --health-interval 10s --health-timeout 5s --health-retries 5 redis: # via mirror.gcr.io (see postgres note above). image: mirror.gcr.io/library/redis:7 ports: - 6379:6379 options: >- --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 steps: - name: Checkout uses: actions/checkout@v4 - name: Set up pnpm uses: pnpm/action-setup@v4 - name: Set up Node uses: actions/setup-node@v4 with: node-version: 22 cache: pnpm - name: Install dependencies run: pnpm install --frozen-lockfile # Required for the client suite, which resolves @docmost/editor-ext via its # dist build (the server suite also rebuilds it through its own pretest). - name: Build editor-ext run: pnpm --filter @docmost/editor-ext build # @docmost/prosemirror-markdown is the shared converter (#293/#326); its # build/ is gitignored, and plain `pnpm -r test` does NOT honour nx # `dependsOn: ^build`, so its consumers (mcp `pretest: tsc`, git-sync vitest # typecheck) fail with TS2307 Cannot find module '@docmost/prosemirror-markdown' # unless it is built first. Build it before the recursive test run. - name: Build prosemirror-markdown run: pnpm --filter @docmost/prosemirror-markdown build - name: Run unit tests run: pnpm -r test # Integration suite against the real Postgres/Redis services above. Runs # the FK-cascade, cost-cap, jsonb-round-trip and real-apply specs that the # unit run (mocks only) cannot cover. global-setup drops/recreates the # isolated `docmost_test` DB and migrates it to latest. - name: Run server integration tests run: pnpm --filter server test:int