import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest" import { type PrismaClient, UserStatus } from "@/generated/prisma/client" import { normalizeEmail } from "@/lib/email" import { getPasswordHash } from "@/lib/security" import { createTestPerson, createTestUser } from "../helpers/factories" import { resetIntegrationTestDatabase, startIntegrationTestDatabase, stopIntegrationTestDatabase, } from "../helpers/test-db" let prisma: PrismaClient let updatePersonUserUseCase: typeof import("@/use-cases/person.use-cases").updatePersonUserUseCase beforeAll(async () => { await startIntegrationTestDatabase() const prismaModule = await import("@/lib/prisma") const personUseCases = await import("@/use-cases/person.use-cases") prisma = prismaModule.prisma updatePersonUserUseCase = personUseCases.updatePersonUserUseCase }) beforeEach(async () => { await resetIntegrationTestDatabase(prisma) }) afterAll(async () => { await prisma?.$disconnect() await stopIntegrationTestDatabase() }) describe("updatePersonUserUseCase", () => { describe("person-only update", () => { it("updates only the Person when person has no linked User", async () => { const person = await createTestPerson(prisma, { firstName: "Old", lastName: "Name", email: "old@example.test", }) const result = await updatePersonUserUseCase({ id: person.id, firstName: "New", lastName: "Name", department: "IT", email: "new@example.test", phone: "1234", }) expect(result).toEqual({ success: true }) const updated = await prisma.person.findUniqueOrThrow({ where: { id: person.id }, }) expect(updated).toMatchObject({ firstName: "New", lastName: "Name", department: "IT", email: "new@example.test", phone: "1234", userId: null, }) }) it("normalizes empty email to null when person has no User", async () => { const person = await createTestPerson(prisma, { email: null }) const result = await updatePersonUserUseCase({ id: person.id, firstName: "Empty", lastName: "Email", department: "OTHER", email: "", phone: null, }) expect(result).toEqual({ success: true }) const updated = await prisma.person.findUniqueOrThrow({ where: { id: person.id }, }) expect(updated.email).toBeNull() }) }) describe("person+user update", () => { it("updates Person fields and User role/isActive when person has a User linked", async () => { const user = await createTestUser(prisma, { email: "user-update@example.test", name: "Old Name", role: "STAFF", isActive: true, }) const person = await createTestPerson(prisma, { firstName: "Linked", lastName: "Person", email: "user-update@example.test", }) await prisma.person.update({ where: { id: person.id }, data: { userId: user.id }, }) const result = await updatePersonUserUseCase({ id: person.id, firstName: "Linked", lastName: "Person", department: "ENGINEERING", email: "user-update@example.test", phone: null, role: "ADMIN", isActive: false, }) expect(result).toEqual({ success: true }) const updatedPerson = await prisma.person.findUniqueOrThrow({ where: { id: person.id }, include: { user: true }, }) expect(updatedPerson.department).toBe("ENGINEERING") expect(updatedPerson.user).toMatchObject({ id: user.id, role: "ADMIN", status: "DISABLED", }) }) it("resets the User password when password is provided", async () => { const user = await createTestUser(prisma, { email: "pw-reset@example.test", }) const person = await createTestPerson(prisma, { email: "pw-reset@example.test", }) await prisma.person.update({ where: { id: person.id }, data: { userId: user.id }, }) const result = await updatePersonUserUseCase({ id: person.id, firstName: person.firstName, lastName: person.lastName, department: "OTHER", email: "pw-reset@example.test", phone: null, role: "STAFF", isActive: true, password: "new-password-1", }) expect(result).toEqual({ success: true }) const updated = await prisma.user.findUniqueOrThrow({ where: { id: user.id }, }) const { verifyPassword } = await import("@/lib/security") if (!updated.passwordHash) throw new Error("Expected password hash") await expect( verifyPassword("new-password-1", updated.passwordHash), ).resolves.toBe(true) }) it("does not change the password when password is not provided", async () => { const originalHash = await getPasswordHash("original-password-1") const user = await prisma.user.create({ data: { email: "no-pw@example.test", emailNormalized: normalizeEmail("no-pw@example.test"), name: "No PW", passwordHash: originalHash, role: "STAFF", status: UserStatus.ACTIVE, activatedAt: new Date(), passwordChangedAt: new Date(), }, }) const person = await createTestPerson(prisma, { email: "no-pw@example.test", }) await prisma.person.update({ where: { id: person.id }, data: { userId: user.id }, }) const result = await updatePersonUserUseCase({ id: person.id, firstName: person.firstName, lastName: person.lastName, department: "OTHER", email: "no-pw@example.test", phone: null, role: "STAFF", isActive: true, }) expect(result).toEqual({ success: true }) const updated = await prisma.user.findUniqueOrThrow({ where: { id: user.id }, }) const { verifyPassword: verify } = await import("@/lib/security") if (!updated.passwordHash) throw new Error("Expected password hash") await expect( verify("original-password-1", updated.passwordHash), ).resolves.toBe(true) }) }) describe("validation errors", () => { it("returns error when person is not found", async () => { const result = await updatePersonUserUseCase({ id: "00000000-0000-0000-0000-000000000000", firstName: "Ghost", lastName: "Person", department: "OTHER", email: "ghost@example.test", phone: null, }) expect(result.success).toBe(false) if (!result.success) { expect(result.errors.id).toBeDefined() } }) it("rejects duplicate email in Person table", async () => { const person = await createTestPerson(prisma, { email: "mine@example.test", }) await createTestPerson(prisma, { email: "theirs@example.test", }) const result = await updatePersonUserUseCase({ id: person.id, firstName: "Mine", lastName: "Person", department: "OTHER", email: "theirs@example.test", phone: null, }) expect(result).toEqual({ success: false, errors: { email: ["Email already exists"] }, }) }) }) })