package web import ( "strings" "testing" "gitea.dooplex.hu/admin/felhom-controller/internal/backup" ) // R-355, the sentence. „Ennek az alkalmazásnak nincs adatbázisa" is a claim ABOUT THE APP, and it was // being inferred from a counter that has two different causes. On 2026-08-21 it was printed over a live // 72-table PostgreSQL that the controller had dumped five minutes earlier. // // `SafetyDump` is the honest discriminator: writeSafetyDump returns a path only when a live database // for this app was found AND successfully dumped. func TestReconstituteOutcome_NoDatabaseOnlyWhenThereIsNone(t *testing.T) { msg := reconstituteOutcomeMsg("opengist", backup.OffsiteReconstituteResult{ FilesPlaced: 3, DBsReplayed: 0, SafetyDump: "", }, "hu") if !strings.Contains(msg, "nincs adatbázisa") { t.Errorf("an app with genuinely no database should still say so; got %q", msg) } } // The one that matters: a database exists, none was replayed. Saying "this app has no database" here // is false, and saying nothing at all would leave a silent loss under a success. func TestReconstituteOutcome_DatabaseExistsButWasNotRestored(t *testing.T) { msg := reconstituteOutcomeMsg("paperless-ngx", backup.OffsiteReconstituteResult{ FilesPlaced: 0, DBsReplayed: 0, SafetyDump: "/mnt/x/db-dumps/pre-restore-20260822T060000Z-paperless-ngx-postgres.sql", }, "hu") if strings.Contains(msg, "nincs adatbázisa") { t.Fatalf("FALSE CLAIM: told the customer the app has no database while its undo copy proves it does; got %q", msg) } for _, want := range []string{"VAN adatbázisa", "NEM állt vissza"} { if !strings.Contains(msg, want) { t.Errorf("the message must state that a database exists and did not come back; missing %q in %q", want, msg) } } // The undo copy is the customer's way back, so it has to be named. if !strings.Contains(msg, "pre-restore-20260822T060000Z-paperless-ngx-postgres.sql") { t.Errorf("the message must name the undo copy; got %q", msg) } // It must never leak the directory — only the file name. if strings.Contains(msg, "/mnt/x/") { t.Errorf("the message leaked a filesystem path; got %q", msg) } } func TestReconstituteOutcome_DatabaseRestoredIsUnchanged(t *testing.T) { msg := reconstituteOutcomeMsg("romm", backup.OffsiteReconstituteResult{ FilesPlaced: 4, DBsReplayed: 1, SafetyDump: "/x/pre-restore-romm-mariadb.sql", }, "hu") if !strings.Contains(msg, "és az adatbázis visszaállítva") { t.Errorf("the full case must keep its wording; got %q", msg) } if strings.Contains(msg, "FIGYELEM") { t.Errorf("a complete restore must not carry a warning; got %q", msg) } } // ── R-354: the volume leg has to reach the sentence ───────────────────────────────────────────── // The 2026-08-21 case, as the customer saw it and as they must see it now. func TestReconstituteOutcome_VolumesAreNamed(t *testing.T) { msg := reconstituteOutcomeMsg("calibre-web", backup.OffsiteReconstituteResult{ FilesPlaced: 5, VolumesReplayed: 1, DBsReplayed: 0, SafetyDump: "", }, "hu") if !strings.Contains(msg, "5 fájl és 1 adatkötet visszaállítva") { t.Errorf("the message must name the volume that came back; got %q", msg) } // The old sentence — five files and nothing else — must be gone. if strings.Contains(msg, "5 fájl visszaállítva") { t.Errorf("the pre-fix wording is still being produced; got %q", msg) } } // A volume-only app: the whole dataset is the volume, and "0 fájl" alone said nothing about it. func TestReconstituteOutcome_VolumeOnlyAppSaysWhatCameBack(t *testing.T) { msg := reconstituteOutcomeMsg("privatebin", backup.OffsiteReconstituteResult{ FilesPlaced: 0, VolumesReplayed: 1, DBsReplayed: 0, SafetyDump: "", }, "hu") if !strings.Contains(msg, "0 fájl és 1 adatkötet visszaállítva") { t.Errorf("a volume-only restore must state the volume; got %q", msg) } } // Scenario C: a snapshot with no volume archives must produce the EXACT sentence it produced before, // so the change cannot be read as "a volume was expected and did not arrive". func TestReconstituteOutcome_NoVolumesWordingUnchanged(t *testing.T) { noDB := reconstituteOutcomeMsg("opengist", backup.OffsiteReconstituteResult{ FilesPlaced: 3, VolumesReplayed: 0, DBsReplayed: 0, SafetyDump: "", }, "hu") if noDB != "A(z) opengist: 3 fájl visszaállítva — az alkalmazás újraindult. Ennek az alkalmazásnak nincs adatbázisa." { t.Errorf("the no-volume, no-database wording changed; got %q", noDB) } withDB := reconstituteOutcomeMsg("romm", backup.OffsiteReconstituteResult{ FilesPlaced: 4, VolumesReplayed: 0, DBsReplayed: 1, SafetyDump: "/x/pre-restore-romm-mariadb.sql", }, "hu") if withDB != "A(z) romm: 4 fájl és az adatbázis visszaállítva — az alkalmazás újraindult." { t.Errorf("the no-volume, with-database wording changed; got %q", withDB) } if strings.Contains(noDB, "adatkötet") || strings.Contains(withDB, "adatkötet") { t.Error("a restore that replayed no volume must not mention volumes at all") } } // All three legs at once. func TestReconstituteOutcome_AllThreeLegs(t *testing.T) { msg := reconstituteOutcomeMsg("paperless-ngx", backup.OffsiteReconstituteResult{ FilesPlaced: 12, VolumesReplayed: 3, DBsReplayed: 1, SafetyDump: "/x/pre-restore-p.sql", }, "hu") want := "A(z) paperless-ngx: 12 fájl és 3 adatkötet és az adatbázis visszaállítva — az alkalmazás újraindult." if msg != want { t.Errorf("got %q\nwant %q", msg, want) } }