f99010fae1
Desktop Artifacts / Desktop Build (Linux) (push) Waiting to run
Desktop Artifacts / Desktop Build (Windows) (push) Waiting to run
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Waiting to run
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Waiting to run
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Waiting to run
CI / lint (push) Failing after 1s
CI / frontend (push) Failing after 1s
CI / scripts (push) Failing after 1s
CI / Go Test (ubuntu-latest) (push) Failing after 0s
CI / frontend-node-25 (push) Failing after 1s
CI / docs (push) Failing after 0s
CI / coverage (push) Failing after 0s
CI / e2e (push) Failing after 0s
Docker / build-and-push (push) Failing after 1s
CI / integration (push) Failing after 4m43s
CI / Go Test (windows-latest) (push) Has been cancelled
CI / Desktop Unit Tests (Windows) (push) Has been cancelled
390 lines
18 KiB
Go
390 lines
18 KiB
Go
// ABOUTME: Tests for DAG fork detection in Claude JSONL session files.
|
|
// ABOUTME: Validates linear, large-gap fork, small-gap retry, and backward compat scenarios.
|
|
package parser
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"go.kenn.io/agentsview/internal/testjsonl"
|
|
)
|
|
|
|
func parseTestContent(t *testing.T, name, content string, expectedLen int) []ParseResult {
|
|
t.Helper()
|
|
path := createTestFile(t, name, content)
|
|
results, err := parseClaudeSession(path, "proj", "local")
|
|
require.NoError(t, err, "ParseClaudeSession")
|
|
require.Len(t, results, expectedLen)
|
|
return results
|
|
}
|
|
|
|
func formatTime(ts time.Time) string {
|
|
return ts.Format(time.RFC3339)
|
|
}
|
|
|
|
func TestForkDetection_LinearSession(t *testing.T) {
|
|
// Linear chain: a -> b -> c -> d, all with uuid/parentUuid.
|
|
// Should return 1 result with 4 messages.
|
|
content := testjsonl.NewSessionBuilder().
|
|
AddClaudeUserWithUUID("2024-01-01T10:00:00Z", "hello", "a", "").
|
|
AddClaudeAssistantWithUUID("2024-01-01T10:00:01Z", "hi there", "b", "a").
|
|
AddClaudeUserWithUUID("2024-01-01T10:00:02Z", "next question", "c", "b").
|
|
AddClaudeAssistantWithUUID("2024-01-01T10:00:03Z", "answer", "d", "c").
|
|
String()
|
|
|
|
results := parseTestContent(t, "linear.jsonl", content, 1)
|
|
|
|
assertSessionMeta(t, &results[0].Session, "linear", "proj", AgentClaude)
|
|
assertMessageCount(t, len(results[0].Messages), 4)
|
|
}
|
|
|
|
func TestForkDetection_LargeGapFork(t *testing.T) {
|
|
// Main branch: a->b->c->d->e->f->g->h (4+ user turns after fork)
|
|
// Fork from b: i->j
|
|
// Fork point is at node b, which has children c (first) and i.
|
|
// First branch (c side) has user turns at c, e, g = 3 user turns,
|
|
// but we need >3, so add more.
|
|
//
|
|
// Let's make:
|
|
// a(user) -> b(asst) -> c(user) -> d(asst) -> e(user) -> f(asst)
|
|
// -> g(user) -> h(asst)
|
|
// -> i(user) -> j(asst) [fork from b]
|
|
//
|
|
// User turns on first branch after fork point b: c, e, g = 3,
|
|
// need >3 so add one more pair.
|
|
// a(user) -> b(asst) -> c(user) -> d(asst) -> e(user) -> f(asst)
|
|
// -> g(user) -> h(asst) -> k(user) -> l(asst)
|
|
// -> i(user) -> j(asst)
|
|
//
|
|
// User turns on first branch from c onward: c, e, g, k = 4 > 3 = large gap.
|
|
content := testjsonl.NewSessionBuilder().
|
|
AddClaudeUserWithUUID("2024-01-01T10:00:00Z", "hello", "a", "").
|
|
AddClaudeAssistantWithUUID("2024-01-01T10:00:01Z", "hi", "b", "a").
|
|
AddClaudeUserWithUUID("2024-01-01T10:00:02Z", "q1", "c", "b").
|
|
AddClaudeAssistantWithUUID("2024-01-01T10:00:03Z", "a1", "d", "c").
|
|
AddClaudeUserWithUUID("2024-01-01T10:00:04Z", "q2", "e", "d").
|
|
AddClaudeAssistantWithUUID("2024-01-01T10:00:05Z", "a2", "f", "e").
|
|
AddClaudeUserWithUUID("2024-01-01T10:00:06Z", "q3", "g", "f").
|
|
AddClaudeAssistantWithUUID("2024-01-01T10:00:07Z", "a3", "h", "g").
|
|
AddClaudeUserWithUUID("2024-01-01T10:00:08Z", "q4", "k", "h").
|
|
AddClaudeAssistantWithUUID("2024-01-01T10:00:09Z", "a4", "l", "k").
|
|
// Fork branch from b
|
|
AddClaudeUserWithUUID("2024-01-01T10:01:00Z", "fork q1", "i", "b").
|
|
AddClaudeAssistantWithUUID("2024-01-01T10:01:01Z", "fork a1", "j", "i").
|
|
String()
|
|
|
|
results := parseTestContent(t, "fork.jsonl", content, 2)
|
|
|
|
// Main session: all entries on first branch (a,b,c,d,e,f,g,h,k,l)
|
|
main := results[0]
|
|
assertSessionMeta(t, &main.Session, "fork", "proj", AgentClaude)
|
|
assertMessageCount(t, len(main.Messages), 10)
|
|
assert.Empty(t, main.Session.ParentSessionID, "main ParentSessionID")
|
|
|
|
// Fork session: entries on fork branch (i,j)
|
|
fork := results[1]
|
|
assert.Equal(t, "fork-i", fork.Session.ID, "fork session ID")
|
|
assertMessageCount(t, len(fork.Messages), 2)
|
|
assert.Equal(t, "fork", fork.Session.ParentSessionID, "fork ParentSessionID")
|
|
assert.Equal(t, RelFork, fork.Session.RelationshipType, "fork RelationshipType")
|
|
assert.Equal(t, "fork q1", fork.Session.FirstMessage, "fork FirstMessage")
|
|
}
|
|
|
|
func TestForkDetection_SmallGapRetry(t *testing.T) {
|
|
// Main: a(user)->b(asst)->c(user)->d(asst) (1 user turn after fork = small gap)
|
|
// Retry from b: e(user)->f(asst)
|
|
// First branch from b has c,d — only 1 user turn (c). ≤3 = small gap.
|
|
// Should follow LAST child (e), so result is: a,b,e,f
|
|
content := testjsonl.NewSessionBuilder().
|
|
AddClaudeUserWithUUID("2024-01-01T10:00:00Z", "hello", "a", "").
|
|
AddClaudeAssistantWithUUID("2024-01-01T10:00:01Z", "hi", "b", "a").
|
|
AddClaudeUserWithUUID("2024-01-01T10:00:02Z", "first try", "c", "b").
|
|
AddClaudeAssistantWithUUID("2024-01-01T10:00:03Z", "first answer", "d", "c").
|
|
// Retry from b (later in file)
|
|
AddClaudeUserWithUUID("2024-01-01T10:01:00Z", "retry", "e", "b").
|
|
AddClaudeAssistantWithUUID("2024-01-01T10:01:01Z", "retry answer", "f", "e").
|
|
String()
|
|
|
|
results := parseTestContent(t, "retry.jsonl", content, 1)
|
|
|
|
// Latest branch wins: a, b, e, f
|
|
assertMessageCount(t, len(results[0].Messages), 4)
|
|
assertMessage(t, results[0].Messages[0], RoleUser, "hello")
|
|
assertMessage(t, results[0].Messages[1], RoleAssistant, "hi")
|
|
assertMessage(t, results[0].Messages[2], RoleUser, "retry")
|
|
assertMessage(t, results[0].Messages[3], RoleAssistant, "retry answer")
|
|
}
|
|
|
|
func TestForkDetection_NoUUIDs(t *testing.T) {
|
|
// Entries without uuid fields — should work as before, 1 result.
|
|
content := testjsonl.NewSessionBuilder().
|
|
AddClaudeUser("2024-01-01T10:00:00Z", "hello").
|
|
AddClaudeAssistant("2024-01-01T10:00:01Z", "hi").
|
|
AddClaudeUser("2024-01-01T10:00:02Z", "bye").
|
|
AddClaudeAssistant("2024-01-01T10:00:03Z", "goodbye").
|
|
String()
|
|
|
|
results := parseTestContent(t, "nouuid.jsonl", content, 1)
|
|
|
|
assertMessageCount(t, len(results[0].Messages), 4)
|
|
assertMessage(t, results[0].Messages[0], RoleUser, "hello")
|
|
}
|
|
|
|
func TestForkDetection_MixedUUIDs(t *testing.T) {
|
|
// Some entries have uuid, some don't — fall back to linear.
|
|
content := testjsonl.NewSessionBuilder().
|
|
AddClaudeUser("2024-01-01T10:00:00Z", "no uuid").
|
|
AddClaudeAssistantWithUUID("2024-01-01T10:00:01Z", "has uuid", "x", "").
|
|
AddClaudeUser("2024-01-01T10:00:02Z", "no uuid again").
|
|
String()
|
|
|
|
results := parseTestContent(t, "mixed.jsonl", content, 1)
|
|
|
|
assertMessageCount(t, len(results[0].Messages), 3)
|
|
}
|
|
|
|
func TestForkDetection_NestedFork(t *testing.T) {
|
|
// Main: a->b->c->d->e->f->g->h->k->l (5 user turns)
|
|
// Fork from b: m->n->o->p->q->r->s->t->u->v (5 user turns on fork branch)
|
|
// Nested fork from n: w->x (fork within the fork branch)
|
|
// Fork from b has 5 user turns on first child path (m,o,q,s,u) > 3 = large gap
|
|
// Nested fork from n has 4 user turns on first child path (o,q,s,u) > 3 = large gap
|
|
content := testjsonl.NewSessionBuilder().
|
|
AddClaudeUserWithUUID("2024-01-01T10:00:00Z", "start", "a", "").
|
|
AddClaudeAssistantWithUUID("2024-01-01T10:00:01Z", "ok", "b", "a").
|
|
// Main branch from b
|
|
AddClaudeUserWithUUID("2024-01-01T10:00:02Z", "main1", "c", "b").
|
|
AddClaudeAssistantWithUUID("2024-01-01T10:00:03Z", "m-ok1", "d", "c").
|
|
AddClaudeUserWithUUID("2024-01-01T10:00:04Z", "main2", "e", "d").
|
|
AddClaudeAssistantWithUUID("2024-01-01T10:00:05Z", "m-ok2", "f", "e").
|
|
AddClaudeUserWithUUID("2024-01-01T10:00:06Z", "main3", "g", "f").
|
|
AddClaudeAssistantWithUUID("2024-01-01T10:00:07Z", "m-ok3", "h", "g").
|
|
AddClaudeUserWithUUID("2024-01-01T10:00:08Z", "main4", "k", "h").
|
|
AddClaudeAssistantWithUUID("2024-01-01T10:00:09Z", "m-ok4", "l", "k").
|
|
// Fork branch from b
|
|
AddClaudeUserWithUUID("2024-01-01T10:01:00Z", "fork1", "m", "b").
|
|
AddClaudeAssistantWithUUID("2024-01-01T10:01:01Z", "f-ok1", "n", "m").
|
|
AddClaudeUserWithUUID("2024-01-01T10:01:02Z", "fork2", "o", "n").
|
|
AddClaudeAssistantWithUUID("2024-01-01T10:01:03Z", "f-ok2", "p", "o").
|
|
AddClaudeUserWithUUID("2024-01-01T10:01:04Z", "fork3", "q", "p").
|
|
AddClaudeAssistantWithUUID("2024-01-01T10:01:05Z", "f-ok3", "r", "q").
|
|
AddClaudeUserWithUUID("2024-01-01T10:01:06Z", "fork4", "s", "r").
|
|
AddClaudeAssistantWithUUID("2024-01-01T10:01:07Z", "f-ok4", "tt", "s").
|
|
AddClaudeUserWithUUID("2024-01-01T10:01:08Z", "fork5", "u", "tt").
|
|
AddClaudeAssistantWithUUID("2024-01-01T10:01:09Z", "f-ok5", "v", "u").
|
|
// Nested fork from n (within the fork branch)
|
|
AddClaudeUserWithUUID("2024-01-01T10:02:00Z", "nested", "w", "n").
|
|
AddClaudeAssistantWithUUID("2024-01-01T10:02:01Z", "n-ok", "x", "w").
|
|
String()
|
|
|
|
// Expect 3 results: main, nested fork from n, fork from b
|
|
// (depth-first: nested fork discovered during recursive walk of b's fork)
|
|
results := parseTestContent(t, "nested-fork.jsonl", content, 3)
|
|
|
|
// Main: a,b,c,d,e,f,g,h,k,l = 10 messages
|
|
assertMessageCount(t, len(results[0].Messages), 10)
|
|
|
|
// Nested fork from n (discovered first during depth-first walk): w,x = 2 messages
|
|
nested := results[1]
|
|
assert.Equal(t, "nested-fork-w", nested.Session.ID, "nested ID")
|
|
assertMessageCount(t, len(nested.Messages), 2)
|
|
assert.Equal(t, RelFork, nested.Session.RelationshipType, "nested RelationshipType")
|
|
// Nested fork's parent should be the fork branch it split
|
|
// from, not the root session.
|
|
assert.Equal(t, "nested-fork-m", nested.Session.ParentSessionID, "nested ParentSessionID")
|
|
|
|
// Fork from b: m,n,o,p,q,r,s,tt,u,v = 10 messages
|
|
fork := results[2]
|
|
assert.Equal(t, "nested-fork-m", fork.Session.ID, "fork ID")
|
|
assertMessageCount(t, len(fork.Messages), 10)
|
|
assert.Equal(t, RelFork, fork.Session.RelationshipType, "fork RelationshipType")
|
|
// Fork from b's parent should be the root session.
|
|
assert.Equal(t, "nested-fork", fork.Session.ParentSessionID, "fork ParentSessionID")
|
|
}
|
|
|
|
func TestForkDetection_MultipleRoots(t *testing.T) {
|
|
// Two entries with empty parentUuid = two roots.
|
|
// A well-formed DAG has exactly one root, so multiple roots
|
|
// should fall back to linear parsing, returning all messages.
|
|
content := testjsonl.NewSessionBuilder().
|
|
AddClaudeUserWithUUID("2024-01-01T10:00:00Z", "root one", "a", "").
|
|
AddClaudeAssistantWithUUID("2024-01-01T10:00:01Z", "reply one", "b", "a").
|
|
AddClaudeUserWithUUID("2024-01-01T10:00:02Z", "root two", "c", "").
|
|
AddClaudeAssistantWithUUID("2024-01-01T10:00:03Z", "reply two", "d", "c").
|
|
String()
|
|
|
|
results := parseTestContent(t, "multi-root.jsonl", content, 1)
|
|
|
|
// All 4 messages must be present.
|
|
assertMessageCount(t, len(results[0].Messages), 4)
|
|
assertMessage(t, results[0].Messages[0], RoleUser, "root one")
|
|
assertMessage(t, results[0].Messages[1], RoleAssistant, "reply one")
|
|
assertMessage(t, results[0].Messages[2], RoleUser, "root two")
|
|
assertMessage(t, results[0].Messages[3], RoleAssistant, "reply two")
|
|
}
|
|
|
|
func TestForkDetection_DisconnectedParent(t *testing.T) {
|
|
// Entry "c" has parentUuid "nonexistent" which doesn't match
|
|
// any entry's uuid. This means the DAG is disconnected, so
|
|
// we should fall back to linear parsing.
|
|
content := testjsonl.NewSessionBuilder().
|
|
AddClaudeUserWithUUID("2024-01-01T10:00:00Z", "hello", "a", "").
|
|
AddClaudeAssistantWithUUID("2024-01-01T10:00:01Z", "hi", "b", "a").
|
|
AddClaudeUserWithUUID("2024-01-01T10:00:02Z", "orphan", "c", "nonexistent").
|
|
AddClaudeAssistantWithUUID("2024-01-01T10:00:03Z", "orphan reply", "d", "c").
|
|
String()
|
|
|
|
results := parseTestContent(t, "disconnected.jsonl", content, 1)
|
|
|
|
// All 4 messages must be present.
|
|
assertMessageCount(t, len(results[0].Messages), 4)
|
|
assertMessage(t, results[0].Messages[0], RoleUser, "hello")
|
|
assertMessage(t, results[0].Messages[1], RoleAssistant, "hi")
|
|
assertMessage(t, results[0].Messages[2], RoleUser, "orphan")
|
|
assertMessage(t, results[0].Messages[3], RoleAssistant, "orphan reply")
|
|
}
|
|
|
|
func TestSessionBoundsIncludeNonMessageEvents(t *testing.T) {
|
|
// A trailing queue-operation event has a later timestamp
|
|
// than any user/assistant message. Session endedAt should
|
|
// still reflect that later timestamp.
|
|
queueLine := `{"type":"queue-operation","operation":"enqueue",` +
|
|
`"timestamp":"2024-01-01T11:00:00Z","content":"{}"}`
|
|
|
|
content := testjsonl.NewSessionBuilder().
|
|
AddClaudeUser("2024-01-01T10:00:00Z", "hello").
|
|
AddClaudeAssistant("2024-01-01T10:00:01Z", "hi").
|
|
AddRaw(queueLine).
|
|
String()
|
|
|
|
results := parseTestContent(t, "queue-ts.jsonl", content, 1)
|
|
|
|
sess := results[0].Session
|
|
assert.Equal(t, "2024-01-01T11:00:00Z", formatTime(sess.EndedAt), "EndedAt")
|
|
}
|
|
|
|
func TestSessionBoundsStartedAtFromLeadingEvent(t *testing.T) {
|
|
// A leading non-message event has an earlier timestamp
|
|
// than the first user message. StartedAt should reflect it.
|
|
earlyLine := `{"type":"queue-operation","operation":"enqueue",` +
|
|
`"timestamp":"2024-01-01T09:00:00Z","content":"{}"}`
|
|
|
|
content := testjsonl.NewSessionBuilder().
|
|
AddRaw(earlyLine).
|
|
AddClaudeUser("2024-01-01T10:00:00Z", "hello").
|
|
AddClaudeAssistant("2024-01-01T10:00:01Z", "hi").
|
|
String()
|
|
|
|
results := parseTestContent(t, "early-queue.jsonl", content, 1)
|
|
|
|
sess := results[0].Session
|
|
assert.Equal(t, "2024-01-01T09:00:00Z", formatTime(sess.StartedAt), "StartedAt")
|
|
}
|
|
|
|
func TestForkDetection_NestedForkCountsFullSubtree(t *testing.T) {
|
|
// Regression test: when the first child at a fork point
|
|
// itself contains nested forks early in its chain, the
|
|
// old first-child-only countUserTurns would see only 1
|
|
// user turn (following first children that dead-end
|
|
// quickly) and treat the entire large branch as a small
|
|
// retry, discarding it.
|
|
//
|
|
// DAG: root(a) -> b (fork point)
|
|
// First child: c -> d (fork) -> e -> f -> g -> h -> i -> j
|
|
// \-> d2 (retry, 1 entry)
|
|
// Second child: z (1 entry, the "retry")
|
|
//
|
|
// The first child subtree has 5 user turns total (c,e,g,i
|
|
// plus d2). With first-child-only traversal, the path
|
|
// c->d->d2 sees only 1 user turn (c is user, d is asst,
|
|
// d2 is user but d2 is the SECOND child not the first) --
|
|
// actually c->d->(first child of d's fork)=e gives more.
|
|
// Let's build a clearer case: the first child at the fork
|
|
// is a dead-end assistant reply, so first-child traversal
|
|
// stops after 0 user turns.
|
|
//
|
|
// DAG: root(a) -> b (fork)
|
|
// First child: c(user) -> d(asst, fork)
|
|
// d -> e(asst, dead-end first child)
|
|
// d -> f(user) -> g(asst) -> h(user) ->
|
|
// i(asst) -> j(user) -> k(asst)
|
|
// Second child: z(user, 1 msg)
|
|
//
|
|
// Old countUserTurns for c: c(user,1) -> d(asst) ->
|
|
// e(asst, no children) = 1 user turn <= 3 -> retry!
|
|
// New countUserTurns for c: 1+0+1+0+1+0+1+0 = 4 > 3
|
|
content := testjsonl.NewSessionBuilder().
|
|
AddClaudeUserWithUUID("2024-01-01T10:00:00Z", "start", "a", "").
|
|
AddClaudeAssistantWithUUID("2024-01-01T10:00:01Z", "ok", "b", "a").
|
|
// First child branch from b: large subtree
|
|
AddClaudeUserWithUUID("2024-01-01T10:00:02Z", "main1", "c", "b").
|
|
AddClaudeAssistantWithUUID("2024-01-01T10:00:03Z", "m-ok1", "d", "c").
|
|
// Nested fork at d: first child is a dead-end
|
|
AddClaudeAssistantWithUUID("2024-01-01T10:00:04Z", "dead-end", "e", "d").
|
|
// Second child of d's fork continues the real conversation
|
|
AddClaudeUserWithUUID("2024-01-01T10:00:05Z", "main2", "f", "d").
|
|
AddClaudeAssistantWithUUID("2024-01-01T10:00:06Z", "m-ok2", "g", "f").
|
|
AddClaudeUserWithUUID("2024-01-01T10:00:07Z", "main3", "h", "g").
|
|
AddClaudeAssistantWithUUID("2024-01-01T10:00:08Z", "m-ok3", "i", "h").
|
|
AddClaudeUserWithUUID("2024-01-01T10:00:09Z", "main4", "j", "i").
|
|
AddClaudeAssistantWithUUID("2024-01-01T10:00:10Z", "m-ok4", "k", "j").
|
|
// Second child of b's fork: trivial retry
|
|
AddClaudeUserWithUUID("2024-01-01T10:01:00Z", "retry", "z", "b").
|
|
String()
|
|
|
|
// The first child subtree has 4 user turns (c,f,h,j) > 3,
|
|
// so it should be treated as a large-gap fork. We expect
|
|
// 2 results: main path (a,b,c,d,f,g,h,i,j,k = 10 msgs)
|
|
// and the fork (z = 1 msg).
|
|
results := parseTestContent(t, "nested-fork-subtree.jsonl", content, 2)
|
|
|
|
// Main path should follow first child at b, then second
|
|
// child at d (the retry heuristic picks last child when
|
|
// first child has <= 3 user turns — here "e" is a dead
|
|
// end with 0 user turns so the nested fork follows "f").
|
|
main := results[0]
|
|
assert.GreaterOrEqual(t, main.Session.MessageCount, 8,
|
|
"main MessageCount (first child subtree should not be discarded)")
|
|
|
|
// The trivial "retry" branch should be the fork.
|
|
fork := results[1]
|
|
assertMessage(t, fork.Messages[0], RoleUser, "retry")
|
|
}
|
|
|
|
func TestSessionBoundsDAGMainWidenedNotFork(t *testing.T) {
|
|
// DAG session with a trailing queue-operation after all
|
|
// messages. Main session's EndedAt should be widened;
|
|
// fork session should use only its own message bounds.
|
|
queueLine := `{"type":"queue-operation","operation":"enqueue",` +
|
|
`"timestamp":"2024-01-01T12:00:00Z","content":"{}"}`
|
|
|
|
// Main: a->b->c->d->e->f->g->h->k->l (5 user turns)
|
|
// Fork from b: i->j
|
|
content := testjsonl.NewSessionBuilder().
|
|
AddClaudeUserWithUUID("2024-01-01T10:00:00Z", "hello", "a", "").
|
|
AddClaudeAssistantWithUUID("2024-01-01T10:00:01Z", "hi", "b", "a").
|
|
AddClaudeUserWithUUID("2024-01-01T10:00:02Z", "q1", "c", "b").
|
|
AddClaudeAssistantWithUUID("2024-01-01T10:00:03Z", "a1", "d", "c").
|
|
AddClaudeUserWithUUID("2024-01-01T10:00:04Z", "q2", "e", "d").
|
|
AddClaudeAssistantWithUUID("2024-01-01T10:00:05Z", "a2", "f", "e").
|
|
AddClaudeUserWithUUID("2024-01-01T10:00:06Z", "q3", "g", "f").
|
|
AddClaudeAssistantWithUUID("2024-01-01T10:00:07Z", "a3", "h", "g").
|
|
AddClaudeUserWithUUID("2024-01-01T10:00:08Z", "q4", "k", "h").
|
|
AddClaudeAssistantWithUUID("2024-01-01T10:00:09Z", "a4", "l", "k").
|
|
// Fork from b
|
|
AddClaudeUserWithUUID("2024-01-01T10:01:00Z", "fork", "i", "b").
|
|
AddClaudeAssistantWithUUID("2024-01-01T10:01:01Z", "fork-a", "j", "i").
|
|
AddRaw(queueLine).
|
|
String()
|
|
|
|
results := parseTestContent(t, "dag-queue.jsonl", content, 2)
|
|
|
|
// Main session EndedAt should be widened to queue timestamp.
|
|
assert.Equal(t, "2024-01-01T12:00:00Z", formatTime(results[0].Session.EndedAt), "main EndedAt")
|
|
|
|
// Fork session EndedAt should NOT be widened.
|
|
assert.Equal(t, "2024-01-01T10:01:01Z", formatTime(results[1].Session.EndedAt), "fork EndedAt")
|
|
}
|