Files
2026-07-27 12:42:18 +05:30

98 lines
3.0 KiB
TypeScript

import { test, expect } from "bun:test";
import { createContext } from "@wrnexus/core";
import {
createTelemetryPipeline,
createTracker,
type ErrorEvent,
type ErrorSink,
} from "../src/index.ts";
function collectingSink(): ErrorSink & { events: ErrorEvent[] } {
const events: ErrorEvent[] = [];
return { name: "collect", events, capture: (e) => void events.push(e) };
}
test("capture fans out to sinks with context + timestamp", async () => {
const sink = collectingSink();
const tracker = createTracker({ sinks: [sink], now: () => 123 });
await tracker.capture(new Error("boom"), { userId: "u1" });
expect(sink.events.length).toBe(1);
expect(sink.events[0]!.error.message).toBe("boom");
expect(sink.events[0]!.context.userId).toBe("u1");
expect(sink.events[0]!.timestamp).toBe(123);
});
test("non-Error values are wrapped", async () => {
const sink = collectingSink();
const tracker = createTracker({ sinks: [sink] });
await tracker.capture("just a string");
expect(sink.events[0]!.error.message).toBe("just a string");
});
test("beforeSend can scrub or drop events", async () => {
const sink = collectingSink();
const tracker = createTracker({
sinks: [sink],
beforeSend: (e) => (e.context.secret ? null : e), // drop events tagged secret
});
await tracker.capture(new Error("a"), { secret: true });
await tracker.capture(new Error("b"));
expect(sink.events.map((e) => e.error.message)).toEqual(["b"]);
});
test("middleware captures a thrown request error and re-throws it", async () => {
const sink = collectingSink();
const tracker = createTracker({ sinks: [sink] });
const url = new URL("http://x/api/boom");
const ctx = createContext(new Request(url, { method: "POST" }), url);
await expect(
tracker.middleware()(ctx, () => {
throw new Error("downstream");
}),
).rejects.toThrow("downstream");
expect(sink.events[0]!.context).toMatchObject({ method: "POST", path: "/api/boom" });
});
test("a failing sink never breaks capture", async () => {
const bad: ErrorSink = {
capture: () => {
throw new Error("sink down");
},
};
const good = collectingSink();
const tracker = createTracker({ sinks: [bad, good] });
await tracker.capture(new Error("x")); // must not throw
expect(good.events.length).toBe(1);
});
test("telemetry requeues a failed batch only once when multiple sinks fail", async () => {
let failures = 0;
const pipeline = createTelemetryPipeline({
batchSize: 2,
flushIntervalMs: 0,
sinks: [
{
send: () => {
throw new Error("one");
},
},
{
send: () => {
throw new Error("two");
},
},
],
onSinkError: () => {
failures++;
},
});
await pipeline.emit({ kind: "event", name: "one", attributes: {} });
await pipeline.emit({ kind: "event", name: "two", attributes: {} });
expect(failures).toBe(2);
expect(pipeline.size()).toBe(2);
await pipeline.close();
expect(pipeline.size()).toBe(2);
});