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