- Vitest multi-project setup (unit / integration / component) - Real-Postgres integration harness via in-process PGlite (TEST_DATABASE_URL override), migrations applied per worker; production code untouched - Unit: routing, locale, validation/Zod schemas, metadata, mail, site-theme, marquee, media, portfolio helpers, plus architecture-rule tests - Integration: Prisma data layer, API routes, and all server actions - Component: UI primitives and form components (jsdom + Testing Library) - 371 tests passing
70 lines
2.1 KiB
TypeScript
70 lines
2.1 KiB
TypeScript
import { readFileSync, readdirSync } from "fs";
|
|
import path from "path";
|
|
|
|
import { afterAll, beforeEach, vi } from "vitest";
|
|
|
|
/**
|
|
* Integration database wiring.
|
|
*
|
|
* - If TEST_DATABASE_URL is set, the real `lib/prisma` singleton is used unchanged,
|
|
* pointed at that Postgres (e.g. the Docker instance). Migrations are applied once
|
|
* by the global setup; files run serially and truncate between tests.
|
|
*
|
|
* - Otherwise, `lib/prisma` is mocked with a Prisma client backed by an in-process
|
|
* PGlite database (Postgres compiled to WASM) — real Postgres semantics, fully
|
|
* isolated per worker, no external server. Production code is never modified.
|
|
*/
|
|
|
|
const realDbUrl = process.env.TEST_DATABASE_URL?.trim();
|
|
if (realDbUrl) {
|
|
process.env.DATABASE_URL = realDbUrl;
|
|
}
|
|
|
|
vi.mock("@/lib/prisma", async () => {
|
|
if (process.env.TEST_DATABASE_URL?.trim()) {
|
|
return await vi.importActual<typeof import("@/lib/prisma")>("@/lib/prisma");
|
|
}
|
|
|
|
const { PGlite } = await import("@electric-sql/pglite");
|
|
const { PrismaPGlite } = await import("pglite-prisma-adapter");
|
|
const { PrismaClient } = await import("@prisma/client");
|
|
|
|
const db = await PGlite.create();
|
|
const migrationsDir = path.resolve(process.cwd(), "prisma", "migrations");
|
|
const dirs = readdirSync(migrationsDir)
|
|
.filter((entry) => /^\d/.test(entry))
|
|
.sort();
|
|
for (const dir of dirs) {
|
|
await db.exec(readFileSync(path.join(migrationsDir, dir, "migration.sql"), "utf8"));
|
|
}
|
|
|
|
const prisma = new PrismaClient({ adapter: new PrismaPGlite(db) });
|
|
return { prisma };
|
|
});
|
|
|
|
const { prisma } = await import("@/lib/prisma");
|
|
|
|
// Truncated in dependency order (children first) between every test for isolation.
|
|
const TABLES = [
|
|
"MediaUsage",
|
|
"MediaAsset",
|
|
"PortfolioAsset",
|
|
"PortfolioSection",
|
|
"PortfolioProject",
|
|
"Category",
|
|
"AppConfig",
|
|
];
|
|
|
|
export async function resetDb() {
|
|
const list = TABLES.map((table) => `"${table}"`).join(", ");
|
|
await prisma.$executeRawUnsafe(`TRUNCATE TABLE ${list} RESTART IDENTITY CASCADE;`);
|
|
}
|
|
|
|
beforeEach(async () => {
|
|
await resetDb();
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await prisma.$disconnect();
|
|
});
|