Files
familysync/.planning/quick/260613-ndv-wire-apps-api-integration-tests-to-a-ded/260613-ndv-SUMMARY.md
T

6.0 KiB

phase, plan, subsystem, tags, dependency_graph, tech_stack, key_files, decisions, metrics
phase plan subsystem tags dependency_graph tech_stack key_files decisions metrics
quick-260613-ndv 01 api/testing
test-isolation
mariadb
drizzle
vitest
globalSetup
requires provides affects
familysync_test DB provisioning
local test isolation
apps/api test suite
CI api job (unaffected — CI-gated)
added patterns
vitest globalSetup
drizzle-orm/mysql2 two-arg form
created modified
apps/api/test/global-setup.ts
apps/api/vitest.config.ts
apps/api/test/setup.ts
apps/api/README.md
D-ndv-pool-form: use drizzle(pool, { mode }) not drizzle({ client: pool, mode }) — drizzle-orm@0.45.2 isConfig() has a tautological OR in the mode branch that always returns false; combined-config form falls through to construct(configObj, undefined) making the config object itself the session client
D-ndv-users-intact: do not delete users in afterEach — tests seed user id=1 once and reuse across test files; users starts empty in familysync_test at run start; per-test user leaks scoped to those tests' own setup
duration completed tasks_completed files_changed
~15 min 2026-06-13 2 4

Quick Task 260613-ndv: Test DB Isolation Summary

One-liner: vitest globalSetup provisions + migrates familysync_test via root MariaDB connection (CI-gated no-op); test.env forces workers to DB_NAME=familysync_test; 244 tests pass without touching the dev DB.

Tasks Completed

Task Name Commit Key Files
1 Add CI-gated globalSetup (provision + migrate familysync_test) 8453b97 apps/api/test/global-setup.ts (new), apps/api/vitest.config.ts
2 Clean-slate comment in setup.ts + README local-test docs 4740d86 apps/api/test/setup.ts, apps/api/README.md, apps/api/test/global-setup.ts (bug fix)

What Was Built

apps/api/test/global-setup.ts (new, 108 lines)

Vitest globalSetup that runs once in the main process before any test file:

  • CI gate: if (process.env.CI) return — CI provisions its own familysync service DB via db:migrate, completely unaffected.
  • Local flow:
    1. Root connection (DB_ROOT_USER/DB_ROOT_PASSWORD with dev defaults root/root) → CREATE DATABASE IF NOT EXISTS familysync_test
    2. GRANT ALL PRIVILEGES ON familysync_test.* TO '<appUser>'@'%' with appUser validated against /^[A-Za-z0-9_]+$/ (T-ndv-03); falls back to @'localhost' grant if @'%' fails.
    3. FLUSH PRIVILEGES, close root connection.
    4. App-user pool → drizzle(pool, { mode: 'default' })migrate(db, { migrationsFolder }) applies committed SQL from apps/api/src/db/migrations/.
    5. Logs [global-setup] provisioned + migrated familysync_test.

apps/api/vitest.config.ts (modified)

  • Added globalSetup: ['./test/global-setup.ts']
  • Added CI-gated test.env: locally sets DB_NAME=familysync_test and DB_HOST=127.0.0.1; under CI the env override is {} so job-level DB_NAME=familysync / DB_HOST=mariadb are preserved.
  • Retained fileParallelism: false and setupFiles: ['./test/setup.ts'].

apps/api/test/setup.ts (modified)

Updated file header to document:

  • Tests now run against familysync_test (not dev familysync)
  • afterEach truncates list/push tables only; users is intentionally left intact within a run
  • Rationale for the users decision (no per-test deletion — tests seed id=1 once and reuse it)

apps/api/README.md (modified)

Added ## Running API tests locally section documenting:

  • Dev MariaDB prerequisite + run command (set -a; source .env; set +a; DB_HOST=127.0.0.1 pnpm --filter @familysync/api test)
  • DB_ROOT_PASSWORD requirement in .env for one-time provisioning
  • CI unaffected note

Deviations from Plan

Auto-fixed Issues

1. [Rule 1 - Bug] drizzle-orm@0.45.2 isConfig() mode branch tautology

  • Found during: Task 1 verification (first test run)
  • Issue: drizzle({ client: pool, mode: 'default' }) — the { client, mode } combined config form — triggers a bug in drizzle-orm@0.45.2/utils.js isConfig(). The mode branch has: if (data["mode"] !== "default" || data["mode"] !== "planetscale" || ...) which is a tautological OR (always true for any mode value), so isConfig returns false. The call falls through to construct(configObj, undefined), making the config object itself the session client. client.query is not a function.
  • Fix: Switched to drizzle(pool, { mode: 'default' }) (two-arg form, which hits the construct(params[0], params[1]) branch directly — correct behavior). Added comment in the code explaining the drizzle-orm bug.
  • Files modified: apps/api/test/global-setup.ts
  • Commit: 4740d86

Verification Results

Check Result
grep -q "globalSetup" apps/api/vitest.config.ts PASS
grep -q "familysync_test" apps/api/vitest.config.ts PASS
grep -q "process.env.CI" apps/api/test/global-setup.ts PASS
grep -q "migrate(" apps/api/test/global-setup.ts PASS
pnpm --filter @familysync/api typecheck PASS (exit 0)
Full local test run (244 tests, 25 files) PASS
Dev familysync users count before=3, after=3 PASS
familysync_test tables after run PASS (10 tables: all schema tables + __drizzle_migrations)
CI code inspection: globalSetup early-returns, env override is {} PASS (confirmed by code)

Known Stubs

None.

Threat Flags

None — no new network endpoints, auth paths, or file access patterns introduced. The root DB credential usage is scoped exclusively to the local non-CI branch of globalSetup and reads from env (T-ndv-01 mitigated as designed).

Self-Check: PASSED

  • apps/api/test/global-setup.ts exists: confirmed
  • apps/api/vitest.config.ts updated: confirmed
  • apps/api/test/setup.ts updated: confirmed
  • apps/api/README.md updated: confirmed
  • Commit 8453b97 exists: confirmed (Task 1)
  • Commit 4740d86 exists: confirmed (Task 2)
  • 244/244 tests pass against familysync_test: confirmed
  • Dev DB users count unchanged (3 before, 3 after): confirmed