package launch import ( "encoding/json" "os" "path/filepath" "testing" ) func TestClineIntegration(t *testing.T) { c := &Cline{} t.Run("String", func(t *testing.T) { if got := c.String(); got != "Cline" { t.Errorf("String() = %q, want %q", got, "Cline") } }) t.Run("implements Runner", func(t *testing.T) { var _ Runner = c }) t.Run("implements Editor", func(t *testing.T) { var _ Editor = c }) } func TestClineEdit(t *testing.T) { c := &Cline{} tmpDir := t.TempDir() setTestHome(t, tmpDir) configDir := filepath.Join(tmpDir, ".cline", "data") configPath := filepath.Join(configDir, "globalState.json") readConfig := func() map[string]any { data, _ := os.ReadFile(configPath) var config map[string]any json.Unmarshal(data, &config) return config } t.Run("creates config from scratch", func(t *testing.T) { os.RemoveAll(filepath.Join(tmpDir, ".cline")) if err := c.Edit([]string{"kimi-k2.5:cloud"}); err != nil { t.Fatal(err) } config := readConfig() if config["actModeApiProvider"] != "ollama" { t.Errorf("actModeApiProvider = %v, want ollama", config["actModeApiProvider"]) } if config["actModeOllamaModelId"] != "kimi-k2.5:cloud" { t.Errorf("actModeOllamaModelId = %v, want kimi-k2.5:cloud", config["actModeOllamaModelId"]) } if config["planModeApiProvider"] != "ollama" { t.Errorf("planModeApiProvider = %v, want ollama", config["planModeApiProvider"]) } if config["planModeOllamaModelId"] != "kimi-k2.5:cloud" { t.Errorf("planModeOllamaModelId = %v, want kimi-k2.5:cloud", config["planModeOllamaModelId"]) } if config["welcomeViewCompleted"] != true { t.Errorf("welcomeViewCompleted = %v, want true", config["welcomeViewCompleted"]) } }) t.Run("preserves existing fields", func(t *testing.T) { os.RemoveAll(filepath.Join(tmpDir, ".cline")) os.MkdirAll(configDir, 0o755) existing := map[string]any{ "remoteRulesToggles": map[string]any{}, "remoteWorkflowToggles": map[string]any{}, "customSetting": "keep-me", } data, _ := json.Marshal(existing) os.WriteFile(configPath, data, 0o644) if err := c.Edit([]string{"glm-5:cloud"}); err != nil { t.Fatal(err) } config := readConfig() if config["customSetting"] != "keep-me" { t.Errorf("customSetting was not preserved") } if config["actModeOllamaModelId"] != "glm-5:cloud" { t.Errorf("actModeOllamaModelId = %v, want glm-5:cloud", config["actModeOllamaModelId"]) } }) t.Run("updates model on re-edit", func(t *testing.T) { os.RemoveAll(filepath.Join(tmpDir, ".cline")) if err := c.Edit([]string{"kimi-k2.5:cloud"}); err != nil { t.Fatal(err) } if err := c.Edit([]string{"glm-5:cloud"}); err != nil { t.Fatal(err) } config := readConfig() if config["actModeOllamaModelId"] != "glm-5:cloud" { t.Errorf("actModeOllamaModelId = %v, want glm-5:cloud", config["actModeOllamaModelId"]) } if config["planModeOllamaModelId"] != "glm-5:cloud" { t.Errorf("planModeOllamaModelId = %v, want glm-5:cloud", config["planModeOllamaModelId"]) } }) t.Run("empty models is no-op", func(t *testing.T) { os.RemoveAll(filepath.Join(tmpDir, ".cline")) if err := c.Edit(nil); err != nil { t.Fatal(err) } if _, err := os.Stat(configPath); !os.IsNotExist(err) { t.Error("expected no config file to be created for empty models") } }) t.Run("uses first model as primary", func(t *testing.T) { os.RemoveAll(filepath.Join(tmpDir, ".cline")) if err := c.Edit([]string{"kimi-k2.5:cloud", "glm-5:cloud"}); err != nil { t.Fatal(err) } config := readConfig() if config["actModeOllamaModelId"] != "kimi-k2.5:cloud" { t.Errorf("actModeOllamaModelId = %v, want kimi-k2.5:cloud (first model)", config["actModeOllamaModelId"]) } }) } func TestClineModels(t *testing.T) { c := &Cline{} tmpDir := t.TempDir() setTestHome(t, tmpDir) configDir := filepath.Join(tmpDir, ".cline", "data") configPath := filepath.Join(configDir, "globalState.json") t.Run("returns nil when no config", func(t *testing.T) { if models := c.Models(); models != nil { t.Errorf("Models() = %v, want nil", models) } }) t.Run("returns nil when provider is not ollama", func(t *testing.T) { os.MkdirAll(configDir, 0o755) config := map[string]any{ "actModeApiProvider": "anthropic", "actModeOllamaModelId": "some-model", } data, _ := json.Marshal(config) os.WriteFile(configPath, data, 0o644) if models := c.Models(); models != nil { t.Errorf("Models() = %v, want nil", models) } }) t.Run("returns model when ollama is configured", func(t *testing.T) { os.MkdirAll(configDir, 0o755) config := map[string]any{ "actModeApiProvider": "ollama", "actModeOllamaModelId": "kimi-k2.5:cloud", } data, _ := json.Marshal(config) os.WriteFile(configPath, data, 0o644) models := c.Models() if len(models) != 1 || models[0] != "kimi-k2.5:cloud" { t.Errorf("Models() = %v, want [kimi-k2.5:cloud]", models) } }) } func TestClinePaths(t *testing.T) { c := &Cline{} tmpDir := t.TempDir() setTestHome(t, tmpDir) t.Run("returns nil when no config exists", func(t *testing.T) { if paths := c.Paths(); paths != nil { t.Errorf("Paths() = %v, want nil", paths) } }) t.Run("returns path when config exists", func(t *testing.T) { configDir := filepath.Join(tmpDir, ".cline", "data") os.MkdirAll(configDir, 0o755) configPath := filepath.Join(configDir, "globalState.json") os.WriteFile(configPath, []byte("{}"), 0o644) paths := c.Paths() if len(paths) != 1 || paths[0] != configPath { t.Errorf("Paths() = %v, want [%s]", paths, configPath) } }) }