import { beforeEach, describe, expect, it } from "bun:test"; import { changePassword, createApiKey, createSession, createUser, deleteApiKey, deleteSession, getSession, getUserCount, listApiKeys, verifyApiKey, verifyPassword, } from "../../src/server/services/auth.service.ts"; import { createTestDb } from "../helpers/db.ts"; describe("Auth Service", () => { let db: any; beforeEach(async () => { db = await createTestDb(); }); describe("User Management", () => { it("creates a user with hashed password (hash !== plaintext)", async () => { const user = await createUser(db, "admin", "secret123"); expect(user).toBeDefined(); expect(user.id).toBeGreaterThan(0); expect(user.username).toBe("admin"); expect(user.passwordHash).not.toBe("secret123"); expect(user.passwordHash.length).toBeGreaterThan(0); }); it("verifies correct password returns user", async () => { await createUser(db, "admin", "secret123"); const user = await verifyPassword(db, "admin", "secret123"); expect(user).not.toBeNull(); expect(user!.username).toBe("admin"); }); it("rejects incorrect password returns null", async () => { await createUser(db, "admin", "secret123"); const user = await verifyPassword(db, "admin", "wrongpassword"); expect(user).toBeNull(); }); it("getUserCount returns 0 then 1", async () => { const countBefore = await getUserCount(db); expect(countBefore).toBe(0); await createUser(db, "admin", "secret123"); const countAfter = await getUserCount(db); expect(countAfter).toBe(1); }); it("changes password successfully", async () => { await createUser(db, "admin", "oldpass"); const changed = await changePassword(db, "admin", "oldpass", "newpass"); expect(changed).toBe(true); // Verify new password works const user = await verifyPassword(db, "admin", "newpass"); expect(user).not.toBeNull(); // Verify old password no longer works const oldAttempt = await verifyPassword(db, "admin", "oldpass"); expect(oldAttempt).toBeNull(); }); it("rejects password change with wrong current password", async () => { await createUser(db, "admin", "secret123"); const changed = await changePassword( db, "admin", "wrongcurrent", "newpass", ); expect(changed).toBe(false); }); }); describe("Session Management", () => { it("creates and retrieves a session (id length is 64 hex chars)", async () => { const user = await createUser(db, "admin", "secret123"); const session = await createSession(db, user.id); expect(session).toBeDefined(); expect(session.id).toHaveLength(64); expect(session.userId).toBe(user.id); expect(session.expiresAt).toBeInstanceOf(Date); const retrieved = await getSession(db, session.id); expect(retrieved).not.toBeNull(); expect(retrieved!.id).toBe(session.id); }); it("returns null for expired session (expiryDays = -1)", async () => { const user = await createUser(db, "admin", "secret123"); const session = await createSession(db, user.id, -1); const retrieved = await getSession(db, session.id); expect(retrieved).toBeNull(); }); it("deletes a session", async () => { const user = await createUser(db, "admin", "secret123"); const session = await createSession(db, user.id); await deleteSession(db, session.id); const retrieved = await getSession(db, session.id); expect(retrieved).toBeNull(); }); }); describe("API Key Management", () => { it("creates key and returns raw key once (length > 16, prefix matches first 8 chars)", async () => { const result = await createApiKey(db, "test-key"); expect(result).toBeDefined(); expect(result.rawKey).toBeDefined(); expect(result.rawKey.length).toBeGreaterThan(16); expect(result.keyPrefix).toBe(result.rawKey.slice(0, 8)); expect(result.name).toBe("test-key"); }); it("verifies valid key returns true", async () => { const result = await createApiKey(db, "test-key"); const isValid = await verifyApiKey(db, result.rawKey); expect(isValid).toBe(true); }); it("rejects invalid key returns false", async () => { await createApiKey(db, "test-key"); const isValid = await verifyApiKey(db, "invalidkey12345678"); expect(isValid).toBe(false); }); it("deletes key so it is no longer valid", async () => { const result = await createApiKey(db, "test-key"); await deleteApiKey(db, result.id); const isValid = await verifyApiKey(db, result.rawKey); expect(isValid).toBe(false); }); it("listApiKeys returns keys without hashes", async () => { await createApiKey(db, "key-one"); await createApiKey(db, "key-two"); const keys = await listApiKeys(db); expect(keys).toHaveLength(2); expect(keys[0].name).toBe("key-one"); expect(keys[1].name).toBe("key-two"); // Ensure no hash is exposed for (const key of keys) { expect(key).toHaveProperty("id"); expect(key).toHaveProperty("name"); expect(key).toHaveProperty("keyPrefix"); expect(key).toHaveProperty("createdAt"); expect(key).not.toHaveProperty("keyHash"); } }); }); });