83 lines
2.8 KiB
TypeScript
83 lines
2.8 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { createElement, type ReactNode } from "react";
|
|
import { act, cleanup, renderHook } from "@testing-library/react";
|
|
import { StateProvider } from "@/components/state-provider";
|
|
|
|
const requestMock = vi.fn();
|
|
|
|
vi.mock("@/lib/ws/connection", () => ({
|
|
getWebSocketClient: () => ({ request: requestMock }),
|
|
}));
|
|
|
|
// listWorkspaceTaskPRs is only used by useWorkspacePRs (not under test
|
|
// here). Stub it so the module import doesn't fail in jsdom.
|
|
vi.mock("@/lib/api/domains/github-api", () => ({
|
|
listWorkspaceTaskPRs: vi.fn().mockResolvedValue(null),
|
|
}));
|
|
|
|
import { useTaskPR } from "./use-task-pr";
|
|
|
|
function wrapper({ children }: { children: ReactNode }) {
|
|
return createElement(StateProvider, null, children);
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.useFakeTimers();
|
|
requestMock.mockReset();
|
|
});
|
|
afterEach(() => {
|
|
cleanup();
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
describe("useTaskPR — permanent flag", () => {
|
|
// The dominant production signal in the SyncWatchesBatched storm was the
|
|
// frontend polling `github.task_pr.sync` every 5s for tasks whose repos
|
|
// were deleted/inaccessible. The backend now returns `permanent: true`
|
|
// on those responses; the hook must stop the retry interval cold.
|
|
it("stops the 5s retry interval when the backend reports permanent: true", async () => {
|
|
requestMock.mockResolvedValue({ prs: [], permanent: true });
|
|
|
|
renderHook(() => useTaskPR("task-1"), { wrapper });
|
|
|
|
// Initial freshness sync fires synchronously from the mount effect.
|
|
// Flush the resolved promise so the permanent flag is applied before
|
|
// the interval would otherwise fire.
|
|
await act(async () => {
|
|
await vi.advanceTimersByTimeAsync(0);
|
|
});
|
|
expect(requestMock).toHaveBeenCalledTimes(1);
|
|
|
|
// Advance well past several retry windows. If the permanent
|
|
// short-circuit regressed, this would burst 5-6 additional calls
|
|
// into requestMock.
|
|
await act(async () => {
|
|
await vi.advanceTimersByTimeAsync(5_000 * 6);
|
|
});
|
|
expect(requestMock).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
// Without permanent, the existing retry cadence must still kick in so
|
|
// tasks waiting on a freshly-pushed branch still get their PR detected.
|
|
it("retries every 5s when permanent is absent and no PR is in the store", async () => {
|
|
requestMock.mockResolvedValue({ prs: [] });
|
|
|
|
renderHook(() => useTaskPR("task-1"), { wrapper });
|
|
|
|
await act(async () => {
|
|
await vi.advanceTimersByTimeAsync(0);
|
|
});
|
|
expect(requestMock).toHaveBeenCalledTimes(1);
|
|
|
|
await act(async () => {
|
|
await vi.advanceTimersByTimeAsync(5_000);
|
|
});
|
|
expect(requestMock).toHaveBeenCalledTimes(2);
|
|
|
|
await act(async () => {
|
|
await vi.advanceTimersByTimeAsync(5_000);
|
|
});
|
|
expect(requestMock).toHaveBeenCalledTimes(3);
|
|
});
|
|
});
|