import express from "express"; import request from "supertest"; import { beforeEach, describe, expect, it, vi } from "vitest"; const mockAccessService = vi.hoisted(() => ({ isInstanceAdmin: vi.fn(), hasPermission: vi.fn(), canUser: vi.fn(), })); const mockAgentService = vi.hoisted(() => ({ getById: vi.fn(), })); const mockBoardAuthService = vi.hoisted(() => ({ createCliAuthChallenge: vi.fn(), describeCliAuthChallenge: vi.fn(), approveCliAuthChallenge: vi.fn(), cancelCliAuthChallenge: vi.fn(), resolveBoardAccess: vi.fn(), resolveBoardActivityCompanyIds: vi.fn(), assertCurrentBoardKey: vi.fn(), revokeBoardApiKey: vi.fn(), })); const mockLogActivity = vi.hoisted(() => vi.fn()); vi.mock("../services/index.js", () => ({ accessService: () => mockAccessService, agentService: () => mockAgentService, boardAuthService: () => mockBoardAuthService, logActivity: mockLogActivity, notifyHireApproved: vi.fn(), deduplicateAgentName: vi.fn((name: string) => name), })); function createApp(actor: any) { const app = express(); app.use(express.json()); app.use((req, _res, next) => { req.actor = actor; next(); }); return import("../routes/access.js").then(({ accessRoutes }) => import("../middleware/index.js").then(({ errorHandler }) => { app.use( "/api", accessRoutes({} as any, { deploymentMode: "authenticated", deploymentExposure: "private", bindHost: "127.0.0.1", allowedHostnames: [], }), ); app.use(errorHandler); return app; }) ); } describe("cli auth routes", () => { beforeEach(() => { vi.clearAllMocks(); }); it("creates a CLI auth challenge with approval metadata", async () => { mockBoardAuthService.createCliAuthChallenge.mockResolvedValue({ challenge: { id: "challenge-1", expiresAt: new Date("2026-03-23T13:00:00.000Z"), }, challengeSecret: "pcp_cli_auth_secret", pendingBoardToken: "pcp_board_token", }); const app = await createApp({ type: "none", source: "none" }); const res = await request(app) .post("/api/cli-auth/challenges") .send({ command: "paperclipai company import", clientName: "paperclipai cli", requestedAccess: "board", }); expect(res.status).toBe(201); expect(res.body).toMatchObject({ id: "challenge-1", token: "pcp_cli_auth_secret", boardApiToken: "pcp_board_token", approvalPath: "/cli-auth/challenge-1?token=pcp_cli_auth_secret", pollPath: "/cli-auth/challenges/challenge-1", expiresAt: "2026-03-23T13:00:00.000Z", }); expect(res.body.approvalUrl).toContain("/cli-auth/challenge-1?token=pcp_cli_auth_secret"); }); it("marks challenge status as requiring sign-in for anonymous viewers", async () => { mockBoardAuthService.describeCliAuthChallenge.mockResolvedValue({ id: "challenge-1", status: "pending", command: "paperclipai company import", clientName: "paperclipai cli", requestedAccess: "board", requestedCompanyId: null, requestedCompanyName: null, approvedAt: null, cancelledAt: null, expiresAt: "2026-03-23T13:00:00.000Z", approvedByUser: null, }); const app = await createApp({ type: "none", source: "none" }); const res = await request(app).get("/api/cli-auth/challenges/challenge-1?token=pcp_cli_auth_secret"); expect(res.status).toBe(200); expect(res.body.requiresSignIn).toBe(true); expect(res.body.canApprove).toBe(false); }); it("approves a CLI auth challenge for a signed-in board user", async () => { mockBoardAuthService.approveCliAuthChallenge.mockResolvedValue({ status: "approved", challenge: { id: "challenge-1", boardApiKeyId: "board-key-1", requestedAccess: "board", requestedCompanyId: "company-1", expiresAt: new Date("2026-03-23T13:00:00.000Z"), }, }); mockBoardAuthService.resolveBoardAccess.mockResolvedValue({ user: { id: "user-1", name: "User One", email: "user@example.com" }, companyIds: ["company-1"], isInstanceAdmin: false, }); mockBoardAuthService.resolveBoardActivityCompanyIds.mockResolvedValue(["company-1"]); const app = await createApp({ type: "board", userId: "user-1", source: "session", isInstanceAdmin: false, companyIds: ["company-1"], }); const res = await request(app) .post("/api/cli-auth/challenges/challenge-1/approve") .send({ token: "pcp_cli_auth_secret" }); expect(res.status).toBe(200); expect(res.body).toEqual({ approved: true, status: "approved", userId: "user-1", keyId: "board-key-1", expiresAt: "2026-03-23T13:00:00.000Z", }); expect(mockLogActivity).toHaveBeenCalledTimes(1); expect(mockLogActivity).toHaveBeenCalledWith( expect.anything(), expect.objectContaining({ companyId: "company-1", action: "board_api_key.created", }), ); }); it("logs approve activity for instance admins without company memberships", async () => { mockBoardAuthService.approveCliAuthChallenge.mockResolvedValue({ status: "approved", challenge: { id: "challenge-2", boardApiKeyId: "board-key-2", requestedAccess: "instance_admin_required", requestedCompanyId: null, expiresAt: new Date("2026-03-23T13:00:00.000Z"), }, }); mockBoardAuthService.resolveBoardActivityCompanyIds.mockResolvedValue(["company-a", "company-b"]); const app = await createApp({ type: "board", userId: "admin-1", source: "session", isInstanceAdmin: true, companyIds: [], }); const res = await request(app) .post("/api/cli-auth/challenges/challenge-2/approve") .send({ token: "pcp_cli_auth_secret" }); expect(res.status).toBe(200); expect(mockBoardAuthService.resolveBoardActivityCompanyIds).toHaveBeenCalledWith({ userId: "admin-1", requestedCompanyId: null, boardApiKeyId: "board-key-2", }); expect(mockLogActivity).toHaveBeenCalledTimes(2); }); it("logs revoke activity with resolved audit company ids", async () => { mockBoardAuthService.assertCurrentBoardKey.mockResolvedValue({ id: "board-key-3", userId: "admin-2", }); mockBoardAuthService.resolveBoardActivityCompanyIds.mockResolvedValue(["company-z"]); const app = await createApp({ type: "board", userId: "admin-2", keyId: "board-key-3", source: "board_key", isInstanceAdmin: true, companyIds: [], }); const res = await request(app).post("/api/cli-auth/revoke-current").send({}); expect(res.status).toBe(200); expect(mockBoardAuthService.resolveBoardActivityCompanyIds).toHaveBeenCalledWith({ userId: "admin-2", boardApiKeyId: "board-key-3", }); expect(mockLogActivity).toHaveBeenCalledWith( expect.anything(), expect.objectContaining({ companyId: "company-z", action: "board_api_key.revoked", }), ); }); });