import { describe, it, expect } from "vitest"; import { parseRecord, isFailure, groupByCorrelation, buildCorrelationTree, type TelemetryRecord, } from "../telemetry"; const env = (messageId: string, causationId: string, correlationId = "c1") => ({ messageId, causationId, correlationId, }); describe("parseRecord", () => { it("parses a JSON string with a kind", () => { const r = parseRecord('{"kind":"action.completed","durationMs":3}'); expect(r?.kind).toBe("action.completed"); expect(r?.durationMs).toBe(3); }); it("accepts an already-parsed object", () => { expect(parseRecord({ kind: "event.published" })?.kind).toBe("event.published"); }); it("returns null for invalid json, missing kind, or non-object", () => { expect(parseRecord("not json")).toBeNull(); expect(parseRecord('{"no":"kind"}')).toBeNull(); expect(parseRecord(42)).toBeNull(); }); }); describe("isFailure", () => { it("flags failure kinds + anything carrying an error", () => { expect(isFailure({ kind: "action.failed" })).toBe(true); expect(isFailure({ kind: "dlq.recorded" })).toBe(true); expect(isFailure({ kind: "external.call.failed" })).toBe(true); expect(isFailure({ kind: "action.completed", error: { name: "E", message: "x" } })).toBe(true); }); it("does not flag success kinds", () => { expect(isFailure({ kind: "action.completed" })).toBe(false); expect(isFailure({ kind: "event.published" })).toBe(false); }); }); describe("groupByCorrelation", () => { it("buckets by correlationId; envelope-less under ''", () => { const recs: TelemetryRecord[] = [ { kind: "a", envelope: env("m1", "m1", "c1") }, { kind: "b", envelope: env("m2", "m1", "c1") }, { kind: "c", envelope: env("m3", "m3", "c2") }, { kind: "hook.step" }, ]; const g = groupByCorrelation(recs); expect(g.get("c1")).toHaveLength(2); expect(g.get("c2")).toHaveLength(1); expect(g.get("")).toHaveLength(1); }); }); describe("buildCorrelationTree", () => { it("roots the chain head and nests children by causationId", () => { const recs: TelemetryRecord[] = [ { kind: "action.dispatched", envelope: env("m1", "m1") }, // head (self-caused) { kind: "event.published", envelope: env("m2", "m1") }, // child of m1 { kind: "listener.fired", envelope: env("m3", "m2") }, // child of m2 ]; const roots = buildCorrelationTree(recs); expect(roots).toHaveLength(1); expect(roots[0]!.record.kind).toBe("action.dispatched"); expect(roots[0]!.children[0]!.record.kind).toBe("event.published"); expect(roots[0]!.children[0]!.children[0]!.record.kind).toBe("listener.fired"); }); it("treats an orphan (parent not present) as a root, and skips envelope-less records", () => { const recs: TelemetryRecord[] = [ { kind: "orphan", envelope: env("m9", "m-missing") }, { kind: "no-envelope" }, ]; const roots = buildCorrelationTree(recs); expect(roots).toHaveLength(1); expect(roots[0]!.record.kind).toBe("orphan"); }); it("keeps the first record per messageId (multi-stage dispatch)", () => { const recs: TelemetryRecord[] = [ { kind: "action.dispatched", envelope: env("m1", "m1") }, { kind: "action.completed", envelope: env("m1", "m1") }, ]; const roots = buildCorrelationTree(recs); expect(roots).toHaveLength(1); expect(roots[0]!.record.kind).toBe("action.dispatched"); }); });