package harness import ( "os" "path/filepath" "strings" "testing" "github.com/bytedance/sonic" ) func TestClaudePreLaunchPinsSelectedModelAcrossClaudeTiers(t *testing.T) { t.Parallel() env, cleanup, err := claudePreLaunch("https://example.com/anthropic", "test-key", "openai/gpt-5") if err != nil { t.Fatalf("claudePreLaunch() error = %v", err) } defer cleanup() for _, want := range []string{ "CLAUDE_CODE_SIMPLE=1", "ANTHROPIC_DEFAULT_SONNET_MODEL=openai/gpt-5", "ANTHROPIC_DEFAULT_OPUS_MODEL=openai/gpt-5", "ANTHROPIC_DEFAULT_HAIKU_MODEL=openai/gpt-5", } { parts := strings.SplitN(want, "=", 2) if got := envValue(env, parts[0]); got != parts[1] { t.Fatalf("env[%q] = %q, want %q", parts[0], got, parts[1]) } } if got := envValue(env, "ANTHROPIC_MODEL"); got != "" { t.Fatalf("did not expect ANTHROPIC_MODEL in env, got %#v", env) } } func TestClaudeWriteNativeConfigPinsTierDefaults(t *testing.T) { home := t.TempDir() t.Setenv("HOME", home) settingsDir := filepath.Join(home, ".claude") if err := os.MkdirAll(settingsDir, 0o755); err != nil { t.Fatalf("mkdir settings dir: %v", err) } settingsPath := filepath.Join(settingsDir, "settings.json") initial := `{"env":{"EXISTING":"keep","ANTHROPIC_MODEL":"stale-model"}}` if err := os.WriteFile(settingsPath, []byte(initial), 0o600); err != nil { t.Fatalf("write initial settings: %v", err) } if err := claudeWriteNativeConfig("https://example.com/anthropic", "test-key", "openai/gpt-5"); err != nil { t.Fatalf("claudeWriteNativeConfig() error = %v", err) } b, err := os.ReadFile(settingsPath) if err != nil { t.Fatalf("read settings: %v", err) } var settings map[string]any if err := sonic.Unmarshal(b, &settings); err != nil { t.Fatalf("unmarshal settings: %v", err) } envRaw, ok := settings["env"] if !ok { t.Fatalf("expected env map in settings, got %#v", settings) } envMap, ok := envRaw.(map[string]any) if !ok { t.Fatalf("env map type = %T, want map[string]any", envRaw) } for key, want := range map[string]string{ "EXISTING": "keep", "ANTHROPIC_BASE_URL": "https://example.com/anthropic", "ANTHROPIC_API_KEY": "test-key", "ANTHROPIC_DEFAULT_SONNET_MODEL": "openai/gpt-5", "ANTHROPIC_DEFAULT_OPUS_MODEL": "openai/gpt-5", "ANTHROPIC_DEFAULT_HAIKU_MODEL": "openai/gpt-5", } { if got, _ := envMap[key].(string); got != want { t.Fatalf("env[%q] = %q, want %q", key, got, want) } } if _, ok := envMap["ANTHROPIC_MODEL"]; ok { t.Fatalf("did not expect legacy ANTHROPIC_MODEL in settings env: %#v", envMap) } } func TestOpencodeModelRef(t *testing.T) { t.Parallel() if got := opencodeModelRef("gpt-4.1"); got != "bifrost/gpt-4.1" { t.Fatalf("opencodeModelRef() = %q, want %q", got, "bifrost/gpt-4.1") } } func TestOpencodePreLaunchWritesCustomProviderConfig(t *testing.T) { xdg := t.TempDir() t.Setenv("XDG_CONFIG_HOME", xdg) env, cleanup, err := opencodePreLaunch("https://example.com/openai", "test-key", "gpt-4.1") if err != nil { t.Fatalf("opencodePreLaunch() error = %v", err) } defer cleanup() if len(env) != 2 { t.Fatalf("unexpected env returned: %#v", env) } configPath := envValue(env, "OPENCODE_CONFIG") if configPath == "" { t.Fatalf("expected OPENCODE_CONFIG in env, got %#v", env) } b, err := os.ReadFile(configPath) if err != nil { t.Fatalf("read generated config: %v", err) } cfg := string(b) for _, want := range []string{ `"model": "bifrost/gpt-4.1"`, `"bifrost": {`, `"npm": "@ai-sdk/openai-compatible"`, `"baseURL": "https://example.com/openai"`, `"apiKey": "test-key"`, `"gpt-4.1": {`, } { if !strings.Contains(cfg, want) { t.Fatalf("expected generated config to contain %q, got %s", want, cfg) } } tuiPath := envValue(env, "OPENCODE_TUI_CONFIG") if tuiPath == "" { t.Fatalf("expected OPENCODE_TUI_CONFIG in env, got %#v", env) } tuiCfg, err := os.ReadFile(tuiPath) if err != nil { t.Fatalf("read generated tui config: %v", err) } if !strings.Contains(string(tuiCfg), `"theme": "system"`) { t.Fatalf("expected generated tui config to set system theme, got %s", string(tuiCfg)) } } func TestOpencodePreLaunchPreservesExistingTheme(t *testing.T) { xdg := t.TempDir() t.Setenv("XDG_CONFIG_HOME", xdg) tuiPath := filepath.Join(xdg, "opencode", "tui.json") if err := os.MkdirAll(filepath.Dir(tuiPath), 0o755); err != nil { t.Fatalf("mkdir tui dir: %v", err) } if err := os.WriteFile(tuiPath, []byte("{\n \"theme\": \"light\"\n}\n"), 0o600); err != nil { t.Fatalf("write tui config: %v", err) } env, cleanup, err := opencodePreLaunch("https://example.com/openai", "test-key", "gpt-4.1") if err != nil { t.Fatalf("opencodePreLaunch() error = %v", err) } defer cleanup() if got := envValue(env, "OPENCODE_TUI_CONFIG"); got != "" { t.Fatalf("did not expect OPENCODE_TUI_CONFIG override when user theme exists, got %#v", env) } if got := envValue(env, "OPENCODE_CONFIG"); got == "" { t.Fatalf("expected OPENCODE_CONFIG to remain present, got %#v", env) } } func TestOpencodePreLaunchAddsSystemThemeWithoutModel(t *testing.T) { xdg := t.TempDir() t.Setenv("XDG_CONFIG_HOME", xdg) env, cleanup, err := opencodePreLaunch("https://example.com/openai", "test-key", "") if err != nil { t.Fatalf("opencodePreLaunch() error = %v", err) } tuiPath := envValue(env, "OPENCODE_TUI_CONFIG") if tuiPath == "" { t.Fatalf("expected OPENCODE_TUI_CONFIG in env, got %#v", env) } if got := envValue(env, "OPENCODE_CONFIG"); got != "" { t.Fatalf("did not expect OPENCODE_CONFIG without a model, got %#v", env) } if _, err := os.Stat(tuiPath); err != nil { t.Fatalf("expected generated tui config to exist: %v", err) } cleanup() if _, err := os.Stat(tuiPath); !os.IsNotExist(err) { t.Fatalf("expected generated tui config to be removed after cleanup, stat err=%v", err) } } func TestLoadOpencodeTUIConfigSupportsJSONC(t *testing.T) { t.Parallel() path := filepath.Join(t.TempDir(), "tui.json") content := "{\n // keep my choice\n \"theme\": \"light\",\n \"foo\": true,\n}\n" if err := os.WriteFile(path, []byte(content), 0o600); err != nil { t.Fatalf("write tui config: %v", err) } cfg, hasTheme, err := loadOpencodeTUIConfig(path) if err != nil { t.Fatalf("loadOpencodeTUIConfig() error = %v", err) } if !hasTheme { t.Fatal("expected theme to be detected") } if cfg["theme"] != "light" { t.Fatalf("cfg[theme] = %#v, want %q", cfg["theme"], "light") } } func TestOpencodeTUIConfigPathPrefersXDG(t *testing.T) { xdg := t.TempDir() t.Setenv("XDG_CONFIG_HOME", xdg) got, err := opencodeTUIConfigPath() if err != nil { t.Fatalf("opencodeTUIConfigPath() error = %v", err) } want := filepath.Join(xdg, "opencode", "tui.json") if got != want { t.Fatalf("opencodeTUIConfigPath() = %q, want %q", got, want) } } func envValue(env []string, key string) string { prefix := key + "=" for _, entry := range env { if strings.HasPrefix(entry, prefix) { return strings.TrimPrefix(entry, prefix) } } return "" }