sflow
Version:
sflow is a powerful and highly-extensible library designed for processing and manipulating streams of data effortlessly. Inspired by the functional programming paradigm, it provides a rich set of utilities for transforming streams, including chunking, fil
127 lines (117 loc) • 3.28 kB
text/typescript
import { retry } from "./retry";
import { sflow } from "./sflow";
describe("retry", () => {
it("returns result on first success", async () => {
const fn = retry((_e, _a, fn, x) => fn(x), async (x: number) => x * 2);
expect(await fn(5)).toBe(10);
});
it("retries on transient errors", async () => {
let calls = 0;
const fn = retry(
(_error, attempt, fn) => {
if (attempt > 5) throw _error;
return fn();
},
async () => {
calls++;
if (calls < 3) throw new Error("transient");
return "ok";
},
);
expect(await fn()).toBe("ok");
expect(calls).toBe(3);
});
it("stops retrying when handler throws", async () => {
let calls = 0;
const fn = retry(
(error) => { throw error; },
async () => {
calls++;
throw new Error("permanent");
},
);
await expect(fn()).rejects.toThrow("permanent");
expect(calls).toBe(1);
});
it("passes attempt count correctly (1-based)", async () => {
const attempts: number[] = [];
let calls = 0;
const fn = retry(
(_error, attempt, fn) => {
attempts.push(attempt);
if (attempt >= 3) throw new Error("give up");
return fn();
},
async () => {
calls++;
throw new Error("fail");
},
);
await expect(fn()).rejects.toThrow("give up");
expect(attempts).toEqual([1, 2, 3]);
expect(calls).toBe(3);
});
it("passes original args to error handler", async () => {
const receivedArgs: [string, number][] = [];
let calls = 0;
const fn = retry(
(_error, _attempt, fn, a, b) => {
receivedArgs.push([a, b]);
if (calls >= 2) throw new Error("stop");
return fn(a, b);
},
async (_a: string, _b: number) => {
calls++;
throw new Error("fail");
},
);
await expect(fn("hello", 42)).rejects.toThrow("stop");
expect(receivedArgs[0]).toEqual(["hello", 42]);
});
it("works with sync functions", async () => {
let calls = 0;
const fn = retry(
(_error, attempt, fn, x) => {
if (attempt > 5) throw _error;
return fn(x);
},
(x: number) => {
calls++;
if (calls < 2) throw new Error("transient");
return x + 1;
},
);
expect(await fn(10)).toBe(11);
expect(calls).toBe(2);
});
it("retries with different args", async () => {
const fn = retry(
(error, attempt, fn, x) => {
if (attempt > 5) throw error;
return fn(x + 1);
},
async (x: number) => {
if (x < 5) throw new Error("too small");
return x;
},
);
expect(await fn(3)).toBe(5);
});
it("works inside sflow().map()", async () => {
const callCounts = new Map<number, number>();
const fn = retry(
(error, attempt, fn, x) => {
if (attempt > 2) throw error;
return fn(x);
},
async (x: number) => {
const count = (callCounts.get(x) ?? 0) + 1;
callCounts.set(x, count);
if (x === 2 && count < 2) throw new Error("transient");
return x * 10;
},
);
const result = await sflow([1, 2, 3]).map(fn).toArray();
expect(result).toEqual([10, 20, 30]);
});
});