186 lines
5.2 KiB
TypeScript
186 lines
5.2 KiB
TypeScript
import { describe, it, mock, beforeEach, afterEach } from "node:test";
|
|
import assert from "node:assert";
|
|
import { FetchController } from "./fetch-controller";
|
|
import { Logger } from "../tracing/logger";
|
|
import { SyncResetError } from "./sync-reset-error";
|
|
import { sleep } from "../utils/sleep";
|
|
|
|
describe("FetchController", () => {
|
|
const createMockFetch = (shouldSleep: boolean) =>
|
|
mock.fn(async () => {
|
|
if (shouldSleep) {
|
|
await sleep(30);
|
|
}
|
|
return Promise.resolve(new Response("OK", { status: 200 }));
|
|
});
|
|
|
|
beforeEach(() => {
|
|
mock.timers.enable({ apis: ["setTimeout"] });
|
|
});
|
|
|
|
afterEach(() => {
|
|
mock.timers.reset();
|
|
});
|
|
|
|
it("should allow fetch when canFetch is true", async () => {
|
|
const logger = new Logger();
|
|
const controller = new FetchController(true, logger);
|
|
const mockFetch = createMockFetch(false);
|
|
const controlledFetch = controller.getControlledFetchImplementation(
|
|
logger,
|
|
mockFetch
|
|
);
|
|
|
|
const response = await controlledFetch("http://example.com");
|
|
|
|
assert.strictEqual(await response.text(), "OK");
|
|
assert.strictEqual(mockFetch.mock.calls.length, 1);
|
|
});
|
|
|
|
it("should block fetch until canFetch becomes true", async () => {
|
|
const logger = new Logger();
|
|
const controller = new FetchController(false, logger);
|
|
const mockFetch = createMockFetch(true);
|
|
const controlledFetch = controller.getControlledFetchImplementation(
|
|
logger,
|
|
mockFetch
|
|
);
|
|
|
|
const fetchPromise = controlledFetch("http://example.com");
|
|
assert.strictEqual(mockFetch.mock.calls.length, 0);
|
|
|
|
controller.canFetch = true;
|
|
await Promise.resolve();
|
|
mock.timers.tick(30);
|
|
|
|
const response = await fetchPromise;
|
|
assert.strictEqual(await response.text(), "OK");
|
|
assert.strictEqual(mockFetch.mock.calls.length, 1);
|
|
});
|
|
|
|
it("should reject during reset", async () => {
|
|
const logger = new Logger();
|
|
const controller = new FetchController(true, logger);
|
|
const mockFetch = createMockFetch(true);
|
|
const controlledFetch = controller.getControlledFetchImplementation(
|
|
logger,
|
|
mockFetch
|
|
);
|
|
|
|
const firstRequest = controlledFetch("http://example.com");
|
|
assert.strictEqual(mockFetch.mock.calls.length, 1);
|
|
|
|
controller.startReset();
|
|
|
|
const secondRequest = controlledFetch("http://example.com");
|
|
|
|
await assert.rejects(
|
|
firstRequest,
|
|
(error: unknown) => error instanceof SyncResetError
|
|
);
|
|
await assert.rejects(
|
|
secondRequest,
|
|
(error: unknown) => error instanceof SyncResetError
|
|
);
|
|
assert.strictEqual(mockFetch.mock.calls.length, 1);
|
|
});
|
|
|
|
it("should allow fetch after reset finishes", async () => {
|
|
const logger = new Logger();
|
|
const controller = new FetchController(true, logger);
|
|
const mockFetch = createMockFetch(false);
|
|
const controlledFetch = controller.getControlledFetchImplementation(
|
|
logger,
|
|
mockFetch
|
|
);
|
|
|
|
controller.startReset();
|
|
controller.finishReset();
|
|
|
|
const response = await controlledFetch("http://example.com");
|
|
assert.strictEqual(await response.text(), "OK");
|
|
});
|
|
|
|
it("should throw when finishing reset without starting", () => {
|
|
const logger = new Logger();
|
|
const controller = new FetchController(true, logger);
|
|
|
|
assert.throws(
|
|
() => controller.finishReset(),
|
|
(error: unknown) =>
|
|
error instanceof Error &&
|
|
error.message === "Cannot finish reset when not resetting"
|
|
);
|
|
});
|
|
|
|
it("should defer canFetch changes during reset", async () => {
|
|
const logger = new Logger();
|
|
const controller = new FetchController(false, logger);
|
|
const mockFetch = createMockFetch(true);
|
|
const controlledFetch = controller.getControlledFetchImplementation(
|
|
logger,
|
|
mockFetch
|
|
);
|
|
|
|
controller.startReset();
|
|
controller.canFetch = true;
|
|
|
|
await assert.rejects(
|
|
async () => controlledFetch("http://example.com"),
|
|
(error: unknown) => error instanceof SyncResetError
|
|
);
|
|
|
|
controller.finishReset();
|
|
|
|
const fetchPromise = controlledFetch("http://example.com");
|
|
mock.timers.tick(30);
|
|
|
|
const response = await fetchPromise;
|
|
assert.strictEqual(await response.text(), "OK");
|
|
});
|
|
|
|
it("should handle different input types", async () => {
|
|
const logger = new Logger();
|
|
const controller = new FetchController(true, logger);
|
|
const mockFetch = createMockFetch(false);
|
|
const controlledFetch = controller.getControlledFetchImplementation(
|
|
logger,
|
|
mockFetch
|
|
);
|
|
|
|
await controlledFetch("http://example.com");
|
|
await controlledFetch(new URL("http://example.com"));
|
|
await controlledFetch(
|
|
new Request("http://example.com", { method: "POST" })
|
|
);
|
|
|
|
assert.strictEqual(mockFetch.mock.calls.length, 3);
|
|
});
|
|
|
|
it("should handle fetch errors", async () => {
|
|
const logger = new Logger();
|
|
const controller = new FetchController(true, logger);
|
|
const mockFetch = mock.fn(async () => {
|
|
throw new Error("Network error");
|
|
});
|
|
const controlledFetch = controller.getControlledFetchImplementation(
|
|
logger,
|
|
mockFetch
|
|
);
|
|
|
|
await assert.rejects(
|
|
async () => controlledFetch("http://example.com"),
|
|
(error: unknown) =>
|
|
error instanceof Error && error.message === "Network error"
|
|
);
|
|
});
|
|
|
|
it("should not create unhandled rejection on reset with no waiting fetches", async () => {
|
|
const logger = new Logger();
|
|
const controller = new FetchController(true, logger);
|
|
|
|
controller.startReset();
|
|
mock.timers.tick(10);
|
|
controller.finishReset();
|
|
});
|
|
});
|