返回 DeepSeek-Reasonix
runtime_initialization_test.go
根目录 / internal / session / runtime_initialization_test.go
1 package session
2
3 import (
4 "context"
5 "encoding/json"
6 "path/filepath"
7 "strings"
8 "testing"
9 "time"
10
11 "reasonix/internal/provider"
12 )
13
14 // Both messages have valid canonical identities, but their display records
15 // collide. This reproduces a fallible transcript constructor after a writer
16 // has already been acquired.
17 func appendConflictingTranscript(t *testing.T, session *Session) {
18 t.Helper()
19 var events []Event
20 for _, id := range []string{"result-one", "result-two"} {
21 payload, err := json.Marshal(map[string]any{"message": provider.Message{
22 ID: id, Role: provider.RoleTool, ToolCallID: "reused-call", Content: id,
23 }})
24 if err != nil {
25 t.Fatal(err)
26 }
27 events = append(events, Event{Kind: "message/complete", Payload: payload})
28 }
29 if _, err := session.AppendBatch(t.Context(), "conflicting-transcript", events); err != nil {
30 t.Fatal(err)
31 }
32 }
33
34 type conflictingTranscriptPersistence struct {
35 SessionPersistence
36 t *testing.T
37 }
38
39 func (p conflictingTranscriptPersistence) Create(options CreateOptions) (*Session, error) {
40 session, err := p.SessionPersistence.Create(options)
41 if err == nil {
42 appendConflictingTranscript(p.t, session)
43 }
44 return session, err
45 }
46
47 func TestRuntimeInitializationFailureReleasesWriterAndAllowsRetry(t *testing.T) {
48 for _, entry := range []string{"create", "open"} {
49 t.Run(entry, func(t *testing.T) {
50 ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second)
51 defer cancel()
52 persistence := NewFilesystemPersistence(filepath.Join(t.TempDir(), "sessions-v4"))
53 var servicePersistence SessionPersistence = persistence
54 if entry == "create" {
55 servicePersistence = conflictingTranscriptPersistence{SessionPersistence: persistence, t: t}
56 } else {
57 session, err := persistence.Create(CreateOptions{SessionID: "conflict"})
58 if err != nil {
59 t.Fatal(err)
60 }
61 appendConflictingTranscript(t, session)
62 if err := session.Close(ctx); err != nil {
63 t.Fatal(err)
64 }
65 }
66 service, err := NewService("local", servicePersistence)
67 if err != nil {
68 t.Fatal(err)
69 }
70 t.Cleanup(func() {
71 if err := service.Shutdown(context.Background()); err != nil {
72 t.Error(err)
73 }
74 })
75 ref := SessionRef{HostID: "local", SessionID: "conflict"}
76 if entry == "create" {
77 prepared, err := service.PrepareCreate(ctx, CreateOptions{SessionID: ref.SessionID})
78 if prepared != nil || err == nil || !strings.Contains(err.Error(), `duplicate transcript record "tool:reused-call"`) {
79 t.Fatalf("prepare = %v, error = %v", prepared, err)
80 }
81 }
82 // A retry must return the original validation error, not wait on a
83 // stranded preparation or fail on a leaked writer lease.
84 for range 2 {
85 binding, err := service.Open(ctx, ref)
86 if binding != nil || err == nil || !strings.Contains(err.Error(), `initialize transcript for "conflict": duplicate transcript record "tool:reused-call"`) {
87 t.Fatalf("open = %v, error = %v", binding, err)
88 }
89 if _, ok := service.Runtime(ref); ok {
90 t.Fatal("failed initialization published a runtime")
91 }
92 }
93 // Reopen through persistence to prove cleanup retained both original
94 // messages and released the writer for a later repair.
95 repair, err := persistence.Open(ref.SessionID, ReadWrite)
96 if err != nil {
97 t.Fatal(err)
98 }
99 messages := repair.ExecutionSnapshot().Projection.ModelMessages
100 if len(messages) != 2 || messages[0].ID != "result-one" || messages[1].ID != "result-two" {
101 t.Fatalf("failed initialization changed stored history: %+v", messages)
102 }
103 payload, err := json.Marshal(map[string]any{"message": provider.Message{
104 ID: "result-two", Role: provider.RoleTool, ToolCallID: "distinct-call", Content: "result-two",
105 }})
106 if err != nil {
107 t.Fatal(err)
108 }
109 if _, err := repair.AppendBatch(ctx, "repair", []Event{{Kind: "message/upsert", Payload: payload}}); err != nil {
110 t.Fatal(err)
111 }
112 if err := repair.Close(ctx); err != nil {
113 t.Fatal(err)
114 }
115 binding, err := service.Open(ctx, ref)
116 if err != nil {
117 t.Fatalf("open after repair: %v", err)
118 }
119 defer binding.Release(context.Background())
120 if binding.Runtime().Transcript() == nil {
121 t.Fatal("successful runtime has no transcript")
122 }
123 })
124 }
125 }
126
126 lines GO