test: add initial unit integration and e2e coverage

Adds the initial testing baseline for the project:

   Unit coverage:
   - Zod schemas for items, assignments, movements, categories, auth, recipients, users, and assets
   - password hashing and verification helpers
   - auth role helper functions

   Integration coverage with PostgreSQL Testcontainers:
   - item use-cases: create, duplicate names, delete constraints
   - assignment use-cases: create, insufficient stock, return, double return
   - asset use-cases: available/assigned creation and lifecycle transitions
   - user use-cases: create/update, uniqueness, admin safeguards, password reset
   - category use-cases: create/update/delete constraints
   - recipient use-cases: create/update and uniqueness constraints

   E2E smoke coverage with Playwright:
   - unauthenticated redirect to login
   - seeded admin login
   - dashboard load
   - admin users page
   - inventory items page
   - assignments page

   Also configures:
   - Vitest
   - Playwright
   - PostgreSQL Testcontainers helpers
   - deterministic E2E admin bootstrap
   - test artifact ignores

   Validation:
   - bun run test: 9 files / 37 tests passed
   - bun run test:e2e: 3 passed
   - bunx tsc --noEmit: passed
   - bunx prisma validate: passed
This commit is contained in:
2026-06-07 04:14:01 +02:00
parent cb01f4f8ef
commit f2b9239d82
18 changed files with 2372 additions and 9 deletions
+72
View File
@@ -0,0 +1,72 @@
import { execFileSync } from "node:child_process"
import {
PostgreSqlContainer,
type StartedPostgreSqlContainer,
} from "@testcontainers/postgresql"
import type { PrismaClient } from "@/generated/prisma/client"
type TestDatabaseState = {
container?: StartedPostgreSqlContainer
databaseUrl?: string
}
const state: TestDatabaseState = {}
const TABLES_TO_TRUNCATE = [
"Movement",
"Assignment",
"Asset",
"Item",
"Category",
"Recipient",
"User",
]
export async function startIntegrationTestDatabase() {
if (state.container && state.databaseUrl) {
return state.databaseUrl
}
const container = await new PostgreSqlContainer("postgres:18-alpine")
.withDatabase("stock_manager_test")
.withUsername("test")
.withPassword("test")
.start()
const databaseUrl = container.getConnectionUri()
state.container = container
state.databaseUrl = databaseUrl
process.env.DATABASE_URL = databaseUrl
process.env.AUTH_SECRET = process.env.AUTH_SECRET || "test-auth-secret"
try {
execFileSync("bunx", ["prisma", "migrate", "deploy"], {
env: {
...process.env,
DATABASE_URL: databaseUrl,
},
stdio: "inherit",
})
} catch (error) {
await stopIntegrationTestDatabase()
throw error
}
return databaseUrl
}
export async function resetIntegrationTestDatabase(prisma: PrismaClient) {
const quotedTables = TABLES_TO_TRUNCATE.map((table) => `"${table}"`).join(
", ",
)
await prisma.$executeRawUnsafe(`TRUNCATE TABLE ${quotedTables} CASCADE`)
}
export async function stopIntegrationTestDatabase() {
await state.container?.stop()
state.container = undefined
state.databaseUrl = undefined
}