1b4448afb4
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
103 lines
3.1 KiB
Go
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")
|
|
}
|
|
}
|