返回 DeepSeek-Reasonix
session_second_fork_test.go
根目录 / desktop / session_second_fork_test.go
1 package main
2
3 import (
4 "encoding/json"
5 "errors"
6 "os"
7 "path/filepath"
8 "testing"
9
10 "reasonix/desktop/internal/workspacestate"
11 "reasonix/internal/provider"
12 "reasonix/internal/session"
13 )
14
15 func TestAttachSecondForkOfSameSourceDoesNotConflict(t *testing.T) {
16 app := NewApp()
17 app.ctx = t.Context()
18 pinDesktopSessionRoot(t, app)
19 installNoopRuntimeEvents(app, nil)
20 root := globalWorkspaceRoot()
21 if err := os.MkdirAll(desktopSessionDir(root), 0755); err != nil {
22 t.Fatal(err)
23 }
24 service := app.desktopSessionService("")
25 parent, err := service.Create(t.Context(), session.CreateOptions{SessionID: "fork-parent", CWD: root, Origin: session.SessionOriginNew})
26 if err != nil {
27 t.Fatal(err)
28 }
29 payload, _ := json.Marshal(map[string]any{"message": provider.Message{ID: "m1", Role: provider.RoleUser, Content: "hi"}})
30 if _, err := parent.Session().Append(t.Context(), session.Batch{OperationID: "op1", TurnID: "turn-1", Events: []session.Event{
31 {Kind: "turn/start"}, {Kind: "message/complete", Payload: payload}, {Kind: "turn/end", Payload: json.RawMessage(`{"status":"completed"}`)},
32 }}); err != nil {
33 t.Fatal(err)
34 }
35 if _, err := app.attachDesktopSession(t.Context(), "global", "", parent.Ref()); err != nil {
36 t.Fatal(err)
37 }
38 var sourceKey string
39 for _, id := range []string{"fork-one", "fork-two"} {
40 child, err := service.Fork(t.Context(), parent.Ref(), "turn-1", id)
41 if err != nil {
42 t.Fatal(err)
43 }
44 if _, err := app.attachDesktopSession(t.Context(), "global", "", child.Ref()); err != nil {
45 t.Fatalf("attach %s: %v", id, err)
46 }
47 state, err := app.workspaceRegistry().Load(t.Context())
48 if err != nil {
49 t.Fatalf("registry load after %s: %v", id, err)
50 }
51 if id == "fork-one" {
52 for key, m := range state.SourceMappings {
53 if m.SessionID == "fork-one" {
54 sourceKey = key
55 }
56 }
57 if sourceKey == "" {
58 t.Fatal("fork-one registered no source mapping")
59 }
60 }
61 }
62 state, err := app.workspaceRegistry().Load(t.Context())
63 if err != nil {
64 t.Fatal(err)
65 }
66 if got := state.SourceMappings[sourceKey].SessionID; got != "fork-one" {
67 t.Fatalf("source mapping owner = %q, want fork-one", got)
68 }
69 for key, m := range state.SourceMappings {
70 if m.SessionID == "fork-two" {
71 t.Fatalf("fork-two must hold no mapping, found %s", key)
72 }
73 }
74 }
75
76 func TestForkSourceOwnedElsewhereOnlySkipsForks(t *testing.T) {
77 app := NewApp()
78 app.ctx = t.Context()
79 pinDesktopSessionRoot(t, app)
80 installNoopRuntimeEvents(app, nil)
81 root := globalWorkspaceRoot()
82 if err := os.MkdirAll(desktopSessionDir(root), 0755); err != nil {
83 t.Fatal(err)
84 }
85 service := app.desktopSessionService("")
86 var refs []session.SessionRef
87 var workspaceID string
88 for _, c := range []struct {
89 id string
90 origin session.SessionOrigin
91 }{{"owner", session.SessionOriginNew}, {"plain", session.SessionOriginNew}, {"forked", session.SessionOriginFork}} {
92 h, err := service.Create(t.Context(), session.CreateOptions{SessionID: c.id, CWD: root, Origin: c.origin})
93 if err != nil {
94 t.Fatal(err)
95 }
96 id, err := app.attachDesktopSession(t.Context(), "global", "", h.Ref())
97 if err != nil {
98 t.Fatal(err)
99 }
100 workspaceID = id
101 refs = append(refs, h.Ref())
102 }
103 path := filepath.Join(t.TempDir(), "legacy.jsonl")
104 if err := os.WriteFile(path, []byte("{}\n"), 0600); err != nil {
105 t.Fatal(err)
106 }
107 if err := app.recordDesktopSource(t.Context(), path, "legacy", "fp", "owner", workspaceID); err != nil {
108 t.Fatal(err)
109 }
110 conflict := app.recordDesktopSource(t.Context(), path, "legacy", "fp", "plain", workspaceID)
111 if !errors.Is(conflict, workspacestate.ErrMutationConflict) {
112 t.Fatalf("setup: want conflict, got %v", conflict)
113 }
114 if app.forkSourceOwnedElsewhere(t.Context(), conflict, path, refs[1]) {
115 t.Fatal("a non-fork session mapped elsewhere must surface the conflict")
116 }
117 if !app.forkSourceOwnedElsewhere(t.Context(), conflict, path, refs[2]) {
118 t.Fatal("a fork whose source is owned by another session must be skipped")
119 }
120 if app.forkSourceOwnedElsewhere(t.Context(), errors.New("other"), path, refs[2]) {
121 t.Fatal("only ErrMutationConflict may be skipped")
122 }
123 }
124
124 lines GO