9167cf53af
gates / gates (push) Successful in 23s
The hub has written every customer e-mail in Hungarian whatever the box was set to. The box has published its language since controller v0.247.0; nothing read it. Now it does. Nothing an operator reads changes. The Hungarian mails are byte-identical, and that is a diff rather than a reading: 56 goldens per language captured from v0.117.0 BEFORE any string moved, and all 56 Hungarian ones pass unchanged after every sentence was routed through the new bundle. - internal/i18n: flat bundle, 79 keys, hu authoritative + hu fallback, ceiling 0. - customerMessages/severityLabels are DERIVED from the bundle, so a sentence is written in one place and all 40+ tests that read those maps still work. - Language order: last reported -> created-with -> hu. reports.language defaults to EMPTY, never hu: "never told us" is not "chose Hungarian". - message_customer on POST /api/v1/event, additive and optional forever, for the sentences the box composes and the hub cannot translate. - The bind page is per-language, and its `expired` state stays Hungarian: it is the state an unknown token lands in, so rendering a real English customer's token in English would make the LANGUAGE answer what the TEXT refuses to. Two defects found inside the release: - R-581: the newest report was picked by received_at, which has SECOND granularity, so same-second reports tied and the winner was arbitrary. Ordered by the autoincrement id now. GetCustomers() still has the shape - row open. - R-582: the English copy-guard stems, ported word for word from Hungarian, convicted 141 honest sentences. The English claim is a phrase with a modal. R-555 closed: the language allowlist entry is out of wire_contract_gate.py. hub_copy_gate.py follows the sentences into the bundle - without that it would have scanned four files that no longer hold any customer text and reported success. Three new decoys incl. an innocent control. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
126 lines
5.5 KiB
Go
126 lines
5.5 KiB
Go
package notify
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-hub/internal/i18n"
|
|
)
|
|
|
|
// Localisation slice 3 (R-558) — the behaviour the bundle exists to produce.
|
|
//
|
|
// The goldens next door prove WHAT each mail says. These prove WHICH ONE a household gets, and that
|
|
// the operator's copy never moves.
|
|
|
|
// S3 — the box's own sentence.
|
|
//
|
|
// Roughly a third of the customer mails carry a sentence the BOX composed, naming a drive or an app
|
|
// or a number. The hub cannot translate one, so the box sends a second copy in the household's
|
|
// language. This is the test that the second copy is actually preferred — and that its absence
|
|
// leaves the mail exactly as it was, which is the state of every box in the fleet until the
|
|
// controller half ships.
|
|
func TestMessageCustomerPreferredForTheHousehold(t *testing.T) {
|
|
const hunSentence = "A /mnt/adat lemez 91%-ig megtelt (12,4 GB szabad)."
|
|
const engSentence = "The /mnt/adat disk is 91% full (12.4 GB free)."
|
|
|
|
// disk_warning has NO bundle entry on purpose, so the box's sentence IS the mail.
|
|
subject, body := FormatCustomerEmail("en", "c1", "disk_warning", "warning", hunSentence, engSentence, "")
|
|
|
|
if !strings.Contains(body, engSentence) {
|
|
t.Errorf("the English household's mail does not carry the English sentence:\n%s", body)
|
|
}
|
|
if strings.Contains(body, hunSentence) {
|
|
t.Errorf("the English household's mail still carries the Hungarian sentence:\n%s", body)
|
|
}
|
|
if !strings.Contains(subject, engSentence) {
|
|
t.Errorf("the subject is not the English sentence: %q", subject)
|
|
}
|
|
|
|
// The OPERATOR's mail is built from `message` and is untouched by any of this.
|
|
opSubject, opBody := FormatOperatorEmail("c1", "disk_warning", "warning", hunSentence, "")
|
|
if !strings.Contains(opBody, hunSentence) {
|
|
t.Errorf("the operator's mail lost the Hungarian sentence:\n%s", opBody)
|
|
}
|
|
if strings.Contains(opBody, engSentence) || strings.Contains(opSubject, engSentence) {
|
|
t.Error("the English sentence leaked into the operator's mail")
|
|
}
|
|
}
|
|
|
|
// S3, the other half: an old controller sends no second sentence, and the mail must be byte-for-byte
|
|
// what it is today. Compared against the RENDER WITH NO NEW FIELD AT ALL, not against a retyped
|
|
// expectation — a retyped one would pass even if both sides changed together.
|
|
func TestMessageCustomerAbsentIsExactlyToday(t *testing.T) {
|
|
const hunSentence = "A /mnt/adat lemez 91%-ig megtelt (12,4 GB szabad)."
|
|
|
|
subject, body := FormatCustomerEmail(i18n.Default, "c1", "disk_warning", "warning", hunSentence, "", "")
|
|
if !strings.Contains(body, hunSentence) || !strings.Contains(subject, hunSentence) {
|
|
t.Fatalf("an absent message_customer lost the box's sentence:\n%s\n%s", subject, body)
|
|
}
|
|
|
|
// And a Hungarian household that DOES get a second sentence gets the Hungarian one, because the
|
|
// box sends the same text twice. The point is that nothing about the hu path depends on it.
|
|
s2, b2 := FormatCustomerEmail(i18n.Default, "c1", "disk_warning", "warning", hunSentence, hunSentence, "")
|
|
if s2 != subject || b2 != body {
|
|
t.Error("a Hungarian household's mail changed when the box sent the sentence twice")
|
|
}
|
|
}
|
|
|
|
// The entry still WINS over the box's sentence, in both languages — the property that keeps the
|
|
// dynamic-message types (disk_warning and friends) working, stated from the other direction.
|
|
func TestEntryWinsAndTheBoxSentenceBecomesItsOwnLine(t *testing.T) {
|
|
const hun = "A cel nincs beallitva."
|
|
const eng = "The target is not set."
|
|
|
|
_, body := FormatCustomerEmail("en", "c1", "backup_failed", "error", hun, eng, "")
|
|
headline := i18n.Shared().Msg("en", "mail.event.backup_failed")
|
|
if !strings.Contains(body, headline) {
|
|
t.Errorf("the English entry is not the headline:\n%s", body)
|
|
}
|
|
if !strings.Contains(body, eng) {
|
|
t.Errorf("the box's English sentence is not on its own line:\n%s", body)
|
|
}
|
|
if strings.Contains(body, hun) {
|
|
t.Errorf("the Hungarian sentence reached an English household:\n%s", body)
|
|
}
|
|
// The line label is translated too — an English mail must not say "Üzenet".
|
|
if strings.Contains(body, "Üzenet") {
|
|
t.Errorf("the English mail carries the Hungarian field label:\n%s", body)
|
|
}
|
|
}
|
|
|
|
// Every mail kind actually differs between the two languages. Without this, a key that silently fell
|
|
// back to Hungarian would pass every other test here: the mail sends, it just sends in the wrong
|
|
// language, which is the exact defect this slice exists to remove.
|
|
func TestEveryCustomerMailDiffersBetweenLanguages(t *testing.T) {
|
|
for _, c := range customerMailCases("hu") {
|
|
if strings.HasPrefix(c.name, "operator_") {
|
|
continue // the operator's mails are English already and are not localised
|
|
}
|
|
name := c.name
|
|
huSubject, huBody := c.render()
|
|
var enSubject, enBody string
|
|
for _, e := range customerMailCases("en") {
|
|
if e.name == name {
|
|
enSubject, enBody = e.render()
|
|
}
|
|
}
|
|
if huBody == enBody {
|
|
t.Errorf("%s renders identically in both languages — it is not localised", name)
|
|
}
|
|
if huSubject == enSubject && !strings.Contains(huSubject, "[Felhom] Információ") {
|
|
t.Errorf("%s has the same subject in both languages: %q", name, huSubject)
|
|
}
|
|
}
|
|
}
|
|
|
|
// An unsupported or absent language renders Hungarian rather than a blank or a key.
|
|
func TestUnknownLanguageRendersHungarian(t *testing.T) {
|
|
want, wantBody := FormatCustomerEmail("hu", "c1", "backup_completed", "info", "", "", "")
|
|
for _, lang := range []string{"", "de", "en-GB", " "} {
|
|
got, gotBody := FormatCustomerEmail(lang, "c1", "backup_completed", "info", "", "", "")
|
|
if got != want || gotBody != wantBody {
|
|
t.Errorf("language %q did not fall back to Hungarian", lang)
|
|
}
|
|
}
|
|
}
|