package notify import ( "encoding/json" "reflect" "sort" "strings" "testing" "gitea.dooplex.hu/admin/felhom-controller/internal/i18n" ) // R-1007 (decision 236) — app_repaired_after_crash carries COUNTS ONLY to the operator. A file name or photo data in // the payload would leave the household's box. Asserted on the marshalled payload, and on the struct's fields (a new // field must come through this test). // RED-PROOF: add `Names []string json:"names"` to AppRepairDetails → the field list test fails. func TestAppRepairedAfterCrash_PayloadIsCountsOnly(t *testing.T) { var fields []string rt := reflect.TypeOf(AppRepairDetails{}) for i := 0; i < rt.NumField(); i++ { fields = append(fields, strings.Split(rt.Field(i).Tag.Get("json"), ",")[0]) } sort.Strings(fields) want := "app,boot_at,duration_s,found,lost,lost_checked,problem,repaired,skipped,stack_name,still_broken" if strings.Join(fields, ",") != want { t.Fatalf("AppRepairDetails fields = %v, want exactly %s — counts only, never a file name", fields, want) } } // The severity: lost or still broken → warning; everything repaired → info; nothing found and nothing lost → no // event at all. RED-PROOF: send `info` when Lost > 0 → the lost case reads info → fails. func TestAppRepairedAfterCrash_SeverityByOutcome(t *testing.T) { n := &Notifier{} var got []string var payloads []string n.pushFn = func(eventType, severity, msg, _ string, d interface{}) { b, _ := json.Marshal(d) got = append(got, eventType+"/"+severity) payloads = append(payloads, string(b)+" "+msg) } n.NotifyAppRepairedAfterCrash(AppRepairDetails{App: "Immich", Found: 81, Repaired: 81}) n.NotifyAppRepairedAfterCrash(AppRepairDetails{App: "Immich", Found: 81, Repaired: 81, Lost: 1, LostChecked: true}) n.NotifyAppRepairedAfterCrash(AppRepairDetails{App: "Immich", Found: 5, Repaired: 4, StillBroken: 1}) n.NotifyAppRepairedAfterCrash(AppRepairDetails{App: "Immich", Skipped: "immich-server runs v3.3.0, which the template has not proven"}) n.NotifyAppRepairedAfterCrash(AppRepairDetails{App: "Immich", LostChecked: true}) want := []string{"app_repaired_after_crash/info", "app_repaired_after_crash/warning", "app_repaired_after_crash/warning", "app_repaired_after_crash/warning"} if strings.Join(got, " ") != strings.Join(want, " ") { t.Fatalf("events %v, want %v (and nothing for a run that found nothing)", got, want) } for _, p := range payloads { if strings.Contains(p, ".jpg") || strings.Contains(p, "IMG_") { t.Fatalf("a file name in the event: %s", p) } } } // The three keys R-1007 was born with, in BOTH languages, with the same placeholders (an English box gets English). func TestAppRepairedAfterCrash_KeysInBothLanguages(t *testing.T) { b, err := i18n.Shared() if err != nil { t.Fatal(err) } for _, k := range []string{"event.app_repaired_after_crash", "event.app_repair_after_crash_skipped", "err.lost_files.nothing_to_dismiss"} { hu, en := b.Msg("hu", k), b.Msg("en", k) if hu == "" || en == "" || hu == k || en == k || hu == en { t.Fatalf("%s: hu=%q en=%q", k, hu, en) } if strings.Count(hu, "%") != strings.Count(en, "%") { t.Fatalf("%s: the two languages take different arguments", k) } } }