139 lines
4.7 KiB
TypeScript
139 lines
4.7 KiB
TypeScript
import { act, renderHook, waitFor } from "@testing-library/react";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { callOperation } from "../connection/api.js";
|
|
import { initialDemoTimelineState } from "../demo/timeline/reducer.js";
|
|
import { usePresentationTargetStatus } from "./usePresentationTargetStatus.js";
|
|
|
|
vi.mock("../connection/api.js", () => ({ callOperation: vi.fn() }));
|
|
const mockedCallOperation = vi.mocked(callOperation);
|
|
const target = { mode: "live" as const, target: "http://127.0.0.1:8765/rpc", source: "default" as const };
|
|
const replayState = {
|
|
...initialDemoTimelineState,
|
|
mode: "replay" as const,
|
|
phase: "paused" as const,
|
|
};
|
|
|
|
beforeEach(() => mockedCallOperation.mockReset());
|
|
|
|
describe("usePresentationTargetStatus", () => {
|
|
it("marks live target ready after workflow health succeeds", async () => {
|
|
mockedCallOperation.mockResolvedValue({
|
|
ok: true as const,
|
|
operation: "workflow.health",
|
|
label: "Health",
|
|
interpreted: { status: "ok", storeRoot: "store" },
|
|
exchange: { request: {}, response: {} },
|
|
equivalentCli: "uv run wf status",
|
|
durationMs: 2,
|
|
});
|
|
|
|
const { result } = renderHook(() =>
|
|
usePresentationTargetStatus(
|
|
target,
|
|
initialDemoTimelineState,
|
|
),
|
|
);
|
|
|
|
await waitFor(() => expect(result.current.status.kind).toBe("ready"));
|
|
expect(result.current.liveTargetReady).toBe(true);
|
|
});
|
|
|
|
it("does not label a completed live run as active", async () => {
|
|
mockedCallOperation.mockResolvedValue({
|
|
ok: true as const,
|
|
operation: "workflow.health",
|
|
label: "Health",
|
|
interpreted: { status: "ok", storeRoot: "store" },
|
|
exchange: { request: {}, response: {} },
|
|
equivalentCli: "uv run wf status",
|
|
durationMs: 2,
|
|
});
|
|
|
|
const { result } = renderHook(() =>
|
|
usePresentationTargetStatus(target, {
|
|
...initialDemoTimelineState,
|
|
phase: "completed",
|
|
}),
|
|
);
|
|
|
|
await waitFor(() => expect(result.current.status.kind).toBe("ready"));
|
|
expect(result.current.status.label).toBe("Live target ready");
|
|
});
|
|
|
|
it("falls back to replay when health fails", async () => {
|
|
mockedCallOperation.mockResolvedValue({
|
|
ok: false as const,
|
|
error: { code: "upstream_unreachable", message: "connection refused" },
|
|
exchange: { request: {}, response: {} },
|
|
});
|
|
|
|
const { result } = renderHook(() =>
|
|
usePresentationTargetStatus(
|
|
target,
|
|
initialDemoTimelineState,
|
|
),
|
|
);
|
|
|
|
await waitFor(() => expect(result.current.status.kind).toBe("failed"));
|
|
expect(result.current.status.label).toBe("Replay fallback");
|
|
expect(result.current.liveTargetReady).toBe(false);
|
|
});
|
|
|
|
it("keeps live target readiness visible while direct replay is active", async () => {
|
|
mockedCallOperation.mockResolvedValue({
|
|
ok: true as const,
|
|
operation: "workflow.health",
|
|
label: "Health",
|
|
interpreted: { status: "ok", storeRoot: "store" },
|
|
exchange: { request: {}, response: {} },
|
|
equivalentCli: "uv run wf status",
|
|
durationMs: 2,
|
|
});
|
|
|
|
const { result } = renderHook(() =>
|
|
usePresentationTargetStatus(target, replayState),
|
|
);
|
|
|
|
await waitFor(() => expect(result.current.liveTargetReady).toBe(true));
|
|
expect(result.current.status.kind).toBe("ready");
|
|
expect(result.current.status.label).toBe("Live target ready");
|
|
});
|
|
|
|
it("does not probe when health probing is disabled", async () => {
|
|
const { result } = renderHook(() =>
|
|
usePresentationTargetStatus(target, replayState, false),
|
|
);
|
|
|
|
await waitFor(() => expect(result.current.status.kind).toBe("replay"));
|
|
expect(mockedCallOperation).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("retries health without changing replay playback", async () => {
|
|
mockedCallOperation
|
|
.mockResolvedValueOnce({
|
|
ok: false as const,
|
|
error: { code: "upstream_unreachable", message: "connection refused" },
|
|
exchange: { request: {}, response: {} },
|
|
})
|
|
.mockResolvedValueOnce({
|
|
ok: true as const,
|
|
operation: "workflow.health",
|
|
label: "Health",
|
|
interpreted: { status: "ok", storeRoot: "store" },
|
|
exchange: { request: {}, response: {} },
|
|
equivalentCli: "uv run wf status",
|
|
durationMs: 2,
|
|
});
|
|
|
|
const { result } = renderHook(() =>
|
|
usePresentationTargetStatus(target, initialDemoTimelineState),
|
|
);
|
|
|
|
await waitFor(() => expect(result.current.status.kind).toBe("failed"));
|
|
act(() => result.current.retryHealth());
|
|
await waitFor(() => expect(result.current.liveTargetReady).toBe(true));
|
|
expect(result.current.status.kind).toBe("ready");
|
|
expect(mockedCallOperation).toHaveBeenCalledTimes(2);
|
|
});
|
|
});
|