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); });