docfast/src/__tests__/admin-integration.test.ts
OpenClaw Subagent db35a0e521
All checks were successful
Build & Deploy to Staging / Build & Deploy to Staging (push) Successful in 19m33s
test: add integration tests for admin.ts and pages.ts routes
- admin-integration.test.ts: 14 tests covering /v1/usage/me, /v1/usage,
  /v1/concurrency, /admin/cleanup, auth middleware (401/403/503)
- pages-integration.test.ts: 10 tests covering favicon, openapi.json,
  docs, landing page, static pages, status, /api

Both files now at 100% function/statement/branch/line coverage.
All 696 tests pass.
2026-03-12 17:10:05 +01:00

187 lines
6.3 KiB
TypeScript

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");
});
});
});