D22: every restore puts the data back exactly on failure and after a power cut (R-1177, R-1195); whole restore asks the unit first (R-1178); no mirror during a restore (R-1180); W22 restore refusals in Hungarian; unreleased
gates / gates (push) Successful in 1m40s
gates / gates (push) Successful in 1m40s
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
package backup
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// R-1178 (data-loss hunt 2026-10-20, R5): the second drive's „Teljes visszaállítás" merged files, THEN the unit half
|
||||
// refused (a cut-off database copy), left the app STOPPED, and said „az alkalmazás és az adatai érintetlenek". Now the
|
||||
// unit's refusals are asked before the file half: nothing moves, the app is never stopped, and the sentence is true.
|
||||
// RED-PROOF: without the CheckOnly pre-check in RestoreTier2Whole this fails on „files changed before the refusal"
|
||||
// (1 restored + 1 replaced, the measured R5 outcome — audits/dataloss-hunt-2026-10-20/C3-R5-whole-restore/).
|
||||
func TestR1178_AUnitRefusalComesBeforeAnyFileMoves(t *testing.T) {
|
||||
cut := strings.Replace(pgDump(1), "PostgreSQL database dump complete", "", 1) // a cut-off copy (R-640)
|
||||
f := r102Tier2Fixture(t, []string{"app_data.tar"}, cut)
|
||||
f.m.stackProvider = fileAppProvider{f.fake}
|
||||
mid := time.Date(2026, 9, 22, 10, 0, 0, 0, time.UTC)
|
||||
mirrorLeg := filepath.Join(f.destBase, "hdd", "appdata", "app")
|
||||
liveLeg := filepath.Join(f.liveDrive, "appdata", "app")
|
||||
writeAt(t, filepath.Join(mirrorLeg, "p1.jpg"), "P1", mid)
|
||||
writeAt(t, filepath.Join(mirrorLeg, "p2.jpg"), "P2 mirror NEWER", mid.Add(72*time.Hour))
|
||||
writeAt(t, filepath.Join(liveLeg, "p2.jpg"), "P2 live older", mid.Add(48*time.Hour))
|
||||
before := fingerprint(t, liveLeg)
|
||||
|
||||
res, err := f.m.RestoreTier2Whole("app")
|
||||
if err == nil {
|
||||
t.Fatal("a cut-off database copy must refuse the whole restore")
|
||||
}
|
||||
if res.Files.Restored+res.Files.Replaced != 0 {
|
||||
t.Fatalf("files changed before the refusal: %+v", res.Files)
|
||||
}
|
||||
if after := fingerprint(t, liveLeg); !sameFingerprint(before, after) {
|
||||
t.Fatalf("the live files changed: before %v after %v", before, after)
|
||||
}
|
||||
if f.fake.stopped {
|
||||
t.Fatalf("the app was stopped by a restore that refused — calls %v", f.fake.calls)
|
||||
}
|
||||
if !strings.Contains(err.Error(), "rintetlenek") { // „érintetlenek" — now true
|
||||
t.Fatalf("the refusal must keep its „untouched\" sentence (true now), got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func sameFingerprint(a, b map[string]string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for k, v := range a {
|
||||
if b[k] != v {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user