ff1758a21c
backup_integrity_ok / backup_integrity_failed now take facts and push bundle keys (Hungarian bytes unchanged - go-parity, pinned verbatim by TestR585_IntegrityHungarianIsUnchanged). The interrupted-operation alert (backup_failed, customer-enabled by default) used to send the operator's ENGLISH sentence to every household; NotifyInterruptedOperation composes it per language, the English byte-identical to the operator's log line. The now-callerless NotifyBackupFailed is removed. local_api_endpoint_drift is operator-only (no customer toggle) and already English by design - not changed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
111 lines
4.5 KiB
Go
111 lines
4.5 KiB
Go
package notify
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/backup"
|
|
)
|
|
|
|
// R-585 — the interrupted-operation alert follows the household's language.
|
|
//
|
|
// It used to push backup.AppStopRecovery.Message(), the OPERATOR's English sentence, through
|
|
// `backup_failed` — a type customer-enabled by default — so a Hungarian household's mail carried one
|
|
// English line. Every shape of the recovery is walked here:
|
|
// - the English rendering must equal Message() byte for byte (what an English household and the
|
|
// operator's log read today is unchanged, and the bundle cannot drift from the log line);
|
|
// - the Hungarian rendering must carry no English and name the operation in Hungarian;
|
|
// - a Hungarian household sends no second sentence, an English one sends exactly Message().
|
|
func TestR585_InterruptedOperationFollowsTheHousehold(t *testing.T) {
|
|
reasons := []backup.AppStopReason{backup.ReasonVolumeDump, backup.ReasonOffboxReconstitute, backup.ReasonAppExport}
|
|
shapes := []struct {
|
|
name string
|
|
restarted, failed, refused []string
|
|
}{
|
|
{"restarted", []string{"a", "b"}, nil, nil},
|
|
{"restarted_held", []string{"a"}, nil, []string{"c"}},
|
|
{"failed", []string{"a"}, []string{"b"}, nil},
|
|
{"failed_held", nil, []string{"b"}, []string{"c", "d"}},
|
|
{"held", nil, nil, []string{"c"}},
|
|
}
|
|
judged := 0
|
|
for _, reason := range reasons {
|
|
if reason.MessageKey() == "" {
|
|
t.Fatalf("%s has no bundle key — the household would read the raw reason %q", reason, reason)
|
|
}
|
|
for _, sh := range shapes {
|
|
r := &backup.AppStopRecovery{Reason: reason, Restarted: sh.restarted, Failed: sh.failed, Refused: sh.refused}
|
|
op := InterruptedOperation{
|
|
ReasonKey: reason.MessageKey(), Reason: string(reason),
|
|
Restarted: len(r.Restarted), Failed: len(r.Failed), Refused: len(r.Refused),
|
|
}
|
|
for _, lang := range []string{"en", "hu"} {
|
|
n := notifierInLang(t, lang)
|
|
var typ, hu, household string
|
|
n.pushFn = func(et, _, message, messageCustomer string, _ interface{}) {
|
|
typ, hu, household = et, message, messageCustomer
|
|
}
|
|
n.NotifyInterruptedOperation(op, r.Detail())
|
|
judged++
|
|
if typ != "backup_failed" {
|
|
t.Errorf("%s/%s: event type %q, want backup_failed (the hub allowlists it)", reason, sh.name, typ)
|
|
}
|
|
for _, english := range []string{"interrupted", "restart", "app(s)", "drive"} {
|
|
if strings.Contains(hu, english) {
|
|
t.Errorf("%s/%s: the Hungarian sentence carries English %q: %q", reason, sh.name, english, hu)
|
|
}
|
|
}
|
|
if !strings.Contains(hu, "Megszakadt") {
|
|
t.Errorf("%s/%s: the Hungarian sentence is not the bundle's: %q", reason, sh.name, hu)
|
|
}
|
|
switch lang {
|
|
case "en":
|
|
if household != r.Message() {
|
|
t.Errorf("%s/%s: the English household sentence drifted from the operator's log line\n got %q\n want %q",
|
|
reason, sh.name, household, r.Message())
|
|
}
|
|
case "hu":
|
|
if household != "" {
|
|
t.Errorf("%s/%s: a Hungarian household sent a second sentence %q", reason, sh.name, household)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if judged != len(reasons)*len(shapes)*2 {
|
|
t.Fatalf("judged %d renderings, want %d", judged, len(reasons)*len(shapes)*2)
|
|
}
|
|
}
|
|
|
|
// The integrity sentences moved from main.go constants into the bundle. Their Hungarian bytes are the
|
|
// ones every box has been sending (the parity rule): pinned verbatim here, the way
|
|
// TestEventMessageWireTextIsFrozen pins the others.
|
|
func TestR585_IntegrityHungarianIsUnchanged(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
call func(*Notifier)
|
|
want string
|
|
}{
|
|
{"ok", func(n *Notifier) { n.NotifyIntegrityOK("42s", "") },
|
|
"A távoli mentés ellenőrzése rendben lezajlott. (42s)"},
|
|
{"ok_subset", func(n *Notifier) { n.NotifyIntegrityOK("1m3s", "10%") },
|
|
"A távoli mentés ellenőrzése rendben lezajlott. (1m3s, a mentett adatok 10%-át újraolvasva)"},
|
|
{"failed", func(n *Notifier) { n.NotifyIntegrityFailed("restic check reported repository errors") },
|
|
"A távoli mentés ellenőrzése hibát talált a tárolóban. A mentések egy része sérült lehet. Ne törölj semmit, és vedd fel velünk a kapcsolatot."},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
n := notifierInLang(t, "hu")
|
|
got, seen := "", 0
|
|
n.pushFn = func(_, _, message, _ string, _ interface{}) { got, seen = message, seen+1 }
|
|
tc.call(n)
|
|
if seen != 1 {
|
|
t.Fatalf("pushed %d events, want 1", seen)
|
|
}
|
|
if got != tc.want {
|
|
t.Errorf("the Hungarian wire text changed\n got %q\n want %q", got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|