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 () => {}, })); // Mock verifyAccessToken from oauth.service mock.module("../../src/server/services/oauth.service", () => ({ verifyAccessToken: mock(() => Promise.resolve(false)), })); // Import routes AFTER mocks const { authRoutes } = await import("../../src/server/routes/auth.ts"); function createTestApp() { const db = createTestDb(); const app = new Hono<{ Variables: { db?: any } }>(); app.use("*", async (c, next) => { c.set("db", db); await next(); }); app.route("/api/auth", authRoutes); return { app, db }; } describe("Auth Routes", () => { let app: Hono; let db: ReturnType; beforeEach(() => { const testApp = createTestApp(); app = testApp.app; db = testApp.db; 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(body.user.id).toBe("logto-user-abc123"); 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 empty key list with API key auth", async () => { const key = await createApiKey(db, "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(); // Contains at least the key we created for auth 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, "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, "auth-key"); const targetKey = await createApiKey(db, "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); }); it("rejects without auth", async () => { const res = await app.request("/api/auth/keys/1", { method: "DELETE", }); expect(res.status).toBe(401); }); }); });