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.queryByRole("button", { name: /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"); }); // Catches a provider switch updating only the saved config while leaving the credential rail // attached to the previously selected provider. test("shows authoritative credential presence after saving a different provider", async () => { const user = userEvent.setup(); let saved = false; server.use( http.get("/api/pi-management/status", () => HttpResponse.json(saved ? { ...readyStatus, credentials: "missing", config: { provider: "deepseek", model: "deepseek-v4", reasoning: "medium" }, checkedAt: "2026-08-05T10:02:00.000Z", } : readyStatus)), http.put("/api/pi-management/config", async ({ request }) => { saved = true; return HttpResponse.json({ ...await request.json() as object, updatedAt: "2026-08-05T10:01:00.000Z", }); }), ); renderManagement(); expect(await screen.findByText("Credentials present")).toBeVisible(); await user.selectOptions(screen.getByLabelText("Provider"), "deepseek"); await user.click(screen.getByRole("button", { name: "Save defaults" })); expect(await screen.findByText("Credentials missing")).toBeVisible(); expect(screen.queryByText("Credentials present")).not.toBeInTheDocument(); expect(screen.getByLabelText("Provider")).toHaveValue("deepseek"); 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("shows a seven-step host-terminal workflow in scrollable 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.getByRole("dialog", { name: "Pi management" })).toHaveClass("max-h-[calc(100vh-6rem)]"); expect(screen.getByTestId("pi-platform-instructions-scroll")).toHaveClass("overflow-y-scroll"); expect(screen.queryByRole("tabpanel")).not.toBeInTheDocument(); expect([linuxTab, macosTab, windowsTab].every((tab) => tab.getAttribute("aria-selected") === "false")).toBe(true); expect(linuxTab).toHaveAttribute("tabindex", "0"); expect(macosTab).toHaveAttribute("tabindex", "-1"); expect(windowsTab).toHaveAttribute("tabindex", "-1"); for (const tab of [linuxTab, macosTab, windowsTab]) { const panelId = tab.getAttribute("aria-controls"); const panel = panelId ? document.getElementById(panelId) : null; expect(panel).toBeInTheDocument(); expect(panel).toHaveAttribute("role", "tabpanel"); expect(panel).toHaveAttribute("aria-labelledby", tab.id); expect(panel).toHaveAttribute("hidden"); expect(panel).toHaveAttribute("tabindex", "-1"); } expect(screen.getByText("Using the host terminal:")).toBeVisible(); expect(screen.queryByText(/not this browser page/i)).not.toBeInTheDocument(); expect(screen.queryByRole("region", { name: "Host update" })).not.toBeInTheDocument(); expect(screen.queryByRole("button", { name: "Copy update command" })).not.toBeInTheDocument(); expect(screen.queryByText(/:5173/)).not.toBeInTheDocument(); expect(await screen.findByText("Select the provider, model, and reasoning used for new Pi work. Credentials stay in protected host files.")).toBeVisible(); expect(screen.getByText("Shows at most 200 recent lines with declared secret values removed.")).toBeVisible(); linuxTab.focus(); await user.keyboard("{ArrowRight}"); expect(macosTab).toHaveFocus(); expect(macosTab).toHaveAttribute("aria-selected", "true"); expect(macosTab).toHaveAttribute("tabindex", "0"); expect(linuxTab).toHaveAttribute("tabindex", "-1"); await user.keyboard("{ArrowRight}"); expect(windowsTab).toHaveFocus(); expect(windowsTab).toHaveAttribute("aria-selected", "true"); expect(windowsTab).toHaveAttribute("tabindex", "0"); expect(macosTab).toHaveAttribute("tabindex", "-1"); await user.keyboard("{ArrowRight}"); expect(linuxTab).toHaveFocus(); expect(linuxTab).toHaveAttribute("aria-selected", "true"); await user.keyboard("{ArrowLeft}"); expect(windowsTab).toHaveFocus(); await user.keyboard("{Home}"); expect(linuxTab).toHaveFocus(); await user.keyboard("{End}"); expect(windowsTab).toHaveFocus(); await user.click(windowsTab); expect(windowsTab).toHaveAttribute("aria-selected", "false"); expect(screen.queryByRole("tabpanel")).not.toBeInTheDocument(); await user.keyboard(" "); expect(windowsTab).toHaveAttribute("aria-selected", "true"); await user.keyboard(" "); expect(windowsTab).toHaveAttribute("aria-selected", "false"); await user.keyboard("{Enter}"); expect(windowsTab).toHaveAttribute("aria-selected", "true"); await user.keyboard("{Enter}"); expect(windowsTab).toHaveAttribute("aria-selected", "false"); 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 provider catalog", "Enable the model", "Set the provider credential", "Reload Pi configuration", "Update the Pi version", "Recover a failed update", ]); expect(linux).toHaveTextContent("The deploy directory is in the ThothII project root, beside compose.yaml"); expect(linux).toHaveTextContent("deploy/pi/models.json"); expect(linux).toHaveTextContent("deploy/pi/settings.json"); expect(linux).toHaveTextContent("baseUrl is the provider API endpoint"); expect(linux).toHaveTextContent("enabledModels uses provider/model identifiers"); expect(linux).toHaveTextContent("PI_AUTH_FILE is a setting in the installation environment file"); expect(linux).toHaveTextContent("~/bin/thothctl --installation ~/thothii-installation.yaml pi restart --yes --drain"); expect(linux).toHaveTextContent("~/bin/thothctl --installation ~/thothii-installation.yaml pi update --version --source build --yes --drain"); expect(linux).toHaveTextContent("~/bin/thothctl --installation ~/thothii-installation.yaml pi update --version --source pull --image @sha256: --yes --drain"); expect(linux).toHaveTextContent(" is a placeholder. Replace it with the Pi release/version you want to install."); expect(linux).toHaveTextContent("~/bin/thothctl --installation ~/thothii-installation.yaml pi maintenance status"); expect(linux).toHaveTextContent("~/bin/thothctl --installation ~/thothii-installation.yaml pi logs"); expect(linux).toHaveTextContent("~/bin/thothctl --installation ~/thothii-installation.yaml pi rollback --yes"); expect(linux).toHaveTextContent("~/bin/thothctl --installation ~/thothii-installation.yaml pi maintenance recover --yes"); expect(linux).not.toHaveTextContent("~/.pi/agent/"); await user.click(macosTab); const macos = screen.getByRole("tabpanel", { name: "macOS" }); expect(within(macos).getAllByRole("listitem")).toHaveLength(7); expect(within(macos).getAllByRole("heading", { level: 4 }).map((heading) => heading.textContent)).toEqual([ "Open the project root", "Edit the provider catalog", "Enable the model", "Set the provider credential", "Reload Pi configuration", "Update the Pi version", "Recover a failed update", ]); expect(macos).toHaveTextContent("The deploy directory is in the ThothII project root, beside compose.yaml"); expect(macos).toHaveTextContent("deploy/pi/models.json"); expect(macos).toHaveTextContent("deploy/pi/settings.json"); expect(macos).toHaveTextContent("~/bin/thothctl --installation ~/thothii-installation.yaml pi restart --yes --drain"); expect(macos).toHaveTextContent("~/bin/thothctl --installation ~/thothii-installation.yaml pi update --version --source build --yes --drain"); expect(macos).toHaveTextContent("~/bin/thothctl --installation ~/thothii-installation.yaml pi update --version --source pull --image @sha256: --yes --drain"); expect(macos).toHaveTextContent(" is a placeholder. Replace it with the Pi release/version you want to install."); expect(macos).toHaveTextContent("~/bin/thothctl --installation ~/thothii-installation.yaml pi maintenance status"); expect(macos).toHaveTextContent("~/bin/thothctl --installation ~/thothii-installation.yaml pi logs"); expect(macos).toHaveTextContent("~/bin/thothctl --installation ~/thothii-installation.yaml pi rollback --yes"); expect(macos).toHaveTextContent("~/bin/thothctl --installation ~/thothii-installation.yaml pi maintenance recover --yes"); await user.click(windowsTab); const windows = screen.getByRole("tabpanel", { name: "Windows" }); expect(within(windows).getAllByRole("listitem")).toHaveLength(7); expect(within(windows).getAllByRole("heading", { level: 4 }).map((heading) => heading.textContent)).toEqual([ "Open the project root", "Edit the provider catalog", "Enable the model", "Set the provider credential", "Reload Pi configuration", "Update the Pi version", "Recover a failed update", ]); expect(windows).toHaveTextContent("The deploy directory is in the ThothII project root, beside compose.yaml"); expect(windows).toHaveTextContent("deploy\\pi\\models.json"); expect(windows).toHaveTextContent("deploy\\pi\\settings.json"); expect(windows).toHaveTextContent('& (Resolve-Path "~\\bin\\thothctl-windows-amd64.exe") --installation (Resolve-Path "~\\thothii-installation.yaml") pi restart --yes --drain'); expect(windows).toHaveTextContent('& (Resolve-Path "~\\bin\\thothctl-windows-amd64.exe") --installation (Resolve-Path "~\\thothii-installation.yaml") pi update --version --source build --yes --drain'); expect(windows).toHaveTextContent('& (Resolve-Path "~\\bin\\thothctl-windows-amd64.exe") --installation (Resolve-Path "~\\thothii-installation.yaml") pi update --version --source pull --image @sha256: --yes --drain'); expect(windows).toHaveTextContent(" is a placeholder. Replace it with the Pi release/version you want to install."); expect(windows).toHaveTextContent('& (Resolve-Path "~\\bin\\thothctl-windows-amd64.exe") --installation (Resolve-Path "~\\thothii-installation.yaml") pi maintenance status'); expect(windows).toHaveTextContent('& (Resolve-Path "~\\bin\\thothctl-windows-amd64.exe") --installation (Resolve-Path "~\\thothii-installation.yaml") pi logs'); expect(windows).toHaveTextContent('& (Resolve-Path "~\\bin\\thothctl-windows-amd64.exe") --installation (Resolve-Path "~\\thothii-installation.yaml") pi rollback --yes'); expect(windows).toHaveTextContent('& (Resolve-Path "~\\bin\\thothctl-windows-amd64.exe") --installation (Resolve-Path "~\\thothii-installation.yaml") pi maintenance recover --yes'); }); test("keeps the required PI_AUTH_FILE guidance in one static text node", async () => { const user = userEvent.setup(); renderManagement(); const tablist = await screen.findByRole("tablist", { name: "Pi host platform" }); await user.click(within(tablist).getByRole("tab", { name: "Linux" })); const credentialStep = screen.getByRole("heading", { name: "Set the provider credential" }).closest("li"); const guidance = credentialStep?.querySelector("p"); expect( Array.from(guidance?.childNodes ?? []).some( (node) => node.nodeType === Node.TEXT_NODE && node.textContent?.includes("PI_AUTH_FILE is a setting in the installation environment file"), ), ).toBe(true); }); 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 () => { let configured = false; const persisted = { ...readyStatus, credentials: "missing", config: { provider: "retired", model: "old-model", reasoning: "medium" }, }; server.use( http.get("/api/pi-management/status", () => HttpResponse.json(configured ? { ...readyStatus, credentials: "missing", config: { provider: "zai", model: "glm-5.2", reasoning: "medium" }, checkedAt: "2026-08-05T10:05:00.000Z", } : persisted)), http.put("/api/pi-management/config", () => { configured = true; return HttpResponse.json({ provider: "zai", model: "glm-5.2", reasoning: "medium", 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(); });