- 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
119 lines
4.0 KiB
Go
119 lines
4.0 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"code.m3ta.dev/m3tam3re/talk/internal/queue"
|
|
)
|
|
|
|
// Diese Tests decken die Steuer-Befehle ohne Daemon ab (in-process über
|
|
// die run-Seam; TALK_PORT=1 aus der setGetenv-Grundausstattung → Daemon-
|
|
// Erkennung schlägt sofort fehl). Das Verhalten MIT Daemon testen die
|
|
// Prozessgrenzen-Tests in /test.
|
|
|
|
// TestStatusOhneDaemon: `talk status` ohne Daemon ist ein klarer Fehler
|
|
// mit Hinweis (kein Stillversagen).
|
|
func TestStatusOhneDaemon(t *testing.T) {
|
|
setGetenv(t, nil)
|
|
var out, errBuf bytes.Buffer
|
|
code := runCLI([]string{"status"}, &out, &errBuf)
|
|
if code != exitFailure {
|
|
t.Fatalf("talk status ohne Daemon: Exit-Code = %d, want %d", code, exitFailure)
|
|
}
|
|
if !strings.Contains(errBuf.String(), "nicht erreichbar") {
|
|
t.Errorf("stderr = %q, will Hinweis auf fehlenden Daemon", errBuf.String())
|
|
}
|
|
if out.Len() != 0 {
|
|
t.Errorf("stdout = %q, will leer", out.String())
|
|
}
|
|
}
|
|
|
|
// TestCancelOhneDaemon: ohne Daemon gibt es nichts zu abbrechen — klarer
|
|
// Fehler.
|
|
func TestCancelOhneDaemon(t *testing.T) {
|
|
setGetenv(t, nil)
|
|
var out, errBuf bytes.Buffer
|
|
code := runCLI([]string{"cancel"}, &out, &errBuf)
|
|
if code != exitFailure {
|
|
t.Fatalf("talk cancel ohne Daemon: Exit-Code = %d, want %d", code, exitFailure)
|
|
}
|
|
if !strings.Contains(errBuf.String(), "nichts zu abbrechen") {
|
|
t.Errorf("stderr = %q, will Hinweis", errBuf.String())
|
|
}
|
|
}
|
|
|
|
// TestMuteOhneDaemonSchreibtDatei: `talk mute` ohne Daemon schreibt die
|
|
// Mute-Datei direkt — und genau diese Datei respektiert der Degraded Mode
|
|
// von say anschließend.
|
|
func TestMuteOhneDaemonSchreibtDatei(t *testing.T) {
|
|
root := t.TempDir()
|
|
setGetenv(t, map[string]string{"XDG_CONFIG_HOME": root})
|
|
mutePath := filepath.Join(root, "talk", queue.MuteFileName)
|
|
|
|
var out, errBuf bytes.Buffer
|
|
if code := runCLI([]string{"mute"}, &out, &errBuf); code != exitOK {
|
|
t.Fatalf("talk mute ohne Daemon: Exit-Code = %d, want %d (stderr: %q)", code, exitOK, errBuf.String())
|
|
}
|
|
if got := out.String(); got != "muted\n" {
|
|
t.Errorf("talk mute stdout = %q, want %q", got, "muted\n")
|
|
}
|
|
if !strings.Contains(errBuf.String(), "direkt") {
|
|
t.Errorf("stderr = %q, will Hinweis auf direkten Datei-Schreib", errBuf.String())
|
|
}
|
|
b, err := os.ReadFile(mutePath)
|
|
if err != nil {
|
|
t.Fatalf("Mute-Datei fehlt: %v", err)
|
|
}
|
|
if strings.TrimSpace(string(b)) != "true" {
|
|
t.Errorf("Mute-Datei = %q, want true", b)
|
|
}
|
|
|
|
// say im Degraded Mode respektiert dieselbe Datei (tonlos verworfen,
|
|
// ohne irgendeinen Provider zu brauchen).
|
|
out.Reset()
|
|
errBuf.Reset()
|
|
if code := runCLI([]string{"say", "stille bitte"}, &out, &errBuf); code != exitOK {
|
|
t.Fatalf("talk say (mute an): Exit-Code = %d, want %d (stderr: %q)", code, exitOK, errBuf.String())
|
|
}
|
|
if got := out.String(); got != "muted\n" {
|
|
t.Errorf("talk say (mute an) stdout = %q, want %q", got, "muted\n")
|
|
}
|
|
|
|
// unmute hebt es auf (Datei false).
|
|
out.Reset()
|
|
errBuf.Reset()
|
|
if code := runCLI([]string{"unmute"}, &out, &errBuf); code != exitOK {
|
|
t.Fatalf("talk unmute: Exit-Code = %d (stderr: %q)", code, errBuf.String())
|
|
}
|
|
if got := out.String(); got != "unmuted\n" {
|
|
t.Errorf("talk unmute stdout = %q, want %q", got, "unmuted\n")
|
|
}
|
|
b, err = os.ReadFile(mutePath)
|
|
if err != nil || strings.TrimSpace(string(b)) != "false" {
|
|
t.Errorf("Mute-Datei nach unmute = %q (%v), want false", b, err)
|
|
}
|
|
}
|
|
|
|
// TestSayOhneConfigDir: Degraded Mode ohne Config-Dir (kein HOME) spielt
|
|
// weiter statt zu scheitern — Best-effort ohne Lock und Mute.
|
|
func TestSayOhneConfigDir(t *testing.T) {
|
|
seen := withFakeEspeak(t)
|
|
setGetenv(t, map[string]string{"TALK_AUDIO_SINK": "null"})
|
|
|
|
var out, errBuf bytes.Buffer
|
|
code := runCLI([]string{"Hallo"}, &out, &errBuf)
|
|
if code != exitOK {
|
|
t.Fatalf("talk ohne HOME: Exit-Code = %d, want %d; stderr = %q", code, exitOK, errBuf.String())
|
|
}
|
|
if _, err := os.Stat(seen); err != nil {
|
|
t.Errorf("fake espeak nicht gerufen: %v", err)
|
|
}
|
|
if !strings.Contains(errBuf.String(), "Degraded Mode") {
|
|
t.Errorf("stderr = %q, will Degraded-Hinweis", errBuf.String())
|
|
}
|
|
}
|