import { beforeEach, describe, expect, it, mock } from "bun:test"; import { Hono } from "hono"; import { createApiKey } from "../../src/server/services/auth.service.ts"; import { createTestDb } from "../helpers/db.ts"; // Mock @hono/oidc-auth const mockGetAuth = mock(() => null as any); mock.module("@hono/oidc-auth", () => ({ getAuth: mockGetAuth, oidcAuthMiddleware: () => async (_c: any, next: any) => next(), processOAuthCallback: async (c: any) => c.json({ ok: true }), revokeSession: async () => {}, })); // Import routes AFTER mocks const { authRoutes } = await import("../../src/server/routes/auth.ts"); async function createTestApp() { const { db, userId } = await createTestDb(); const app = new Hono<{ Variables: { db?: any; userId?: number } }>(); app.use("*", async (c, next) => { c.set("db", db); c.set("userId", userId); await next(); }); app.route("/api/auth", authRoutes); return { app, db, userId }; } describe("Auth Routes", () => { let app: Hono; let db: Awaited>["db"]; let userId: number; beforeEach(async () => { const testApp = await createTestApp(); app = testApp.app; db = testApp.db; userId = testApp.userId; mockGetAuth.mockReset(); mockGetAuth.mockReturnValue(null); }); describe("GET /api/auth/me", () => { it("returns authenticated false when no OIDC session", async () => { const res = await app.request("/api/auth/me"); expect(res.status).toBe(200); const body = await res.json(); expect(body.user).toBeNull(); expect(body.authenticated).toBe(false); }); it("returns user info when OIDC session exists", async () => { mockGetAuth.mockReturnValue({ sub: "logto-user-abc123", email: "user@example.com", }); const res = await app.request("/api/auth/me"); expect(res.status).toBe(200); const body = await res.json(); expect(body.authenticated).toBe(true); expect(typeof body.user.id).toBe("number"); expect(body.user.email).toBe("user@example.com"); }); }); describe("GET /api/auth/keys", () => { it("returns 401 without authentication", async () => { const res = await app.request("/api/auth/keys"); expect(res.status).toBe(401); }); it("returns key list with API key auth", async () => { const key = await createApiKey(db, userId, "test-key"); const res = await app.request("/api/auth/keys", { headers: { "X-API-Key": key.rawKey }, }); expect(res.status).toBe(200); const body = await res.json(); expect(Array.isArray(body)).toBe(true); expect(body.length).toBeGreaterThanOrEqual(1); }); }); describe("POST /api/auth/keys", () => { it("creates a new API key when authenticated", async () => { const authKey = await createApiKey(db, userId, "auth-key"); const res = await app.request("/api/auth/keys", { method: "POST", headers: { "Content-Type": "application/json", "X-API-Key": authKey.rawKey, }, body: JSON.stringify({ name: "my-new-key" }), }); expect(res.status).toBe(201); const body = await res.json(); expect(body.name).toBe("my-new-key"); expect(body.key).toBeDefined(); expect(body.prefix).toBeDefined(); }); it("rejects without auth", async () => { const res = await app.request("/api/auth/keys", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: "my-key" }), }); expect(res.status).toBe(401); }); }); describe("DELETE /api/auth/keys/:id", () => { it("deletes an API key when authenticated", async () => { const authKey = await createApiKey(db, userId, "auth-key"); const targetKey = await createApiKey(db, userId, "to-delete"); const res = await app.request(`/api/auth/keys/${targetKey.id}`, { method: "DELETE", headers: { "X-API-Key": authKey.rawKey }, }); expect(res.status).toBe(200); const body = await res.json(); expect(body.ok).toBe(true); }); }); });