返回 DeepSeek-Reasonix
remote_tab_model_catalog_test.go
根目录 / desktop / remote_tab_model_catalog_test.go
1 package main
2
3 import (
4 "slices"
5 "strings"
6 "testing"
7
8 "reasonix/internal/config"
9 )
10
11 // TestSetModelForTabRemoteCredentialPostsServeModel: remote-credential hosts
12 // switch through the serve's per-session endpoint.
13 func TestSetModelForTabRemoteCredentialPostsServeModel(t *testing.T) {
14 isolateDesktopUserDirs(t)
15 cfg := config.Default()
16 if err := cfg.UpsertRemoteHost(config.RemoteHostEntry{Name: "box", Host: "127.0.0.1", Port: 22, User: "dev"}); err != nil {
17 t.Fatal(err)
18 }
19 if err := cfg.SaveTo(config.UserConfigPath()); err != nil {
20 t.Fatalf("save config: %v", err)
21 }
22 fs := newFakeServe(t, "s3cret", nil)
23 kernel := &fakeRemoteKernel{
24 statuses: []RemoteConnectionStatusView{{HostID: "box", State: "connected"}},
25 ensureView: RemoteServerView{HostID: "box", State: "ready", LocalURL: fs.server.URL},
26 ensureToken: "s3cret",
27 }
28 log := &eventLog{}
29 a := &App{remoteRuntime: kernel, remoteEventHook: log.add}
30 cleanupRemoteTabPumps(t, a)
31 meta := openReadyRemoteTab(t, a, RemoteTabOpenOptions{NewSession: true})
32 a.remoteTabMu.Lock()
33 a.remoteTabLayout.activeID = "other-tab"
34 a.remoteTabMu.Unlock()
35
36 if err := a.SetModelForTab(meta.ID, "remote/chat"); err != nil {
37 t.Fatalf("SetModelForTab: %v", err)
38 }
39 posted := false
40 for _, c := range fs.recorded() {
41 if strings.HasPrefix(c, "POST /model ") && strings.Contains(c, `"ref":"remote/chat"`) {
42 posted = true
43 }
44 }
45 if !posted {
46 t.Fatalf("serve never saw POST /model with the ref: %v", fs.recorded())
47 }
48 a.remoteTabMu.Lock()
49 model, activeID := "", a.remoteTabLayout.activeID
50 if tab := a.remoteTabs[meta.ID]; tab != nil {
51 model = tab.model
52 }
53 a.remoteTabMu.Unlock()
54 if model != "remote/chat" {
55 t.Fatalf("tab.model = %q, want remote/chat", model)
56 }
57 if activeID != "other-tab" {
58 t.Fatalf("completed model switch reactivated %q, want other-tab to stay active", activeID)
59 }
60 if !slices.ContainsFunc(log.recorded(), func(event string) bool { return strings.HasPrefix(event, "remote-tab:updated ") }) {
61 t.Fatalf("model switch did not publish metadata update: %v", log.recorded())
62 }
63 }
64
65 // Remote-credential hosts must adopt the Serve catalog and active ref without
66 // briefly inheriting the desktop's configured default model.
67 func TestModelsForTabRemoteCredentialHostOffersServeCatalog(t *testing.T) {
68 isolateDesktopUserDirs(t)
69 setDesktopTestCredential(t, "REMOTE_MODEL_TEST_KEY", "sk-test")
70 cfg := config.Default()
71 cfg.DefaultModel = "desktop/local-model"
72 cfg.Desktop.ProviderAccess = append(cfg.Desktop.ProviderAccess, "desktop")
73 cfg.Providers = append(cfg.Providers, config.ProviderEntry{
74 Name: "desktop", BaseURL: "https://desktop.invalid/v1", Models: []string{"local-model"},
75 Default: "local-model", APIKeyEnv: "REMOTE_MODEL_TEST_KEY",
76 })
77 if err := cfg.UpsertRemoteHost(config.RemoteHostEntry{Name: "box", Host: "127.0.0.1", Port: 22, User: "dev"}); err != nil {
78 t.Fatal(err)
79 }
80 if err := cfg.SaveTo(config.UserConfigPath()); err != nil {
81 t.Fatalf("save config: %v", err)
82 }
83 fs := newFakeServe(t, "s3cret", nil)
84 kernel := &fakeRemoteKernel{
85 statuses: []RemoteConnectionStatusView{{HostID: "box", State: "connected"}},
86 ensureView: RemoteServerView{HostID: "box", State: "ready", LocalURL: fs.server.URL},
87 ensureToken: "s3cret",
88 }
89 a := &App{remoteRuntime: kernel}
90 cleanupRemoteTabPumps(t, a)
91 meta := openReadyRemoteTab(t, a, RemoteTabOpenOptions{NewSession: true})
92 a.remoteTabMu.Lock()
93 seeded := a.remoteTabs[meta.ID].model
94 a.remoteTabMu.Unlock()
95 if seeded != "" {
96 t.Fatalf("remote-credential tab seeded desktop model %q", seeded)
97 }
98 if _, err := a.RemoteTabSnapshot(meta.ID); err != nil {
99 t.Fatalf("RemoteTabSnapshot: %v", err)
100 }
101 a.remoteTabMu.Lock()
102 adopted := a.remoteTabs[meta.ID].model
103 a.remoteTabMu.Unlock()
104 if adopted != "remote/chat" {
105 t.Fatalf("remote-credential tab model = %q, want Serve active ref", adopted)
106 }
107 got := a.ModelsForTab(meta.ID)
108 if len(got) != 1 || got[0].Ref != "remote/chat" || !got[0].Current {
109 t.Fatalf("ModelsForTab = %+v, want the Serve catalog with remote/chat current", got)
110 }
111 }
112
113 func TestSetRemoteTabModelFailureKeepsPreviousModel(t *testing.T) {
114 isolateDesktopUserDirs(t)
115 setDesktopTestCredential(t, "DEEPSEEK_API_KEY", "sk-test")
116 cfg := config.Default()
117 cfg.DefaultModel = "deepseek/deepseek-v4-flash"
118 cfg.Desktop.ProviderAccess = []string{"deepseek"}
119 cfg.Providers = append(cfg.Providers, config.ProviderEntry{
120 Name: "deepseek", Kind: "anthropic", BaseURL: "https://api.deepseek.com/anthropic",
121 Models: []string{"deepseek-v4-flash", "deepseek-v4-pro"}, Default: "deepseek-v4-flash", APIKeyEnv: "DEEPSEEK_API_KEY",
122 })
123 if err := cfg.UpsertRemoteHost(config.RemoteHostEntry{Name: "box", Host: "127.0.0.1", Port: 22, User: "dev", CredentialMode: "local-proxy"}); err != nil {
124 t.Fatal(err)
125 }
126 if err := cfg.SaveTo(config.UserConfigPath()); err != nil {
127 t.Fatalf("save config: %v", err)
128 }
129 fs := newFakeServe(t, "s3cret", nil)
130 kernel := &fakeRemoteKernel{
131 statuses: []RemoteConnectionStatusView{{HostID: "box", State: "connected"}},
132 ensureView: RemoteServerView{HostID: "box", State: "ready", LocalURL: fs.server.URL},
133 ensureToken: "s3cret",
134 }
135 a := &App{remoteRuntime: kernel}
136 cleanupRemoteTabPumps(t, a)
137 meta := openReadyRemoteTab(t, a, RemoteTabOpenOptions{NewSession: true})
138
139 // Clear the stored key after the tab seeds its default. The proxy switch
140 // must then fail while preserving that model.
141 if _, err := config.SetCredential("DEEPSEEK_API_KEY", ""); err != nil {
142 t.Fatalf("clear credential: %v", err)
143 }
144 t.Setenv("DEEPSEEK_API_KEY", "")
145 if err := a.SetModelForTab(meta.ID, "deepseek/deepseek-v4-pro"); err == nil {
146 t.Fatal("SetModelForTab must fail without the local key")
147 }
148 a.remoteTabMu.Lock()
149 model := ""
150 if tab := a.remoteTabs[meta.ID]; tab != nil {
151 model = tab.model
152 }
153 a.remoteTabMu.Unlock()
154 if model != "deepseek/deepseek-v4-flash" {
155 t.Fatalf("tab.model = %q, want the untouched previous model", model)
156 }
157 }
158
158 lines GO