feat(notify): Desktop-Benachrichtigung für den Ruhezeit-Toast
quiet_mode = "toast" war bisher nur eine Protokollzeile. Jetzt zeigt der Daemon eine Desktop-Benachrichtigung — über die Bordmittel des Systems, exec-basiert wie osbuiltin, ohne Bibliothek und ohne CGO: notify-send (Linux), osascript display notification (macOS), PowerShell Toast (Windows; Text base64, kein Quoting-Problem). Best effort: fehlt das Werkzeug, bleibt es bei der Protokollzeile plus Start-Warnung; `talk doctor` zeigt den Weg unter „Toast". Der Aufruf läuft asynchron, ein hängender Notification-Daemon blockiert die Queue nie. Queue: OnToast-Hook, gefeuert bei Toast-Verwurf beim Eintritt und im Worker (nie unter dem Mutex). Tests: notify-Paket mit fake notify-send im PATH, Prozessgrenze: Ruhezeit + toast → fake notify-send bekommt Titel und Text, der Provider keinen Treffer. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
a9ca32bed9
commit
9e2aa506a6
7 files changed
+279
-6
No files matched your search
@@ -0,0 +1,55 @@
|
||||
package test
|
||||
|
||||
// Prozessgrenzen-Test für den Ruhezeit-Toast: quiet_mode = toast in aktiver
|
||||
// Ruhezeit löst eine Desktop-Benachrichtigung aus — hier ein fake
|
||||
// notify-send im PATH des Daemons, das seine Argumente protokolliert.
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestRuhezeitToastZeigtNotification(t *testing.T) {
|
||||
if runtime.GOOS != "linux" {
|
||||
t.Skip("fake notify-send gibt es nur auf Linux")
|
||||
}
|
||||
root := t.TempDir()
|
||||
bin := filepath.Join(root, "bin")
|
||||
if err := os.MkdirAll(bin, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
seen := filepath.Join(root, "seen.txt")
|
||||
script := "#!/bin/sh\nprintf '%s\\n' \"$@\" > \"" + seen + "\"\n"
|
||||
if err := os.WriteFile(filepath.Join(bin, "notify-send"), []byte(script), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fake := newFakeProvider(t, 0)
|
||||
d := startDaemon(t, root, fake.srv.URL, map[string]string{
|
||||
"TALK_QUIET_HOURS": fensterUm(),
|
||||
"TALK_QUIET_MODE": "toast",
|
||||
"PATH": bin + string(os.PathListSeparator) + os.Getenv("PATH"),
|
||||
})
|
||||
|
||||
code, m := speak(t, d, `{"text":"Build fertig, bitte Review"}`)
|
||||
if code != 202 || m["discarded"] != "quiet_hours" || m["toast"] != true {
|
||||
t.Fatalf("speak in Ruhezeit (toast): %d %v", code, m)
|
||||
}
|
||||
deadline := time.Now().Add(3 * time.Second)
|
||||
for {
|
||||
if b, err := os.ReadFile(seen); err == nil {
|
||||
if !strings.Contains(string(b), "Build fertig, bitte Review") || !strings.Contains(string(b), "talk") {
|
||||
t.Errorf("notify-send-Argumente = %q", b)
|
||||
}
|
||||
break
|
||||
}
|
||||
if time.Now().After(deadline) {
|
||||
t.Fatalf("notify-send wurde nicht gerufen — Daemon-Log:\n%s", d.stderr.String())
|
||||
}
|
||||
time.Sleep(25 * time.Millisecond)
|
||||
}
|
||||
fake.assertNoHits(t, 200*time.Millisecond)
|
||||
}
|
||||
Reference in new issue
Block a user