Files
teabot/internal/cli/cli_test.go
T
unkinben 1b4448afb4
ci/woodpecker/pr/test Pipeline failed
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
Add teabot daemon implementation
teabot watches Gitea repos and dispatches one-shot Claude Code sessions in
Docker containers to work issues and review PRs, acting as configurable bot
personalities.

Claude-Session: https://claude.ai/code/session_015ur3i7D2azsMAWTSVABApv
2026-07-26 23:36:21 +10:00

103 lines
3.1 KiB
Go

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")
}
}