/** * Unit: W3C traceparent capture/resume round-trip (the API→worker * propagation primitive — the dispatcher stores `captureTraceparent()` in * the job metadata; the processor `resumeTraceparent()`s it). */ import { describe, expect, it } from "vitest"; import { context, trace } from "@opentelemetry/api"; import { AsyncLocalStorageContextManager } from "@opentelemetry/context-async-hooks"; import { captureTraceparent, resumeTraceparent, parseTraceparent, buildTraceparent, metadataTraceparent, registerTraceparentPropagator, traceparentPropagator } from "../traceparent.js"; registerTraceparentPropagator(); // The API's DEFAULT context manager is a no-op (context.with runs the fn // ambient) — register the AsyncLocalStorage manager so a RESUMED context is // actually active inside the callback. The no-op contract under test is the // propagator/tracer side, not the context storage. context.setGlobalContextManager(new AsyncLocalStorageContextManager()); const TRACE_ID = "4bf92f3577b34da6a3ce929d0e0e4736"; const SPAN_ID = "00f067aa0ba902b7"; describe("parseTraceparent", () => { it("parses a valid v00 header", () => { expect(parseTraceparent(`00-${TRACE_ID}-${SPAN_ID}-01`)).toEqual({ traceId: TRACE_ID, spanId: SPAN_ID, flags: 1 }); }); it("rejects bad versions, short ids, and all-zero ids", () => { expect(parseTraceparent(`01-${TRACE_ID}-${SPAN_ID}-01`)).toBeNull(); expect(parseTraceparent(`00-${TRACE_ID}-abc-01`)).toBeNull(); expect(parseTraceparent(`00-00000000000000000000000000000000-${SPAN_ID}-01`)).toBeNull(); expect(parseTraceparent("not-a-header")).toBeNull(); }); }); describe("captureTraceparent / resumeTraceparent round-trip", () => { it("captures the active span context under an ambient span", () => { const tracer = trace.getTracer("test"); const captured: string | undefined = tracer .startActiveSpan("api-request", (span) => { const tp = captureTraceparent(); span.end(); return tp; }); // With the no-op tracer (no SDK registered in this test process) the // captured value is undefined — assert the no-op contract explicitly. expect(captured).toBeUndefined(); }); it("resume re-extracts the SAME trace+span ids the header carried", () => { const tp = buildTraceparent(TRACE_ID, SPAN_ID, true); let seen: { traceId?: string | undefined; spanId?: string | undefined } = {}; resumeTraceparent(tp, () => { const sc = trace.getSpanContext(context.active()); seen = { traceId: sc?.traceId, spanId: sc?.spanId }; }); expect(seen.traceId).toBe(TRACE_ID); expect(seen.spanId).toBe(SPAN_ID); }); it("resume with an invalid/absent header runs ambient (no-op contract)", () => { let ran = 0; resumeTraceparent(undefined, () => { ran++; expect(trace.getSpanContext(context.active())).toBeUndefined(); }); resumeTraceparent("garbage", () => { ran++; }); expect(ran).toBe(2); }); it("a resumed parent propagates INTO a new span's context (child-parent link)", () => { const tp = buildTraceparent(TRACE_ID, SPAN_ID, true); let childTraceId: string | undefined; resumeTraceparent(tp, () => { childTraceId = trace .getTracer("test") .startActiveSpan("worker-job", (span) => { const sc = trace.getSpanContext(context.active()); span.end(); return sc?.traceId; }); }); expect(childTraceId).toBe(TRACE_ID); }); }); describe("metadataTraceparent (the BullMQ job-metadata carrier)", () => { it("extracts a valid traceparent from a job metadata map", () => { const tp = buildTraceparent(TRACE_ID, SPAN_ID); expect(metadataTraceparent({ traceparent: tp, other: 1 })).toBe(tp); }); it("rejects missing/null/invalid carriers", () => { expect(metadataTraceparent(null)).toBeUndefined(); expect(metadataTraceparent({})).toBeUndefined(); expect(metadataTraceparent({ traceparent: "nope" })).toBeUndefined(); expect(metadataTraceparent({ traceparent: 42 })).toBeUndefined(); }); }); describe("the W3C propagator itself", () => { it("inject→extract is a lossless round-trip", () => { const carrierIn: Record = {}; // Build a context with a span context via the propagator's own extract // (no SDK needed): the context we extract IS the round-trip subject. const original = traceparentPropagator.extract( context.active(), { traceparent: buildTraceparent(TRACE_ID, SPAN_ID, true) } ); traceparentPropagator.inject(original, carrierIn); expect(carrierIn["traceparent"]).toBe(`00-${TRACE_ID}-${SPAN_ID}-01`); const again = traceparentPropagator.extract(context.active(), { traceparent: carrierIn["traceparent"] ?? "" }); const sc = trace.getSpanContext(again); expect(sc?.traceId).toBe(TRACE_ID); expect(sc?.spanId).toBe(SPAN_ID); expect(sc?.isRemote).toBe(true); }); });