12552acabd
gates / gates (push) Successful in 6m45s
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
56 lines
3.1 KiB
Go
56 lines
3.1 KiB
Go
package notify
|
||
|
||
import (
|
||
"io"
|
||
"log"
|
||
"testing"
|
||
)
|
||
|
||
// TestR1049_DriveBackMailsBothEdges pins the CONSEQUENCE of R-1049: a drive that is pulled and comes back mails the
|
||
// operator BOTH edges, twice in a row inside the hour (the 2026-10-14 night: pulls at 22:55 and 23:21), and the household
|
||
// hears the return only if it heard the loss. Measured before the fix: the hub received `storage_reconnected` and
|
||
// `health_recovered` and logged no notification at all (audits/media-drive-events-2026-10-14/K8-hub-mails.txt).
|
||
//
|
||
// COMPANION RED-PROOF (2026-10-15): remove the two R-1049 entries from recoveredPairedDownTypes → the operator gets 4
|
||
// mails, not 8, and the household 1, not 2; this test fails. Restored.
|
||
func TestR1049_DriveBackMailsBothEdges(t *testing.T) {
|
||
st := newDispStore(t)
|
||
if err := st.SaveNotificationPrefs("on", "house-on@example.com", []string{"storage_disconnected"}, 6); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := st.SaveNotificationPrefs("off", "house-off@example.com", []string{"backup_failed"}, 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)
|
||
|
||
// One pull per customer for the household leg: the customer pairing compares SECOND-granular send times and a tie
|
||
// is „no mail" by design (flap-safe), so a second pull in the same test second would be skipped for a reason that
|
||
// has nothing to do with R-1049. The operator leg has no pairing: two pulls inside the hour, both edges each time.
|
||
for _, c := range []string{"on", "off"} {
|
||
d.ProcessEvent(c, "storage_disconnected", "error", "Meghajtó váratlanul leválasztva: Media B", `{"label":"Media B"}`, "controller")
|
||
d.ProcessEvent(c, "storage_reconnected", "info", "Meghajtó újra csatlakoztatva: Media B", `{"label":"Media B"}`, "controller")
|
||
}
|
||
if got := len(mailsFor(*sent, "house-on@example.com")); got != 2 {
|
||
t.Fatalf("household that hears losses: %d mails, want 2 (the loss and the return)", got)
|
||
}
|
||
for _, c := range []string{"on", "off"} {
|
||
d.ProcessEvent(c, "storage_disconnected", "error", "Meghajtó váratlanul leválasztva: Media C", `{"label":"Media C"}`, "controller")
|
||
d.ProcessEvent(c, "storage_reconnected", "info", "Meghajtó újra csatlakoztatva: Media C", `{"label":"Media C"}`, "controller")
|
||
}
|
||
if got := len(mailsFor(*sent, "op@felhom.eu")); got != 8 {
|
||
t.Fatalf("operator: %d mails, want 8 (two customers × two pulls × loss + return)", got)
|
||
}
|
||
if got := len(mailsFor(*sent, "house-off@example.com")); got != 0 {
|
||
t.Fatalf("household that hears no losses: %d mails, want 0 — a return it was never told about is noise", got)
|
||
}
|
||
|
||
// health_recovered pairs with health_degraded for the operator the same way.
|
||
d.ProcessEvent("on", "health_degraded", "warning", "Rendszer állapot romlott (volt: ok)", `{}`, "controller")
|
||
before := len(mailsFor(*sent, "op@felhom.eu"))
|
||
d.ProcessEvent("on", "health_recovered", "info", "Rendszer állapot helyreállt: ok (volt: warn)", `{}`, "controller")
|
||
if got := len(mailsFor(*sent, "op@felhom.eu")); got != before+1 {
|
||
t.Fatalf("health_recovered: the operator got %d new mail(s), want 1", got-before)
|
||
}
|
||
}
|