test: add comprehensive automated test coverage
- 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
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
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();
|
||||
});
|
||||
Reference in New Issue
Block a user