package backup import ( "os" "os/exec" "path/filepath" "testing" ) // R-1181 + D25 for app folders — the data-loss hunt's C3 case (felhom.eu audits/dataloss-hunt-2026-10-20/ // C3-D2-missing-leg/), asserting: a MISSING declared folder keeps its second-drive copy and is listed as kept; an // EMPTIED folder is not mirrored over its full copy (the production `rsync -a --delete` mirror is used when present). func TestR1181_MissingAndEmptiedAppFoldersKeepTheirCopies(t *testing.T) { m, src, target, prov := newTier2V2(t, "app") if _, err := exec.LookPath("rsync"); err == nil { m.tier2Mirror = rsyncMirror } prov.has["app"] = true prov.binds["app"] = []ClassifiedBind{mHDD("appdata/app/photos"), mHDD("appdata/app/docs")} mustWrite(t, filepath.Join(src, "appdata", "app", "photos", "family.jpg"), "PHOTO") mustWrite(t, filepath.Join(src, "appdata", "app", "docs", "contract.pdf"), "PDF") if err := m.RunTier2("app"); err != nil { t.Fatal(err) } destBase := filepath.Join(target, "backups", "secondary", "app") photo := filepath.Join(destBase, "hdd", "appdata", "app", "photos", "family.jpg") doc := filepath.Join(destBase, "hdd", "appdata", "app", "docs", "contract.pdf") if !exists(photo) || !exists(doc) { t.Fatal("precondition: run 1 did not copy both folders") } if err := os.RemoveAll(filepath.Join(src, "appdata", "app", "photos")); err != nil { t.Fatal(err) } if err := os.Remove(filepath.Join(src, "appdata", "app", "docs", "contract.pdf")); err != nil { t.Fatal(err) } if err := m.RunTier2("app"); err != nil { t.Fatal(err) } if !exists(photo) { t.Fatal("the MISSING folder's second-drive copy was deleted (R-1181)") } if !exists(doc) { t.Fatal("the EMPTIED folder was mirrored over its full copy (R-1181)") } kept := m.KeptCopies() if len(kept) != 1 || kept[0].Kind != "app" || kept[0].Reason != "folder_missing" { t.Fatalf("kept copies = %+v, want the photos folder", kept) } if cd := m.settings.GetCrossDriveConfig("app"); cd == nil || cd.LastWarning == "" { t.Fatalf("the run did not warn: %+v", cd) } // The folder comes back: mirrored again, the record goes. mustWrite(t, filepath.Join(src, "appdata", "app", "photos", "family.jpg"), "PHOTO") if err := m.RunTier2("app"); err != nil { t.Fatal(err) } if len(m.KeptCopies()) != 0 { t.Fatalf("the record outlived the folder's return: %+v", m.KeptCopies()) } }