diff --git a/.claude/.gitignore b/.claude/.gitignore new file mode 100644 index 0000000000..9df52a07c2 --- /dev/null +++ b/.claude/.gitignore @@ -0,0 +1,4 @@ +* +!skills/ +!skills/** +!.gitignore diff --git a/.claude/skills/tests/SKILL.md b/.claude/skills/tests/SKILL.md new file mode 100644 index 0000000000..e3b140fc93 --- /dev/null +++ b/.claude/skills/tests/SKILL.md @@ -0,0 +1,179 @@ +--- +name: tests +description: Anything test-related in the drizzle-orm monorepo — running, writing, debugging, or fixing tests. Use whenever the user mentions tests in any way. +--- + +# Running tests in drizzle-orm + +This monorepo's tests fall into two buckets: **unit-style** (no DB, run anywhere) and **DB-backed** (need a real Postgres / MySQL / MariaDB / CockroachDB / MSSQL / SingleStore on a specific port). The DB-backed ones read connection-string env vars from `drizzle-kit/.env`, which itself maps to ports owned by `compose/*.yml`. If any of those three layers is missing — env file, DB container, port match — the test fails with a confusing error (most often `connect ECONNREFUSED` or `process.env.X is not set`). This skill is the fast path through the setup so the user doesn't waste time debugging environment instead of code. + +## ⛔ Hard rule — never spawn Docker from test code, test runs, or agent narration + +**This rule has four halves. Violating any one of them reads to the user as "tests spawned Docker again".** + +**1. Test code must not spawn containers.** Never `import Docker`/`dockerode`/`testcontainers`/`GenericContainer` and never `execSync('docker ...')` in a new test file. The legacy `drizzle-kit/tests//mocks.ts::createDockerDB()` and `integration-tests/tests/{pg,mysql,cockroach,mssql,bun,seeder}/*.test.ts::createDockerDB()` paths exist for historical reasons — DO NOT add new ones, and DO NOT call existing ones from new tests. Wrap new DB-backed tests in `describe.skipIf(!process.env.X)` so a missing env var skips silently. + +**2. Latent trigger: legacy `?? createDockerDB()` fallbacks are still wired up.** Many `integration-tests/tests/{pg,mysql,cockroach,mssql,bun,seeder}/*.test.ts` files contain `process.env.X ?? await createDockerDB()`. Running `pnpm --filter integration-tests test:*` without the matching env var will silently spin up a container via `dockerode` — even if you've already brought DBs up via `compose/dockers.sh`. The same fallthrough exists in `drizzle-kit/tests//mocks.ts::prepareTestDatabase()`. **Setting env vars is not optional — it is what prevents Docker spawn.** Always export the connection string before invoking these scripts, even when "just running the suite to see what passes". If you discover a new test using this pattern, flag it as tech debt — do not extend it. + +**3. Agent narration must not tempt operators into `docker run`.** When you (or a spawned subagent) summarize how to run a new DB-backed test, **always** reference `bash compose/dockers.sh up ` plus the connection-string env var. Never write "this test starts a postgres container" or suggest `docker run` in a final message — the user pattern-matches that as "tests spawned Docker again", even when the underlying code is well-behaved. + +**4. Spawned subagents do not inherit this skill.** When delegating any test-writing or test-running work via the Agent tool, paste rules 1–3 verbatim into the subagent prompt. Subagents only see the prompt you give them, not the project's `.claude/skills/` directory. If a subagent's diff adds `import Docker` or its summary tells the operator to `docker run`, that's the failure mode this rule prevents. + +## Writing tests — design & quality + +The full, authoritative rules live in `.planning/codebase/TESTING.md` § **Test Quality Guidelines (drizzle-kit)**. The essentials (drizzle-kit): + +- **Minimize mocks; drive against real in-memory DBs.** PGlite (postgres, via the real `pglite` driver — pass `{ driver: 'pglite', client }` as credentials), `new BetterSqlite3(':memory:')` (sqlite), `@libsql/client` `:memory:` (turso); real servers for mysql/mssql/cockroach/singlestore. Run the REAL diff/introspect/`suggestions` engine — never `vi.doMock` `*/ddl`/`*/diff`/`*/serializer`/`*/introspect` to fabricate output. +- **Only test what a real DB can actually produce.** Test redaction / error envelopes by triggering a real failure (unreachable URL + sentinel credential), not by injecting a fabricated error string. If a behavior can't occur on a real DB, drop it or move it to that dialect's real-DB test. +- **One real-DB test per distinct behavior** — no per-dialect envelope matrices; keep dialect-specific behavior in that dialect's tests. +- **Prefer dependency injection over module mocking** — e.g. `SchemaSource.fromSchema(fromExports(entities))` instead of `vi.doMock`-ing `prepareFromSchemaFiles`. +- **Drop low-value tests** — things unlikely to ever change (presence/frontmatter/static reads), CLI≡SDK parity (shared core guarantees it), pure-function idempotence, defensive `expect(x).toBeDefined()`. +- **No subprocess / built-`dist` / network deps** — no `spawnSync` tsx/node/npx, no `npx …@latest` — unless a single deliberate E2E smoke needs it (`tests/other/bin.test.ts`). +- **House style** — no requirement-ID nomenclature (`MCP-05`, `CHECK-05`, `D-13`) or essay JSDoc in tests; explicit named tests over `test.each` where it aids readability; reuse `tests//mocks.ts`; express fixtures via `fromExports(...)`, not hand-built `{ tables, views, relations }` literals. +- **Tests must run in a CI shard** — `kit:*` runs `./mysql ./sqlite ./other`, `./postgres`, `./cockroach`, `./mssql`, NOT `tests/sdk`/`tests/mcp`/`tests/skills`. Land new tests in `tests/other/`. + +When delegating test-writing to a subagent, paste these into the prompt too (subagents don't see this skill). + +## Workflow (always run in this order) + +### 1. Ensure `drizzle-kit/.env` exists + +The committed template is `drizzle-kit/.env.example`; the real file `drizzle-kit/.env` is gitignored and needs to exist for `dotenv/config` (loaded by `tests/{mariadb,mysql,singlestore}/pull.test.ts` and Vitest's built-in dotenv loader) to populate `process.env`. + +```bash +[ -f drizzle-kit/.env ] || cp drizzle-kit/.env.example drizzle-kit/.env +``` + +Skip if `drizzle-kit/.env` already exists — never overwrite the user's local edits. If a test still says `process.env.X is not set` after this, look in `drizzle-kit/.env.example` to see whether the variable is even defined there; if not, the test is reading a key the example doesn't ship and that's a real bug. + +### 2. Identify the test target and which DB(s) it needs + +Pick the right script. Most users want one of these: + +| Command | Cwd | Needs DB? | Compose dialect(s) | Env vars consumed | +|---|---|---|---|---| +| `pnpm --filter drizzle-kit test` | repo root | no | — | (TEST_CONFIG_PATH_PREFIX) | +| `pnpm --filter drizzle-kit test:types` | repo root | no | — | — | +| `pnpm --filter drizzle-kit test:postgres` | repo root | yes (PGlite by default) | none required; **set `PG_VERSION=16\|17\|18`** to use real postgres → then need `postgres16` / `postgres17` / `postgres18`; postgis subset → `postgres-postgis` | `PG_VERSION`, `PG16_URL`, `PG17_URL`, `PG18_URL`, `POSTGIS_URL` | +| `pnpm --filter drizzle-kit test:other` | repo root | yes (mysql subset) | `mysql` | `MYSQL_CONNECTION_STRING` | +| `pnpm --filter drizzle-kit test:cockroach` | repo root | yes | `cockroach` (or `cockroach-many` for `;`-separated multi-URL parallelism) | `COCKROACH_CONNECTION_STRING` | +| `pnpm --filter drizzle-kit test:mssql` | repo root | yes | `mssql` | `MSSQL_CONNECTION_STRING` | +| `pnpm --filter drizzle-kit test:singlestore` | repo root | yes | `singlestore` (see overload below) | `MYSQL_CONNECTION_STRING` | +| `pnpm --filter drizzle-orm test` | repo root | no | — | — | +| `pnpm --filter drizzle-orm test:types` | repo root | no | — | — | +| `pnpm --filter drizzle-seed test` | repo root | no | — | — | +| `pnpm --filter eslint-plugin-drizzle test` | repo root | no | — | — | +| `pnpm --filter integration-tests test:postgres` | repo root | yes | `postgres` (canonical compose port 55433) | `PG_CONNECTION_STRING` (NB: integration-tests has its own `.env`, not drizzle-kit's) | +| `pnpm --filter integration-tests test:mysql` | repo root | yes | `mysql` | `MYSQL_CONNECTION_STRING` | + +Notes: +- `drizzle-kit test:postgres` runs against PGlite (in-process) **unless** `PG_VERSION` is set. If the user wants to test against real Postgres (e.g., to repro a production-only behavior), set `PG_VERSION=18` (or 17/16) AND have the matching `postgres18` dialect running. +- `drizzle-kit test:other` mixes pure-vitest sqlite/`other` tests with mysql tests — only the mysql portion needs Docker. +- `integration-tests` is a separate package with a separate `.env` (`integration-tests/.env`) and different port conventions — do not confuse the two .env files. + +### 3. SingleStore overload — important footgun + +`drizzle-kit/tests/singlestore/mocks.ts` reads **`MYSQL_CONNECTION_STRING`**, not a separate `SINGLESTORE_CONNECTION_STRING`. So when running `test:singlestore`, the user must: + +- Comment the MySQL line in `drizzle-kit/.env` +- Uncomment the SingleStore line that points at `mysql://root:singlestore@127.0.0.1:33307/` + +The `.env.example` documents this in a comment block. Don't try to "fix" this in the test code — the overload is intentional and documented as out-of-scope in the `.env.example` header. If the user is running `test:mysql` and `test:singlestore` back-to-back, remind them about the swap. + +### 4. Bring up the DBs via `compose/dockers.sh` + +Always use `compose/dockers.sh` — never let test mocks spin up Docker via `dockerode`, and never invent ad-hoc `docker run` commands. The script is the single source of truth for "which port is which dialect on", and each compose YAML carries the canonical healthcheck. Spawned subagents must be told this explicitly because they don't see this skill by default. + +```bash +# Start exactly the dialects the test needs: +bash compose/dockers.sh up [...] + +# Examples: +bash compose/dockers.sh up postgres # for PG_CONNECTION_STRING tests +bash compose/dockers.sh up postgres18 # for PG_VERSION=18 ./postgres/ tests +bash compose/dockers.sh up mysql # for test:other (mysql subset) or test:mysql +bash compose/dockers.sh up cockroach # for test:cockroach +bash compose/dockers.sh up mssql # for test:mssql +bash compose/dockers.sh up singlestore # for test:singlestore (after .env swap) +bash compose/dockers.sh up postgres mysql cockroach # multiple dialects in one go + +# No-args = bring up every supported dialect (postgres, postgres16/17/18, postgres-postgis, +# postgres-vector, mysql, mariadb, cockroach, cockroach-many, mssql, singlestore, singlestore-many). +# Heavy on macOS — ~10GB RAM peak. Prefer naming the dialects you actually need. +bash compose/dockers.sh up +``` + +The `up` subcommand runs `docker compose ... up -d --wait --wait-timeout 120` and then `bash compose/wait.sh ` so the script returns only after the host-side TCP probe succeeds. **Tests are safe to launch immediately after the script returns.** No `sleep` loops needed. + +If `bash compose/dockers.sh up ` reports `Unknown dialect '...'`, the dialect name is wrong; the script prints the valid list. Common mistakes: `postgres-15` (doesn't exist — only 16/17/18), `singlestore2` (use `singlestore-many`). + +### 5. Run the test + +After `dockers.sh up` returns: + +```bash +pnpm --filter