Files
stock-manager/tests/integration/use-cases/user.use-cases.test.ts
T
aferrer f2b9239d82 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
2026-06-07 04:14:01 +02:00

290 lines
8.1 KiB
TypeScript

import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest"
import type { PrismaClient } from "@/generated/prisma/client"
import { createTestUser } from "../helpers/factories"
import {
resetIntegrationTestDatabase,
startIntegrationTestDatabase,
stopIntegrationTestDatabase,
} from "../helpers/test-db"
let prisma: PrismaClient
let createUserUseCase: typeof import("@/use-cases/user.use-cases").createUserUseCase
let updateUserUseCase: typeof import("@/use-cases/user.use-cases").updateUserUseCase
let setUserActiveUseCase: typeof import("@/use-cases/user.use-cases").setUserActiveUseCase
let resetUserPasswordUseCase: typeof import("@/use-cases/user.use-cases").resetUserPasswordUseCase
let verifyPassword: typeof import("@/lib/security").verifyPassword
beforeAll(async () => {
await startIntegrationTestDatabase()
const prismaModule = await import("@/lib/prisma")
const userUseCases = await import("@/use-cases/user.use-cases")
const security = await import("@/lib/security")
prisma = prismaModule.prisma
createUserUseCase = userUseCases.createUserUseCase
updateUserUseCase = userUseCases.updateUserUseCase
setUserActiveUseCase = userUseCases.setUserActiveUseCase
resetUserPasswordUseCase = userUseCases.resetUserPasswordUseCase
verifyPassword = security.verifyPassword
})
beforeEach(async () => {
await resetIntegrationTestDatabase(prisma)
})
afterAll(async () => {
await prisma?.$disconnect()
await stopIntegrationTestDatabase()
})
describe("user use-cases", () => {
it("creates a user with a hashed password", async () => {
const result = await createUserUseCase({
username: "new-user",
name: "New User",
email: "new-user@example.test",
password: "secure-password",
role: "STAFF",
isActive: true,
})
expect(result).toEqual({ success: true })
const user = await prisma.user.findUniqueOrThrow({
where: { username: "new-user" },
})
expect(user).toMatchObject({
username: "new-user",
name: "New User",
email: "new-user@example.test",
role: "STAFF",
isActive: true,
})
expect(user.password).not.toBe("secure-password")
await expect(
verifyPassword("secure-password", user.password),
).resolves.toBe(true)
})
it("rejects duplicate usernames and duplicate emails", async () => {
await createTestUser(prisma, {
username: "existing-user",
email: "existing@example.test",
})
await expect(
createUserUseCase({
username: "existing-user",
name: "Duplicate Username",
email: "unique@example.test",
password: "secure-password",
role: "STAFF",
isActive: true,
}),
).resolves.toEqual({
success: false,
errors: { username: ["Username already exists"] },
})
await expect(
createUserUseCase({
username: "unique-user",
name: "Duplicate Email",
email: "existing@example.test",
password: "secure-password",
role: "STAFF",
isActive: true,
}),
).resolves.toEqual({
success: false,
errors: { email: ["Email already exists"] },
})
await expect(prisma.user.count()).resolves.toBe(1)
await expect(
prisma.user.findUniqueOrThrow({ where: { username: "existing-user" } }),
).resolves.toMatchObject({ email: "existing@example.test" })
})
it("updates a user while preserving uniqueness constraints", async () => {
const actor = await createTestUser(prisma, { role: "ADMIN" })
const user = await createTestUser(prisma, {
username: "editable-user",
email: "editable@example.test",
role: "STAFF",
})
const other = await createTestUser(prisma, {
username: "other-user",
email: "other@example.test",
role: "STAFF",
})
await expect(
updateUserUseCase({
actorId: actor.id,
id: user.id,
username: "edited-user",
name: "Edited User",
email: "edited@example.test",
role: "MANAGER",
isActive: true,
}),
).resolves.toEqual({ success: true })
await expect(
prisma.user.findUniqueOrThrow({ where: { id: user.id } }),
).resolves.toMatchObject({
username: "edited-user",
name: "Edited User",
email: "edited@example.test",
role: "MANAGER",
isActive: true,
})
await expect(
updateUserUseCase({
actorId: actor.id,
id: user.id,
username: other.username,
name: "Edited User",
email: "another-email@example.test",
role: "MANAGER",
isActive: true,
}),
).resolves.toEqual({
success: false,
errors: { username: ["Username already exists"] },
})
await expect(
prisma.user.findUniqueOrThrow({ where: { id: user.id } }),
).resolves.toMatchObject({
username: "edited-user",
email: "edited@example.test",
})
})
it("prevents an administrator from removing their own administrator access", async () => {
const admin = await createTestUser(prisma, { role: "ADMIN" })
await expect(
updateUserUseCase({
actorId: admin.id,
id: admin.id,
username: admin.username,
name: admin.name,
email: admin.email,
role: "STAFF",
isActive: true,
}),
).resolves.toEqual({
success: false,
errors: { id: ["You cannot remove your own administrator access"] },
})
await expect(
prisma.user.findUniqueOrThrow({ where: { id: admin.id } }),
).resolves.toMatchObject({ role: "ADMIN", isActive: true })
})
it("protects the last active administrator but allows deactivation when another active admin exists", async () => {
const firstAdmin = await createTestUser(prisma, {
username: "first-admin",
email: "first-admin@example.test",
role: "ADMIN",
})
const staffActor = await createTestUser(prisma, {
username: "staff-actor",
email: "staff-actor@example.test",
role: "STAFF",
})
await expect(
setUserActiveUseCase({
actorId: staffActor.id,
id: firstAdmin.id,
isActive: false,
}),
).resolves.toEqual({
success: false,
errors: {
id: ["Cannot remove access from the last active administrator"],
},
})
const secondAdmin = await createTestUser(prisma, {
username: "second-admin",
email: "second-admin@example.test",
role: "ADMIN",
})
await expect(
setUserActiveUseCase({
actorId: secondAdmin.id,
id: firstAdmin.id,
isActive: false,
}),
).resolves.toEqual({ success: true })
await expect(
prisma.user.findUniqueOrThrow({ where: { id: firstAdmin.id } }),
).resolves.toMatchObject({ isActive: false })
})
it("prevents self-deactivation", async () => {
const admin = await createTestUser(prisma, { role: "ADMIN" })
await createTestUser(prisma, { role: "ADMIN" })
await expect(
setUserActiveUseCase({
actorId: admin.id,
id: admin.id,
isActive: false,
}),
).resolves.toEqual({
success: false,
errors: { id: ["You cannot deactivate your own user"] },
})
await expect(
prisma.user.findUniqueOrThrow({ where: { id: admin.id } }),
).resolves.toMatchObject({ isActive: true })
})
it("resets a user password and rejects missing users", async () => {
const user = await createTestUser(prisma, {
username: "password-user",
email: "password-user@example.test",
role: "STAFF",
})
await expect(
resetUserPasswordUseCase({
id: user.id,
password: "new-secure-password",
}),
).resolves.toEqual({ success: true })
const updatedUser = await prisma.user.findUniqueOrThrow({
where: { id: user.id },
})
expect(updatedUser.password).not.toBe(user.password)
await expect(
verifyPassword("new-secure-password", updatedUser.password),
).resolves.toBe(true)
await expect(
resetUserPasswordUseCase({
id: "00000000-0000-0000-0000-000000000000",
password: "new-secure-password",
}),
).resolves.toEqual({
success: false,
errors: { id: ["User not found"] },
})
})
})