247 lines
11 KiB
TypeScript
247 lines
11 KiB
TypeScript
import { act, render, screen, waitFor, within } from "@testing-library/react";
|
|
import { setLocale } from "../i18n";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
import { http, HttpResponse } from "msw";
|
|
import { server } from "../test/msw";
|
|
import { PiManagement } from "./PiManagement";
|
|
|
|
const readyStatus = {
|
|
version: "0.80.3",
|
|
ready: true,
|
|
credentials: "present",
|
|
config: { provider: "zai", model: "glm-5.2", reasoning: "medium" },
|
|
checkedAt: "2026-08-05T10:00:00.000Z",
|
|
};
|
|
|
|
test("updates Pi controls and host instructions when the locale changes without remounting", async () => {
|
|
renderManagement(true);
|
|
await screen.findByRole("button", { name: "Test catalog default" });
|
|
try {
|
|
act(() => setLocale("it"));
|
|
expect(screen.getByRole("button", { name: "Verifica il modello predefinito del catalogo" })).toBeVisible();
|
|
await userEvent.click(screen.getByRole("button", { name: "Manutenzione host" }));
|
|
expect(screen.getByRole("heading", { name: "Apri la directory del progetto" })).toBeVisible();
|
|
act(() => setLocale("en"));
|
|
expect(screen.getByRole("heading", { name: "Open the project root" })).toBeVisible();
|
|
expect(screen.getByRole("button", { name: "Runtime and models" })).toBeVisible();
|
|
} finally { act(() => setLocale("en")); }
|
|
});
|
|
|
|
function renderManagement(page = false) {
|
|
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
|
return render(
|
|
<QueryClientProvider client={client}>
|
|
<PiManagement open page={page} onClose={() => undefined} />
|
|
</QueryClientProvider>,
|
|
);
|
|
}
|
|
|
|
beforeEach(() => {
|
|
server.use(
|
|
http.get("/api/pi-management/status", () => HttpResponse.json(readyStatus)),
|
|
http.get("/api/models", () => HttpResponse.json({ models: [] })),
|
|
);
|
|
});
|
|
|
|
test("shows the catalog default as read-only installation state", async () => {
|
|
renderManagement();
|
|
|
|
expect(screen.getByText("Loading Pi management…")).toBeVisible();
|
|
expect(await screen.findByRole("heading", { name: "Pi management" })).toBeVisible();
|
|
const dialog = screen.getByRole("dialog", { name: "Pi management" });
|
|
expect(dialog).toHaveAttribute("aria-modal", "false");
|
|
expect(dialog).toHaveAttribute("data-work-area-panel");
|
|
expect(dialog).toHaveAttribute("data-width", "expanded");
|
|
expect(await screen.findByText("Pi 0.80.3")).toBeVisible();
|
|
expect(screen.getByTestId("pi-readiness-rail")).toHaveTextContent("Runtime ready");
|
|
expect(screen.getByRole("status", { name: "Pi readiness" })).toHaveTextContent("Ready");
|
|
expect(screen.getByText("Defaults ready")).toBeVisible();
|
|
const catalog = screen.getByRole("region", { name: "Installation catalog default" });
|
|
expect(catalog).toHaveTextContent("zai");
|
|
expect(catalog).toHaveTextContent("glm-5.2");
|
|
expect(catalog).toHaveTextContent("medium");
|
|
expect(screen.getByRole("button", { name: "Test catalog default" })).toBeVisible();
|
|
expect(screen.queryByRole("button", { name: /save defaults/i })).not.toBeInTheDocument();
|
|
expect(screen.queryByRole("combobox", { name: /provider|model|reasoning/i })).not.toBeInTheDocument();
|
|
expect(document.querySelector('input[type="password"]')).toBeNull();
|
|
});
|
|
|
|
test.each(["linux", "macos", "windows"])("opens host instructions for the installation platform %s", async hostPlatform => {
|
|
server.use(http.get("/api/pi-management/status", () => HttpResponse.json({ ...readyStatus, hostPlatform })));
|
|
renderManagement(true);
|
|
await screen.findByText("Pi 0.80.3");
|
|
await userEvent.click(screen.getByRole("button", { name: "Host maintenance" }));
|
|
const label = hostPlatform === "macos" ? "macOS" : hostPlatform === "windows" ? "Windows" : "Linux";
|
|
expect(screen.getByRole("tab", { name: label })).toHaveAttribute("aria-selected", "true");
|
|
expect(screen.getByRole("tabpanel", { name: label })).toBeVisible();
|
|
await userEvent.click(screen.getByRole("tab", { name: "Windows" }));
|
|
await userEvent.click(screen.getByRole("button", { name: "Runtime and models" }));
|
|
await userEvent.click(screen.getByRole("button", { name: "Host maintenance" }));
|
|
expect(screen.getByRole("tabpanel", { name: "Windows" })).toBeVisible();
|
|
});
|
|
|
|
test("shows every session model exposed by Core", async () => {
|
|
server.use(http.get("/api/models", () => HttpResponse.json({ models: [
|
|
{ provider: "zai", id: "glm-5.3", name: "GLM 5.3", reasoning: true },
|
|
{ provider: "deepseek", id: "deepseek-v4-pro", name: "DeepSeek V4 Pro", reasoning: true },
|
|
{ provider: "deepseek", id: "deepseek-v4-flash", name: "DeepSeek V4 Flash", reasoning: true },
|
|
{ provider: "local-qwen", id: "qwen3.6-35b-a3b", name: "Local Qwen 3.6 35B A3B", reasoning: false },
|
|
] })));
|
|
renderManagement();
|
|
|
|
const catalog = await screen.findByRole("region", { name: "Available models" });
|
|
expect(catalog).toHaveTextContent("GLM 5.3");
|
|
expect(catalog).toHaveTextContent("DeepSeek V4 Pro");
|
|
expect(catalog).toHaveTextContent("DeepSeek V4 Flash");
|
|
expect(catalog).toHaveTextContent("Local Qwen 3.6 35B A3B");
|
|
});
|
|
|
|
test("tests the catalog default without mutating credential presence", async () => {
|
|
const user = userEvent.setup();
|
|
let tests = 0;
|
|
server.use(http.post("/api/pi-management/test", () => {
|
|
tests += 1;
|
|
return HttpResponse.json(tests === 1
|
|
? { ready: true, checkedAt: "2026-08-05T10:02:00.000Z" }
|
|
: { ready: false, message: "Pi provider smoke check failed", checkedAt: "2026-08-05T10:03:00.000Z" });
|
|
}));
|
|
renderManagement();
|
|
|
|
const testButton = await screen.findByRole("button", { name: "Test catalog default" });
|
|
await user.click(testButton);
|
|
expect(await screen.findByText("Catalog default test passed.")).toBeVisible();
|
|
expect(screen.getByText("Credentials present")).toBeVisible();
|
|
await user.click(testButton);
|
|
expect(await screen.findByText(/Catalog default test failed/)).toBeVisible();
|
|
expect(screen.getByText("Credentials present")).toBeVisible();
|
|
});
|
|
|
|
test("fetches bounded sanitized diagnostics only on request", async () => {
|
|
const user = userEvent.setup();
|
|
let logRequests = 0;
|
|
server.use(http.get("/api/pi-management/logs", () => {
|
|
logRequests += 1;
|
|
return HttpResponse.json({
|
|
lines: ["Pi smoke check succeeded", "provider token=[REDACTED]"],
|
|
checkedAt: "2026-08-05T10:04:00.000Z",
|
|
});
|
|
}));
|
|
renderManagement();
|
|
|
|
await screen.findByText("Pi 0.80.3");
|
|
expect(logRequests).toBe(0);
|
|
await user.click(screen.getByRole("button", { name: "Show sanitized logs" }));
|
|
expect(await screen.findByLabelText("Sanitized Pi diagnostics")).toHaveTextContent("provider token=[REDACTED]");
|
|
expect(logRequests).toBe(1);
|
|
});
|
|
|
|
test("explains forbidden management access without offering controls", async () => {
|
|
server.use(http.get("/api/pi-management/status", () =>
|
|
HttpResponse.json(
|
|
{ code: "pi_management_forbidden", error: "Pi management is not permitted" },
|
|
{ status: 403 },
|
|
)));
|
|
renderManagement();
|
|
|
|
expect(await screen.findByRole("alert", { name: "Pi management unavailable" }))
|
|
.toHaveTextContent("Pi management is not permitted");
|
|
expect(screen.queryByRole("button", { name: "Test catalog default" })).not.toBeInTheDocument();
|
|
});
|
|
|
|
test("shows the single-source host workflow in accessible platform tabs", async () => {
|
|
const user = userEvent.setup();
|
|
renderManagement();
|
|
|
|
const tablist = await screen.findByRole("tablist", { name: "Pi host platform" });
|
|
const [linuxTab, macosTab, windowsTab] = within(tablist).getAllByRole("tab");
|
|
expect([linuxTab, macosTab, windowsTab].map((tab) => tab.textContent)).toEqual(["Linux", "macOS", "Windows"]);
|
|
expect(screen.getByTestId("pi-platform-instructions-scroll")).toHaveClass("overflow-y-scroll");
|
|
|
|
linuxTab.focus();
|
|
await user.keyboard("{ArrowRight}");
|
|
expect(macosTab).toHaveFocus();
|
|
await user.keyboard("{End}");
|
|
expect(windowsTab).toHaveFocus();
|
|
await user.click(linuxTab);
|
|
|
|
const linux = screen.getByRole("tabpanel", { name: "Linux" });
|
|
expect(within(linux).getAllByRole("listitem")).toHaveLength(7);
|
|
expect(within(linux).getAllByRole("heading", { level: 4 }).map((heading) => heading.textContent)).toEqual([
|
|
"Open the project root",
|
|
"Edit the Installation Model Catalog",
|
|
"Choose eligibility and defaults",
|
|
"Complete the provider setup",
|
|
"Reload Pi configuration",
|
|
"Update the Pi version",
|
|
"Diagnose and recover a failed operation",
|
|
]);
|
|
for (const expected of [
|
|
"deploy/<installation-id>/thothii-installation.yaml",
|
|
"modelCatalog.defaults.interaction",
|
|
"provider/model",
|
|
"generated projections",
|
|
"tht pi restart --yes --drain",
|
|
"tht pi doctor",
|
|
"Invalid YAML",
|
|
]) expect(linux).toHaveTextContent(expected);
|
|
for (const retired of ["deploy/pi/models.json", "deploy/pi/settings.json", "tht pi configure", "enabledModels"])
|
|
expect(linux).not.toHaveTextContent(retired);
|
|
|
|
await user.click(macosTab);
|
|
expect(screen.getByRole("tabpanel", { name: "macOS" }))
|
|
.toHaveTextContent("deploy/<installation-id>/thothii-installation.yaml");
|
|
await user.click(windowsTab);
|
|
expect(screen.getByRole("tabpanel", { name: "Windows" }))
|
|
.toHaveTextContent("deploy\\<installation-id>\\thothii-installation.yaml");
|
|
});
|
|
|
|
test("reloads the catalog-derived default when reopened", async () => {
|
|
let statusCalls = 0;
|
|
server.use(http.get("/api/pi-management/status", () => {
|
|
statusCalls += 1;
|
|
return HttpResponse.json(statusCalls === 1
|
|
? readyStatus
|
|
: { ...readyStatus, config: { provider: "deepseek", model: "deepseek-v4", reasoning: "high" } });
|
|
}));
|
|
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
|
const view = render(
|
|
<QueryClientProvider client={client}><PiManagement open onClose={() => undefined} /></QueryClientProvider>,
|
|
);
|
|
|
|
expect(await screen.findByText("glm-5.2")).toBeVisible();
|
|
view.rerender(
|
|
<QueryClientProvider client={client}><PiManagement open={false} onClose={() => undefined} /></QueryClientProvider>,
|
|
);
|
|
await waitFor(() => expect(screen.queryByRole("dialog", { name: "Pi management" })).not.toBeInTheDocument());
|
|
view.rerender(
|
|
<QueryClientProvider client={client}><PiManagement open onClose={() => undefined} /></QueryClientProvider>,
|
|
);
|
|
expect(await screen.findByText("deepseek-v4")).toBeVisible();
|
|
});
|
|
|
|
test("shows an explicit incomplete state when the catalog default is unavailable", async () => {
|
|
server.use(http.get("/api/pi-management/status", () => HttpResponse.json({
|
|
...readyStatus,
|
|
config: { reasoning: "medium" },
|
|
})));
|
|
renderManagement();
|
|
|
|
const incomplete = await screen.findByRole("alert", { name: "Pi configuration incomplete" });
|
|
expect(incomplete).toHaveTextContent("The catalog default is unavailable");
|
|
expect(incomplete).toHaveTextContent("modelCatalog.defaults.interaction");
|
|
expect(screen.getByRole("button", { name: "Test catalog default" })).toBeDisabled();
|
|
});
|
|
|
|
test.each(["present", "missing"] as const)(
|
|
"shows credentials %s from status without inferring them from smoke",
|
|
async (credentials) => {
|
|
server.use(http.get("/api/pi-management/status", () => HttpResponse.json({
|
|
...readyStatus,
|
|
credentials,
|
|
})));
|
|
renderManagement();
|
|
expect(await screen.findByText(`Credentials ${credentials}`)).toBeVisible();
|
|
},
|
|
);
|