package config import ( "os" "path/filepath" "reflect" "testing" ) func TestDefaults(t *testing.T) { d := Defaults() if d.Port != 8976 { t.Errorf("expected port 8976, got %d", d.Port) } if d.CursorCLIPath != "agent" { t.Errorf("expected cursor_cli_path 'agent', got %q", d.CursorCLIPath) } if d.DefaultModel != "auto" { t.Errorf("expected default_model 'auto', got %q", d.DefaultModel) } if d.Timeout != 300 { t.Errorf("expected timeout 300, got %d", d.Timeout) } if d.MaxConcurrent != 5 { t.Errorf("expected max_concurrent 5, got %d", d.MaxConcurrent) } if d.LogLevel != "INFO" { t.Errorf("expected log_level 'INFO', got %q", d.LogLevel) } if d.UseACP { t.Errorf("expected use_acp false by default, got true") } } func TestLoadValidYAML(t *testing.T) { content := `port: 9000 cursor_cli_path: mycli default_model: gpt-5.2 timeout: 60 max_concurrent: 10 use_acp: true log_level: DEBUG available_models: - gpt-5.2 - claude-sonnet-4-20250514 ` dir := t.TempDir() path := filepath.Join(dir, "config.yaml") if err := os.WriteFile(path, []byte(content), 0644); err != nil { t.Fatal(err) } cfg, err := Load(path) if err != nil { t.Fatalf("unexpected error: %v", err) } want := Config{ Port: 9000, CursorCLIPath: "mycli", DefaultModel: "gpt-5.2", Timeout: 60, MaxConcurrent: 10, UseACP: true, ChatOnlyWorkspace: true, LogLevel: "DEBUG", AvailableModels: []string{"gpt-5.2", "claude-sonnet-4-20250514"}, SystemPrompt: DefaultSystemPrompt, CursorMode: "plan", WorkspaceRoot: "", } if !reflect.DeepEqual(*cfg, want) { t.Errorf("mismatch\n got: %+v\nwant: %+v", *cfg, want) } } func TestLoadMissingFile(t *testing.T) { path := filepath.Join(t.TempDir(), "does-not-exist.yaml") cfg, err := Load(path) if err != nil { t.Fatalf("expected no error for missing file, got: %v", err) } d := Defaults() if !reflect.DeepEqual(*cfg, d) { t.Errorf("expected defaults\n got: %+v\nwant: %+v", *cfg, d) } } func TestLoadInvalidYAML(t *testing.T) { content := `{{not valid yaml` dir := t.TempDir() path := filepath.Join(dir, "bad.yaml") if err := os.WriteFile(path, []byte(content), 0644); err != nil { t.Fatal(err) } _, err := Load(path) if err == nil { t.Fatal("expected error for invalid YAML, got nil") } } func TestLoadValidation(t *testing.T) { tests := []struct { name string yaml string wantErr string }{ { name: "zero port", yaml: "port: 0\ncursor_cli_path: agent\ntimeout: 10\n", wantErr: "port must be > 0", }, { name: "empty cursor_cli_path", yaml: "port: 80\ncursor_cli_path: \"\"\ntimeout: 10\n", wantErr: "cursor_cli_path must not be empty", }, { name: "zero timeout", yaml: "port: 80\ncursor_cli_path: agent\ntimeout: 0\n", wantErr: "timeout must be > 0", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "config.yaml") if err := os.WriteFile(path, []byte(tt.yaml), 0644); err != nil { t.Fatal(err) } _, err := Load(path) if err == nil { t.Fatal("expected error, got nil") } if got := err.Error(); !contains(got, tt.wantErr) { t.Errorf("error %q should contain %q", got, tt.wantErr) } }) } } func contains(s, substr string) bool { return len(s) >= len(substr) && searchSubstring(s, substr) } func searchSubstring(s, substr string) bool { for i := 0; i <= len(s)-len(substr); i++ { if s[i:i+len(substr)] == substr { return true } } return false }