import { beforeEach, describe, expect, it, vi } from "vitest" const mocks = vi.hoisted(() => ({ getPasswordHash: vi.fn(), })) vi.mock("@/lib/security", () => ({ getPasswordHash: mocks.getPasswordHash, })) vi.mock("../../../src/lib/prisma", () => ({ default: {}, })) import { bootstrapAdmin } from "../../../prisma/bootstrap-admin" describe("bootstrapAdmin", () => { beforeEach(() => { vi.clearAllMocks() process.env.ADMIN_BOOTSTRAP_ENABLED = "true" process.env.ADMIN_EMAIL = "Admin@Example.Test" process.env.ADMIN_NAME = "E2E Admin" process.env.ADMIN_PASSWORD = "admin-password" vi.stubEnv("NODE_ENV", "development") mocks.getPasswordHash.mockResolvedValue("hashed-password") }) it("creates an active user and links a person on first run", async () => { const userFindUnique = vi.fn().mockResolvedValue(null) const userCreate = vi.fn().mockResolvedValue({ id: "user-1", person: null, }) const userUpdate = vi.fn() const personUpsert = vi.fn().mockResolvedValue({ id: "person-1" }) const client = { user: { findUnique: userFindUnique, create: userCreate, update: userUpdate, }, person: { upsert: personUpsert, }, } await bootstrapAdmin(client as never) expect(userFindUnique).toHaveBeenCalledWith( expect.objectContaining({ where: { emailNormalized: "admin@example.test", }, }), ) expect(userCreate).toHaveBeenCalledWith( expect.objectContaining({ data: expect.objectContaining({ emailNormalized: "admin@example.test", passwordHash: "hashed-password", status: "ACTIVE", }), }), ) expect(userUpdate).not.toHaveBeenCalled() expect(personUpsert).toHaveBeenCalledWith( expect.objectContaining({ where: { userId: "user-1", }, create: expect.objectContaining({ firstName: "E2E", lastName: "Admin", email: "Admin@Example.Test", }), update: expect.objectContaining({ firstName: "E2E", lastName: "Admin", email: "Admin@Example.Test", }), }), ) }) it("is idempotent when the admin already has a linked person", async () => { const userFindUnique = vi.fn().mockResolvedValue({ id: "user-1", passwordHash: "existing-hash", activatedAt: new Date("2024-01-01T00:00:00.000Z"), person: { id: "person-1" }, }) const userCreate = vi.fn() const userUpdate = vi.fn().mockResolvedValue({ id: "user-1", person: { id: "person-1" }, }) const personUpsert = vi.fn() const client = { user: { findUnique: userFindUnique, create: userCreate, update: userUpdate, }, person: { upsert: personUpsert, }, } await bootstrapAdmin(client as never) expect(mocks.getPasswordHash).not.toHaveBeenCalled() expect(userCreate).not.toHaveBeenCalled() expect(userUpdate).toHaveBeenCalledWith( expect.objectContaining({ data: expect.not.objectContaining({ passwordHash: expect.any(String), activatedAt: expect.any(Date), passwordChangedAt: expect.any(Date), }), }), ) expect(personUpsert).not.toHaveBeenCalled() }) it("links a missing person without rehashing an existing admin password", async () => { const userFindUnique = vi.fn().mockResolvedValue({ id: "user-1", passwordHash: "existing-hash", activatedAt: new Date("2024-01-01T00:00:00.000Z"), person: null, }) const userCreate = vi.fn() const userUpdate = vi.fn().mockResolvedValue({ id: "user-1", person: null, }) const personUpsert = vi.fn().mockResolvedValue({ id: "person-1" }) const client = { user: { findUnique: userFindUnique, create: userCreate, update: userUpdate, }, person: { upsert: personUpsert, }, } await bootstrapAdmin(client as never) expect(mocks.getPasswordHash).not.toHaveBeenCalled() expect(userCreate).not.toHaveBeenCalled() expect(personUpsert).toHaveBeenCalledWith( expect.objectContaining({ where: { userId: "user-1", }, }), ) }) })