Files
fusion/packages/dashboard/app/hooks/__tests__/useDevServerLogs.resume-instrumentation.test.ts
Fusion (runfusion.ai) 854045f430 feat(FN-5416): instrument SSE stream resume events across PR checks, dev-se
Adds structured stream-resume instrumentation to the dashboard across four SSE hooks (PR checks, dev-server logs, research, background sessions), wiring `[wake-trigger-diagnostics]` log markers and view-resume route guards so the engine can distinguish cold starts from resume paths; includes tests f

Fusion-Task-Id: FN-5416
2026-05-21 18:15:26 -07:00

79 lines
2.6 KiB
TypeScript

import { act, renderHook, waitFor } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
const recordResumeEvent = vi.fn();
const subscribeSse = vi.fn();
vi.mock("../../utils/resumeInstrumentation", () => ({
recordResumeEvent,
}));
vi.mock("../../sse-bus", () => ({
subscribeSse,
}));
vi.mock("../../api", () => ({
fetchDevServerLogHistory: vi.fn(),
getDevServerLogsStreamUrl: vi.fn((projectId?: string) => `/api/dev-server/logs/stream${projectId ? `?projectId=${projectId}` : ""}`),
}));
describe("useDevServerLogs resume instrumentation", () => {
beforeEach(() => {
recordResumeEvent.mockReset();
subscribeSse.mockReset();
});
it("emits sse-open and sse-reconnect with replay event id", async () => {
const { fetchDevServerLogHistory } = await import("../../api");
vi.mocked(fetchDevServerLogHistory)
.mockResolvedValueOnce({ lines: [{ id: 5, text: "a", stream: "stdout", timestamp: "" }], totalLines: 1 })
.mockResolvedValueOnce({ lines: [], totalLines: 1 });
let reconnect: (() => void) | undefined;
subscribeSse.mockImplementation((_url, handlers) => {
reconnect = handlers.onReconnect;
return () => {};
});
const { useDevServerLogs } = await import("../useDevServerLogs");
renderHook(({ projectId }) => useDevServerLogs(projectId, true), { initialProps: { projectId: "proj-1" } });
await waitFor(() => {
expect(recordResumeEvent).toHaveBeenCalledWith(expect.objectContaining({
view: "useDevServerLogs",
trigger: "sse-open",
sseChannel: "/api/dev-server/logs/stream?projectId=proj-1",
}));
});
act(() => reconnect?.());
expect(recordResumeEvent).toHaveBeenCalledWith(expect.objectContaining({
view: "useDevServerLogs",
trigger: "sse-reconnect",
replayFromEventId: 5,
replayAttempted: true,
}));
});
it("emits project-context-change when project id changes", async () => {
const { fetchDevServerLogHistory } = await import("../../api");
vi.mocked(fetchDevServerLogHistory).mockResolvedValue({ lines: [], totalLines: 0 });
subscribeSse.mockImplementation(() => () => {});
const { useDevServerLogs } = await import("../useDevServerLogs");
const { rerender } = renderHook(({ projectId }) => useDevServerLogs(projectId, true), {
initialProps: { projectId: "proj-1" },
});
rerender({ projectId: "proj-2" });
expect(recordResumeEvent).toHaveBeenCalledWith(expect.objectContaining({
view: "useDevServerLogs",
trigger: "project-context-change",
projectId: "proj-2",
reason: "context-version-bumped",
}));
});
});