Files
accounted/vitest.config.ts
T
Jakob Wennberg 325c827322 test(mcp): run the tools against a real PostgREST, not a fake supabase (#1983)
All 100 files in extensions/general/mcp-server/__tests__ fake supabase.
query-journal.test.ts says out loud that its query chain is "exercised by the
live MCP smoke test", and no such test exists in CI. So the PostgREST grammar
of 157 tools, every .select() column string, every resource embed, every
or=(...) form, is gated by nothing and fails first in production.

pg-real cannot cover this: it holds a pg Pool and writes SQL, and none of that
grammar is resolved by Postgres. It is resolved by PostgREST at request time.

Adds a tool-pg vitest project, a docker-compose stack, a reset script that
replays every migration the way the pg-real CI job does, and a CI job.

The first sweep covers 74 read tools and finds no malformed query, across 87
real requests. That number is honest rather than impressive: with an empty
argument set many tools bail before querying. Per-tool fixtures are what
deepen it, and this harness is what makes writing them worth the effort.

Includes a self-test that injects a bad column and asserts the harness detects
it. That is not ceremony. It caught this file passing green while exercising
nothing, twice: once locally where supabase-js prefixes /rest/v1 onto a bare
PostgREST that does not serve it, and once on CI where Node 20 has no native
WebSocket, so every client construction threw and was swallowed by the
per-tool catch as a domain refusal. The client is now built once outside that
catch, the proof-of-life assertion counts real requests instead of being
trivially satisfiable, and realtime gets an inert transport.

Also excludes .next from all three vitest projects. These projects override
vitest's default excludes, so a local `npm run build` leaves a traced copy of
the repo that gets collected as a second set of test files.

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 18:11:41 +02:00

94 lines
3.3 KiB
TypeScript

import { defineConfig } from 'vitest/config'
import path from 'path'
const alias = {
'@': path.resolve(__dirname, '.'),
// `server-only` is a build-time guard whose real entry point always throws;
// Next.js swaps it out during bundling, Vitest cannot. Without this stub any
// test that transitively imports a server-only module fails at import time.
'server-only': path.resolve(__dirname, 'tests/stubs/server-only.ts'),
}
const unitProject = {
resolve: { alias },
test: {
name: 'unit',
globals: true,
environment: 'node' as const,
include: ['**/*.test.ts'],
// `.claude/worktrees/*` are ephemeral agent checkouts whose `@/*` imports
// resolve back to this root: never part of the suite.
// Two additions to the exclude list, both learned the hard way:
// * `*.tool.test.ts` also matches `**/*.test.ts`, so without it the unit
// project runs the PostgREST suite with no stack up.
// * `.next/standalone` is a traced COPY of the repo left by a local
// build, so `npm run build && npm test` collects those files a second
// time. Only two match here today, but the duplication is silent and
// grows with the build's tracing.
exclude: [
'**/node_modules/**',
'**/*.pg.test.ts',
'**/*.tool.test.ts',
'**/.claude/**',
'**/.next/**',
],
},
}
const pgRealProject = {
resolve: { alias },
test: {
name: 'pg-real',
globals: true,
environment: 'node' as const,
include: ['**/*.pg.test.ts'],
// `.next/standalone` holds a traced copy of these files after a local
// build; 128 of them match this project's glob.
exclude: ['**/node_modules/**', '**/.claude/**', '**/.next/**'],
setupFiles: ['tests/pg/setup.ts'],
// One-connection-at-a-time to avoid cross-file DB contention.
fileParallelism: false,
testTimeout: 15000,
},
}
// MCP tools queried through a REAL supabase-js client against a REAL
// PostgREST. Separate from pg-real because that project holds a `pg` Pool and
// writes SQL, which cannot see the half of a tool that PostgREST resolves: the
// `.select()` column strings, the resource embeds, the or=(...) grammar.
const toolPgProject = {
resolve: { alias },
test: {
name: 'tool-pg',
globals: true,
environment: 'node' as const,
include: ['**/*.tool.test.ts'],
// See the unit project's note: `.next/standalone` is a build artifact that
// would otherwise be collected as a second copy of this suite.
exclude: ['**/node_modules/**', '**/.claude/**', '**/.next/**'],
setupFiles: ['tests/tool-pg/setup.ts'],
// The suite shares one database; parallel files would race on seeded rows.
fileParallelism: false,
testTimeout: 30000,
},
}
// Only register the pg-real project when DATABASE_URL is set. Local devs
// running a bare `vitest run` would otherwise hit the schema sanity check
// against a non-existent DB. `npm run test:pg` is the opt-in entry point, and
// `npm run test:tools` is the equivalent for tool-pg.
const projects = [
unitProject,
...(process.env.DATABASE_URL ? [pgRealProject] : []),
...(process.env.TOOL_PG_REST_URL ? [toolPgProject] : []),
]
export default defineConfig({
resolve: { alias },
test: {
globals: true,
environment: 'node',
projects,
},
})