Files
talk/cmd/talk/cli_test.go
T
m3ta-chiron e1a3981d17 feat(cli): say über den Daemon, Steuer-Befehle als Daemon-Aufrufe, Degraded Mode
- say/Default erkennt den Daemon via GET /status: enqueue + sofort zurück
  (stdout = Item-ID), --wait pollt bis zur Fertigstellung, --now springt vor
- mute/unmute/status/cancel mit plain Output; mute/unmute schreiben ohne
  Daemon die Mute-Datei direkt (gleiche Quelle, wirkt sofort im Daemon)
- Degraded Mode: Lock-Datei talk.lock im Config-Dir (Kollision = warten,
  verwaist nach 60 s), sequenzielles lokales Abspielen, mute respektiert
- Handler-Signatur der Befehlstabelle erhält stdout (say/Steuer-Befehle
  geben Ergebnisse aus); --priority wird validiert
2026-10-18 17:10:36 +02:00

142 lines
4.5 KiB
Go

package main
import (
"bytes"
"io"
"strings"
"testing"
)
// wantSubcommands ist die bindende CLI-Surface aus der Spec — bewusst als
// hartkodierte Liste getestet (nicht aus der Befehlstabelle abgeleitet),
// damit die Tests anschlagen, wenn Tabelle und Spec auseinanderlaufen.
var wantSubcommands = []string{
"say", "wrap", "mute", "unmute", "status", "cancel", "voices", "test", "doctor", "setup",
}
// wantStubCommands ist wantSubcommands ohne die implementierten Befehle —
// say (Task 02/04) sowie mute/unmute/status/cancel (Task 04, Daemon-
// Aufrufe) sind real; die restlichen bleiben Stubs bis zu ihren Tasks.
var wantStubCommands = []string{
"wrap", "voices", "test", "doctor", "setup",
}
// runCLI ist der Test-Einstieg in run mit leerem stdin (kein Text über
// Pipe) — dem üblichen Aufruf ohne Eingabe.
func runCLI(argv []string, stdout, stderr io.Writer) int {
return run(argv, strings.NewReader(""), stdout, stderr)
}
func TestHelpListetAlleSubcommands(t *testing.T) {
var out, errBuf bytes.Buffer
code := runCLI([]string{"--help"}, &out, &errBuf)
if code != exitOK {
t.Fatalf("--help: Exit-Code = %d, want %d (stderr: %q)", code, exitOK, errBuf.String())
}
lines := strings.Split(out.String(), "\n")
for _, name := range wantSubcommands {
found := false
for _, ln := range lines {
if strings.HasPrefix(strings.TrimLeft(ln, " "), name+" ") {
found = true
break
}
}
if !found {
t.Errorf("--help listet Befehl %q nicht (Output: %q)", name, out.String())
}
}
}
func TestVersionDefault(t *testing.T) {
var out, errBuf bytes.Buffer
code := runCLI([]string{"--version"}, &out, &errBuf)
if code != exitOK {
t.Fatalf("--version: Exit-Code = %d, want %d", code, exitOK)
}
if got, want := out.String(), "talk version dev\n"; got != want {
t.Errorf("--version Output = %q, want %q", got, want)
}
}
func TestVersionStringMitCommit(t *testing.T) {
oldCommit := commit
commit = "c0ffee1"
defer func() { commit = oldCommit }()
if got, want := versionString(), "talk version dev (c0ffee1)"; got != want {
t.Errorf("versionString() = %q, want %q", got, want)
}
}
func TestSubcommandStubs(t *testing.T) {
for _, name := range wantStubCommands {
t.Run(name, func(t *testing.T) {
var out, errBuf bytes.Buffer
code := runCLI([]string{name}, &out, &errBuf)
if code == exitOK {
t.Errorf("talk %s: Exit-Code 0, want non-zero für Stub", name)
}
if !strings.Contains(errBuf.String(), "not implemented yet") {
t.Errorf("talk %s: stderr = %q, will Hinweis auf nicht implementiert", name, errBuf.String())
}
if !strings.Contains(errBuf.String(), name) {
t.Errorf("talk %s: stderr nennt den Befehlsnamen nicht: %q", name, errBuf.String())
}
if out.Len() != 0 {
t.Errorf("talk %s: unerwartete stdout-Ausgabe %q", name, out.String())
}
})
}
}
func TestSayHelpZeigtFlags(t *testing.T) {
var out, errBuf bytes.Buffer
code := runCLI([]string{"say", "--help"}, &out, &errBuf)
if code != exitOK {
t.Fatalf("talk say --help: Exit-Code = %d, want %d", code, exitOK)
}
for _, f := range []string{"-wait", "-now", "-priority", "-voice", "-lang", "-provider"} {
if !strings.Contains(out.String(), f) {
t.Errorf("talk say --help zeigt Flag %s nicht (Output: %q)", f, out.String())
}
}
if strings.Contains(out.String(), "noch nicht implementiert") {
t.Errorf("talk say --help soll say nicht mehr als Stub markieren: %q", out.String())
}
}
func TestHelpSubcommand(t *testing.T) {
t.Run("ohne Argument", func(t *testing.T) {
var out, errBuf bytes.Buffer
if code := runCLI([]string{"help"}, &out, &errBuf); code != exitOK {
t.Fatalf("talk help: Exit-Code = %d, want %d", code, exitOK)
}
for _, name := range wantSubcommands {
if !strings.Contains(out.String(), name) {
t.Errorf("talk help listet %q nicht", name)
}
}
})
t.Run("mit Befehl", func(t *testing.T) {
var out, errBuf bytes.Buffer
if code := runCLI([]string{"help", "mute"}, &out, &errBuf); code != exitOK {
t.Fatalf("talk help mute: Exit-Code = %d, want %d", code, exitOK)
}
if !strings.Contains(out.String(), "talk mute") {
t.Errorf("talk help mute zeigt Usage von mute nicht: %q", out.String())
}
})
}
func TestUnbekanntesFlag(t *testing.T) {
var out, errBuf bytes.Buffer
code := runCLI([]string{"--bogus"}, &out, &errBuf)
if code != exitUsage {
t.Fatalf("talk --bogus: Exit-Code = %d, want %d", code, exitUsage)
}
if !strings.Contains(errBuf.String(), "bogus") {
t.Errorf("talk --bogus: Fehlermeldung fehlt auf stderr: %q", errBuf.String())
}
}