import { describe, it, expect, vi } from "vitest"; import { withSpan } from "@/instrumentation/with-span"; import type { ITracer, ISpan, SpanOpts } from "@/instrumentation/tracer.interface"; function makeRecordingTracer() { const calls: SpanOpts[] = []; const tracer: ITracer = { startSpan: vi.fn(async (opts, fn) => { calls.push(opts); const span: ISpan = { setAttribute: () => {}, setStatus: () => {} }; return fn(span); }), }; return { tracer, calls }; } describe("withSpan", () => { it("wraps fn with a span using static opts", async () => { const { tracer, calls } = makeRecordingTracer(); const fn = async (a: number, b: number) => a + b; const wrapped = withSpan(tracer, { name: "test.add", op: "use-case" }, fn); const result = await wrapped(2, 3); expect(result).toBe(5); expect(calls).toHaveLength(1); expect(calls[0]).toEqual({ name: "test.add", op: "use-case" }); }); it("wraps fn with span opts derived from args (function form)", async () => { const { tracer, calls } = makeRecordingTracer(); const fn = async (id: string) => `result-${id}`; const wrapped = withSpan( tracer, ([id]) => ({ name: "test.byId", op: "repository", attributes: { id } }), fn, ); const result = await wrapped("abc"); expect(result).toBe("result-abc"); expect(calls).toHaveLength(1); expect(calls[0]).toEqual({ name: "test.byId", op: "repository", attributes: { id: "abc" }, }); }); it("propagates errors thrown by fn", async () => { const { tracer } = makeRecordingTracer(); const wrapped = withSpan(tracer, { name: "test.err" }, async () => { throw new Error("boom"); }); await expect(wrapped()).rejects.toThrow("boom"); }); it("preserves identity across multiple invocations (closure stable)", async () => { const { tracer, calls } = makeRecordingTracer(); const wrapped = withSpan(tracer, { name: "test.same" }, async (n: number) => n); await wrapped(1); await wrapped(2); expect(calls).toHaveLength(2); expect(calls.every((c) => c.name === "test.same")).toBe(true); }); });