263 lines
7.3 KiB
TypeScript
263 lines
7.3 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({
|
|
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: { email: "new-user@example.test" },
|
|
})
|
|
|
|
expect(user).toMatchObject({
|
|
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 emails", async () => {
|
|
await createTestUser(prisma, {
|
|
email: "existing@example.test",
|
|
})
|
|
|
|
await expect(
|
|
createUserUseCase({
|
|
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: { email: "existing@example.test" },
|
|
}),
|
|
).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, {
|
|
email: "editable@example.test",
|
|
role: "STAFF",
|
|
})
|
|
const other = await createTestUser(prisma, {
|
|
email: "other@example.test",
|
|
role: "STAFF",
|
|
})
|
|
|
|
await expect(
|
|
updateUserUseCase({
|
|
actorId: actor.id,
|
|
id: user.id,
|
|
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({
|
|
name: "Edited User",
|
|
email: "edited@example.test",
|
|
role: "MANAGER",
|
|
isActive: true,
|
|
})
|
|
|
|
await expect(
|
|
updateUserUseCase({
|
|
actorId: actor.id,
|
|
id: user.id,
|
|
name: "Edited User",
|
|
email: other.email,
|
|
role: "MANAGER",
|
|
isActive: true,
|
|
}),
|
|
).resolves.toEqual({
|
|
success: false,
|
|
errors: { email: ["Email already exists"] },
|
|
})
|
|
|
|
await expect(
|
|
prisma.user.findUniqueOrThrow({ where: { id: user.id } }),
|
|
).resolves.toMatchObject({
|
|
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,
|
|
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, {
|
|
email: "first-admin@example.test",
|
|
role: "ADMIN",
|
|
})
|
|
const staffActor = await createTestUser(prisma, {
|
|
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, {
|
|
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, {
|
|
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"] },
|
|
})
|
|
})
|
|
})
|