57 lines
2.3 KiB
TypeScript
57 lines
2.3 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
|
|
import { retryTransient } from "./sync-retry";
|
|
|
|
const label = "Asset catalog/materials/UI_ItemIcon_121767.png";
|
|
const quorumError = new Error('Internal error: Could not reach quorum of 2 (sets=Some(1)). 1 of 2 request succeeded, others returned errors: ["5f70d8ec32eade44: Network error: Not connected: 5f70d8ec32eade44"]');
|
|
|
|
afterEach(() => vi.useRealTimers());
|
|
|
|
describe("catalog sync retries", () => {
|
|
it.each([quorumError, new Error("Network error: Not connected: 5f70d8ec32eade44"), new Error("Request timed out")])("recovers from %s", async (cause) => {
|
|
vi.useFakeTimers();
|
|
const task = vi.fn().mockRejectedValueOnce(cause).mockResolvedValue("uploaded");
|
|
const result = retryTransient(label, task);
|
|
|
|
await vi.advanceTimersByTimeAsync(999);
|
|
expect(task).toHaveBeenCalledTimes(1);
|
|
await vi.advanceTimersByTimeAsync(1);
|
|
await expect(result).resolves.toBe("uploaded");
|
|
expect(task).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it("stops after three failed attempts and preserves the asset and cause", async () => {
|
|
vi.useFakeTimers();
|
|
const task = vi.fn().mockRejectedValue(quorumError);
|
|
const result = expect(retryTransient(label, task)).rejects.toMatchObject({
|
|
message: `${label}: ${quorumError.message}`,
|
|
cause: quorumError,
|
|
});
|
|
|
|
await vi.advanceTimersByTimeAsync(1_000);
|
|
expect(task).toHaveBeenCalledTimes(2);
|
|
await vi.advanceTimersByTimeAsync(2_000);
|
|
await result;
|
|
expect(task).toHaveBeenCalledTimes(3);
|
|
});
|
|
|
|
it("fails immediately on a permanent storage error", async () => {
|
|
const task = vi.fn().mockRejectedValue(new Error("AccessDenied"));
|
|
await expect(retryTransient(label, task)).rejects.toThrow(`${label}: AccessDenied`);
|
|
expect(task).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("does not retry after cancellation during backoff", async () => {
|
|
vi.useFakeTimers();
|
|
const controller = new AbortController();
|
|
const task = vi.fn().mockRejectedValue(quorumError);
|
|
const result = expect(retryTransient(label, task, controller.signal)).rejects.toMatchObject({ name: "AbortError" });
|
|
|
|
await vi.advanceTimersByTimeAsync(0);
|
|
controller.abort();
|
|
await vi.advanceTimersByTimeAsync(1_000);
|
|
await result;
|
|
expect(task).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|