diff --git a/web/backend/api/config_test.go b/web/backend/api/config_test.go index 1daa4a835..392471094 100644 --- a/web/backend/api/config_test.go +++ b/web/backend/api/config_test.go @@ -1,126 +1,126 @@ -package api - -import ( - "bytes" - "net/http" - "net/http/httptest" - "reflect" - "testing" - - "github.com/sipeed/picoclaw/pkg/config" -) - -func TestHandleUpdateConfig_PreservesExecAllowRemoteDefaultWhenOmitted(t *testing.T) { - configPath, cleanup := setupOAuthTestEnv(t) - defer cleanup() - - h := NewHandler(configPath) - mux := http.NewServeMux() - h.RegisterRoutes(mux) - - req := httptest.NewRequest(http.MethodPut, "/api/config", bytes.NewBufferString(`{ - "agents": { - "defaults": { - "workspace": "~/.picoclaw/workspace" - } - }, - "model_list": [ - { - "model_name": "custom-default", - "model": "openai/gpt-4o", - "api_key": "sk-default" - } - ] - }`)) - req.Header.Set("Content-Type", "application/json") - - rec := httptest.NewRecorder() - mux.ServeHTTP(rec, req) - if rec.Code != http.StatusOK { - t.Fatalf("status = %d, want %d, body=%s", rec.Code, http.StatusOK, rec.Body.String()) - } - - cfg, err := config.LoadConfig(configPath) - if err != nil { - t.Fatalf("LoadConfig() error = %v", err) - } - if !cfg.Tools.Exec.AllowRemote { - t.Fatal("tools.exec.allow_remote should remain true when omitted from PUT /api/config") - } -} - -func TestHandleUpdateConfig_DoesNotInheritDefaultModelFields(t *testing.T) { - configPath, cleanup := setupOAuthTestEnv(t) - defer cleanup() - - h := NewHandler(configPath) - mux := http.NewServeMux() - h.RegisterRoutes(mux) - - req := httptest.NewRequest(http.MethodPut, "/api/config", bytes.NewBufferString(`{ - "agents": { - "defaults": { - "workspace": "~/.picoclaw/workspace" - } - }, - "model_list": [ - { - "model_name": "custom-default", - "model": "openai/gpt-4o", - "api_key": "sk-default" - } - ] - }`)) - req.Header.Set("Content-Type", "application/json") - - rec := httptest.NewRecorder() - mux.ServeHTTP(rec, req) - if rec.Code != http.StatusOK { - t.Fatalf("status = %d, want %d, body=%s", rec.Code, http.StatusOK, rec.Body.String()) - } - - cfg, err := config.LoadConfig(configPath) - if err != nil { - t.Fatalf("LoadConfig() error = %v", err) - } - if got := cfg.ModelList[0].APIBase; got != "" { - t.Fatalf("model_list[0].api_base = %q, want empty string", got) - } -} - -func TestHandlePatchConfig_AcceptsFlexibleStringSliceString(t *testing.T) { - configPath, cleanup := setupOAuthTestEnv(t) - defer cleanup() - - h := NewHandler(configPath) - mux := http.NewServeMux() - h.RegisterRoutes(mux) - - req := httptest.NewRequest(http.MethodPatch, "/api/config", bytes.NewBufferString(`{ - "channels": { - "irc": { - "enabled": true, - "server": "irc.example.com:6667", - "nick": "testbot", - "channels": "general, #ops,dev" - } - } - }`)) - req.Header.Set("Content-Type", "application/json") - - rec := httptest.NewRecorder() - mux.ServeHTTP(rec, req) - if rec.Code != http.StatusOK { - t.Fatalf("status = %d, want %d, body=%s", rec.Code, http.StatusOK, rec.Body.String()) - } - - cfg, err := config.LoadConfig(configPath) - if err != nil { - t.Fatalf("LoadConfig() error = %v", err) - } - - want := config.FlexibleStringSlice{"general", "#ops", "dev"} - if !reflect.DeepEqual(cfg.Channels.IRC.Channels, want) { - t.Fatalf("channels.irc.channels = %#v, want %#v", cfg.Channels.IRC.Channels, want) - } -} +package api + +import ( + "bytes" + "net/http" + "net/http/httptest" + "reflect" + "testing" + + "github.com/sipeed/picoclaw/pkg/config" +) + +func TestHandleUpdateConfig_PreservesExecAllowRemoteDefaultWhenOmitted(t *testing.T) { + configPath, cleanup := setupOAuthTestEnv(t) + defer cleanup() + + h := NewHandler(configPath) + mux := http.NewServeMux() + h.RegisterRoutes(mux) + + req := httptest.NewRequest(http.MethodPut, "/api/config", bytes.NewBufferString(`{ + "agents": { + "defaults": { + "workspace": "~/.picoclaw/workspace" + } + }, + "model_list": [ + { + "model_name": "custom-default", + "model": "openai/gpt-4o", + "api_key": "sk-default" + } + ] + }`)) + req.Header.Set("Content-Type", "application/json") + + rec := httptest.NewRecorder() + mux.ServeHTTP(rec, req) + if rec.Code != http.StatusOK { + t.Fatalf("status = %d, want %d, body=%s", rec.Code, http.StatusOK, rec.Body.String()) + } + + cfg, err := config.LoadConfig(configPath) + if err != nil { + t.Fatalf("LoadConfig() error = %v", err) + } + if !cfg.Tools.Exec.AllowRemote { + t.Fatal("tools.exec.allow_remote should remain true when omitted from PUT /api/config") + } +} + +func TestHandleUpdateConfig_DoesNotInheritDefaultModelFields(t *testing.T) { + configPath, cleanup := setupOAuthTestEnv(t) + defer cleanup() + + h := NewHandler(configPath) + mux := http.NewServeMux() + h.RegisterRoutes(mux) + + req := httptest.NewRequest(http.MethodPut, "/api/config", bytes.NewBufferString(`{ + "agents": { + "defaults": { + "workspace": "~/.picoclaw/workspace" + } + }, + "model_list": [ + { + "model_name": "custom-default", + "model": "openai/gpt-4o", + "api_key": "sk-default" + } + ] + }`)) + req.Header.Set("Content-Type", "application/json") + + rec := httptest.NewRecorder() + mux.ServeHTTP(rec, req) + if rec.Code != http.StatusOK { + t.Fatalf("status = %d, want %d, body=%s", rec.Code, http.StatusOK, rec.Body.String()) + } + + cfg, err := config.LoadConfig(configPath) + if err != nil { + t.Fatalf("LoadConfig() error = %v", err) + } + if got := cfg.ModelList[0].APIBase; got != "" { + t.Fatalf("model_list[0].api_base = %q, want empty string", got) + } +} + +func TestHandlePatchConfig_AcceptsFlexibleStringSliceString(t *testing.T) { + configPath, cleanup := setupOAuthTestEnv(t) + defer cleanup() + + h := NewHandler(configPath) + mux := http.NewServeMux() + h.RegisterRoutes(mux) + + req := httptest.NewRequest(http.MethodPatch, "/api/config", bytes.NewBufferString(`{ + "channels": { + "irc": { + "enabled": true, + "server": "irc.example.com:6667", + "nick": "testbot", + "channels": "general, #ops,dev" + } + } + }`)) + req.Header.Set("Content-Type", "application/json") + + rec := httptest.NewRecorder() + mux.ServeHTTP(rec, req) + if rec.Code != http.StatusOK { + t.Fatalf("status = %d, want %d, body=%s", rec.Code, http.StatusOK, rec.Body.String()) + } + + cfg, err := config.LoadConfig(configPath) + if err != nil { + t.Fatalf("LoadConfig() error = %v", err) + } + + want := config.FlexibleStringSlice{"general", "#ops", "dev"} + if !reflect.DeepEqual(cfg.Channels.IRC.Channels, want) { + t.Fatalf("channels.irc.channels = %#v, want %#v", cfg.Channels.IRC.Channels, want) + } +}