254 lines
7.3 KiB
TypeScript
254 lines
7.3 KiB
TypeScript
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"] },
|
|
})
|
|
})
|
|
})
|
|
})
|