98 lines
3.0 KiB
TypeScript
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);
|
|
});
|