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