diff --git a/tests/__snapshots__/board.spec.ts.snap b/tests/__snapshots__/board.nuxt.spec.ts.snap similarity index 100% rename from tests/__snapshots__/board.spec.ts.snap rename to tests/__snapshots__/board.nuxt.spec.ts.snap diff --git a/tests/board.spec.ts b/tests/board.nuxt.spec.ts similarity index 100% rename from tests/board.spec.ts rename to tests/board.nuxt.spec.ts diff --git a/tests/helpers/db.ts b/tests/helpers/db.ts new file mode 100644 index 0000000..883c9ae --- /dev/null +++ b/tests/helpers/db.ts @@ -0,0 +1,57 @@ +// Integration-test harness for the Postgres-backed repositories. +// +// Tests run against a dedicated database (default `andon_test`) so they never +// touch dev data. The database is created if missing and migrated to the +// current schema; `truncateAll` resets state between tests. +import { createRequire } from 'node:module' +import type pgType from 'pg' +import { drizzle, type NodePgDatabase } from 'drizzle-orm/node-postgres' +import { migrate } from 'drizzle-orm/node-postgres/migrator' +import { sql } from 'drizzle-orm' +import * as schema from '../../server/db/schema' + +// Load `pg` through a native CJS require. Importing it via Vite's SSR transform +// breaks pg's internal `class … extends Pool` (the require of `pg-pool` resolves +// to a module namespace), so we sidestep the transform entirely. +const pg = createRequire(import.meta.url)('pg') as typeof pgType + +const TEST_URL = + process.env.TEST_DATABASE_URL ?? + 'postgres://andon:andon@localhost:5432/andon_test' + +export type TestDb = NodePgDatabase + +/** Connect to the test database (creating + migrating it first) and return a client + pool. */ +export async function setupTestDb(): Promise<{ db: TestDb; pool: pgType.Pool }> { + await ensureDatabaseExists(TEST_URL) + const pool = new pg.Pool({ connectionString: TEST_URL }) + const db = drizzle(pool, { schema }) + await migrate(db, { migrationsFolder: 'server/db/migrations' }) + return { db, pool } +} + +/** Wipe all rows so each test starts from a clean slate. */ +export async function truncateAll(db: TestDb): Promise { + await db.execute( + sql`TRUNCATE TABLE defects, projects, push_subscriptions RESTART IDENTITY CASCADE`, + ) +} + +/** Create the target database via the maintenance `postgres` DB if it doesn't exist. */ +async function ensureDatabaseExists(url: string): Promise { + const dbName = new URL(url).pathname.slice(1) + const adminUrl = new URL(url) + adminUrl.pathname = '/postgres' + + const admin = new pg.Client({ connectionString: adminUrl.toString() }) + await admin.connect() + try { + const { rowCount } = await admin.query( + 'SELECT 1 FROM pg_database WHERE datname = $1', + [dbName], + ) + if (!rowCount) await admin.query(`CREATE DATABASE "${dbName}"`) + } finally { + await admin.end() + } +} diff --git a/vitest.config.ts b/vitest.config.ts index 1d73d30..ca829da 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,9 +1,30 @@ +import { defineConfig } from 'vitest/config' import { defineVitestConfig } from '@nuxt/test-utils/config' -export default defineVitestConfig({ +// Two projects, split by filename: +// • *.nuxt.spec.ts → run in the Nuxt runtime (components, anything needing +// auto-imports or the app environment). +// • everything else → a plain node environment. node_modules stay external, +// so native CJS deps like `pg` load correctly (the Nuxt runtime inlines them +// and breaks pg's internal `class … extends Pool`). +export default defineConfig({ test: { - // Default to a plain node environment; component/integration specs opt into - // the Nuxt runtime per-file with `// @vitest-environment nuxt`. - environment: 'node', + projects: [ + { + test: { + name: 'server', + environment: 'node', + include: ['tests/**/*.spec.ts'], + exclude: ['tests/**/*.nuxt.spec.ts'], + }, + }, + defineVitestConfig({ + test: { + name: 'nuxt', + environment: 'nuxt', + include: ['tests/**/*.nuxt.spec.ts'], + }, + }), + ], }, })