bef39598d0
gates / gates (push) Successful in 24s
MinAgent: 0.131.0 (unchanged). Needs hub v0.118.0+, which shipped first and tolerates a box that sends none of this - every box in the fleet is that box until this release reaches it. The hub writes a household's e-mails in their language now, but about a third of those mails carry a sentence the BOX composed, naming a drive, an app or a number. The hub cannot translate one. So the box sends it twice. - message_customer on POST /api/v1/event, omitempty. A HUNGARIAN household sends nothing extra at all, so its payload stays byte-for-byte what every box sends today and the hub's fallback path keeps being the one production exercises rather than a branch nobody takes. - 19 producers render both sentences from ONE bundle key. `message` stays Hungarian always: it is what the operator is mailed and what the hub logs. - customer.language bootstraps a new box - stored choice, then config, then Hungarian. The config value is NEVER written into settings.json: that would record a choice the household never made. The Hungarian did not move, measured twice: the wire golden from the slice-2 base commit, and the Go parity gate over all 19 new keys. Three guards had to learn the change and one caught me: the test seam now carries the new field; the R-329 severity register reported two dynamic sites as no longer existing the moment they moved off PushEvent (the walk now checks 36 severity literals, up from 20); and TestConfigLanguageIsWiredInMain reads main.go, because cmd/ is gitignored and ripgrep does not. A mistake, named: the first pass dropped displayName from three producers, which would have mailed customers "Alkalmazás telepítve: %!s(MISSING)". Caught reading the diff; now pinned by a test that refuses %!/MISSING/%s/%d in either language. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
123 lines
4.8 KiB
Go
123 lines
4.8 KiB
Go
package notify
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// TestEventMessageWireTextIsFrozen — localisation slice 2 (R-557), scenario S6.
|
|
//
|
|
// An event's `message` is ON THE WIRE and, unlike most wire text, a CUSTOMER READS IT. The hub's
|
|
// FormatCustomerEmail (felhom.eu/hub/internal/notify/templates.go) uses the per-type Hungarian
|
|
// `customerMessages` entry when it has one and FALLS BACK TO THIS MESSAGE when it has none — and
|
|
// appends it as „- Üzenet: %s" whenever it differs from the entry. Several event types deliberately
|
|
// have no entry precisely so the controller's dynamic sentence is what the household is sent
|
|
// (hub/internal/api/handler.go, the notes around offbox_enlarge_blocked and its neighbours).
|
|
//
|
|
// So these sentences are NOT the dashboard's to translate. They follow the household's language in
|
|
// slice 3 (R-558), where the hub learns which language the box reports and composes the mail. Until
|
|
// then they stay Hungarian in every language, and this test is what makes "stay" measurable: the
|
|
// golden strings below were captured from the producers at 736f54b49610, the base commit of
|
|
// v0.252.0, before any conversion.
|
|
//
|
|
// Not every producer is here — the table covers one representative of each SHAPE (fixed sentence,
|
|
// one parameter, two parameters, a numeric parameter, a branch per case), which is what a conversion
|
|
// would break. A producer converted by accident would have to escape every shape to escape this.
|
|
func TestEventMessageWireTextIsFrozen(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
call func(n *Notifier)
|
|
want string
|
|
}{
|
|
{
|
|
name: "fixed sentence",
|
|
call: func(n *Notifier) { n.NotifyDBDumpCompleted(DBDumpDetails{}) },
|
|
want: "Adatbázis mentés elkészült",
|
|
},
|
|
{
|
|
name: "one parameter",
|
|
call: func(n *Notifier) { n.NotifyAppDeployed("privatebin", "PrivateBin") },
|
|
want: "Alkalmazás telepítve: PrivateBin",
|
|
},
|
|
{
|
|
name: "two parameters",
|
|
call: func(n *Notifier) { n.NotifyBackupTargetAbsent("Kulso HDD", "/mnt/hdd_1") },
|
|
want: "A rendszermentés meghajtója nem érhető el: Kulso HDD (/mnt/hdd_1)",
|
|
},
|
|
{
|
|
name: "numeric parameter",
|
|
call: func(n *Notifier) { n.NotifyDRStarted(3) },
|
|
want: "Katasztrófa helyreállítás elindítva (3 alkalmazás)",
|
|
},
|
|
{
|
|
name: "two numeric parameters",
|
|
call: func(n *Notifier) { n.NotifyDRCompleted(7, 2) },
|
|
want: "Katasztrófa helyreállítás befejezve (7 sikeres, 2 sikertelen)",
|
|
},
|
|
{
|
|
name: "branch: update succeeded",
|
|
call: func(n *Notifier) { n.NotifyControllerUpdated("0.251.0", "0.252.0", true) },
|
|
want: "Controller frissítve: 0.251.0 → 0.252.0",
|
|
},
|
|
{
|
|
name: "branch: update failed",
|
|
call: func(n *Notifier) { n.NotifyControllerUpdated("0.251.0", "0.252.0", false) },
|
|
want: "Controller frissítés sikertelen: 0.251.0 → 0.252.0",
|
|
},
|
|
{
|
|
name: "disk health, sector count",
|
|
call: func(n *Notifier) {
|
|
n.NotifyDiskHealthDegraded(DiskAlert{Label: "Kulso HDD", Kind: DiskAlertFailSectors, Sectors: 352})
|
|
},
|
|
want: "Lemez hiba: Kulso HDD — a meghajtón 352 olvashatatlan szektor van. " +
|
|
"Mentse az adatait, és keressen meg minket a meghajtó cseréjéhez.",
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
var got string
|
|
var seen int
|
|
n := &Notifier{}
|
|
n.pushFn = func(_, _, message, _ string, _ interface{}) {
|
|
got, seen = message, seen+1
|
|
}
|
|
tc.call(n)
|
|
// A producer that stopped pushing would otherwise leave `got` empty and compare unequal
|
|
// with a confusing message; say which it was.
|
|
if seen == 0 {
|
|
t.Fatalf("the producer pushed no event at all — the golden below was never compared")
|
|
}
|
|
if got != tc.want {
|
|
t.Errorf("event message moved (slice 2 may not translate what the hub mails)\n"+
|
|
" got %q\n want %q", got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestEventMessagesCarryNoBundleKey is the other half: a converted producer would push a KEY, or a
|
|
// sentence assembled from one. Neither can look Hungarian, so this catches the accident the golden
|
|
// table above would miss for a producer it does not list.
|
|
func TestEventMessagesCarryNoBundleKey(t *testing.T) {
|
|
var msgs []string
|
|
n := &Notifier{}
|
|
n.pushFn = func(_, _, message, _ string, _ interface{}) { msgs = append(msgs, message) }
|
|
|
|
n.NotifyAppDeployed("privatebin", "PrivateBin")
|
|
n.NotifyAppRemoved("privatebin", "PrivateBin")
|
|
n.NotifyStorageReconnected("Kulso HDD")
|
|
n.NotifyDRStarted(1)
|
|
n.NotifyCrossDriveCompleted(CrossDriveDetails{StackName: "privatebin"})
|
|
|
|
if len(msgs) == 0 {
|
|
t.Fatal("no producer pushed — nothing was checked")
|
|
}
|
|
for _, m := range msgs {
|
|
// A bundle key is lower-case, dotted and has no space: "event.app_deployed".
|
|
if !strings.Contains(m, " ") && strings.Contains(m, ".") && m == strings.ToLower(m) {
|
|
t.Errorf("an event message looks like a bundle key, not a sentence: %q", m)
|
|
}
|
|
}
|
|
}
|