Files
wehub-resource-sync 70bf21e064
Deploy (to testing) and Test Playground Preview Worker / Deploy Playground Preview Worker (testing) (push) Has been skipped
Deploy Workers Shared Staging / Deploy Workers Shared Staging (push) Failing after 0s
Prerelease / build (push) Has been skipped
Handle Changesets / Handle Changesets (push) Has been cancelled
Semgrep OSS scan / semgrep-oss (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:30:11 +08:00

165 lines
4.1 KiB
TypeScript

import { describe, it, vi } from "vitest";
import {
DEFAULT_RETRY_DELAY_MS,
invokeDelayFunction,
} from "../../src/lib/delay";
import { DelayFunctionError } from "../../src/lib/retries";
import type { WorkflowDynamicDelayContext } from "cloudflare:workers";
const input: WorkflowDynamicDelayContext = {
ctx: {
step: { name: "step", count: 1 },
attempt: 1,
config: { retries: { limit: 5, backoff: "constant" }, timeout: 0 },
},
error: new Error("boom"),
};
const makeLogger = () => ({ warn: vi.fn(), debug: vi.fn() });
const noTimeout =
(): ((ms: number, opts?: { signal?: AbortSignal }) => Promise<void>) => () =>
new Promise<void>(() => {});
describe("invokeDelayFunction", () => {
it("returns the value a synchronous delay function produces", async ({
expect,
}) => {
const logger = makeLogger();
const result = await invokeDelayFunction(() => 1234, input, {
timeoutMs: 5000,
wait: noTimeout(),
logger,
});
expect(result).toBe(1234);
expect(logger.warn).not.toHaveBeenCalled();
expect(logger.debug).not.toHaveBeenCalled();
});
it("awaits an asynchronous delay function", async ({ expect }) => {
const logger = makeLogger();
const result = await invokeDelayFunction(async () => 1234, input, {
timeoutMs: 5000,
wait: noTimeout(),
logger,
});
expect(result).toBe(1234);
expect(logger.warn).not.toHaveBeenCalled();
expect(logger.debug).not.toHaveBeenCalled();
});
it("throws when the delay function never resolves (timeout)", async ({
expect,
}) => {
const logger = makeLogger();
const promise = invokeDelayFunction(
() => new Promise<number>(() => {}),
input,
{
timeoutMs: 5000,
wait: () => Promise.resolve(),
logger,
}
);
await expect(promise).rejects.toThrow(
new DelayFunctionError("did not return within 5 seconds")
);
expect(logger.warn).toHaveBeenCalledTimes(1);
expect(logger.warn).toHaveBeenCalledWith(
expect.stringContaining("did not resolve"),
{ step: "step", attempt: 1 }
);
expect(logger.debug).not.toHaveBeenCalled();
});
it("throws when the delay function throws synchronously", async ({
expect,
}) => {
const logger = makeLogger();
const promise = invokeDelayFunction(
() => {
throw new Error("sync boom");
},
input,
{
timeoutMs: 5000,
wait: noTimeout(),
logger,
}
);
await expect(promise).rejects.toThrow(
new DelayFunctionError("threw an error: sync boom")
);
expect(logger.warn).toHaveBeenCalledWith(
expect.stringContaining("threw"),
expect.objectContaining({ error: "sync boom" })
);
});
it("throws when the delay function rejects", async ({ expect }) => {
const logger = makeLogger();
const promise = invokeDelayFunction(
async () => {
throw new Error("async boom");
},
input,
{
timeoutMs: 5000,
wait: noTimeout(),
logger,
}
);
await expect(promise).rejects.toThrow(
new DelayFunctionError("threw an error: async boom")
);
expect(logger.warn).toHaveBeenCalledWith(
expect.stringContaining("threw"),
expect.objectContaining({ error: "async boom" })
);
});
it("logs a debug (not a warn) when the provided signal aborts before the function resolves", async ({
expect,
}) => {
const logger = makeLogger();
const abort = new AbortController();
const promise = invokeDelayFunction(
() => new Promise<number>(() => {}),
input,
{
timeoutMs: 5000,
wait: (_ms, opts) =>
new Promise<void>((resolve) => {
opts?.signal?.addEventListener("abort", () => resolve(), {
once: true,
});
}),
signal: abort.signal,
logger,
}
);
abort.abort();
await expect(promise).resolves.toBe(DEFAULT_RETRY_DELAY_MS);
expect(logger.debug).toHaveBeenCalledTimes(1);
expect(logger.debug).toHaveBeenCalledWith(
expect.stringContaining("engine shutdown"),
{ step: "step", attempt: 1 }
);
expect(logger.warn).not.toHaveBeenCalled();
});
it("throws without a logger", async ({ expect }) => {
const promise = invokeDelayFunction(
() => {
throw new Error("sync boom");
},
input,
{
timeoutMs: 5000,
wait: noTimeout(),
}
);
await expect(promise).rejects.toThrow(DelayFunctionError);
});
});