Files
felhom.eu/hub/internal/notify/upwatch_events_test.go
T
admin af5c005a4e
gates / gates (push) Successful in 12m47s
hub 0.169.0 (unreleased, not built): the upload watch's four events — allowed, operator-only, per-app cooldown, one readable Watch line in the operator mail
app_unexplained_upload / app_unexplained_cpu (warning) and their _cleared (info), from controller v0.342.0 (08 section 9,
operator ruling 2026-10-23). Red-proved: operator-only, the render line, the allow-list.
2026-10-23 21:26:36 +02:00

50 lines
2.7 KiB
Go

package notify
import (
"io"
"log"
"strings"
"testing"
)
// The upload and CPU watch's events (hub v0.169.0, controller v0.342.0) reach the OPERATOR and never the household — even
// one that enabled them by name; the alarm mails once with a readable line; the clear (info) is recorded, not mailed.
// RED-PROOF: drop app_unexplained_upload from operatorOnlyEvents → the household gets a mail → fails; drop the
// renderUnexplained call → the body lacks the Watch line → fails.
func TestUpwatchEvents_OperatorOnlyWithAReadableLine(t *testing.T) {
st := newDispStore(t)
if err := st.SaveNotificationPrefs("c1", "cust@example.com", []string{"app_unexplained_upload", "app_unexplained_cpu", "node_down"}, 6); err != nil {
t.Fatal(err)
}
d := NewDispatcher(st, "test-key", "from@felhom.eu", "op@felhom.eu", true, log.New(io.Discard, "", 0))
sent := captureSeam(d)
up := `{"app":"qbittorrent","stack_name":"qbittorrent","kind":"upload","minutes":15,"rate_mbps":10136.77,"app_mbps":0,"explainer":"qbittorrent","viewers":0,"transcodes":0,"line_mbps":47}`
d.ProcessEvent("c1", "app_unexplained_upload", "warning", "qbittorrent: ...", up, "controller")
d.ProcessEvent("c1", "app_unexplained_cpu", "warning", "plex: ...", `{"app":"plex","kind":"cpu","minutes":30,"cpu_percent":380,"app_mbps":-1,"explainer":"plex transcodes","transcodes":0}`, "controller")
if got := mailsFor(*sent, "cust@example.com"); len(got) != 0 {
t.Fatalf("the household got %d mail(s) — a hacked app is the operator's to handle", len(got))
}
if got := len(mailsFor(*sent, "op@felhom.eu")); got != 2 {
t.Fatalf("operator mails = %d, want 2", got)
}
*sent = nil
d.ProcessEvent("c1", "app_unexplained_upload_cleared", "info", "qbittorrent: stopped", `{"app":"qbittorrent","cleared":true,"app_mbps":-1}`, "controller")
if len(*sent) != 0 {
t.Fatalf("a clear (info) mailed %d time(s)", len(*sent))
}
_, body := FormatOperatorEmail("c1", "app_unexplained_upload", "warning", "msg", up)
want := "Watch: qbittorrent — upload 10136.8 Mbit/s for 15 min; the app counted 0.0 Mbit/s (asked: qbittorrent, viewers 0); the box's line: at least 47 Mbit/s"
if !strings.Contains(body, want) {
t.Fatalf("body lacks %q:\n%s", want, body)
}
_, cpu := FormatOperatorEmail("c1", "app_unexplained_cpu", "warning", "msg", `{"app":"plex","kind":"cpu","minutes":30,"cpu_percent":380,"explainer":"plex transcodes"}`)
if !strings.Contains(cpu, "Watch: plex — 380% CPU (of one core) for 30 min; asked: plex transcodes, transcodes 0") {
t.Fatalf("cpu body:\n%s", cpu)
}
for _, et := range []string{"app_unexplained_upload", "app_unexplained_upload_cleared", "app_unexplained_cpu", "app_unexplained_cpu_cleared"} {
if !IsOperatorOnly(et) {
t.Errorf("%s is not operator-only", et)
}
}
}