package backup import ( "errors" "testing" ) // R-538 — a unit restore must REFUSE when the unit holds no copy of the app's files. // // The defect this pins, measured live on 2026-09-16: five photos were put into Nextcloud, the // customer pressed „Visszaállítás indítása" on the Tier-1 unit, and the restore replayed three // volume tars and a database dump over an app whose files live on the data drive. It reported // „3 adatkötet és az adatbázis visszaállítva", and afterwards the folder listed all five photos and // none of them opened — the replayed database referenced files that were never captured, and it had // also stopped referencing the app's own wastebasket, which still held every byte. // // The assertion is the CONSEQUENCE, not the mechanism: the call returns the refusal and the app is // left alone. Red-proof: delete the guard in RestoreFromRecoveryUnitAtWith → this test fails at // "a restore that cannot return the files must refuse". func TestUnitRestore_RefusesWhenTheUnitCannotHoldTheFiles(t *testing.T) { drive := t.TempDir() m, _, prov := classifiedOffboxManager(t, drive) // A class-A app: it declares a MANDATORY bind under the drive, which is where its files live and // which a Tier-1 unit structurally cannot capture. prov.hdd["nextcloud"] = drive prov.binds["nextcloud"] = []ClassifiedBind{mandatoryHDD("appdata/nextcloud")} prov.has["nextcloud"] = true mkUnit(t, drive, "nextcloud") _, err := m.RestoreFromRecoveryUnitAt("nextcloud", RecoveryUnitPath(drive, "nextcloud")) var refusal *ErrUnitLacksFileLegs if !errors.As(err, &refusal) { t.Fatalf("a restore that cannot return the files must refuse; got err=%v", err) } if refusal.Stack != "nextcloud" || len(refusal.Paths) == 0 { t.Fatalf("the refusal must name the app and the paths it cannot return: %+v", refusal) } // NEGATIVE CONTROL, and it is the half that keeps the guard from being over-broad: an app that // declares no drive-side files (all 45 class-B templates, whose data IS in the volumes the unit // captured) must NOT be refused. If this ever starts refusing, the guard has stopped asking about // the unit and started asking about nothing in particular. prov.hdd["privatebin"] = drive prov.has["privatebin"] = false mkUnit(t, drive, "privatebin") _, err = m.RestoreFromRecoveryUnitAt("privatebin", RecoveryUnitPath(drive, "privatebin")) if errors.As(err, &refusal) { t.Fatalf("an app with no drive-side files must not be refused: %v", err) } // The explicit second step („csak az adatbázist és a beállításokat") passes the guard. It may // still fail further down for unrelated fixture reasons — what is asserted is only that consent // is what the guard consults. _, err = m.RestoreFromRecoveryUnitAtWith("nextcloud", RecoveryUnitPath(drive, "nextcloud"), UnitRestoreOptions{AcceptMissingFiles: true}) if errors.As(err, &refusal) { t.Fatalf("explicit consent must pass the guard, not be refused by it: %v", err) } } // DeclaredDriveFileLegs is the ONE predicate the label (R-537) and the refusal (R-538) share. A // second copy of this question is how a page and a guard drift apart, so it is pinned here too. func TestDeclaredDriveFileLegs_IsAboutDeclarationNotExistence(t *testing.T) { drive := t.TempDir() m, _, prov := classifiedOffboxManager(t, drive) prov.hdd["nextcloud"] = drive prov.binds["nextcloud"] = []ClassifiedBind{mandatoryHDD("appdata/nextcloud")} prov.has["nextcloud"] = true // The folder does NOT exist on disk in this fixture. It must still count: a label that tells the // truth only until the customer starts using the app is not telling the truth. if !m.HasDriveFileLegs("nextcloud") { t.Fatal("a declared mandatory drive path must count even before the customer has put anything in it") } if got := m.DeclaredDriveFileLegs("unknown-app"); got != nil { t.Fatalf("an app the provider does not know has no declared legs; got %v", got) } }