/** * @file AccountPoolCard.test.tsx * @description The Users page's account pool card: one tab per AI tool — the * Claude, Codex and Cursor pools with their accounts' live panes and quota, * Oh My Pi's login sharing, and a "no pool" note for any other installed * tool; says what an empty pool means per tool, an * admin adds an account only with their own password typed (the token goes * in once and is not shown back) — a saved login moved in (the server's own * included), or a setup-token — and removes one the same way, and an analyst * sees no add or remove control. An account's Edit ticks the users who run on * it, saved with the admin's own password; the users show on the account. * @author Nguyễn Ngọc Trí Vĩ */ import { beforeEach, describe, expect, it, vi } from "vitest"; import { render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { MemoryRouter } from "react-router-dom"; import type { AccountPool } from "../../../lib/api"; const accountPool = vi.hoisted(() => ({ list: vi.fn(), add: vi.fn(), remove: vi.fn(), profiles: vi.fn(), setUsers: vi.fn(), createGroup: vi.fn(), updateGroup: vi.fn(), deleteGroup: vi.fn(), })); const users = vi.hoisted(() => ({ list: vi.fn() })); const aiTools = vi.hoisted(() => ({ list: vi.fn(), ompSharing: vi.fn(), setOmpSharing: vi.fn(), iconUrl: (id: string) => `/api/ai-tools/${id}/icon`, })); vi.mock("../../../lib/api", async (importOriginal) => { const actual = await importOriginal>(); return { ...actual, api: { accountPool, users, aiTools } }; }); import { AccountPoolCard } from "../AccountPoolCard"; /** The card reads `?poolAdd=` and links to Settings: it needs a router. */ const card = (isAdmin: boolean, url = "/admin") => ( ); const POOL: AccountPool = { accounts: [ { name: "team-1", kind: "login", email: "team1@example.com", refreshError: null, addedAt: "2026-10-01T00:00:00Z", expiresAt: null, expired: false, livePanes: 2, spend5h: 1.5, spend7d: 12, tokens5h: 250_000, tokens7d: 3_400_000, quota: { fiveHour: { utilization: 42, resetsAt: new Date(Date.now() + 2 * 3600e3 + 630e3).toISOString(), }, sevenDay: { utilization: 15, resetsAt: new Date(Date.now() + 3 * 86400e3 + 3630e3).toISOString(), }, checkedAt: "2026-10-01T00:00:00Z", error: null, }, quotaPercent: 42, full: false, }, { name: "team-2", kind: "token", email: null, refreshError: null, addedAt: "2026-10-01T00:00:00Z", expiresAt: null, expired: false, livePanes: 0, spend5h: 0, spend7d: 0, tokens5h: 0, tokens7d: 0, // A 5-hour window that has reset since the reading is full again. quota: { fiveHour: { utilization: 100, resetsAt: "2020-01-01T00:00:00Z" }, sevenDay: null, checkedAt: "2026-10-01T00:00:00Z", error: null, }, quotaPercent: null, full: false, }, ], codex: [ { name: "cx-1", kind: "apikey", addedAt: "2026-10-02T00:00:00Z", livePanes: 0, spend5h: 0, spend7d: 0, tokens5h: 0, tokens7d: 0, quotaPercent: null, full: false, }, ], blockPercent: 90, }; const tool = (id: string, label: string, installed = true) => ({ id, label, installed }); const OMP = { available: true, enabled: false, installed: true, broker: { running: false }, gateway: { running: false }, }; beforeEach(() => { for (const fn of Object.values(accountPool)) fn.mockReset(); aiTools.list.mockResolvedValue({ tools: [ tool("claude", "Claude Code"), tool("codex", "Codex"), tool("cursor", "Cursor CLI"), tool("omp", "Oh My Pi"), tool("antigravity", "Antigravity"), tool("goose", "Goose", false), ], default: "claude", }); aiTools.ompSharing.mockResolvedValue(OMP); aiTools.setOmpSharing.mockResolvedValue({ ...OMP, enabled: true }); accountPool.list.mockResolvedValue(POOL); users.list.mockResolvedValue({ users: [ { id: 7, username: "mia", disabled: false }, { id: 8, username: "leo", disabled: false }, ], }); accountPool.profiles.mockResolvedValue({ profiles: [ { name: "work", label: "Work", email: "w@example.com", subscriptionType: "max", active: false, }, { name: "me", label: "Me", email: "me@example.com", subscriptionType: "pro", active: true }, ], }); }); describe("AccountPoolCard", () => { it("shows each tool's pool on its own tab, counted, and explains an empty one", async () => { const user = userEvent.setup(); const { unmount } = render(card(false)); expect(await screen.findByText("team-1")).toBeInTheDocument(); expect(screen.queryByText("cx-1")).toBeNull(); const [account, second] = screen.getAllByTestId("pool-account"); // Every account of the pool has its card; quota is shown as what is LEFT. expect(second).toHaveTextContent("team-2"); const quota = screen.getAllByTestId("pool-quota")[0]!; expect(quota).toHaveTextContent("5 hours"); expect(quota).toHaveTextContent("58%"); expect(quota).toHaveTextContent("resets in 2h 10m"); expect(quota).toHaveTextContent("Week"); expect(quota).toHaveTextContent("85%"); expect(quota).toHaveTextContent("resets in 3d 1h"); const reset = screen.getAllByTestId("pool-quota")[1]!; expect(reset).toHaveTextContent("100%"); expect(reset).toHaveTextContent("—"); expect(account).toHaveTextContent("250.0K tokens"); expect(account).toHaveTextContent("$12.00 · 3.4M tokens in 7 days"); expect(account).toHaveTextContent("2 live"); expect(screen.getByText("team1@example.com")).toBeInTheDocument(); // Without groups nothing is said about them on an account. expect(screen.queryByText("Shared pool")).toBeNull(); expect(screen.queryByRole("button", { name: /^add/i })).toBeNull(); expect(screen.queryByRole("button", { name: /remove/i })).toBeNull(); const codexTab = screen.getByRole("tab", { name: /Codex/ }); expect(codexTab).toHaveTextContent("1"); expect(screen.getByRole("tab", { name: /Claude Code/ })).toHaveTextContent("2"); await user.click(codexTab); expect(screen.getByText("cx-1")).toBeInTheDocument(); expect(screen.queryByText("team-1")).toBeNull(); // A pool with no account still has its tab, with what that means. await user.click(screen.getByRole("tab", { name: /Cursor CLI/ })); expect(screen.getByText(/No Cursor CLI accounts yet/)).toBeInTheDocument(); // An installed tool without a pool says so; an uninstalled one gets no tab. await user.click(screen.getByRole("tab", { name: /Antigravity/ })); expect(screen.getByText("Antigravity has no account pool")).toBeInTheDocument(); expect(screen.getByText(/system keyring/)).toBeInTheDocument(); expect(screen.queryByRole("tab", { name: /Goose/ })).toBeNull(); unmount(); accountPool.list.mockResolvedValue({ accounts: [], codex: [], blockPercent: 90 }); render(card(true)); expect(await screen.findByText(/No Claude Code accounts yet/)).toBeInTheDocument(); expect(screen.getByRole("button", { name: "Add Claude Code account" })).toBeInTheDocument(); }); it("adds a Cursor API key from the Cursor tab", async () => { accountPool.add.mockResolvedValue({ account: { name: "cu-1" } }); accountPool.list.mockResolvedValue({ ...POOL, cursor: [] }); const user = userEvent.setup(); render(card(true)); await screen.findByText("team-1"); await user.click(screen.getByRole("tab", { name: /Cursor CLI/ })); await user.click(screen.getByRole("button", { name: "Add Cursor CLI account" })); const dialog = await screen.findByRole("dialog"); expect(screen.queryByLabelText("From")).toBeNull(); await user.type(screen.getByLabelText("Name"), "cu-1"); await user.type(screen.getByLabelText("Cursor API key"), "key_abc"); await user.type(screen.getByLabelText("Your password"), "boss-pass"); await user.click( Array.from(dialog.querySelectorAll("button")).find((b) => b.textContent === "Add account")! ); await waitFor(() => expect(accountPool.add).toHaveBeenCalledWith( { tool: "cursor", name: "cu-1", token: "key_abc" }, "boss-pass" ) ); expect(accountPool.profiles).not.toHaveBeenCalledWith("cursor"); }); it("shares a saved profile from its card", async () => { accountPool.add.mockResolvedValue({ account: { name: "work" } }); const user = userEvent.setup(); render(card(true)); await screen.findByText("team-1"); await user.click(await screen.findByRole("button", { name: "Share Work to the pool" })); const dialog = await screen.findByRole("dialog"); await waitFor(() => expect(screen.getByLabelText("Profile")).toHaveValue("work")); await user.type(screen.getByLabelText("Your password"), "boss-pass"); await user.click( Array.from(dialog.querySelectorAll("button")).find((b) => b.textContent === "Add account")! ); await waitFor(() => expect(accountPool.add).toHaveBeenCalledWith( { tool: "claude", name: "work", profile: "work" }, "boss-pass" ) ); }); it("shows the groups as cards and saves a new one with members and accounts", async () => { accountPool.list.mockResolvedValue({ ...POOL, groups: [ { id: 1, name: "Team A", users: [{ userId: 8, username: "leo" }], accounts: { claude: ["team-1"], codex: [], cursor: [] }, }, ], }); accountPool.createGroup.mockResolvedValue({ group: { id: 2, name: "Team B" } }); accountPool.updateGroup.mockResolvedValue({ group: {} }); const user = userEvent.setup(); render(card(true)); const groupCard = await screen.findByTestId("pool-group"); expect(groupCard).toHaveTextContent("Team A"); expect(groupCard).toHaveTextContent("leo"); expect(screen.getAllByTestId("pool-account")[0]).toHaveTextContent("Team A"); await user.click(screen.getByRole("button", { name: "New group" })); await screen.findByRole("dialog"); await user.type(screen.getByLabelText("Group name"), "Team B"); await user.click(await screen.findByRole("checkbox", { name: /leo/ })); expect(screen.getByText("now in Team A")).toBeInTheDocument(); await user.click(screen.getByRole("checkbox", { name: "cx-1" })); await user.type(screen.getByLabelText("Your password"), "boss-pass"); await user.click(screen.getByRole("button", { name: "Save" })); await waitFor(() => expect(accountPool.updateGroup).toHaveBeenCalledWith( 2, { userIds: [8], accounts: { claude: [], codex: ["cx-1"], cursor: [] } }, "boss-pass" ) ); expect(accountPool.createGroup).toHaveBeenCalledWith("Team B", "boss-pass"); }); it("turns Oh My Pi login sharing on from its tab", async () => { const user = userEvent.setup(); render(card(true)); await screen.findByText("team-1"); await user.click(screen.getByRole("tab", { name: /Oh My Pi/ })); expect(screen.getByTestId("omp-pool-status")).toHaveTextContent(/Sharing is off/); await user.click(screen.getByRole("switch")); await waitFor(() => expect(aiTools.setOmpSharing).toHaveBeenCalledWith(true)); expect(screen.getByRole("tab", { name: /Oh My Pi/ })).toHaveTextContent("shared"); }); it("moves a saved login in, the server's own included", async () => { accountPool.add.mockResolvedValue({ account: { name: "work" } }); const user = userEvent.setup(); render(card(true)); await screen.findByText("team-1"); await user.click(screen.getByRole("button", { name: "Add Claude Code account" })); const dialog = await screen.findByRole("dialog"); const select = await screen.findByLabelText("Profile"); await waitFor(() => expect(screen.getByRole("option", { name: /Work/ })).toBeInTheDocument()); expect(screen.getByRole("option", { name: /Me/ })).toBeEnabled(); await user.selectOptions(select, "work"); const confirm = Array.from(dialog.querySelectorAll("button")).find( (b) => b.textContent === "Add account" )!; expect(confirm).toBeDisabled(); await user.type(screen.getByLabelText("Your password"), "boss-pass"); await user.click(confirm); await waitFor(() => expect(accountPool.add).toHaveBeenCalledWith( { tool: "claude", name: "work", profile: "work" }, "boss-pass" ) ); }); it("adds a setup-token only with the admin's own password", async () => { accountPool.add.mockResolvedValue({ account: { name: "team-2" } }); const user = userEvent.setup(); render(card(true)); await screen.findByText("team-1"); await user.click(screen.getByRole("button", { name: "Add Claude Code account" })); const dialog = await screen.findByRole("dialog"); await user.selectOptions(screen.getByLabelText("From"), "token"); await user.type(screen.getByLabelText("Name"), "team-2"); await user.type(screen.getByLabelText("Token"), "sk-ant-oat01-xyz"); const confirm = Array.from(dialog.querySelectorAll("button")).find( (b) => b.textContent === "Add account" )!; expect(confirm).toBeDisabled(); await user.type(screen.getByLabelText("Your password"), "boss-pass"); expect(confirm).toBeEnabled(); await user.click(confirm); await waitFor(() => expect(accountPool.add).toHaveBeenCalledWith( { tool: "claude", name: "team-2", token: "sk-ant-oat01-xyz" }, "boss-pass" ) ); }); it("removes an account with the admin's own password", async () => { accountPool.remove.mockResolvedValue({ ok: true }); const user = userEvent.setup(); render(card(true)); await screen.findByText("team-1"); await user.click(screen.getByRole("tab", { name: /Codex/ })); await user.click(await screen.findByRole("button", { name: "Remove cx-1" })); await user.type(await screen.findByLabelText("Your password"), "boss-pass"); await user.click(screen.getByRole("button", { name: "Remove" })); await waitFor(() => expect(accountPool.remove).toHaveBeenCalledWith("codex", "cx-1", "boss-pass") ); }); it("sets who runs on an account from its Edit, with the admin's own password", async () => { accountPool.list.mockResolvedValue({ ...POOL, pins: { claude: [ { userId: 8, username: "leo", account: "team-1" }, { userId: 7, username: "mia", account: "team-0" }, ], codex: [], }, }); accountPool.setUsers.mockResolvedValue({ added: [{ userId: 7, username: "mia", switched: "switched" }], removed: [{ userId: 8, username: "leo" }], }); const user = userEvent.setup(); render(card(true)); expect(await screen.findByText("Used by: leo")).toBeInTheDocument(); expect(screen.queryByRole("button", { name: /switch a user/i })).toBeNull(); await user.click(screen.getByRole("button", { name: "Edit team-1" })); const dialog = await screen.findByRole("dialog"); const leo = await screen.findByRole("checkbox", { name: /leo/ }); const mia = screen.getByRole("checkbox", { name: /mia/ }); expect(leo).toHaveAttribute("aria-checked", "true"); expect(mia).toHaveAttribute("aria-checked", "false"); expect(screen.getByText("now on team-0")).toBeInTheDocument(); await user.click(mia); await user.click(leo); const save = Array.from(dialog.querySelectorAll("button")).find( (b) => b.textContent === "Save" )!; expect(save).toBeDisabled(); await user.type(screen.getByLabelText("Your password"), "boss-pass"); await user.click(save); await waitFor(() => expect(accountPool.setUsers).toHaveBeenCalledWith("claude", "team-1", [7], "boss-pass") ); }); it("moves a saved Codex profile in, and opens on one from its Move to pool link", async () => { accountPool.add.mockResolvedValue({ account: { name: "cx" } }); accountPool.profiles.mockImplementation(async (tool: string) => tool === "codex" ? { profiles: [ { name: "cx", label: "Codex", email: "c@x", subscriptionType: "plus", active: false }, ], } : { profiles: [] } ); const user = userEvent.setup(); render(card(true, "/admin?poolAdd=codex&poolProfile=cx")); const dialog = await screen.findByRole("dialog"); await waitFor(() => expect(accountPool.profiles).toHaveBeenCalledWith("codex")); expect(screen.getByRole("tab", { name: /Codex/ })).toHaveAttribute("aria-selected", "true"); await waitFor(() => expect(screen.getByLabelText("Profile")).toHaveValue("cx")); await user.type(screen.getByLabelText("Your password"), "boss-pass"); const confirm = Array.from(dialog.querySelectorAll("button")).find( (b) => b.textContent === "Add account" )!; await user.click(confirm); await waitFor(() => expect(accountPool.add).toHaveBeenCalledWith( { tool: "codex", name: "cx", profile: "cx" }, "boss-pass" ) ); }); it("links back to the profiles in Settings", async () => { render(card(false)); const link = await screen.findByRole("link", { name: "Profiles in Settings" }); expect(link.getAttribute("href")).toBe("/settings/agents#tool-profiles"); }); });