import { describe, expect, it, vi } from "vitest"; import type { Db } from "../db/client.js"; import { IdentityConflictError, upsertUserFromIdentity } from "./upsert-user.js"; function createDb(options: { bySub?: Record | null; byEmail?: Record | null; }): Db { const findFirst = vi.fn(async (_args: { where: unknown }) => { // drizzle eq objects aren't introspectable here; alternate by call order. if (findFirst.mock.calls.length === 1) { return options.bySub ?? null; } return options.byEmail ?? null; }); const returningRow = { id: "11111111-1111-4111-8111-111111111111", cognitoSub: "seed-sub-admin", email: "admin@seahavenind.com", name: "Dev Admin", role: "admin" as const, }; return { driver: "postgres", pool: { end: vi.fn(async () => undefined) } as never, db: { query: { users: { findFirst } }, update: vi.fn(() => ({ set: vi.fn(() => ({ where: vi.fn(() => ({ returning: vi.fn(async () => [returningRow]), })), })), })), insert: vi.fn(() => ({ values: vi.fn(() => ({ returning: vi.fn(async () => [returningRow]), })), })), } as never, }; } describe("upsertUserFromIdentity", () => { it("updates an existing row matched by cognito sub", async () => { const handle = createDb({ bySub: { id: "11111111-1111-4111-8111-111111111111", cognitoSub: "seed-sub-admin", email: "admin@seahavenind.com", name: "Old Name", role: "admin", }, }); const user = await upsertUserFromIdentity(handle, { cognitoSub: "seed-sub-admin", email: "admin@seahavenind.com", name: "Dev Admin", role: "admin", }); expect(user.cognitoSub).toBe("seed-sub-admin"); expect(handle.db.update).toHaveBeenCalled(); }); it("refuses to rebind an email owned by a different cognito sub", async () => { const handle = createDb({ bySub: null, byEmail: { id: "11111111-1111-4111-8111-111111111111", cognitoSub: "other-sub", email: "admin@seahavenind.com", name: "Seed Admin", role: "admin", }, }); await expect( upsertUserFromIdentity(handle, { cognitoSub: "attacker-sub", email: "admin@seahavenind.com", name: "Attacker", role: "admin", }), ).rejects.toBeInstanceOf(IdentityConflictError); }); });