mirror of
https://github.com/cexll/myclaude.git
synced 2026-02-10 03:14:32 +08:00
- Move all source files to internal/{app,backend,config,executor,logger,parser,utils}
- Integrate third-party libraries: zerolog, goccy/go-json, gopsutil, cobra/viper
- Add comprehensive unit tests for utils package (94.3% coverage)
- Add performance benchmarks for string operations
- Fix error display: cleanup warnings no longer pollute Recent Errors
- Add GitHub Actions CI workflow
- Add Makefile for build automation
- Add README documentation
Generated with SWE-Agent.ai
Co-Authored-By: SWE-Agent.ai <noreply@swe-agent.ai>
222 lines
6.5 KiB
Go
222 lines
6.5 KiB
Go
package config
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func TestResolveAgentConfig_Defaults(t *testing.T) {
|
|
home := t.TempDir()
|
|
t.Setenv("HOME", home)
|
|
t.Setenv("USERPROFILE", home)
|
|
t.Cleanup(ResetModelsConfigCacheForTest)
|
|
ResetModelsConfigCacheForTest()
|
|
|
|
// Test that default agents resolve correctly without config file
|
|
tests := []struct {
|
|
agent string
|
|
wantBackend string
|
|
wantModel string
|
|
wantPromptFile string
|
|
}{
|
|
{"oracle", "claude", "claude-opus-4-5-20251101", "~/.claude/skills/omo/references/oracle.md"},
|
|
{"librarian", "claude", "claude-sonnet-4-5-20250929", "~/.claude/skills/omo/references/librarian.md"},
|
|
{"explore", "opencode", "opencode/grok-code", "~/.claude/skills/omo/references/explore.md"},
|
|
{"frontend-ui-ux-engineer", "gemini", "", "~/.claude/skills/omo/references/frontend-ui-ux-engineer.md"},
|
|
{"document-writer", "gemini", "", "~/.claude/skills/omo/references/document-writer.md"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.agent, func(t *testing.T) {
|
|
backend, model, promptFile, _, _, _, _ := resolveAgentConfig(tt.agent)
|
|
if backend != tt.wantBackend {
|
|
t.Errorf("backend = %q, want %q", backend, tt.wantBackend)
|
|
}
|
|
if model != tt.wantModel {
|
|
t.Errorf("model = %q, want %q", model, tt.wantModel)
|
|
}
|
|
if promptFile != tt.wantPromptFile {
|
|
t.Errorf("promptFile = %q, want %q", promptFile, tt.wantPromptFile)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestResolveAgentConfig_UnknownAgent(t *testing.T) {
|
|
home := t.TempDir()
|
|
t.Setenv("HOME", home)
|
|
t.Setenv("USERPROFILE", home)
|
|
t.Cleanup(ResetModelsConfigCacheForTest)
|
|
ResetModelsConfigCacheForTest()
|
|
|
|
backend, model, promptFile, _, _, _, _ := resolveAgentConfig("unknown-agent")
|
|
if backend != "opencode" {
|
|
t.Errorf("unknown agent backend = %q, want %q", backend, "opencode")
|
|
}
|
|
if model != "opencode/grok-code" {
|
|
t.Errorf("unknown agent model = %q, want %q", model, "opencode/grok-code")
|
|
}
|
|
if promptFile != "" {
|
|
t.Errorf("unknown agent promptFile = %q, want empty", promptFile)
|
|
}
|
|
}
|
|
|
|
func TestLoadModelsConfig_NoFile(t *testing.T) {
|
|
home := "/nonexistent/path/that/does/not/exist"
|
|
t.Setenv("HOME", home)
|
|
t.Setenv("USERPROFILE", home)
|
|
t.Cleanup(ResetModelsConfigCacheForTest)
|
|
ResetModelsConfigCacheForTest()
|
|
|
|
cfg := loadModelsConfig()
|
|
if cfg.DefaultBackend != "opencode" {
|
|
t.Errorf("DefaultBackend = %q, want %q", cfg.DefaultBackend, "opencode")
|
|
}
|
|
if len(cfg.Agents) != 6 {
|
|
t.Errorf("len(Agents) = %d, want 6", len(cfg.Agents))
|
|
}
|
|
}
|
|
|
|
func TestLoadModelsConfig_WithFile(t *testing.T) {
|
|
// Create temp dir and config file
|
|
tmpDir := t.TempDir()
|
|
configDir := filepath.Join(tmpDir, ".codeagent")
|
|
if err := os.MkdirAll(configDir, 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
configContent := `{
|
|
"default_backend": "claude",
|
|
"default_model": "claude-opus-4",
|
|
"backends": {
|
|
"Claude": {
|
|
"base_url": "https://backend.example",
|
|
"api_key": "backend-key"
|
|
},
|
|
"codex": {
|
|
"base_url": "https://openai.example",
|
|
"api_key": "openai-key"
|
|
}
|
|
},
|
|
"agents": {
|
|
"custom-agent": {
|
|
"backend": "codex",
|
|
"model": "gpt-4o",
|
|
"description": "Custom agent",
|
|
"base_url": "https://agent.example",
|
|
"api_key": "agent-key"
|
|
}
|
|
}
|
|
}`
|
|
configPath := filepath.Join(configDir, "models.json")
|
|
if err := os.WriteFile(configPath, []byte(configContent), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
t.Setenv("HOME", tmpDir)
|
|
t.Setenv("USERPROFILE", tmpDir)
|
|
t.Cleanup(ResetModelsConfigCacheForTest)
|
|
ResetModelsConfigCacheForTest()
|
|
|
|
cfg := loadModelsConfig()
|
|
|
|
if cfg.DefaultBackend != "claude" {
|
|
t.Errorf("DefaultBackend = %q, want %q", cfg.DefaultBackend, "claude")
|
|
}
|
|
if cfg.DefaultModel != "claude-opus-4" {
|
|
t.Errorf("DefaultModel = %q, want %q", cfg.DefaultModel, "claude-opus-4")
|
|
}
|
|
|
|
// Check custom agent
|
|
if agent, ok := cfg.Agents["custom-agent"]; !ok {
|
|
t.Error("custom-agent not found")
|
|
} else {
|
|
if agent.Backend != "codex" {
|
|
t.Errorf("custom-agent.Backend = %q, want %q", agent.Backend, "codex")
|
|
}
|
|
if agent.Model != "gpt-4o" {
|
|
t.Errorf("custom-agent.Model = %q, want %q", agent.Model, "gpt-4o")
|
|
}
|
|
}
|
|
|
|
// Check that defaults are merged
|
|
if _, ok := cfg.Agents["oracle"]; !ok {
|
|
t.Error("default agent oracle should be merged")
|
|
}
|
|
|
|
baseURL, apiKey := ResolveBackendConfig("claude")
|
|
if baseURL != "https://backend.example" {
|
|
t.Errorf("ResolveBackendConfig(baseURL) = %q, want %q", baseURL, "https://backend.example")
|
|
}
|
|
if apiKey != "backend-key" {
|
|
t.Errorf("ResolveBackendConfig(apiKey) = %q, want %q", apiKey, "backend-key")
|
|
}
|
|
|
|
backend, model, _, _, agentBaseURL, agentAPIKey, _ := ResolveAgentConfig("custom-agent")
|
|
if backend != "codex" {
|
|
t.Errorf("ResolveAgentConfig(backend) = %q, want %q", backend, "codex")
|
|
}
|
|
if model != "gpt-4o" {
|
|
t.Errorf("ResolveAgentConfig(model) = %q, want %q", model, "gpt-4o")
|
|
}
|
|
if agentBaseURL != "https://agent.example" {
|
|
t.Errorf("ResolveAgentConfig(baseURL) = %q, want %q", agentBaseURL, "https://agent.example")
|
|
}
|
|
if agentAPIKey != "agent-key" {
|
|
t.Errorf("ResolveAgentConfig(apiKey) = %q, want %q", agentAPIKey, "agent-key")
|
|
}
|
|
}
|
|
|
|
func TestResolveAgentConfig_DynamicAgent(t *testing.T) {
|
|
home := t.TempDir()
|
|
t.Setenv("HOME", home)
|
|
t.Setenv("USERPROFILE", home)
|
|
t.Cleanup(ResetModelsConfigCacheForTest)
|
|
ResetModelsConfigCacheForTest()
|
|
|
|
agentDir := filepath.Join(home, ".codeagent", "agents")
|
|
if err := os.MkdirAll(agentDir, 0o755); err != nil {
|
|
t.Fatalf("MkdirAll: %v", err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(agentDir, "sarsh.md"), []byte("prompt\n"), 0o644); err != nil {
|
|
t.Fatalf("WriteFile: %v", err)
|
|
}
|
|
|
|
backend, model, promptFile, _, _, _, _ := resolveAgentConfig("sarsh")
|
|
if backend != "opencode" {
|
|
t.Errorf("backend = %q, want %q", backend, "opencode")
|
|
}
|
|
if model != "opencode/grok-code" {
|
|
t.Errorf("model = %q, want %q", model, "opencode/grok-code")
|
|
}
|
|
if promptFile != "~/.codeagent/agents/sarsh.md" {
|
|
t.Errorf("promptFile = %q, want %q", promptFile, "~/.codeagent/agents/sarsh.md")
|
|
}
|
|
}
|
|
|
|
func TestLoadModelsConfig_InvalidJSON(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
configDir := filepath.Join(tmpDir, ".codeagent")
|
|
if err := os.MkdirAll(configDir, 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Write invalid JSON
|
|
configPath := filepath.Join(configDir, "models.json")
|
|
if err := os.WriteFile(configPath, []byte("invalid json {"), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
t.Setenv("HOME", tmpDir)
|
|
t.Setenv("USERPROFILE", tmpDir)
|
|
t.Cleanup(ResetModelsConfigCacheForTest)
|
|
ResetModelsConfigCacheForTest()
|
|
|
|
cfg := loadModelsConfig()
|
|
// Should fall back to defaults
|
|
if cfg.DefaultBackend != "opencode" {
|
|
t.Errorf("invalid JSON should fallback, got DefaultBackend = %q", cfg.DefaultBackend)
|
|
}
|
|
}
|