0f17efe3b4
- Skill snapshot/upload: a key file's name (.env, *.pem, id_rsa, .netrc, .credentials.json…) is always refused, but a word like token/secret/ password/credential only in a data file's name — design-tokens.md or count_tokens.py is skill content, not a key. - System Health: off Linux the History, CPU threads and Busiest processes cards (all read from /proc) are left out instead of shown empty; the CPU bar without a sample and Windows' always-zero Load row go too. - Users pick their own pool account per tool (/api/me/pool-accounts, Settings → AI tools), within their groups, refused while pinned. - Admin user detail, 9router update, session replay and Antigravity catalog changes from parallel sessions.
86 lines
2.9 KiB
TypeScript
86 lines
2.9 KiB
TypeScript
/**
|
|
* @file Tests for PoolAccountSettings — Settings → AI tools → Pool account:
|
|
* hidden in local mode and with no usable account; one select per tool that
|
|
* starts on the user's choice (or Automatic) and saves a pick through
|
|
* `PUT /api/me/pool-accounts`; locked when an admin pinned the user.
|
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
*/
|
|
|
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
import { render, screen, act } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import "../../../i18n";
|
|
import { PoolAccountSettings } from "../PoolAccountSettings";
|
|
|
|
const perms = vi.hoisted(() => ({ local: false }));
|
|
vi.mock("../../AuthGate", () => ({ usePermissions: () => perms }));
|
|
|
|
const m = vi.hoisted(() => ({ poolAccounts: vi.fn(), choosePoolAccount: vi.fn() }));
|
|
vi.mock("../../../lib/api", () => ({
|
|
api: { me: m, aiTools: { iconUrl: (id: string) => `/icons/${id}.png` } },
|
|
}));
|
|
|
|
const account = (name: string) => ({
|
|
name,
|
|
kind: "login",
|
|
email: `${name}@x.io`,
|
|
expired: false,
|
|
quotaPercent: 20,
|
|
full: false,
|
|
});
|
|
|
|
async function show() {
|
|
render(<PoolAccountSettings />);
|
|
await act(async () => {
|
|
await new Promise((res) => setTimeout(res, 0));
|
|
});
|
|
}
|
|
|
|
beforeEach(() => {
|
|
perms.local = false;
|
|
m.poolAccounts.mockReset();
|
|
m.choosePoolAccount.mockReset();
|
|
});
|
|
|
|
describe("PoolAccountSettings", () => {
|
|
it("renders nothing in local mode or with no account to choose", async () => {
|
|
perms.local = true;
|
|
await show();
|
|
expect(m.poolAccounts).not.toHaveBeenCalled();
|
|
expect(screen.queryByRole("combobox")).toBeNull();
|
|
perms.local = false;
|
|
m.poolAccounts.mockResolvedValue({ tools: [] });
|
|
await show();
|
|
expect(screen.queryByRole("combobox")).toBeNull();
|
|
});
|
|
|
|
it("starts on the user's choice and saves a new pick", async () => {
|
|
m.poolAccounts.mockResolvedValue({
|
|
tools: [
|
|
{ tool: "claude", pin: null, choice: "two", accounts: [account("one"), account("two")] },
|
|
],
|
|
});
|
|
m.choosePoolAccount.mockResolvedValue({ tool: "claude", choice: "one", switched: "switched" });
|
|
await show();
|
|
const select = screen.getByRole("combobox") as HTMLSelectElement;
|
|
expect(select.value).toBe("two");
|
|
await userEvent.selectOptions(select, "one");
|
|
expect(m.choosePoolAccount).toHaveBeenCalledWith("claude", "one");
|
|
await userEvent.selectOptions(select, "");
|
|
expect(m.choosePoolAccount).toHaveBeenLastCalledWith("claude", null);
|
|
});
|
|
|
|
it("is locked on an admin's pin", async () => {
|
|
m.poolAccounts.mockResolvedValue({
|
|
tools: [
|
|
{ tool: "codex", pin: "one", choice: null, accounts: [account("one"), account("two")] },
|
|
],
|
|
});
|
|
await show();
|
|
const select = screen.getByRole("combobox") as HTMLSelectElement;
|
|
expect(select.disabled).toBe(true);
|
|
expect(select.value).toBe("one");
|
|
expect(screen.getByText(/one/, { selector: "span" })).toBeTruthy();
|
|
});
|
|
});
|