返回 DeepSeek-Reasonix
fork_test.go
根目录 / internal / session / fork_test.go
1 package session
2
3 import (
4 "context"
5 "encoding/json"
6 "path/filepath"
7 "testing"
8
9 "reasonix/internal/provider"
10 )
11
12 func TestForkCreatesIndependentSessionAtCompletedTurnBoundary(t *testing.T) {
13 root := t.TempDir()
14 parentDir := filepath.Join(root, "parent")
15 parent, err := Open(parentDir, "parent")
16 if err != nil {
17 t.Fatal(err)
18 }
19 message, _ := json.Marshal(map[string]any{"message": provider.Message{ID: "m1", Role: provider.RoleUser, Content: "one"}})
20 first, err := parent.Append(t.Context(), Batch{OperationID: "turn-1", TurnID: "t1", Events: []Event{
21 {Kind: "turn/start"}, {Kind: "message/complete", Payload: message}, {Kind: "turn/end", Payload: json.RawMessage(`{"status":"completed"}`)},
22 }})
23 if err != nil {
24 t.Fatal(err)
25 }
26 secondMessage, _ := json.Marshal(map[string]any{"message": provider.Message{ID: "m2", Role: provider.RoleUser, Content: "two"}})
27 if _, err := parent.Append(t.Context(), Batch{OperationID: "turn-2", TurnID: "t2", Events: []Event{
28 {Kind: "turn/start"}, {Kind: "message/complete", Payload: secondMessage}, {Kind: "turn/end", Payload: json.RawMessage(`{"status":"completed"}`)},
29 }}); err != nil {
30 t.Fatal(err)
31 }
32 childDir := filepath.Join(root, "child")
33 if _, err := parent.Fork(t.Context(), childDir, "child", first.LastSequence()); err != nil {
34 t.Fatal(err)
35 }
36 if err := parent.Close(t.Context()); err != nil {
37 t.Fatal(err)
38 }
39
40 child, err := Open(childDir, "child")
41 if err != nil {
42 t.Fatal(err)
43 }
44 defer child.Close(context.Background())
45 projection := child.Snapshot().Projection
46 if len(projection.Messages) != 1 || projection.Messages[0].ID != "m1" {
47 t.Fatalf("child projection = %+v", projection)
48 }
49 if _, err := child.Append(t.Context(), Batch{OperationID: "child-turn", TurnID: "ct", Events: []Event{{Kind: "turn/start"}, {Kind: "turn/end", Payload: json.RawMessage(`{"status":"completed"}`)}}}); err != nil {
50 t.Fatal(err)
51 }
52 if child.Snapshot().EventSequence == parent.Snapshot().EventSequence {
53 t.Fatal("child append mutated parent sequence")
54 }
55 if _, err := Replay(parentDir, nil); err != nil {
56 t.Fatal(err)
57 }
58 }
59
60 func TestForkRejectsMidTurnCut(t *testing.T) {
61 root := t.TempDir()
62 parent, err := Open(filepath.Join(root, "parent"), "parent")
63 if err != nil {
64 t.Fatal(err)
65 }
66 defer parent.Close(context.Background())
67 if _, err := parent.Append(t.Context(), Batch{OperationID: "open", TurnID: "t1", Events: []Event{{Kind: "turn/start"}, {Kind: "step/start", Payload: json.RawMessage(`{"id":"step-1"}`)}}}); err != nil {
68 t.Fatal(err)
69 }
70 if _, err := parent.Fork(t.Context(), filepath.Join(root, "child"), "child", 1); err == nil {
71 t.Fatal("mid-turn fork succeeded")
72 }
73 }
74
74 lines GO