package notify import ( "strings" "testing" ) // R-79 (D3, operator ruling 2026-10-08, `09` §3 decision 187): the HOUSEHOLD's system-health mail // no longer carries the raw details JSON — a curly-bracket note with English lines in it that the // household could not read. It says in plain words that something needs attention and points to the // dashboard, where each issue is shown in the household's language. The OPERATOR's mail keeps the // note, and every other event type keeps its note too. const r79HealthDetails = `{"previous_status":"ok","current_status":"fail","issues":["Protected container not running: cloudflared"]}` func TestR79_CustomerHealthMailHasNoRawNote(t *testing.T) { for _, et := range []string{"health_critical", "health_degraded", "health_recovered"} { for _, tc := range []struct{ lang, pointer string }{ {"hu", "vezerl"}, // ASCII fragment of „vezérlőpult" (accented greps lie) {"en", "dashboard"}, } { _, body := FormatCustomerEmail(tc.lang, "c1", et, "error", "msg", "", r79HealthDetails) if strings.Contains(body, "{") || strings.Contains(body, "issues") || strings.Contains(body, "Protected container") { t.Errorf("%s/%s: customer mail still carries the raw note:\n%s", et, tc.lang, body) } if !strings.Contains(asciiFold(body), tc.pointer) { t.Errorf("%s/%s: customer mail lacks the dashboard pointer %q:\n%s", et, tc.lang, tc.pointer, body) } } } } // Negative control: a health event with no details still gets the pointer (it is the event that // carries it, not the note), and the pointer never appears on a non-health event. func TestR79_PointerOnlyOnHealthEvents(t *testing.T) { _, body := FormatCustomerEmail("en", "c1", "health_critical", "error", "msg", "", "") if !strings.Contains(body, "dashboard") { t.Errorf("health mail without details lacks the pointer:\n%s", body) } _, other := FormatCustomerEmail("en", "c1", "backup_failed", "error", "msg", "", `{"stack_name":"x"}`) if strings.Contains(other, "on your dashboard") { t.Errorf("a non-health event got the health pointer:\n%s", other) } } func TestR79_OperatorHealthMailKeepsNote(t *testing.T) { _, body := FormatOperatorEmail("c1", "health_critical", "error", "msg", r79HealthDetails) if !strings.Contains(body, "Protected container not running: cloudflared") { t.Errorf("operator mail lost the details:\n%s", body) } } func TestR79_NonHealthCustomerMailKeepsNote(t *testing.T) { _, body := FormatCustomerEmail("hu", "c1", "backup_failed", "error", "msg", "", `{"reason":"disk full"}`) if !strings.Contains(body, `{"reason":"disk full"}`) { t.Errorf("non-health customer mail lost its note:\n%s", body) } } // asciiFold drops the Hungarian accents so a search can use an ASCII fragment. func asciiFold(s string) string { r := strings.NewReplacer("á", "a", "é", "e", "í", "i", "ó", "o", "ö", "o", "ő", "o", "ú", "u", "ü", "u", "ű", "u", "Á", "A", "É", "E", "Í", "I", "Ó", "O", "Ö", "O", "Ő", "O", "Ú", "U", "Ü", "U", "Ű", "U") return r.Replace(s) }