import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import express from "express"; import request from "supertest"; // Mock all heavy dependencies vi.mock("../services/browser.js", () => ({ renderPdf: vi.fn(), renderUrlPdf: vi.fn(), initBrowser: vi.fn(), closeBrowser: vi.fn(), })); vi.mock("../services/keys.js", () => ({ loadKeys: vi.fn(), getAllKeys: vi.fn().mockReturnValue([]), isValidKey: vi.fn(), getKeyInfo: vi.fn(), isProKey: vi.fn(), keyStore: new Map(), })); vi.mock("../services/db.js", () => ({ initDatabase: vi.fn(), pool: { query: vi.fn(), end: vi.fn() }, queryWithRetry: vi.fn(), connectWithRetry: vi.fn(), cleanupStaleData: vi.fn(), })); vi.mock("../services/verification.js", () => ({ verifyToken: vi.fn(), loadVerifications: vi.fn(), })); vi.mock("../middleware/usage.js", () => ({ usageMiddleware: (_req: any, _res: any, next: any) => next(), loadUsageData: vi.fn(), getUsageStats: vi.fn().mockReturnValue({ "test-key": { count: 42, month: "2026-03" } }), getUsageForKey: vi.fn().mockReturnValue({ count: 10, monthKey: "2026-03" }), flushDirtyEntries: vi.fn(), })); vi.mock("../middleware/pdfRateLimit.js", () => ({ pdfRateLimitMiddleware: (_req: any, _res: any, next: any) => next(), getConcurrencyStats: vi.fn().mockReturnValue({ active: 2, queued: 0, maxConcurrent: 10 }), })); const TEST_KEY = "test-key-123"; const ADMIN_KEY = "admin-key-456"; describe("Admin integration tests", () => { let app: express.Express; let originalAdminKey: string | undefined; beforeEach(async () => { vi.clearAllMocks(); vi.resetModules(); originalAdminKey = process.env.ADMIN_API_KEY; process.env.ADMIN_API_KEY = ADMIN_KEY; // Set up key mocks const keys = await import("../services/keys.js"); vi.mocked(keys.isValidKey).mockImplementation((k: string) => k === TEST_KEY || k === ADMIN_KEY); vi.mocked(keys.getKeyInfo).mockImplementation((k: string) => { if (k === TEST_KEY) return { key: TEST_KEY, tier: "free" as const, email: "test@test.com", createdAt: "2026-01-01" }; if (k === ADMIN_KEY) return { key: ADMIN_KEY, tier: "pro" as const, email: "admin@test.com", createdAt: "2026-01-01" }; return undefined; }); vi.mocked(keys.isProKey).mockImplementation((k: string) => k === ADMIN_KEY); const { adminRouter } = await import("../routes/admin.js"); app = express(); app.use(express.json()); app.use(adminRouter); }); afterEach(() => { if (originalAdminKey !== undefined) { process.env.ADMIN_API_KEY = originalAdminKey; } else { delete process.env.ADMIN_API_KEY; } }); describe("GET /v1/usage/me", () => { it("returns 401 without auth", async () => { const res = await request(app).get("/v1/usage/me"); expect(res.status).toBe(401); }); it("returns 403 with invalid key", async () => { const res = await request(app).get("/v1/usage/me").set("X-API-Key", "bad-key"); expect(res.status).toBe(403); }); it("returns usage stats with Bearer auth", async () => { const res = await request(app).get("/v1/usage/me").set("Authorization", `Bearer ${TEST_KEY}`); expect(res.status).toBe(200); expect(res.body).toMatchObject({ used: 10, limit: 100, plan: "demo", month: "2026-03", }); }); it("returns usage stats with X-API-Key auth", async () => { const res = await request(app).get("/v1/usage/me").set("X-API-Key", TEST_KEY); expect(res.status).toBe(200); expect(res.body.plan).toBe("demo"); }); it("returns pro plan for pro key", async () => { const res = await request(app).get("/v1/usage/me").set("X-API-Key", ADMIN_KEY); expect(res.status).toBe(200); expect(res.body).toMatchObject({ plan: "pro", limit: 5000, }); }); }); describe("GET /v1/usage (admin)", () => { it("returns 401 without auth", async () => { const res = await request(app).get("/v1/usage"); expect(res.status).toBe(401); }); it("returns 403 with non-admin key", async () => { const res = await request(app).get("/v1/usage").set("X-API-Key", TEST_KEY); expect(res.status).toBe(403); expect(res.body.error).toBe("Admin access required"); }); it("returns usage stats with admin key", async () => { const res = await request(app).get("/v1/usage").set("X-API-Key", ADMIN_KEY); expect(res.status).toBe(200); expect(res.body).toHaveProperty("test-key"); }); it("returns 503 when ADMIN_API_KEY not set", async () => { delete process.env.ADMIN_API_KEY; const res = await request(app).get("/v1/usage").set("X-API-Key", ADMIN_KEY); expect(res.status).toBe(503); expect(res.body.error).toBe("Admin access not configured"); }); }); describe("GET /v1/concurrency (admin)", () => { it("returns 403 with non-admin key", async () => { const res = await request(app).get("/v1/concurrency").set("X-API-Key", TEST_KEY); expect(res.status).toBe(403); }); it("returns concurrency stats with admin key", async () => { const res = await request(app).get("/v1/concurrency").set("X-API-Key", ADMIN_KEY); expect(res.status).toBe(200); expect(res.body).toMatchObject({ active: 2, queued: 0, maxConcurrent: 10 }); }); }); describe("POST /admin/cleanup (admin)", () => { it("returns 403 with non-admin key", async () => { const res = await request(app).post("/admin/cleanup").set("X-API-Key", TEST_KEY); expect(res.status).toBe(403); }); it("returns cleanup results with admin key", async () => { const { cleanupStaleData } = await import("../services/db.js"); vi.mocked(cleanupStaleData).mockResolvedValue({ deletedRows: 5 } as any); const res = await request(app).post("/admin/cleanup").set("X-API-Key", ADMIN_KEY); expect(res.status).toBe(200); expect(res.body).toMatchObject({ status: "ok", cleaned: { deletedRows: 5 } }); }); it("returns 500 when cleanup fails", async () => { const { cleanupStaleData } = await import("../services/db.js"); vi.mocked(cleanupStaleData).mockRejectedValue(new Error("DB error")); const res = await request(app).post("/admin/cleanup").set("X-API-Key", ADMIN_KEY); expect(res.status).toBe(500); expect(res.body.error).toBe("Cleanup failed"); }); }); });