// @vitest-environment node import { describe, expect, it } from "vitest"; import { findUserByEmail, updateLastModelId, upsertExternalUser, } from "@/lib/db/users"; const validProfile = { id: "42", login: "alice", fullName: "Alice Smith", position: "Engineer", email: "alice@example.com", lastName: "Smith", firstName: "Alice", middleName: "Jane", roles: ["admin", "employee"], createdAt: "2026-07-01T12:00:00.000Z", updatedAt: "2026-07-02T12:00:00.000Z", }; class RecordingDatabase { queries: Array<{ text: string; values?: readonly unknown[] }> = []; resultRows: unknown[] = []; async query(text: string, values?: readonly unknown[]) { this.queries.push({ text, values }); return { rows: this.resultRows as T[] }; } } describe("user repository", () => { it("normalizes emails and persists the last selected model", async () => { const database = new RecordingDatabase(); await findUserByEmail(database, " DEMO@AI-CONTROL.LOCAL "); await updateLastModelId(database, "user-id", "qwen"); expect(database.queries[0]).toMatchObject({ values: ["demo@ai-control.local"], }); expect(database.queries[1]).toMatchObject({ values: ["qwen", "user-id"], }); }); it("upserts by external ID while preserving model preference", async () => { const database = new RecordingDatabase(); database.resultRows = [ { id: "00000000-0000-4000-8000-000000000042", external_user_id: "42", email: "alice@example.com", login: "alice", full_name: "Alice Smith", position: "Engineer", first_name: "Alice", last_name: "Smith", middle_name: "Jane", roles: ["admin", "employee"], password_hash: null, last_model_id: "qwen", created_at: "2026-07-01T12:00:00.000Z", updated_at: "2026-07-02T12:00:00.000Z", }, ]; await expect(upsertExternalUser(database, validProfile)).resolves.toMatchObject({ id: "00000000-0000-4000-8000-000000000042", externalUserId: "42", roles: ["admin", "employee"], lastModelId: "qwen", }); expect(database.queries[0]).toMatchObject({ values: [ "42", "alice@example.com", "alice", "Alice Smith", "Engineer", "Alice", "Smith", "Jane", JSON.stringify(["admin", "employee"]), ], }); expect(database.queries[0]?.text).toContain("ON CONFLICT (external_user_id)"); expect(database.queries[0]?.text).not.toContain("last_model_id = EXCLUDED"); }); it("atomically adopts a same-email legacy user without replacing its UUID or model", async () => { const database = new RecordingDatabase(); database.resultRows = [ { id: "00000000-0000-4000-8000-000000000099", external_user_id: "42", email: "alice@example.com", login: "alice", full_name: "Alice Smith", position: "Engineer", first_name: "Alice", last_name: "Smith", middle_name: "Jane", roles: ["admin", "employee"], password_hash: "legacy-password-hash", last_model_id: "qwen", created_at: "2025-01-01T12:00:00.000Z", updated_at: "2026-07-02T12:00:00.000Z", }, ]; await expect(upsertExternalUser(database, validProfile)).resolves.toMatchObject({ id: "00000000-0000-4000-8000-000000000099", externalUserId: "42", lastModelId: "qwen", createdAt: "2025-01-01T12:00:00.000Z", }); expect(database.queries).toHaveLength(1); expect(database.queries[0]?.text).toContain("WITH adopted AS"); expect(database.queries[0]?.text).toContain("external_user_id IS NULL"); expect(database.queries[0]?.text).toContain("WHERE NOT EXISTS (SELECT 1 FROM adopted)"); }); });