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.
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:
Root connection (DB_ROOT_USER/DB_ROOT_PASSWORD with dev defaults root/root) → CREATE DATABASE IF NOT EXISTS familysync_test
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.
FLUSH PRIVILEGES, close root connection.
App-user pool → drizzle(pool, { mode: 'default' }) → migrate(db, { migrationsFolder }) applies committed SQL from apps/api/src/db/migrations/.
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
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.jsisConfig(). 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.
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).