a2c52ebf2a
gates / gates (push) Successful in 23s
Found during v0.118.0's own live proof, which is the only reason it was found: I went to press "send test notification" for the English demo box and read sendTestEmail first. It had its own hardcoded Hungarian subject and body and never went through FormatCustomerEmail, so it was the one customer mail v0.118.0 did not localise — and it is the only customer mail an operator can trigger on demand, which makes it the one most likely to be used to check whether the localisation works. Pressing the button for an English household would have answered that question wrongly, and convincingly. The two sentences are extracted byte-for-byte into the bundle, so the Hungarian test mail is unchanged. Red-proofed against the hardcoded version. The general form worth keeping: the surface you would use to CHECK a feature is the one most worth checking first. A broken instrument that reports success is worse than a broken feature. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
170 lines
7.3 KiB
Go
170 lines
7.3 KiB
Go
package notify
|
|
|
|
import (
|
|
"io"
|
|
"log"
|
|
"strings"
|
|
"testing"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-hub/internal/i18n"
|
|
"gitea.dooplex.hu/admin/felhom-hub/internal/store"
|
|
)
|
|
|
|
// 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)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The TEST mail follows the household's language too.
|
|
//
|
|
// It is the one customer mail with its own text — it never goes through FormatCustomerEmail — and
|
|
// it is also the only one an operator can trigger on demand, so it is the one most likely to be
|
|
// used to CHECK whether the localisation works. It was still hardcoded Hungarian when the rest of
|
|
// this release was finished; pressing "send test" for an English household would have answered that
|
|
// question wrongly, in the most convincing possible way.
|
|
func TestTestMailFollowsTheHouseholdLanguage(t *testing.T) {
|
|
for _, tc := range []struct{ lang, want, notWant string }{
|
|
{"en", "Test notification", "Teszt"},
|
|
{"hu", "Teszt értesítés", "Test notification"},
|
|
} {
|
|
st := newDispStore(t)
|
|
if err := st.SaveCustomerConfig(&store.CustomerConfig{
|
|
CustomerID: "c1", APIKey: "k", RetrievalPassword: "p", Language: tc.lang,
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := st.SaveNotificationPrefs("c1", "fam@example.com", []string{"test"}, 6); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
d := NewDispatcher(st, "test-key", "from@felhom.eu", "", false, log.New(io.Discard, "", 0))
|
|
var subject, body string
|
|
d.sendEmailFn = func(_, s, b string, _ map[string]string) error {
|
|
subject, body = s, b
|
|
return nil
|
|
}
|
|
d.ProcessEvent("c1", "test", "info", "", "{}", "hub")
|
|
|
|
if !strings.Contains(subject, tc.want) {
|
|
t.Errorf("%s test mail subject = %q, want it to contain %q", tc.lang, subject, tc.want)
|
|
}
|
|
if strings.Contains(subject, tc.notWant) || strings.Contains(body, tc.notWant) {
|
|
t.Errorf("%s test mail carries the other language (%q):\n%s\n%s", tc.lang, tc.notWant, subject, body)
|
|
}
|
|
if body == "" {
|
|
t.Fatalf("%s test mail has an empty body — the seam did not fire", tc.lang)
|
|
}
|
|
}
|
|
}
|