fix(HAI-018): complete Step 2 — add client-side retry for fetchTaskDetail
- Added withRetry wrapper in api.ts (1 retry, 200ms delay) - fetchTaskDetail now retries transient 500s transparently - TaskCard shows 'Failed to load task details' instead of raw error - Added api.test.ts with success, retry-success, and exhaustion tests
This commit is contained in:
65
packages/dashboard/app/api.test.ts
Normal file
65
packages/dashboard/app/api.test.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import { fetchTaskDetail } from "./api";
|
||||
import type { TaskDetail } from "@hai/core";
|
||||
|
||||
const FAKE_DETAIL: TaskDetail = {
|
||||
id: "HAI-001",
|
||||
description: "Test",
|
||||
column: "in-progress",
|
||||
dependencies: [],
|
||||
steps: [],
|
||||
currentStep: 0,
|
||||
log: [],
|
||||
createdAt: "2026-01-01T00:00:00.000Z",
|
||||
updatedAt: "2026-01-01T00:00:00.000Z",
|
||||
prompt: "# HAI-001",
|
||||
};
|
||||
|
||||
function mockFetchResponse(ok: boolean, body: unknown, status = ok ? 200 : 500) {
|
||||
return Promise.resolve({
|
||||
ok,
|
||||
status,
|
||||
json: () => Promise.resolve(body),
|
||||
} as Response);
|
||||
}
|
||||
|
||||
describe("fetchTaskDetail", () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers({ shouldAdvanceTime: true });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
globalThis.fetch = originalFetch;
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it("returns data on first success", async () => {
|
||||
globalThis.fetch = vi.fn().mockReturnValue(mockFetchResponse(true, FAKE_DETAIL));
|
||||
|
||||
const result = await fetchTaskDetail("HAI-001");
|
||||
|
||||
expect(result.id).toBe("HAI-001");
|
||||
expect(globalThis.fetch).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("retries once on failure then succeeds", async () => {
|
||||
globalThis.fetch = vi.fn()
|
||||
.mockReturnValueOnce(mockFetchResponse(false, { error: "Transient error" }))
|
||||
.mockReturnValueOnce(mockFetchResponse(true, FAKE_DETAIL));
|
||||
|
||||
const result = await fetchTaskDetail("HAI-001");
|
||||
|
||||
expect(result.id).toBe("HAI-001");
|
||||
expect(globalThis.fetch).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("throws after retry exhaustion", async () => {
|
||||
globalThis.fetch = vi.fn()
|
||||
.mockReturnValue(mockFetchResponse(false, { error: "Server error" }));
|
||||
|
||||
await expect(fetchTaskDetail("HAI-001")).rejects.toThrow("Server error");
|
||||
expect(globalThis.fetch).toHaveBeenCalledTimes(2); // initial + 1 retry
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user