返回 DeepSeek-Reasonix
model_settings_test.go
根目录 / internal / bot / model_settings_test.go
1 package bot
2
3 import (
4 "context"
5 "io"
6 "log/slog"
7 "testing"
8 "time"
9
10 "reasonix/internal/config"
11 "reasonix/internal/control"
12 "reasonix/internal/history"
13 "reasonix/internal/provider"
14 "reasonix/internal/stats"
15 )
16
17 func TestBotNewRunAppliesModelSettingsAndKeepsSessionOnFailure(t *testing.T) {
18 closeCatalogs := func() {
19 t.Helper()
20 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
21 defer cancel()
22 if err := history.CloseSharedCatalog(ctx); err != nil {
23 t.Fatalf("close shared history catalog: %v", err)
24 }
25 if err := stats.CloseUsageCatalogs(ctx); err != nil {
26 t.Fatalf("close usage catalogs: %v", err)
27 }
28 }
29 closeCatalogs()
30 t.Setenv("REASONIX_HOME", t.TempDir())
31 root := t.TempDir()
32 // These projections belong to the process, not an individual controller.
33 // Release SQLite handles before the isolated home is removed on Windows.
34 t.Cleanup(closeCatalogs)
35 cfg := config.Default()
36 cfg.Providers = []config.ProviderEntry{{Name: "snapshot", Kind: "openai", BaseURL: "http://127.0.0.1:1/v1", Model: "m", APIKeyEnv: "BOT_SNAPSHOT_TEST_KEY"}}
37 cfg.DefaultModel = "snapshot/m"
38 if _, err := config.SetCredential("BOT_SNAPSHOT_TEST_KEY", "test-key"); err != nil {
39 t.Fatal(err)
40 }
41 save := func() {
42 t.Helper()
43 if err := cfg.SaveTo(config.UserConfigPath()); err != nil {
44 t.Fatal(err)
45 }
46 }
47 save()
48 gw := NewGateway(GatewayConfig{Model: cfg.DefaultModel, WorkspaceRoot: root}, nil, slog.New(slog.NewTextHandler(io.Discard, nil)))
49 msg := InboundMessage{Platform: PlatformFeishu, ChatType: ChatDM, ChatID: "snapshot-test", UserID: "test"}
50 key := BuildSessionKey(msg.Session())
51 built, err := gw.buildSessionState(context.Background(), key, msg, gw.sessionProfileForMessage(msg), nil)
52 if err != nil {
53 t.Fatal(err)
54 }
55 old := built.state
56 gw.controllers[key] = old
57 t.Cleanup(func() {
58 gw.closeSessionState(gw.controllers[key])
59 // Retiring the state is not enough: the per-root session service caches
60 // the writer lease, and Stop is what normally releases it.
61 gw.closeSessionServices()
62 })
63 oldCtrl := old.ctrl
64 path := oldCtrl.SessionPath()
65 oldConcrete := built.state.ctrl
66 // Carry a real transcript across the same lease and runtime replacement.
67 oldConcrete.(*control.Controller).AdoptHistory(append(oldConcrete.(*control.Controller).History(), provider.Message{Role: provider.RoleUser, Content: "preserved bot history"}), path)
68 oldLease := old.leases
69 cfg.Agent.PlannerModel = "missing/model"
70 save()
71 if _, err := gw.applySessionModelSettings(context.Background(), key, msg, old); err == nil {
72 t.Fatal("invalid saved planner admitted a new bot run")
73 }
74 if gw.controllers[key] != old || old.ctrl != oldCtrl || old.leases != oldLease || old.retired {
75 t.Fatal("failed application replaced the original runtime or lease")
76 }
77 cfg.Agent.PlannerModel = ""
78 cfg.Agent.SubagentModel = "snapshot/m"
79 save()
80 next, err := gw.applySessionModelSettings(context.Background(), key, msg, old)
81 if err != nil {
82 t.Fatal(err)
83 }
84 if next == old || next.ctrl == oldCtrl || next.ctrl.SessionPath() != path || next.leases != oldLease {
85 t.Fatal("new snapshot did not preserve the session binding")
86 }
87 history := next.ctrl.(*control.Controller).History()
88 if history[len(history)-1].Content != "preserved bot history" {
89 t.Fatal("new snapshot lost the transcript")
90 }
91 same, err := gw.applySessionModelSettings(context.Background(), key, msg, next)
92 if err != nil || same != next {
93 t.Fatalf("unchanged settings caused another rebuild: %v", err)
94 }
95 }
96
96 lines GO