// @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<T>(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)");
});
});