package cli import ( "bytes" "log/slog" "os" "path/filepath" "strings" "testing" ) // runCLI executes the root command with args and returns combined stdout. func runCLI(t *testing.T, args ...string) (string, error) { t.Helper() root := newRootCmd() var out bytes.Buffer root.SetOut(&out) root.SetErr(&out) root.SetArgs(args) err := root.Execute() return out.String(), err } func TestConfigInitWritesAndRefusesOverwrite(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "config.yaml") out, err := runCLI(t, "config", "init", "--config", path) if err != nil { t.Fatalf("config init: %v", err) } if !strings.Contains(out, "wrote example config") { t.Errorf("unexpected output: %q", out) } data, err := os.ReadFile(path) if err != nil { t.Fatalf("reading written config: %v", err) } if !strings.Contains(string(data), "personalities:") { t.Error("written config missing personalities section") } // Second init without --force must fail. if _, err := runCLI(t, "config", "init", "--config", path); err == nil { t.Error("expected error re-initialising existing config without --force") } // With --force it should succeed. if _, err := runCLI(t, "config", "init", "--config", path, "--force"); err != nil { t.Errorf("config init --force: %v", err) } } func TestConfigShowValidatesAndPrints(t *testing.T) { dir := t.TempDir() tea := filepath.Join(dir, "tea.yml") if err := os.WriteFile(tea, []byte("logins:\n - name: b\n url: https://git.unkin.net\n token: tok\n default: true\n user: botuser\n"), 0o600); err != nil { t.Fatal(err) } cfgPath := filepath.Join(dir, "config.yaml") body := "gitea_url: https://git.unkin.net\nrepos: [unkin/teabot]\npersonalities:\n" + " - {name: solo, tea_config: " + tea + ", role: both, git_name: S, git_email: s@x}\n" if err := os.WriteFile(cfgPath, []byte(body), 0o600); err != nil { t.Fatal(err) } out, err := runCLI(t, "config", "show", "--config", cfgPath) if err != nil { t.Fatalf("config show: %v", err) } for _, want := range []string{"unkin/teabot", "solo", "botuser", "agent-dev"} { if !strings.Contains(out, want) { t.Errorf("config show output missing %q\n%s", want, out) } } } func TestConfigShowReportsInvalidConfig(t *testing.T) { dir := t.TempDir() cfgPath := filepath.Join(dir, "config.yaml") // No repos, no personalities -> validation error. if err := os.WriteFile(cfgPath, []byte("gitea_url: https://git.unkin.net\n"), 0o600); err != nil { t.Fatal(err) } if _, err := runCLI(t, "config", "show", "--config", cfgPath); err == nil { t.Error("expected validation error for empty config") } } func TestNewLoggerLevels(t *testing.T) { for _, lvl := range []string{"debug", "info", "warn", "error", "unknown"} { if l := newLogger(false, lvl); l == nil { t.Errorf("newLogger(%q) returned nil", lvl) } } if l := newLogger(true, "info"); l == nil { t.Error("JSON logger nil") } // Sanity: debug logger actually enables debug level. if !newLogger(false, "debug").Enabled(nil, slog.LevelDebug) { //nolint:staticcheck t.Error("debug logger should enable debug level") } }