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) } } }