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
+110
View File
@@ -0,0 +1,110 @@
import { execFileSync, spawn, type ChildProcess } from "node:child_process"
import process from "node:process"
import {
PostgreSqlContainer,
type StartedPostgreSqlContainer,
} from "@testcontainers/postgresql"
const port = process.env.E2E_PORT ?? "3100"
const host = "127.0.0.1"
const baseUrl = `http://${host}:${port}`
let container: StartedPostgreSqlContainer | undefined
let nextProcess: ChildProcess | undefined
let shuttingDown = false
async function cleanup() {
if (shuttingDown) return
shuttingDown = true
if (nextProcess && !nextProcess.killed) {
nextProcess.kill("SIGTERM")
}
await container?.stop()
container = undefined
}
async function main() {
container = await new PostgreSqlContainer("postgres:18-alpine")
.withDatabase("stock_manager_e2e")
.withUsername("e2e")
.withPassword("e2e")
.start()
const databaseUrl = container.getConnectionUri()
const env = {
...process.env,
DATABASE_URL: databaseUrl,
AUTH_SECRET: process.env.AUTH_SECRET ?? "e2e-auth-secret",
AUTH_URL: baseUrl,
NEXTAUTH_URL: baseUrl,
ADMIN_BOOTSTRAP_ENABLED: "true",
ADMIN_USERNAME: "admin",
ADMIN_EMAIL: "admin@example.test",
ADMIN_NAME: "E2E Admin",
ADMIN_PASSWORD: "admin-password",
}
process.env.DATABASE_URL = databaseUrl
process.env.AUTH_SECRET = env.AUTH_SECRET
process.env.AUTH_URL = baseUrl
process.env.NEXTAUTH_URL = baseUrl
process.env.ADMIN_BOOTSTRAP_ENABLED = "true"
process.env.ADMIN_USERNAME = "admin"
process.env.ADMIN_EMAIL = "admin@example.test"
process.env.ADMIN_NAME = "E2E Admin"
process.env.ADMIN_PASSWORD = "admin-password"
try {
execFileSync("bunx", ["prisma", "migrate", "deploy"], {
env,
stdio: "inherit",
})
const prismaModule = await import("@/lib/prisma")
const bootstrapModule = await import("../../../prisma/bootstrap-admin")
await bootstrapModule.bootstrapAdmin(prismaModule.prisma)
await prismaModule.prisma.$disconnect()
} catch (error) {
await cleanup()
throw error
}
const startedNextProcess = spawn(
"bunx",
["next", "dev", "--webpack", "--hostname", host, "--port", port],
{
env,
stdio: "inherit",
},
)
nextProcess = startedNextProcess
startedNextProcess.on("exit", async (code, signal) => {
const wasShuttingDown = shuttingDown
await cleanup()
if (!wasShuttingDown && code !== 0) {
console.error(`Next dev server exited with code ${code} signal ${signal}`)
process.exit(code ?? 1)
}
})
}
process.on("SIGTERM", async () => {
await cleanup()
process.exit(0)
})
process.on("SIGINT", async () => {
await cleanup()
process.exit(0)
})
main().catch(async (error) => {
console.error(error)
await cleanup()
process.exit(1)
})