返回 DeepSeek-Reasonix
session_listing_model_authority_test.go
根目录 / internal / agent / session_listing_model_authority_test.go
1 package agent
2
3 import (
4 "errors"
5 "path/filepath"
6 "testing"
7
8 "reasonix/internal/provider"
9 )
10
11 // A Controller snapshot saves the transcript before publishing its listing.
12 // Resume the latter half only after another runtime owns the same transcript
13 // and has published a different connection. No timing or fake authority is used.
14 func TestListingProjectionCannotOverwriteModelAfterAuthorityReplacement(t *testing.T) {
15 path := filepath.Join(t.TempDir(), "connection.jsonl")
16 old := NewSession("same system prompt")
17 old.Add(provider.Message{Role: provider.RoleUser, Content: "keep this conversation"})
18 writer := bindSessionWriter(t, old, path)
19 if err := old.SaveSnapshot(path); err != nil {
20 t.Fatal(err)
21 }
22 expected, ok := old.PersistedState(path)
23 if !ok {
24 t.Fatal("missing saved transcript identity")
25 }
26 oldAuthority := old.WriteAuthority()
27 if applied, err := UpdateOwnedSessionListingProjectionIfCurrent(path, "connection-one/same-model", "", "keep this conversation", 1, false, expected, oldAuthority); err != nil || !applied {
28 t.Fatalf("initial listing: applied=%v err=%v", applied, err)
29 }
30
31 // This is SetModelForTab's publication ordering: bind the new controller,
32 // then persist its canonical selection without rewriting the transcript.
33 replacement := NewSession("same system prompt")
34 if err := writer.Bind(replacement, NextSessionWriteGeneration()); err != nil {
35 t.Fatal(err)
36 }
37 if oldAuthority.Valid() || !replacement.WriteAuthority().Valid() {
38 t.Fatal("replacement did not fence the previous write authority")
39 }
40 const selected = "connection-two/same-model"
41 if err := SetBranchModelPreserveUpdated(path, selected); err != nil {
42 t.Fatal(err)
43 }
44
45 // The retired snapshot's transcript has already been committed. Its delayed
46 // listing publication must not undo the subsequently committed model choice.
47 _, err := UpdateOwnedSessionListingProjectionIfCurrent(path, "connection-one/same-model", "", "keep this conversation", 1, false, expected, oldAuthority)
48 if !errors.Is(err, ErrSessionWriteAuthorityStale) {
49 t.Fatalf("retired publication error = %v, want stale authority", err)
50 }
51 if got, ok := LoadSessionModel(path); !ok || got != selected {
52 t.Fatalf("retired listing overwrote selected connection: model=%q present=%v want=%q", got, ok, selected)
53 }
54 }
55
56 func TestListingAuthorityGuardRetainsGenerationThroughCommit(t *testing.T) {
57 path := filepath.Join(t.TempDir(), "guarded-connection.jsonl")
58 old := NewSession("system")
59 old.Add(provider.Message{Role: provider.RoleUser, Content: "question"})
60 writer := bindSessionWriter(t, old, path)
61 if err := old.SaveSnapshot(path); err != nil {
62 t.Fatal(err)
63 }
64 authority := old.WriteAuthority()
65 unlock, err := authority.lockCurrentLease(path)
66 if err != nil {
67 t.Fatal(err)
68 }
69 defer func() {
70 if unlock != nil {
71 unlock()
72 }
73 }()
74 // This directly checks the exclusion invariant, without relying on a
75 // scheduler delay to infer that the competing bind is blocked.
76 if authority.lease.mu.TryLock() {
77 authority.lease.mu.Unlock()
78 t.Fatal("publication guard released the generation mutex before commit")
79 }
80 entered, bound := make(chan struct{}), make(chan error, 1)
81 replacement := NewSession("system")
82 go func() {
83 close(entered)
84 bound <- writer.Bind(replacement, NextSessionWriteGeneration())
85 }()
86 <-entered
87 if err := SetBranchModelPreserveUpdated(path, "connection-one/model"); err != nil {
88 t.Fatal(err)
89 }
90 if authority.lease.writeGeneration != authority.generation {
91 t.Fatal("generation changed inside the publication critical section")
92 }
93 unlock()
94 unlock = nil
95 if err := <-bound; err != nil {
96 t.Fatal(err)
97 }
98 if authority.Valid() || !replacement.WriteAuthority().Valid() {
99 t.Fatal("replacement failed to advance generation after publication")
100 }
101 }
102
103 func TestOwnedListingRejectsMissingAuthority(t *testing.T) {
104 path := filepath.Join(t.TempDir(), "missing-authority.jsonl")
105 session := NewSession("system")
106 session.Add(provider.Message{Role: provider.RoleUser, Content: "question"})
107 bindSessionWriter(t, session, path)
108 if err := session.SaveSnapshot(path); err != nil {
109 t.Fatal(err)
110 }
111 expected, ok := session.PersistedState(path)
112 if !ok {
113 t.Fatal("missing saved transcript identity")
114 }
115 if err := SetBranchModelPreserveUpdated(path, "selected/model"); err != nil {
116 t.Fatal(err)
117 }
118 applied, err := UpdateOwnedSessionListingProjectionIfCurrent(path, "stale/model", "", "question", 1, false, expected, nil)
119 if applied || !errors.Is(err, ErrSessionWriteAuthorityMissing) {
120 t.Fatalf("missing authority publication: applied=%v err=%v", applied, err)
121 }
122 if model, ok := LoadSessionModel(path); !ok || model != "selected/model" {
123 t.Fatalf("missing authority changed model: %q", model)
124 }
125 }
126
126 lines GO