mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-05-02 09:47:28 +08:00
fix: tighten release review regressions
This commit is contained in:
@@ -54,14 +54,14 @@ func statusCmd() {
|
||||
}
|
||||
fmt.Printf("Model: %s\n", activeModel)
|
||||
fmt.Printf("Proxy: %s\n", activeProxyName)
|
||||
fmt.Printf("CLIProxyAPI Base: %s\n", cfg.Providers.Proxy.APIBase)
|
||||
fmt.Printf("Provider API Base: %s\n", activeProvider.APIBase)
|
||||
fmt.Printf("Supports /v1/responses/compact: %v\n", providers.ProviderSupportsResponsesCompact(cfg, activeProxyName))
|
||||
hasKey := cfg.Providers.Proxy.APIKey != ""
|
||||
hasKey := strings.TrimSpace(activeProvider.APIKey) != ""
|
||||
status := "not set"
|
||||
if hasKey {
|
||||
status = "✓"
|
||||
}
|
||||
fmt.Printf("CLIProxyAPI Key: %s\n", status)
|
||||
fmt.Printf("Provider API Key: %s\n", status)
|
||||
fmt.Printf("Logging: %v\n", cfg.Logging.Enabled)
|
||||
if cfg.Logging.Enabled {
|
||||
fmt.Printf("Log File: %s\n", cfg.LogFilePath())
|
||||
|
||||
71
cmd/clawgo/cmd_status_test.go
Normal file
71
cmd/clawgo/cmd_status_test.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"clawgo/pkg/config"
|
||||
)
|
||||
|
||||
func TestStatusCmdUsesActiveProviderDetails(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tmp := t.TempDir()
|
||||
cfgPath := filepath.Join(tmp, "config.json")
|
||||
workspace := filepath.Join(tmp, "workspace")
|
||||
if err := os.MkdirAll(workspace, 0755); err != nil {
|
||||
t.Fatalf("mkdir workspace: %v", err)
|
||||
}
|
||||
|
||||
cfg := config.DefaultConfig()
|
||||
cfg.Logging.Enabled = false
|
||||
cfg.Agents.Defaults.Workspace = workspace
|
||||
cfg.Agents.Defaults.Proxy = "backup"
|
||||
cfg.Providers.Proxy.APIBase = "https://primary.example/v1"
|
||||
cfg.Providers.Proxy.APIKey = ""
|
||||
cfg.Providers.Proxies["backup"] = config.ProviderConfig{
|
||||
APIBase: "https://backup.example/v1",
|
||||
APIKey: "backup-key",
|
||||
Models: []string{"backup-model"},
|
||||
Auth: "bearer",
|
||||
TimeoutSec: 30,
|
||||
}
|
||||
if err := config.SaveConfig(cfgPath, cfg); err != nil {
|
||||
t.Fatalf("save config: %v", err)
|
||||
}
|
||||
|
||||
prev := globalConfigPathOverride
|
||||
globalConfigPathOverride = cfgPath
|
||||
defer func() { globalConfigPathOverride = prev }()
|
||||
|
||||
oldStdout := os.Stdout
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatalf("pipe: %v", err)
|
||||
}
|
||||
os.Stdout = w
|
||||
defer func() { os.Stdout = oldStdout }()
|
||||
|
||||
statusCmd()
|
||||
|
||||
_ = w.Close()
|
||||
var buf bytes.Buffer
|
||||
if _, err := io.Copy(&buf, r); err != nil {
|
||||
t.Fatalf("read stdout: %v", err)
|
||||
}
|
||||
|
||||
out := buf.String()
|
||||
if !strings.Contains(out, "Proxy: backup") {
|
||||
t.Fatalf("expected backup proxy in output, got: %s", out)
|
||||
}
|
||||
if !strings.Contains(out, "Provider API Base: https://backup.example/v1") {
|
||||
t.Fatalf("expected active provider api base in output, got: %s", out)
|
||||
}
|
||||
if !strings.Contains(out, "Provider API Key: ✓") {
|
||||
t.Fatalf("expected active provider api key status in output, got: %s", out)
|
||||
}
|
||||
}
|
||||
@@ -311,15 +311,7 @@ func (s *Server) handleWebUIConfig(w http.ResponseWriter, r *http.Request) {
|
||||
var oldMap map[string]interface{}
|
||||
_ = json.Unmarshal(oldCfgRaw, &oldMap)
|
||||
|
||||
riskyPaths := []string{
|
||||
"channels.telegram.token",
|
||||
"channels.telegram.allow_from",
|
||||
"channels.telegram.allow_chats",
|
||||
"providers.proxy.base_url",
|
||||
"providers.proxy.api_key",
|
||||
"gateway.token",
|
||||
"gateway.port",
|
||||
}
|
||||
riskyPaths := collectRiskyConfigPaths(oldMap, body)
|
||||
changedRisky := make([]string, 0)
|
||||
for _, p := range riskyPaths {
|
||||
if fmt.Sprintf("%v", getPathValue(oldMap, p)) != fmt.Sprintf("%v", getPathValue(body, p)) {
|
||||
@@ -418,6 +410,50 @@ func getPathValue(m map[string]interface{}, path string) interface{} {
|
||||
return cur
|
||||
}
|
||||
|
||||
func collectRiskyConfigPaths(oldMap, newMap map[string]interface{}) []string {
|
||||
paths := []string{
|
||||
"channels.telegram.token",
|
||||
"channels.telegram.allow_from",
|
||||
"channels.telegram.allow_chats",
|
||||
"providers.proxy.api_base",
|
||||
"providers.proxy.api_key",
|
||||
"gateway.token",
|
||||
"gateway.port",
|
||||
}
|
||||
seen := map[string]bool{}
|
||||
for _, path := range paths {
|
||||
seen[path] = true
|
||||
}
|
||||
for _, name := range collectProviderProxyNames(oldMap, newMap) {
|
||||
for _, field := range []string{"api_base", "api_key"} {
|
||||
path := "providers.proxies." + name + "." + field
|
||||
if !seen[path] {
|
||||
paths = append(paths, path)
|
||||
seen[path] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return paths
|
||||
}
|
||||
|
||||
func collectProviderProxyNames(maps ...map[string]interface{}) []string {
|
||||
seen := map[string]bool{}
|
||||
names := make([]string, 0)
|
||||
for _, root := range maps {
|
||||
providers, _ := root["providers"].(map[string]interface{})
|
||||
proxies, _ := providers["proxies"].(map[string]interface{})
|
||||
for name := range proxies {
|
||||
if strings.TrimSpace(name) == "" || seen[name] {
|
||||
continue
|
||||
}
|
||||
seen[name] = true
|
||||
names = append(names, name)
|
||||
}
|
||||
}
|
||||
sort.Strings(names)
|
||||
return names
|
||||
}
|
||||
|
||||
func (s *Server) handleWebUIUpload(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.checkAuth(r) {
|
||||
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||
|
||||
109
pkg/api/server_test.go
Normal file
109
pkg/api/server_test.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
cfgpkg "clawgo/pkg/config"
|
||||
)
|
||||
|
||||
func TestHandleWebUIConfigRequiresConfirmForProviderAPIBaseChange(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tmp := t.TempDir()
|
||||
cfgPath := filepath.Join(tmp, "config.json")
|
||||
|
||||
cfg := cfgpkg.DefaultConfig()
|
||||
cfg.Logging.Enabled = false
|
||||
cfg.Providers.Proxy.APIBase = "https://old.example/v1"
|
||||
cfg.Providers.Proxy.APIKey = "test-key"
|
||||
if err := cfgpkg.SaveConfig(cfgPath, cfg); err != nil {
|
||||
t.Fatalf("save config: %v", err)
|
||||
}
|
||||
|
||||
bodyCfg := cfgpkg.DefaultConfig()
|
||||
bodyCfg.Logging.Enabled = false
|
||||
bodyCfg.Providers.Proxy.APIBase = "https://new.example/v1"
|
||||
bodyCfg.Providers.Proxy.APIKey = "test-key"
|
||||
body, err := json.Marshal(bodyCfg)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal body: %v", err)
|
||||
}
|
||||
|
||||
srv := NewServer("127.0.0.1", 0, "", nil)
|
||||
srv.SetConfigPath(cfgPath)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/webui/api/config", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
srv.handleWebUIConfig(rec, req)
|
||||
|
||||
if rec.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400, got %d: %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
if !strings.Contains(rec.Body.String(), `"requires_confirm":true`) {
|
||||
t.Fatalf("expected requires_confirm response, got: %s", rec.Body.String())
|
||||
}
|
||||
if !strings.Contains(rec.Body.String(), `providers.proxy.api_base`) {
|
||||
t.Fatalf("expected providers.proxy.api_base in changed_fields, got: %s", rec.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleWebUIConfigRequiresConfirmForCustomProviderSecretChange(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tmp := t.TempDir()
|
||||
cfgPath := filepath.Join(tmp, "config.json")
|
||||
|
||||
cfg := cfgpkg.DefaultConfig()
|
||||
cfg.Logging.Enabled = false
|
||||
cfg.Providers.Proxies["backup"] = cfgpkg.ProviderConfig{
|
||||
APIBase: "https://backup.example/v1",
|
||||
APIKey: "old-secret",
|
||||
Models: []string{"backup-model"},
|
||||
Auth: "bearer",
|
||||
TimeoutSec: 30,
|
||||
}
|
||||
if err := cfgpkg.SaveConfig(cfgPath, cfg); err != nil {
|
||||
t.Fatalf("save config: %v", err)
|
||||
}
|
||||
|
||||
bodyCfg := cfgpkg.DefaultConfig()
|
||||
bodyCfg.Logging.Enabled = false
|
||||
bodyCfg.Providers.Proxies["backup"] = cfgpkg.ProviderConfig{
|
||||
APIBase: "https://backup.example/v1",
|
||||
APIKey: "new-secret",
|
||||
Models: []string{"backup-model"},
|
||||
Auth: "bearer",
|
||||
TimeoutSec: 30,
|
||||
}
|
||||
body, err := json.Marshal(bodyCfg)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal body: %v", err)
|
||||
}
|
||||
|
||||
srv := NewServer("127.0.0.1", 0, "", nil)
|
||||
srv.SetConfigPath(cfgPath)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/webui/api/config", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
srv.handleWebUIConfig(rec, req)
|
||||
|
||||
if rec.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400, got %d: %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
if !strings.Contains(rec.Body.String(), `"requires_confirm":true`) {
|
||||
t.Fatalf("expected requires_confirm response, got: %s", rec.Body.String())
|
||||
}
|
||||
if !strings.Contains(rec.Body.String(), `providers.proxies.backup.api_key`) {
|
||||
t.Fatalf("expected providers.proxies.backup.api_key in changed_fields, got: %s", rec.Body.String())
|
||||
}
|
||||
}
|
||||
@@ -673,7 +673,15 @@ func waitSubagentDone(t *testing.T, manager *SubagentManager, timeout time.Durat
|
||||
tasks := manager.ListTasks()
|
||||
if len(tasks) > 0 {
|
||||
task := tasks[0]
|
||||
if task.Status != "running" {
|
||||
for _, candidate := range tasks[1:] {
|
||||
if candidate.Created > task.Created || (candidate.Created == task.Created && candidate.ID > task.ID) {
|
||||
task = candidate
|
||||
}
|
||||
}
|
||||
manager.mu.RLock()
|
||||
_, stillRunning := manager.cancelFuncs[task.ID]
|
||||
manager.mu.RUnlock()
|
||||
if task.Status != "running" && !stillRunning {
|
||||
return task
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user