mesh/merge-gate pass: builds dunst, messenger → g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery-group group fix/notifications-after-review ready: every member ready, and composed together they pass
mesh/delivery superseded: a newer head of the same pull request
Review found that any process of the operator's account can choose a dunst action, so a click could release or stop a delivery in the operator's name; that tokens lived for ever; that a warning's answers died in ten seconds; and that a restarted notifier left dead buttons (hq ADR 0258).
111 lines
3.5 KiB
Go
111 lines
3.5 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// **A notification offers answers, and the chosen one is said as an event** (novox/hq ADR 0253): the id
|
|
// is answered at once, the answer comes when the operator chooses, and nothing is said for a
|
|
// notification closed without one.
|
|
func TestANotificationsChosenAnswerIsSaidAsAnEvent(t *testing.T) {
|
|
withBus(t)
|
|
bin := fakeBinaries(t, map[string]string{"notify-send": `for a in "$@"; do printf '[%s]' "$a"; done > "$LOG"; echo 42; sleep 0.2; echo "$CHOSEN"`})
|
|
t.Setenv("LOG", filepath.Join(bin, "log"))
|
|
t.Setenv("MESH_NODE", "g14")
|
|
var mu sync.Mutex
|
|
var said []map[string]any
|
|
before := emit
|
|
emit = func(event string, body any) error {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
if event != ChosenEvent {
|
|
t.Errorf("event %q", event)
|
|
}
|
|
said = append(said, body.(map[string]any))
|
|
return nil
|
|
}
|
|
t.Cleanup(func() { emit = before })
|
|
|
|
n, err := notificationOf(map[string]any{"summary": "Delivery of hq held for 36 hours", "body": "Needs you: release it, or stop it.",
|
|
"actions": []any{map[string]any{"token": "a1b2", "label": "Release"}, map[string]any{"token": "c3d4", "label": "Stop"}}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Setenv("CHOSEN", "c3d4")
|
|
start := time.Now()
|
|
got, err := Send(n)
|
|
if err != nil || got.ID != 42 || time.Since(start) > 150*time.Millisecond {
|
|
t.Fatalf("%+v, %v, after %s: the id is answered before the answer", got, err, time.Since(start))
|
|
}
|
|
asked, _ := os.ReadFile(filepath.Join(bin, "log"))
|
|
if !strings.Contains(string(asked), "[--expire-time=0][--wait][--action=a1b2=Release][--action=c3d4=Stop][--]") {
|
|
t.Fatalf("notify-send was asked %s", asked)
|
|
}
|
|
deadline := time.Now().Add(3 * time.Second)
|
|
for {
|
|
mu.Lock()
|
|
n := len(said)
|
|
mu.Unlock()
|
|
if n > 0 || time.Now().After(deadline) {
|
|
break
|
|
}
|
|
time.Sleep(20 * time.Millisecond)
|
|
}
|
|
mu.Lock()
|
|
if len(said) != 1 || said[0]["token"] != "c3d4" || said[0]["id"] != 42 || said[0]["machine"] != "g14" {
|
|
t.Fatalf("said %v", said)
|
|
}
|
|
said = nil
|
|
mu.Unlock()
|
|
|
|
// Closed without an answer, or an answer nobody offered: nothing is said.
|
|
for _, chosen := range []string{"", "zzzz"} {
|
|
t.Setenv("CHOSEN", chosen)
|
|
if _, err := Send(n); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
time.Sleep(600 * time.Millisecond)
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
if len(said) != 0 {
|
|
t.Fatalf("an answer was invented: %v", said)
|
|
}
|
|
}
|
|
|
|
func TestActionsAreRefusedForWhatTheyCannotBe(t *testing.T) {
|
|
for _, bad := range []any{"x", []any{"x"}, []any{map[string]any{"token": "a b", "label": "x"}},
|
|
[]any{map[string]any{"token": "a", "label": ""}}, []any{map[string]any{"token": "a", "label": "x=y"}},
|
|
[]any{map[string]any{"token": "a", "label": "x"}, map[string]any{"token": "a", "label": "y"}},
|
|
[]any{map[string]any{"token": "a", "label": "1"}, map[string]any{"token": "b", "label": "2"},
|
|
map[string]any{"token": "c", "label": "3"}, map[string]any{"token": "d", "label": "4"},
|
|
map[string]any{"token": "e", "label": "5"}}} {
|
|
if _, err := notificationOf(map[string]any{"summary": "x", "actions": bad}); err == nil {
|
|
t.Errorf("accepted %v", bad)
|
|
}
|
|
}
|
|
}
|
|
|
|
// **A bundle that started says so**, so the operator channel shows again the answers lost with the one
|
|
// before.
|
|
func TestABundleThatStartedSaysSo(t *testing.T) {
|
|
t.Setenv("MESH_NODE", "g14")
|
|
var said []string
|
|
before := emit
|
|
emit = func(event string, body any) error {
|
|
said = append(said, fmt.Sprintf("%s %v", event, body))
|
|
return nil
|
|
}
|
|
t.Cleanup(func() { emit = before })
|
|
sayStarted()
|
|
if len(said) != 1 || said[0] != "started map[machine:g14]" {
|
|
t.Fatalf("%v", said)
|
|
}
|
|
}
|