import { render, screen, waitFor, within } from "@testing-library/react"; 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", }; const options = { providers: ["zai", "deepseek"], models: [ { provider: "zai", id: "glm-5.2" }, { provider: "deepseek", id: "deepseek-v4" }, ], reasoning: ["low", "medium", "high"], checkedAt: "2026-08-05T10:00:00.000Z", }; function renderManagement() { const client = new QueryClient({ defaultOptions: { queries: { retry: false } } }); return render( undefined} />); } beforeEach(() => { server.use( http.get("/api/pi-management/status", () => HttpResponse.json(readyStatus)), http.get("/api/pi-management/options", () => HttpResponse.json(options)), ); }); test("loads Pi version and readiness as an accessible operational rail", async () => { renderManagement(); expect(screen.getByText("Loading Pi management…")).toBeVisible(); expect(await screen.findByRole("heading", { name: "Pi management" })).toBeVisible(); await screen.findByLabelText("Provider"); expect(screen.getByRole("dialog", { name: "Pi management" })).toHaveClass("sm:max-w-[min(94vw,58rem)]"); expect(screen.getByTestId("pi-readiness-rail")).toHaveTextContent("Runtime ready"); expect(screen.getByRole("status", { name: "Pi readiness" })).toHaveTextContent("Ready"); expect(screen.getByText("Pi 0.80.3")).toBeVisible(); expect(screen.getByText("Defaults ready")).toBeVisible(); expect(screen.getByRole("button", { name: "Test saved defaults" })).toBeVisible(); }); test("uses closed provider, model, and reasoning choices without a secret field or terminal", async () => { renderManagement(); const provider = await screen.findByLabelText("Provider"); expect(provider).toHaveValue("zai"); expect(screen.getByLabelText("Model")).toHaveValue("glm-5.2"); expect(screen.getByLabelText("Reasoning level")).toHaveValue("medium"); expect(within(provider).getAllByRole("option").map((option) => option.textContent)).toEqual(["zai", "deepseek"]); expect(screen.getByLabelText("Model")).toHaveTextContent("GLM 5.2"); expect(screen.queryByRole("textbox", { name: /provider|model|reasoning|credential/i })).not.toBeInTheDocument(); expect(document.querySelector('input[type="password"]')).toBeNull(); expect(screen.queryByText(/terminal|shell access/i)).not.toBeInTheDocument(); expect(screen.queryByRole("button", { name: "Update Pi" })).not.toBeInTheDocument(); }); test("saves only a selected non-secret configuration", async () => { const user = userEvent.setup(); let saved: unknown; server.use(http.put("/api/pi-management/config", async ({ request }) => { saved = await request.json(); return HttpResponse.json({ ...saved as object, updatedAt: "2026-08-05T10:02:00.000Z" }); })); renderManagement(); await user.selectOptions(await screen.findByLabelText("Provider"), "deepseek"); await user.selectOptions(screen.getByLabelText("Reasoning level"), "high"); await user.click(screen.getByRole("button", { name: "Save defaults" })); await waitFor(() => expect(saved).toEqual({ provider: "deepseek", model: "deepseek-v4", reasoning: "high" })); expect(screen.getByRole("status", { name: "Pi management feedback" })).toHaveTextContent("Defaults saved"); }); test("runs the saved-configuration test without changing 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 saved defaults" }); expect(screen.getByText("Defaults ready")).toBeVisible(); expect(screen.queryByText("Changes are not saved yet.")).not.toBeInTheDocument(); expect(testButton).toBeEnabled(); await user.click(testButton); await waitFor(() => expect(tests).toBe(1)); expect(await screen.findByText("Saved configuration test passed")).toBeVisible(); expect(screen.getByText("Credentials present")).toBeVisible(); await user.click(screen.getByRole("button", { name: "Test saved defaults" })); expect(await screen.findByText("Saved configuration test failed")).toBeVisible(); expect(screen.getByText("Credentials present")).toBeVisible(); }); test("fetches and displays bounded sanitized diagnostic logs 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.findByLabelText("Provider"); 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); expect(screen.queryByText("raw-provider-token")).not.toBeInTheDocument(); }); test("explains forbidden management access without offering mutation 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.queryByLabelText("Provider")).not.toBeInTheDocument(); expect(screen.queryByRole("button", { name: "Save defaults" })).not.toBeInTheDocument(); expect(screen.queryByRole("button", { name: "Test saved defaults" })).not.toBeInTheDocument(); }); test("offers a copyable host-side Pi update instruction without an update action", async () => { const user = userEvent.setup(); const writeText = vi.fn().mockResolvedValue(undefined); Object.defineProperty(navigator, "clipboard", { configurable: true, value: { writeText } }); renderManagement(); expect(await screen.findByText("thothctl pi update")).toBeVisible(); await user.click(screen.getByRole("button", { name: "Copy update command" })); expect(writeText).toHaveBeenCalledWith("thothctl pi update"); expect(screen.getByRole("status", { name: "Pi management feedback" })).toHaveTextContent("Update command copied"); expect(screen.queryByRole("button", { name: /update Pi/i })).not.toBeInTheDocument(); }); test("reports when the browser cannot copy the host-side update command", async () => { const user = userEvent.setup(); Object.defineProperty(navigator, "clipboard", { configurable: true, value: undefined }); renderManagement(); await user.click(await screen.findByRole("button", { name: "Copy update command" })); expect(screen.getByRole("status", { name: "Pi management feedback" })).toHaveTextContent("Could not copy the update command"); }); test("reloads installation defaults when the panel is 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( undefined} />); expect(await screen.findByLabelText("Provider")).toHaveValue("zai"); view.rerender( undefined} />); await waitFor(() => expect(screen.queryByRole("dialog", { name: "Pi management" })).not.toBeInTheDocument()); view.rerender( undefined} />); await waitFor(() => expect(screen.getByLabelText("Provider")).toHaveValue("deepseek")); }); test("shows an explicit recoverable incomplete state when no provider model is available", async () => { server.use( http.get("/api/pi-management/status", () => HttpResponse.json({ ...readyStatus, credentials: "present" })), http.get("/api/pi-management/options", () => HttpResponse.json({ ...options, providers: ["zai"], models: [], })), ); renderManagement(); const incomplete = await screen.findByRole("alert", { name: "Pi configuration incomplete" }); expect(incomplete).toHaveTextContent("No enabled provider and model choices are available"); expect(incomplete).toHaveTextContent("Check the host-managed Pi model configuration"); expect(screen.queryByText("Loading Pi management…")).not.toBeInTheDocument(); expect(screen.getByRole("button", { name: "Save defaults" })).toBeDisabled(); expect(screen.getByRole("button", { name: "Test saved defaults" })).toBeDisabled(); }); test("keeps suggested draft choices distinct from invalid persisted defaults until save succeeds", async () => { const persisted = { ...readyStatus, credentials: "missing", config: { provider: "retired", model: "old-model", reasoning: "medium" }, }; server.use( http.get("/api/pi-management/status", () => HttpResponse.json(persisted)), http.put("/api/pi-management/config", async ({ request }) => HttpResponse.json({ ...await request.json() as object, updatedAt: "2026-08-05T10:05:00.000Z", })), ); const user = userEvent.setup(); renderManagement(); expect(await screen.findByLabelText("Provider")).toHaveValue("zai"); expect(screen.getByText("Defaults incomplete")).toBeVisible(); expect(screen.getByText("Suggested choices are not saved yet.")).toBeVisible(); expect(screen.getByRole("button", { name: "Test saved defaults" })).toBeDisabled(); await user.click(screen.getByRole("button", { name: "Save defaults" })); expect(await screen.findByText("Defaults ready")).toBeVisible(); expect(screen.getByRole("button", { name: "Test saved defaults" })).toBeEnabled(); }); 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(); }, ); test("labels smoke only as a saved-configuration test and resets it when the draft changes", async () => { server.use( http.get("/api/pi-management/status", () => HttpResponse.json({ ...readyStatus, credentials: "present" })), http.post("/api/pi-management/test", () => HttpResponse.json({ ready: true, checkedAt: "2026-08-05T10:06:00.000Z", })), ); const user = userEvent.setup(); renderManagement(); await user.click(await screen.findByRole("button", { name: "Test saved defaults" })); expect(await screen.findByText("Saved configuration test passed")).toBeVisible(); expect(screen.getByText("Credentials present")).toBeVisible(); await user.selectOptions(screen.getByLabelText("Provider"), "deepseek"); expect(screen.getByText("Saved configuration test not run")).toBeVisible(); expect(screen.getByText("Save these changes before testing. The test always uses saved defaults.")).toBeVisible(); expect(screen.getByRole("button", { name: "Test saved defaults" })).toBeDisabled(); expect(screen.getByText("Credentials present")).toBeVisible(); }); test("a failed saved-configuration test does not change backend credential presence", async () => { server.use( http.get("/api/pi-management/status", () => HttpResponse.json({ ...readyStatus, credentials: "present" })), http.post("/api/pi-management/test", () => HttpResponse.json({ ready: false, message: "Pi runtime is unavailable", checkedAt: "2026-08-05T10:07:00.000Z", })), ); const user = userEvent.setup(); renderManagement(); await user.click(await screen.findByRole("button", { name: "Test saved defaults" })); expect(await screen.findByText("Saved configuration test failed")).toBeVisible(); expect(screen.getByText("Credentials present")).toBeVisible(); expect(screen.queryByText("Credentials missing")).not.toBeInTheDocument(); });