/** * F35.1 — local model server launch state in {@link useAgents}. * * Drives the hook behind the real {@link DIProvider} with an in-memory * {@link MockSystemGateway} to emit the two F35 domain events, and verifies they * fold into `modelServerStatusByServer` (keyed by serverId) and * `launchFailureByAgent` (keyed by agentId). * * Cases: * - `modelServerStatusChanged` → status by server id (probing → starting → ready) * - `agentLaunchFailed` → actionable failure by agent id * - `agentLaunched` clears a prior launch failure for that agent * - a `failed` status is retained per server (independent of the agent map) */ import { describe, it, expect } from "vitest"; import { act, renderHook } from "@testing-library/react"; import type { DomainEvent } from "@/domain"; import type { Gateways } from "@/ports"; import { MockSystemGateway } from "@/adapters/mock"; import { DIProvider } from "@/app/di"; import { useAgents } from "./useAgents"; const PROJECT_ID = "proj-modelserver-001"; const AGENT = "agent-oc"; const SERVER = "srv-42"; function setup() { const system = new MockSystemGateway(); const agent = { listAgents: async () => [], listLiveAgents: async () => [], }; const profile = { listProfiles: async () => [] }; const gateways = { system, agent, profile } as unknown as Gateways; const wrapper = ({ children }: { children: React.ReactNode }) => ( {children} ); const view = renderHook(() => useAgents(PROJECT_ID), { wrapper }); return { system, view }; } async function emit(system: MockSystemGateway, event: DomainEvent) { await act(async () => { system.emit(event); await Promise.resolve(); }); } describe("useAgents — local model server launch state (F35.1)", () => { it("folds modelServerStatusChanged into modelServerStatusByServer by serverId", async () => { const { system, view } = setup(); await emit(system, { type: "modelServerStatusChanged", serverId: SERVER, status: { state: "probing" }, }); expect(view.result.current.modelServerStatusByServer[SERVER]).toEqual({ state: "probing", }); await emit(system, { type: "modelServerStatusChanged", serverId: SERVER, status: { state: "starting" }, }); await emit(system, { type: "modelServerStatusChanged", serverId: SERVER, status: { state: "ready", reused: true }, }); expect(view.result.current.modelServerStatusByServer[SERVER]).toEqual({ state: "ready", reused: true, }); }); it("folds agentLaunchFailed into launchFailureByAgent by agentId", async () => { const { system, view } = setup(); await emit(system, { type: "agentLaunchFailed", agentId: AGENT, cause: "model_server", code: "SERVER_UNREACHABLE", message: "llama-server did not become ready on :8080", }); expect(view.result.current.launchFailureByAgent[AGENT]).toEqual({ cause: "model_server", code: "SERVER_UNREACHABLE", message: "llama-server did not become ready on :8080", }); }); it("a successful agentLaunched clears the agent's prior launch failure", async () => { const { system, view } = setup(); await emit(system, { type: "agentLaunchFailed", agentId: AGENT, cause: "model_server", code: "SERVER_UNREACHABLE", message: "boom", }); expect(view.result.current.launchFailureByAgent[AGENT]).toBeTruthy(); await emit(system, { type: "agentLaunched", agentId: AGENT, sessionId: "sess-1", }); expect(view.result.current.launchFailureByAgent[AGENT]).toBeUndefined(); }); it("keeps a per-server failed status independent of the agent failure map", async () => { const { system, view } = setup(); await emit(system, { type: "modelServerStatusChanged", serverId: SERVER, status: { state: "failed", code: "SPAWN", message: "binary not found" }, }); expect(view.result.current.modelServerStatusByServer[SERVER]).toEqual({ state: "failed", code: "SPAWN", message: "binary not found", }); expect(view.result.current.launchFailureByAgent[AGENT]).toBeUndefined(); }); });