import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { useRef } from "react"; import { MemoryRouter, Outlet, Route, Routes, useNavigate } from "react-router-dom"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { connectToServer, callOperation } from "../connection/api.js"; import type { ConnectResponse, RpcResponse } from "../connection/contracts.js"; import { useConsoleWorkspace } from "./context.js"; import { ConsoleWorkspace } from "./ConsoleWorkspace.js"; vi.mock("../connection/api.js", () => ({ connectToServer: vi.fn(), callOperation: vi.fn(), })); const mockedConnectToServer = vi.mocked(connectToServer); const mockedCallOperation = vi.mocked(callOperation); const successfulConnection = (target: string): ConnectResponse => ({ ok: true, connection: { status: "connected", target, serverStatus: "ok", storeRoot: "/tmp/store", durationMs: 11, }, exchange: { request: { target }, response: { status: 200 } }, equivalentCli: "uv run wf status", }); const successfulRead = (): RpcResponse => ({ ok: true, operation: "workflow.capabilities.list", label: "List capabilities", interpreted: { items: [], nextCursor: null, total: 0 }, exchange: { request: {}, response: { status: 200 } }, equivalentCli: "uv run wf cap list", durationMs: 4, }); const successfulWrite = (): RpcResponse => ({ ok: true, operation: "workflow.draft_workspaces.validate", label: "Validate draft workspace", interpreted: {}, exchange: { request: {}, response: { status: 200 } }, equivalentCli: "uv run wf draft validate draft-report", durationMs: 4, }); const deferred = () => { let resolve!: (value: T) => void; const promise = new Promise((resolvePromise) => { resolve = resolvePromise; }); return { promise, resolve }; }; const OutletProbe = () => { const workspace = useConsoleWorkspace(); const executorIdentity = useRef | null>(null); if (workspace.readExecutor && executorIdentity.current === null) { executorIdentity.current = workspace.readExecutor; } const navigate = useNavigate(); return ( <> {workspace.connectedTarget ?? "none"} {workspace.readExecutor ? "available" : "unavailable"} {workspace.writeExecutor ? "available" : "unavailable"} {workspace.readExecutor === executorIdentity.current ? "yes" : "no"} {workspace.connection.evidence.map((record) => record.id).join("|")} ); }; const renderWorkspace = (initialEntry = "/console/discover") => render( }> }> Discover route

} /> Drafts route

} />
, ); beforeEach(() => { mockedConnectToServer.mockReset(); mockedCallOperation.mockReset(); sessionStorage.clear(); }); afterEach(() => cleanup()); describe("ConsoleWorkspace", () => { it("exposes no executor and issues no reads while disconnected", () => { renderWorkspace(); expect(screen.getByTestId("executor-state")).toHaveTextContent("unavailable"); expect(screen.getByTestId("write-executor-state")).toHaveTextContent("unavailable"); expect(mockedConnectToServer).not.toHaveBeenCalled(); expect(mockedCallOperation).not.toHaveBeenCalled(); }); it("records health once, exposes one target-scoped executor, and preserves evidence across routes", async () => { mockedConnectToServer.mockResolvedValue(successfulConnection("http://one.example/rpc")); renderWorkspace(); await userEvent.click(screen.getByRole("button", { name: "Connect" })); expect(await screen.findByTestId("connected-target")).toHaveTextContent( "http://one.example/rpc", ); expect(screen.getByTestId("executor-state")).toHaveTextContent("available"); expect(screen.getByTestId("write-executor-state")).toHaveTextContent("available"); expect(screen.getByTestId("executor-stable")).toHaveTextContent("yes"); expect(screen.getAllByText("Health check")).toHaveLength(1); expect(mockedCallOperation).not.toHaveBeenCalled(); await userEvent.click(screen.getByRole("button", { name: "Navigate to drafts" })); expect(await screen.findByText("Drafts route")).toBeInTheDocument(); expect(screen.getAllByText("Health check")).toHaveLength(1); expect(screen.getByTestId("connected-target")).toHaveTextContent("http://one.example/rpc"); }); it("keeps health and read evidence ids unique across reconnects", async () => { mockedConnectToServer.mockResolvedValue(successfulConnection("http://one.example/rpc")); mockedCallOperation.mockResolvedValue(successfulRead()); renderWorkspace(); const user = userEvent.setup(); await user.click(screen.getByRole("button", { name: "Connect" })); await user.click(await screen.findByRole("button", { name: "Read capabilities" })); await waitFor(() => { expect(screen.getByTestId("evidence-ids")).toHaveTextContent( "workflow.health-0|workflow.capabilities.list-1", ); }); await user.click(screen.getByRole("button", { name: "Reconnect" })); await waitFor(() => { expect(screen.getByRole("button", { name: "Read capabilities" })).toBeEnabled(); }); await user.click(screen.getByRole("button", { name: "Read capabilities" })); await waitFor(() => { const ids = screen.getByTestId("evidence-ids").textContent?.split("|") ?? []; expect(ids).toHaveLength(4); expect(new Set(ids).size).toBe(ids.length); }); }); it("drops a late read receipt from the old connection after reconnect", async () => { const oldRead = deferred(); mockedConnectToServer .mockResolvedValueOnce(successfulConnection("http://one.example/rpc")) .mockResolvedValueOnce(successfulConnection("http://two.example/rpc")); mockedCallOperation.mockReturnValueOnce(oldRead.promise); renderWorkspace(); const user = userEvent.setup(); await user.click(screen.getByRole("button", { name: "Connect" })); await user.click(await screen.findByRole("button", { name: "Read capabilities" })); expect(mockedCallOperation).toHaveBeenCalledTimes(1); await user.click(screen.getByRole("button", { name: "Reconnect" })); await waitFor(() => { expect(screen.getByTestId("connected-target")).toHaveTextContent( "http://two.example/rpc", ); expect(screen.getByTestId("evidence-ids")).toHaveTextContent( "workflow.health-0|workflow.health-1", ); }); oldRead.resolve(successfulRead()); await waitFor(() => { expect(screen.getByTestId("evidence-ids")).toHaveTextContent( "workflow.health-0|workflow.health-1", ); }); }); it("drops a late write receipt from the old connection after reconnect", async () => { const oldWrite = deferred(); mockedConnectToServer .mockResolvedValueOnce(successfulConnection("http://one.example/rpc")) .mockResolvedValueOnce(successfulConnection("http://two.example/rpc")); mockedCallOperation.mockReturnValueOnce(oldWrite.promise); renderWorkspace(); const user = userEvent.setup(); await user.click(screen.getByRole("button", { name: "Connect" })); await user.click(await screen.findByRole("button", { name: "Validate draft" })); expect(mockedCallOperation).toHaveBeenCalledTimes(1); await user.click(screen.getByRole("button", { name: "Reconnect" })); await waitFor(() => { expect(screen.getByTestId("connected-target")).toHaveTextContent( "http://two.example/rpc", ); expect(screen.getByTestId("evidence-ids")).toHaveTextContent( "workflow.health-0|workflow.health-1", ); }); oldWrite.resolve(successfulWrite()); await waitFor(() => { expect(screen.getByTestId("evidence-ids")).toHaveTextContent( "workflow.health-0|workflow.health-1", ); }); }); it("ignores a stale health response after a newer target connects", async () => { let resolveFirst!: (response: ConnectResponse) => void; const first = new Promise((resolve) => { resolveFirst = resolve; }); mockedConnectToServer.mockReturnValueOnce(first).mockResolvedValueOnce( successfulConnection("http://two.example/rpc"), ); renderWorkspace(); const user = userEvent.setup(); const form = screen.getByLabelText("Workflow JSON-RPC URL").closest("form"); expect(form).not.toBeNull(); fireEvent.submit(form!); fireEvent.submit(form!); expect(await screen.findByTestId("connected-target")).toHaveTextContent( "http://two.example/rpc", ); resolveFirst(successfulConnection("http://one.example/rpc")); await waitFor(() => { expect(screen.getByTestId("connected-target")).toHaveTextContent( "http://two.example/rpc", ); }); expect(screen.getAllByText("Health check")).toHaveLength(1); }); });