Files
felhom-controller/controller/internal/backup/w25_offsite_household_test.go
T

70 lines
3.2 KiB
Go

package backup
import (
"context"
"encoding/json"
"errors"
"os"
"strings"
"testing"
"time"
)
// W25 (rehearsal 2026-10-20): a failed off-site run showed the household „Utolsó hiba: offbox backup immich: exit
// status 1". The household now reads which app failed (its own name), that the other apps are fine when they are,
// and that the box tries again the next night; the raw text stays in the log and reaches the operator's event.
func TestW25_FailedOffsiteRunSpeaksToTheHousehold(t *testing.T) {
m, sett := newOffboxManager(t)
usb := t.TempDir()
addSchedulablePath(t, sett, usb)
ns := m.namespaceRoot(usb)
writeUnit(t, ns, "immich", "2026-10-20T00:00:00Z")
writeUnit(t, ns, "mealie", "2026-10-20T00:00:00Z")
b, _ := json.Marshal(RecoveryManifest{AppName: "immich", DisplayName: "Immich Fotók", CreatedAt: "2026-10-20T00:00:00Z"})
if err := os.WriteFile(RecoveryUnitManifestPath(ns, "immich"), b, 0o644); err != nil {
t.Fatal(err)
}
_ = sett.SetAppOffbox("immich", true)
_ = sett.SetAppOffbox("mealie", true)
rr := &recordingOffboxRunner{backupErr: map[string]error{"immich": errors.New("exit status 1")}}
m.SetOffboxRunner(rr.run)
var operatorErr error
m.SetOffboxNotify(func(_ time.Duration, _ int, err error) { operatorErr = err })
if err := m.RunOffboxBackup(context.Background()); err == nil {
t.Fatal("a run with a failed app must report an error")
}
got := sett.GetOffboxTarget().LastError
for _, want := range []string{"Nem sikerült a távoli mentés: Immich Fotók.", "A többi alkalmazás távoli mentése rendben van.",
"A doboz a következő éjjel újra próbálkozik."} {
if !strings.Contains(got, want) {
t.Errorf("the household's note lacks %q: %q", want, got)
}
}
for _, raw := range []string{"exit status", "offbox backup", "immich:"} {
if strings.Contains(got, raw) {
t.Errorf("raw technical text %q reached the household's note: %q", raw, got)
}
}
// the operator's event keeps the technical detail, sanitised
if op := m.OffsiteFailureMessage(operatorErr, time.Second); !strings.Contains(op, "offbox backup immich: exit status 1") {
t.Errorf("the operator's event lost the technical detail: %q", op)
}
// English, and the cases where the other apps are NOT fine or no night comes
res := offboxRunResult{backedUp: 1, failedApps: []string{"Immich", offsiteSharesName}}
runErr := errors.New("offbox backup immich: exit status 1")
en := offsiteHouseholdFailure("en", runErr, res, true)
if en != "The remote backup did not work for: Immich, Shared folders. The remote backup of the other apps is fine. The box tries again the next night." {
t.Errorf("en: %q", en)
}
res.missingUnprotected = []string{"paperless"}
if hu := offsiteHouseholdFailure("hu", runErr, res, false); strings.Contains(hu, "többi") || strings.Contains(hu, "éjjel") {
t.Errorf("with an app left out the others are not all fine, and with no nightly run there is no retry: %q", hu)
}
// a whole-run failure: its class sentence, never the raw text
tr := offsiteHouseholdFailure("hu", errors.New("ssh: connect to host x port 23: connection refused"), offboxRunResult{}, true)
if tr != "A távoli tárhely nem érhető el (hálózat vagy bejelentkezés). A doboz a következő éjjel újra próbálkozik." {
t.Errorf("transport: %q", tr)
}
}