feat: add unified Person+User creation backend
This commit is contained in:
@@ -0,0 +1,261 @@
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest"
|
||||
import type { PrismaClient } from "@/generated/prisma/client"
|
||||
import { createTestPerson, createTestUser } from "../helpers/factories"
|
||||
import {
|
||||
resetIntegrationTestDatabase,
|
||||
startIntegrationTestDatabase,
|
||||
stopIntegrationTestDatabase,
|
||||
} from "../helpers/test-db"
|
||||
|
||||
let prisma: PrismaClient
|
||||
let createPersonUserUseCase: typeof import("@/use-cases/person.use-cases").createPersonUserUseCase
|
||||
|
||||
beforeAll(async () => {
|
||||
await startIntegrationTestDatabase()
|
||||
|
||||
const prismaModule = await import("@/lib/prisma")
|
||||
const personUseCases = await import("@/use-cases/person.use-cases")
|
||||
|
||||
prisma = prismaModule.prisma
|
||||
createPersonUserUseCase = personUseCases.createPersonUserUseCase
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
await resetIntegrationTestDatabase(prisma)
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await prisma?.$disconnect()
|
||||
await stopIntegrationTestDatabase()
|
||||
})
|
||||
|
||||
describe("createPersonUserUseCase", () => {
|
||||
describe("NO_USER role (person-only creation)", () => {
|
||||
it("creates a Person without a User record when role is NO_USER", async () => {
|
||||
const result = await createPersonUserUseCase({
|
||||
firstName: "John",
|
||||
lastName: "Doe",
|
||||
department: "IT",
|
||||
email: "john@example.test",
|
||||
phone: null,
|
||||
role: "NO_USER",
|
||||
isActive: true,
|
||||
})
|
||||
|
||||
expect(result).toEqual({ success: true })
|
||||
|
||||
const person = await prisma.person.findFirstOrThrow({
|
||||
where: { firstName: "John", lastName: "Doe" },
|
||||
})
|
||||
expect(person).toMatchObject({
|
||||
firstName: "John",
|
||||
lastName: "Doe",
|
||||
department: "IT",
|
||||
email: "john@example.test",
|
||||
phone: null,
|
||||
userId: null,
|
||||
})
|
||||
|
||||
// No User record created
|
||||
await expect(
|
||||
prisma.user.findUnique({ where: { email: "john@example.test" } }),
|
||||
).resolves.toBeNull()
|
||||
})
|
||||
|
||||
it("creates a Person with null email when not providing email and role is NO_USER", async () => {
|
||||
const result = await createPersonUserUseCase({
|
||||
firstName: "Jane",
|
||||
lastName: "Smith",
|
||||
department: "ENGINEERING",
|
||||
email: "jane-noemail@example.test",
|
||||
phone: "555-1234",
|
||||
role: "NO_USER",
|
||||
isActive: true,
|
||||
})
|
||||
|
||||
expect(result).toEqual({ success: true })
|
||||
|
||||
const person = await prisma.person.findFirstOrThrow({
|
||||
where: { firstName: "Jane", lastName: "Smith" },
|
||||
})
|
||||
expect(person.phone).toBe("555-1234")
|
||||
})
|
||||
})
|
||||
|
||||
describe("real role (person + user creation)", () => {
|
||||
it("creates Person and User with linked userId when role is ADMIN", async () => {
|
||||
const result = await createPersonUserUseCase({
|
||||
firstName: "Admin",
|
||||
lastName: "User",
|
||||
department: "IT",
|
||||
email: "admin@example.test",
|
||||
phone: null,
|
||||
role: "ADMIN",
|
||||
password: "secure-password",
|
||||
isActive: true,
|
||||
})
|
||||
|
||||
expect(result).toEqual({ success: true })
|
||||
|
||||
const person = await prisma.person.findFirstOrThrow({
|
||||
where: { firstName: "Admin", lastName: "User" },
|
||||
})
|
||||
expect(person).toMatchObject({
|
||||
firstName: "Admin",
|
||||
lastName: "User",
|
||||
department: "IT",
|
||||
email: "admin@example.test",
|
||||
})
|
||||
|
||||
// User record should exist with derived name
|
||||
expect(person.userId).not.toBeNull()
|
||||
|
||||
const user = await prisma.user.findUniqueOrThrow({
|
||||
where: { id: person.userId! },
|
||||
})
|
||||
expect(user).toMatchObject({
|
||||
name: "Admin User",
|
||||
email: "admin@example.test",
|
||||
role: "ADMIN",
|
||||
isActive: true,
|
||||
})
|
||||
})
|
||||
|
||||
it("creates Person and User for all real roles (MANAGER, STAFF, VIEWER)", async () => {
|
||||
const roles = ["MANAGER", "STAFF", "VIEWER"] as const
|
||||
|
||||
for (const role of roles) {
|
||||
const suffix = role.toLowerCase()
|
||||
const result = await createPersonUserUseCase({
|
||||
firstName: "Person",
|
||||
lastName: suffix,
|
||||
department: "IT",
|
||||
email: `${suffix}@example.test`,
|
||||
phone: null,
|
||||
role,
|
||||
password: "secure-password",
|
||||
isActive: true,
|
||||
})
|
||||
|
||||
expect(result).toEqual({ success: true })
|
||||
|
||||
const person = await prisma.person.findFirstOrThrow({
|
||||
where: { lastName: suffix },
|
||||
})
|
||||
expect(person.userId).not.toBeNull()
|
||||
|
||||
const user = await prisma.user.findUniqueOrThrow({
|
||||
where: { id: person.userId! },
|
||||
})
|
||||
expect(user.role).toBe(role)
|
||||
expect(user.name).toBe(`Person ${suffix}`)
|
||||
}
|
||||
})
|
||||
|
||||
it("derives User.name from firstName + lastName", async () => {
|
||||
await createPersonUserUseCase({
|
||||
firstName: "Maria",
|
||||
lastName: "Garcia",
|
||||
department: "SALES",
|
||||
email: "maria@example.test",
|
||||
phone: null,
|
||||
role: "STAFF",
|
||||
password: "secure-password",
|
||||
isActive: true,
|
||||
})
|
||||
|
||||
const user = await prisma.user.findUniqueOrThrow({
|
||||
where: { email: "maria@example.test" },
|
||||
})
|
||||
expect(user.name).toBe("Maria Garcia")
|
||||
})
|
||||
|
||||
it("hashes the password when creating a User", async () => {
|
||||
await createPersonUserUseCase({
|
||||
firstName: "Hash",
|
||||
lastName: "Test",
|
||||
department: "IT",
|
||||
email: "hash-test@example.test",
|
||||
phone: null,
|
||||
role: "STAFF",
|
||||
password: "plaintext-password",
|
||||
isActive: true,
|
||||
})
|
||||
|
||||
const user = await prisma.user.findUniqueOrThrow({
|
||||
where: { email: "hash-test@example.test" },
|
||||
})
|
||||
expect(user.password).not.toBe("plaintext-password")
|
||||
|
||||
const { verifyPassword } = await import("@/lib/security")
|
||||
await expect(
|
||||
verifyPassword("plaintext-password", user.password),
|
||||
).resolves.toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("cross-table email uniqueness", () => {
|
||||
it("rejects submission when email already exists in Person table", async () => {
|
||||
await createTestPerson(prisma, { email: "existing-person@example.test" })
|
||||
|
||||
const result = await createPersonUserUseCase({
|
||||
firstName: "Duplicate",
|
||||
lastName: "Person",
|
||||
department: "IT",
|
||||
email: "existing-person@example.test",
|
||||
phone: null,
|
||||
role: "NO_USER",
|
||||
isActive: true,
|
||||
})
|
||||
|
||||
expect(result).toEqual({
|
||||
success: false,
|
||||
errors: { email: ["Email already exists"] },
|
||||
})
|
||||
|
||||
await expect(prisma.person.count()).resolves.toBe(1)
|
||||
})
|
||||
|
||||
it("rejects submission when email already exists in User table", async () => {
|
||||
await createTestUser(prisma, { email: "existing-user@example.test" })
|
||||
|
||||
const result = await createPersonUserUseCase({
|
||||
firstName: "Duplicate",
|
||||
lastName: "User",
|
||||
department: "IT",
|
||||
email: "existing-user@example.test",
|
||||
phone: null,
|
||||
role: "STAFF",
|
||||
password: "secure-password",
|
||||
isActive: true,
|
||||
})
|
||||
|
||||
expect(result).toEqual({
|
||||
success: false,
|
||||
errors: { email: ["Email already exists"] },
|
||||
})
|
||||
|
||||
// No new Person or User was created
|
||||
await expect(prisma.person.count()).resolves.toBe(0)
|
||||
await expect(prisma.user.count()).resolves.toBe(1)
|
||||
})
|
||||
|
||||
it("accepts submission when email is unique across both tables", async () => {
|
||||
// Create a Person and a User with different emails
|
||||
await createTestPerson(prisma, { email: "person@example.test" })
|
||||
await createTestUser(prisma, { email: "user@example.test" })
|
||||
|
||||
const result = await createPersonUserUseCase({
|
||||
firstName: "New",
|
||||
lastName: "Person",
|
||||
department: "IT",
|
||||
email: "new@example.test",
|
||||
phone: null,
|
||||
role: "NO_USER",
|
||||
isActive: true,
|
||||
})
|
||||
|
||||
expect(result).toEqual({ success: true })
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user