package config import ( "encoding/json" "os" "path/filepath" "testing" ) func TestOpenCodeIntegration(t *testing.T) { o := &OpenCode{} t.Run("String", func(t *testing.T) { if got := o.String(); got != "OpenCode" { t.Errorf("String() = %q, want %q", got, "OpenCode") } }) t.Run("implements Runner", func(t *testing.T) { var _ Runner = o }) t.Run("implements Editor", func(t *testing.T) { var _ Editor = o }) } func TestOpenCodeEdit(t *testing.T) { o := &OpenCode{} tmpDir := t.TempDir() setTestHome(t, tmpDir) configDir := filepath.Join(tmpDir, ".config", "opencode") configPath := filepath.Join(configDir, "opencode.json") stateDir := filepath.Join(tmpDir, ".local", "state", "opencode") statePath := filepath.Join(stateDir, "model.json") cleanup := func() { os.RemoveAll(configDir) os.RemoveAll(stateDir) } t.Run("fresh install", func(t *testing.T) { cleanup() if err := o.Edit([]string{"llama3.2"}); err != nil { t.Fatal(err) } assertOpenCodeModelExists(t, configPath, "llama3.2") assertOpenCodeRecentModel(t, statePath, 0, "ollama", "llama3.2") }) t.Run("preserve other providers", func(t *testing.T) { cleanup() os.MkdirAll(configDir, 0o755) os.WriteFile(configPath, []byte(`{"provider":{"anthropic":{"apiKey":"xxx"}}}`), 0o644) if err := o.Edit([]string{"llama3.2"}); err != nil { t.Fatal(err) } data, _ := os.ReadFile(configPath) var cfg map[string]any json.Unmarshal(data, &cfg) provider := cfg["provider"].(map[string]any) if provider["anthropic"] == nil { t.Error("anthropic provider was removed") } assertOpenCodeModelExists(t, configPath, "llama3.2") }) t.Run("preserve other models", func(t *testing.T) { cleanup() os.MkdirAll(configDir, 0o755) os.WriteFile(configPath, []byte(`{"provider":{"ollama":{"models":{"mistral":{"name":"Mistral"}}}}}`), 0o644) if err := o.Edit([]string{"llama3.2"}); err != nil { t.Fatal(err) } assertOpenCodeModelExists(t, configPath, "mistral") assertOpenCodeModelExists(t, configPath, "llama3.2") }) t.Run("update existing model", func(t *testing.T) { cleanup() o.Edit([]string{"llama3.2"}) o.Edit([]string{"llama3.2"}) assertOpenCodeModelExists(t, configPath, "llama3.2") }) t.Run("preserve top-level keys", func(t *testing.T) { cleanup() os.MkdirAll(configDir, 0o755) os.WriteFile(configPath, []byte(`{"theme":"dark","keybindings":{}}`), 0o644) if err := o.Edit([]string{"llama3.2"}); err != nil { t.Fatal(err) } data, _ := os.ReadFile(configPath) var cfg map[string]any json.Unmarshal(data, &cfg) if cfg["theme"] != "dark" { t.Error("theme was removed") } if cfg["keybindings"] == nil { t.Error("keybindings was removed") } }) t.Run("model state - insert at index 0", func(t *testing.T) { cleanup() os.MkdirAll(stateDir, 0o755) os.WriteFile(statePath, []byte(`{"recent":[{"providerID":"anthropic","modelID":"claude"}],"favorite":[],"variant":{}}`), 0o644) if err := o.Edit([]string{"llama3.2"}); err != nil { t.Fatal(err) } assertOpenCodeRecentModel(t, statePath, 0, "ollama", "llama3.2") assertOpenCodeRecentModel(t, statePath, 1, "anthropic", "claude") }) t.Run("model state - preserve favorites and variants", func(t *testing.T) { cleanup() os.MkdirAll(stateDir, 0o755) os.WriteFile(statePath, []byte(`{"recent":[],"favorite":[{"providerID":"x","modelID":"y"}],"variant":{"a":"b"}}`), 0o644) if err := o.Edit([]string{"llama3.2"}); err != nil { t.Fatal(err) } data, _ := os.ReadFile(statePath) var state map[string]any json.Unmarshal(data, &state) if len(state["favorite"].([]any)) != 1 { t.Error("favorite was modified") } if state["variant"].(map[string]any)["a"] != "b" { t.Error("variant was modified") } }) t.Run("model state - deduplicate on re-add", func(t *testing.T) { cleanup() os.MkdirAll(stateDir, 0o755) os.WriteFile(statePath, []byte(`{"recent":[{"providerID":"ollama","modelID":"llama3.2"},{"providerID":"anthropic","modelID":"claude"}],"favorite":[],"variant":{}}`), 0o644) if err := o.Edit([]string{"llama3.2"}); err != nil { t.Fatal(err) } data, _ := os.ReadFile(statePath) var state map[string]any json.Unmarshal(data, &state) recent := state["recent"].([]any) if len(recent) != 2 { t.Errorf("expected 2 recent entries, got %d", len(recent)) } assertOpenCodeRecentModel(t, statePath, 0, "ollama", "llama3.2") }) t.Run("remove model", func(t *testing.T) { cleanup() // First add two models o.Edit([]string{"llama3.2", "mistral"}) assertOpenCodeModelExists(t, configPath, "llama3.2") assertOpenCodeModelExists(t, configPath, "mistral") // Then remove one by only selecting the other o.Edit([]string{"llama3.2"}) assertOpenCodeModelExists(t, configPath, "llama3.2") assertOpenCodeModelNotExists(t, configPath, "mistral") }) t.Run("preserve user customizations on managed models", func(t *testing.T) { cleanup() if err := o.Edit([]string{"llama3.2"}); err != nil { t.Fatal(err) } // Add custom fields to the model entry (simulating user edits) data, _ := os.ReadFile(configPath) var cfg map[string]any json.Unmarshal(data, &cfg) provider := cfg["provider"].(map[string]any) ollama := provider["ollama"].(map[string]any) models := ollama["models"].(map[string]any) entry := models["llama3.2"].(map[string]any) entry["_myPref"] = "custom-value" entry["_myNum"] = 42 configData, _ := json.MarshalIndent(cfg, "", " ") os.WriteFile(configPath, configData, 0o644) // Re-run Edit — should preserve custom fields if err := o.Edit([]string{"llama3.2"}); err != nil { t.Fatal(err) } data, _ = os.ReadFile(configPath) json.Unmarshal(data, &cfg) provider = cfg["provider"].(map[string]any) ollama = provider["ollama"].(map[string]any) models = ollama["models"].(map[string]any) entry = models["llama3.2"].(map[string]any) if entry["_myPref"] != "custom-value" { t.Errorf("_myPref was lost: got %v", entry["_myPref"]) } if entry["_myNum"] != float64(42) { t.Errorf("_myNum was lost: got %v", entry["_myNum"]) } if v, ok := entry["_launch"].(bool); !ok || !v { t.Errorf("_launch marker missing or false: got %v", entry["_launch"]) } }) t.Run("migrate legacy [Ollama] suffix entries", func(t *testing.T) { cleanup() // Write a config with a legacy entry (has [Ollama] suffix but no _launch marker) os.MkdirAll(configDir, 0o755) os.WriteFile(configPath, []byte(`{"provider":{"ollama":{"models":{"llama3.2":{"name":"llama3.2 [Ollama]"}}}}}`), 0o644) if err := o.Edit([]string{"llama3.2"}); err != nil { t.Fatal(err) } data, _ := os.ReadFile(configPath) var cfg map[string]any json.Unmarshal(data, &cfg) provider := cfg["provider"].(map[string]any) ollama := provider["ollama"].(map[string]any) models := ollama["models"].(map[string]any) entry := models["llama3.2"].(map[string]any) // _launch marker should be added if v, ok := entry["_launch"].(bool); !ok || !v { t.Errorf("_launch marker not added during migration: got %v", entry["_launch"]) } // [Ollama] suffix should be stripped if name, ok := entry["name"].(string); !ok || name != "llama3.2" { t.Errorf("name suffix not stripped: got %q", entry["name"]) } }) t.Run("remove model preserves non-ollama models", func(t *testing.T) { cleanup() os.MkdirAll(configDir, 0o755) // Add a non-Ollama model manually os.WriteFile(configPath, []byte(`{"provider":{"ollama":{"models":{"external":{"name":"External Model"}}}}}`), 0o644) o.Edit([]string{"llama3.2"}) assertOpenCodeModelExists(t, configPath, "llama3.2") assertOpenCodeModelExists(t, configPath, "external") // Should be preserved }) } func assertOpenCodeModelExists(t *testing.T, path, model string) { t.Helper() data, err := os.ReadFile(path) if err != nil { t.Fatal(err) } var cfg map[string]any if err := json.Unmarshal(data, &cfg); err != nil { t.Fatal(err) } provider, ok := cfg["provider"].(map[string]any) if !ok { t.Fatal("provider not found") } ollama, ok := provider["ollama"].(map[string]any) if !ok { t.Fatal("ollama provider not found") } models, ok := ollama["models"].(map[string]any) if !ok { t.Fatal("models not found") } if models[model] == nil { t.Errorf("model %s not found", model) } } func assertOpenCodeModelNotExists(t *testing.T, path, model string) { t.Helper() data, err := os.ReadFile(path) if err != nil { t.Fatal(err) } var cfg map[string]any if err := json.Unmarshal(data, &cfg); err != nil { t.Fatal(err) } provider, ok := cfg["provider"].(map[string]any) if !ok { return // No provider means no model } ollama, ok := provider["ollama"].(map[string]any) if !ok { return // No ollama means no model } models, ok := ollama["models"].(map[string]any) if !ok { return // No models means no model } if models[model] != nil { t.Errorf("model %s should not exist but was found", model) } } func assertOpenCodeRecentModel(t *testing.T, path string, index int, providerID, modelID string) { t.Helper() data, err := os.ReadFile(path) if err != nil { t.Fatal(err) } var state map[string]any if err := json.Unmarshal(data, &state); err != nil { t.Fatal(err) } recent, ok := state["recent"].([]any) if !ok { t.Fatal("recent not found") } if index >= len(recent) { t.Fatalf("index %d out of range (len=%d)", index, len(recent)) } entry, ok := recent[index].(map[string]any) if !ok { t.Fatal("entry is not a map") } if entry["providerID"] != providerID { t.Errorf("expected providerID %s, got %s", providerID, entry["providerID"]) } if entry["modelID"] != modelID { t.Errorf("expected modelID %s, got %s", modelID, entry["modelID"]) } } // Edge case tests for opencode.go func TestOpenCodeEdit_CorruptedConfigJSON(t *testing.T) { o := &OpenCode{} tmpDir := t.TempDir() setTestHome(t, tmpDir) configDir := filepath.Join(tmpDir, ".config", "opencode") configPath := filepath.Join(configDir, "opencode.json") os.MkdirAll(configDir, 0o755) os.WriteFile(configPath, []byte(`{corrupted json content`), 0o644) // Should not panic - corrupted JSON should be treated as empty err := o.Edit([]string{"llama3.2"}) if err != nil { t.Fatalf("Edit failed with corrupted config: %v", err) } // Verify valid JSON was created data, _ := os.ReadFile(configPath) var cfg map[string]any if err := json.Unmarshal(data, &cfg); err != nil { t.Errorf("resulting config is not valid JSON: %v", err) } } func TestOpenCodeEdit_CorruptedStateJSON(t *testing.T) { o := &OpenCode{} tmpDir := t.TempDir() setTestHome(t, tmpDir) stateDir := filepath.Join(tmpDir, ".local", "state", "opencode") statePath := filepath.Join(stateDir, "model.json") os.MkdirAll(stateDir, 0o755) os.WriteFile(statePath, []byte(`{corrupted state`), 0o644) err := o.Edit([]string{"llama3.2"}) if err != nil { t.Fatalf("Edit failed with corrupted state: %v", err) } // Verify valid state was created data, _ := os.ReadFile(statePath) var state map[string]any if err := json.Unmarshal(data, &state); err != nil { t.Errorf("resulting state is not valid JSON: %v", err) } } func TestOpenCodeEdit_WrongTypeProvider(t *testing.T) { o := &OpenCode{} tmpDir := t.TempDir() setTestHome(t, tmpDir) configDir := filepath.Join(tmpDir, ".config", "opencode") configPath := filepath.Join(configDir, "opencode.json") os.MkdirAll(configDir, 0o755) os.WriteFile(configPath, []byte(`{"provider": "not a map"}`), 0o644) err := o.Edit([]string{"llama3.2"}) if err != nil { t.Fatalf("Edit with wrong type provider failed: %v", err) } // Verify provider is now correct type data, _ := os.ReadFile(configPath) var cfg map[string]any json.Unmarshal(data, &cfg) provider, ok := cfg["provider"].(map[string]any) if !ok { t.Fatalf("provider should be map after setup, got %T", cfg["provider"]) } if provider["ollama"] == nil { t.Error("ollama provider should be created") } } func TestOpenCodeEdit_WrongTypeRecent(t *testing.T) { o := &OpenCode{} tmpDir := t.TempDir() setTestHome(t, tmpDir) stateDir := filepath.Join(tmpDir, ".local", "state", "opencode") statePath := filepath.Join(stateDir, "model.json") os.MkdirAll(stateDir, 0o755) os.WriteFile(statePath, []byte(`{"recent": "not an array", "favorite": [], "variant": {}}`), 0o644) err := o.Edit([]string{"llama3.2"}) if err != nil { t.Fatalf("Edit with wrong type recent failed: %v", err) } // The function should handle this gracefully data, _ := os.ReadFile(statePath) var state map[string]any json.Unmarshal(data, &state) // recent should be properly set after setup recent, ok := state["recent"].([]any) if !ok { t.Logf("Note: recent type after setup is %T (documenting behavior)", state["recent"]) } else if len(recent) == 0 { t.Logf("Note: recent is empty (documenting behavior)") } } func TestOpenCodeEdit_EmptyModels(t *testing.T) { o := &OpenCode{} tmpDir := t.TempDir() setTestHome(t, tmpDir) configDir := filepath.Join(tmpDir, ".config", "opencode") configPath := filepath.Join(configDir, "opencode.json") os.MkdirAll(configDir, 0o755) originalContent := `{"provider":{"ollama":{"models":{"existing":{}}}}}` os.WriteFile(configPath, []byte(originalContent), 0o644) // Empty models should be no-op err := o.Edit([]string{}) if err != nil { t.Fatalf("Edit with empty models failed: %v", err) } // Original content should be preserved (file not modified) data, _ := os.ReadFile(configPath) if string(data) != originalContent { t.Errorf("empty models should not modify file, but content changed") } } func TestOpenCodeEdit_SpecialCharsInModelName(t *testing.T) { o := &OpenCode{} tmpDir := t.TempDir() setTestHome(t, tmpDir) // Model name with special characters (though unusual) specialModel := `model-with-"quotes"` err := o.Edit([]string{specialModel}) if err != nil { t.Fatalf("Edit with special chars failed: %v", err) } // Verify it was stored correctly configDir := filepath.Join(tmpDir, ".config", "opencode") configPath := filepath.Join(configDir, "opencode.json") data, _ := os.ReadFile(configPath) var cfg map[string]any if err := json.Unmarshal(data, &cfg); err != nil { t.Fatalf("resulting config is invalid JSON: %v", err) } // Model should be accessible provider, _ := cfg["provider"].(map[string]any) ollama, _ := provider["ollama"].(map[string]any) models, _ := ollama["models"].(map[string]any) if models[specialModel] == nil { t.Errorf("model with special chars not found in config") } } func TestOpenCodeModels_NoConfig(t *testing.T) { o := &OpenCode{} tmpDir := t.TempDir() setTestHome(t, tmpDir) models := o.Models() if len(models) > 0 { t.Errorf("expected nil/empty for missing config, got %v", models) } }