Skip to content

Commit fe633cd

Browse files
d-csclaude
andcommitted
test(webapp): cover buildSyntheticTraceForBufferedRun terminal-state derivation
syntheticTrace.server.ts shipped without a test file; this adds one, covering the identity-field passthrough, taskIdentifier-and-spanId defaults, the three rootSpanStatus branches (QUEUED→executing, CANCELED→completed, FAILED→failed) with their isPartial/isError/isCancelled flags, the 1ms duration floor, rootStartedAt mapping, and the single-span trace shape (empty events/timelineEvents, empty linkedRunIdBySpanId, undefined overridesBySpanId/queuedDuration). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 14a83dc commit fe633cd

1 file changed

Lines changed: 149 additions & 0 deletions

File tree

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import { describe, expect, it, vi } from "vitest";
2+
3+
vi.mock("~/db.server", () => ({ prisma: {}, $replica: {} }));
4+
5+
import { buildSyntheticTraceForBufferedRun } from "~/v3/mollifier/syntheticTrace.server";
6+
import type { SyntheticRun } from "~/v3/mollifier/readFallback.server";
7+
8+
const NOW = new Date("2026-05-22T10:00:00Z");
9+
const ONE_MS_IN_NS = 1_000_000;
10+
11+
function makeSyntheticRun(overrides: Partial<SyntheticRun> = {}): SyntheticRun {
12+
return {
13+
id: "run_internal_1",
14+
friendlyId: "run_friendly_1",
15+
status: "QUEUED",
16+
cancelledAt: undefined,
17+
cancelReason: undefined,
18+
delayUntil: undefined,
19+
taskIdentifier: "hello-world",
20+
createdAt: NOW,
21+
payload: undefined,
22+
payloadType: undefined,
23+
metadata: undefined,
24+
metadataType: undefined,
25+
seedMetadata: undefined,
26+
seedMetadataType: undefined,
27+
idempotencyKey: undefined,
28+
idempotencyKeyOptions: undefined,
29+
isTest: false,
30+
depth: 0,
31+
ttl: undefined,
32+
tags: [],
33+
runTags: [],
34+
lockedToVersion: undefined,
35+
resumeParentOnCompletion: false,
36+
parentTaskRunId: undefined,
37+
traceId: "trace_1",
38+
spanId: "span_1",
39+
parentSpanId: undefined,
40+
runtimeEnvironmentId: "env_a",
41+
engine: "V2",
42+
workerQueue: undefined,
43+
queue: undefined,
44+
concurrencyKey: undefined,
45+
machinePreset: undefined,
46+
realtimeStreamsVersion: undefined,
47+
maxAttempts: undefined,
48+
maxDurationInSeconds: undefined,
49+
replayedFromTaskRunFriendlyId: undefined,
50+
annotations: undefined,
51+
traceContext: undefined,
52+
scheduleId: undefined,
53+
batchId: undefined,
54+
parentTaskRunFriendlyId: undefined,
55+
rootTaskRunFriendlyId: undefined,
56+
...overrides,
57+
};
58+
}
59+
60+
describe("buildSyntheticTraceForBufferedRun", () => {
61+
it("populates the synthesised root span from snapshot identity fields", () => {
62+
const trace = buildSyntheticTraceForBufferedRun(makeSyntheticRun());
63+
expect(trace.events).toHaveLength(1);
64+
const root = trace.events[0];
65+
expect(root.id).toBe("span_1");
66+
expect(root.data.message).toBe("hello-world");
67+
expect(root.data.startTime).toEqual(NOW);
68+
expect(root.data.isRoot).toBe(true);
69+
expect(root.data.offset).toBe(0);
70+
expect(root.data.level).toBe("TRACE");
71+
});
72+
73+
it("defaults the span message to 'Task' when the snapshot has no taskIdentifier", () => {
74+
const trace = buildSyntheticTraceForBufferedRun(
75+
makeSyntheticRun({ taskIdentifier: undefined })
76+
);
77+
expect(trace.events[0].data.message).toBe("Task");
78+
});
79+
80+
it("falls back to an empty-string span id when the snapshot has no spanId", () => {
81+
const trace = buildSyntheticTraceForBufferedRun(
82+
makeSyntheticRun({ spanId: undefined })
83+
);
84+
expect(trace.events[0].id).toBe("");
85+
// Empty id still marks as root (it matches the rootId fallback).
86+
expect(trace.events[0].data.isRoot).toBe(true);
87+
});
88+
89+
it("renders a QUEUED buffered run as an executing, partial root span", () => {
90+
const trace = buildSyntheticTraceForBufferedRun(makeSyntheticRun({ status: "QUEUED" }));
91+
expect(trace.rootSpanStatus).toBe("executing");
92+
expect(trace.events[0].data.isPartial).toBe(true);
93+
expect(trace.events[0].data.isError).toBe(false);
94+
expect(trace.events[0].data.isCancelled).toBe(false);
95+
// A partial span exposes duration as null (the timeline reads it as
96+
// "still running"); see syntheticTrace.server.ts duration mapping.
97+
expect(trace.events[0].data.duration).toBeNull();
98+
});
99+
100+
it("renders a CANCELED buffered run as a completed, non-partial cancelled span", () => {
101+
const trace = buildSyntheticTraceForBufferedRun(
102+
makeSyntheticRun({ status: "CANCELED", cancelledAt: NOW })
103+
);
104+
expect(trace.rootSpanStatus).toBe("completed");
105+
expect(trace.events[0].data.isPartial).toBe(false);
106+
expect(trace.events[0].data.isCancelled).toBe(true);
107+
expect(trace.events[0].data.isError).toBe(false);
108+
// Non-partial: duration is the span's numeric value (0 here), not null.
109+
expect(trace.events[0].data.duration).toBe(0);
110+
});
111+
112+
it("renders a FAILED buffered run as a failed, non-partial errored span", () => {
113+
const trace = buildSyntheticTraceForBufferedRun(
114+
makeSyntheticRun({
115+
status: "FAILED",
116+
error: { code: "GATE_REJECTED", message: "buffer rejected the run" },
117+
})
118+
);
119+
expect(trace.rootSpanStatus).toBe("failed");
120+
expect(trace.events[0].data.isPartial).toBe(false);
121+
expect(trace.events[0].data.isError).toBe(true);
122+
expect(trace.events[0].data.isCancelled).toBe(false);
123+
expect(trace.events[0].data.duration).toBe(0);
124+
});
125+
126+
it("floors the trace duration to a minimum of 1ms (in nanoseconds) so the timeline has a positive extent", () => {
127+
const trace = buildSyntheticTraceForBufferedRun(makeSyntheticRun());
128+
expect(trace.duration).toBe(ONE_MS_IN_NS);
129+
});
130+
131+
it("reports the buffered createdAt as the trace's rootStartedAt and leaves startedAt null", () => {
132+
const trace = buildSyntheticTraceForBufferedRun(makeSyntheticRun());
133+
expect(trace.rootStartedAt).toEqual(NOW);
134+
expect(trace.startedAt).toBeNull();
135+
});
136+
137+
it("returns no link or override metadata (buffered traces are single-span)", () => {
138+
const trace = buildSyntheticTraceForBufferedRun(makeSyntheticRun());
139+
expect(trace.linkedRunIdBySpanId).toEqual({});
140+
expect(trace.overridesBySpanId).toBeUndefined();
141+
expect(trace.queuedDuration).toBeUndefined();
142+
});
143+
144+
it("synthesises an empty events list (no timeline events from the buffer)", () => {
145+
const trace = buildSyntheticTraceForBufferedRun(makeSyntheticRun());
146+
expect(trace.events[0].data.events).toEqual([]);
147+
expect(trace.events[0].data.timelineEvents).toEqual([]);
148+
});
149+
});

0 commit comments

Comments
 (0)