v0.9.2: add manifest repair
This commit is contained in:
@@ -39,6 +39,15 @@ type errWriter struct{}
|
||||
|
||||
func (errWriter) Write([]byte) (int, error) { return 0, fmt.Errorf("write") }
|
||||
|
||||
type testFileInfo struct{ size int64 }
|
||||
|
||||
func (t testFileInfo) Name() string { return "test" }
|
||||
func (t testFileInfo) Size() int64 { return t.size }
|
||||
func (t testFileInfo) Mode() os.FileMode { return 0644 }
|
||||
func (t testFileInfo) ModTime() time.Time { return time.Time{} }
|
||||
func (t testFileInfo) IsDir() bool { return false }
|
||||
func (t testFileInfo) Sys() any { return nil }
|
||||
|
||||
type noEntryManifest struct{}
|
||||
|
||||
func (noEntryManifest) Has(string) bool { return false }
|
||||
@@ -4306,6 +4315,115 @@ func TestVerifyDeepChecksums(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestManifestRepair(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
if err := os.WriteFile(filepath.Join(dir, "photo.jpg"), []byte("abc"), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
m := manifest.LoadJSONL(dir)
|
||||
if err := m.OpenAppend(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
m.AddEntry(manifest.Entry{ID: "x1", Filename: "photo.jpg", Path: "photo.jpg", Size: 0, Cloud: "local", Exported: time.Now().Unix()})
|
||||
m.AddEntry(manifest.Entry{ID: "missing", Filename: "missing.jpg", Path: "missing.jpg", Size: 0, Cloud: "local", Exported: time.Now().Unix()})
|
||||
m.Close()
|
||||
out, stderr, rc := runWith([]string{"manifest", "repair", "--out", dir, "--checksum", "sha256", "--dry-run"}, &mockBridge{})
|
||||
if rc != exitOK || stderr != "" || !strings.Contains(out, "x1\tphoto.jpg\trepaired") || !strings.Contains(out, "skipped\t1") {
|
||||
t.Fatalf("dry repair rc=%d out=%q stderr=%q", rc, out, stderr)
|
||||
}
|
||||
if got := manifest.LoadJSONL(dir).Entries()["x1"].Checksum; got != "" {
|
||||
t.Fatalf("dry run wrote checksum %q", got)
|
||||
}
|
||||
out, stderr, rc = runWith([]string{"manifest", "repair", "--out", dir, "--checksum", "sha256"}, &mockBridge{})
|
||||
if rc != exitOK || stderr != "" || !strings.Contains(out, "repaired\t1") {
|
||||
t.Fatalf("repair rc=%d out=%q stderr=%q", rc, out, stderr)
|
||||
}
|
||||
entry := manifest.LoadJSONL(dir).Entries()["x1"]
|
||||
if entry.Size != 3 || entry.Checksum != "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" {
|
||||
t.Fatalf("entry not repaired: %+v", entry)
|
||||
}
|
||||
out, stderr, rc = runWith([]string{"manifest", "repair", "--out", dir, "--checksum", "sha256"}, &mockBridge{})
|
||||
if rc != exitOK || stderr != "" || !strings.Contains(out, "repaired\t0") {
|
||||
t.Fatalf("second repair rc=%d out=%q stderr=%q", rc, out, stderr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestManifestRepairErrors(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
_, stderr, rc := runWith([]string{"manifest"}, &mockBridge{})
|
||||
if rc != exitErr || !strings.Contains(stderr, "expected manifest repair") {
|
||||
t.Fatalf("manifest missing subcommand rc=%d stderr=%q", rc, stderr)
|
||||
}
|
||||
_, stderr, rc = runWith([]string{"manifest", "repair"}, &mockBridge{})
|
||||
if rc != exitErr || !strings.Contains(stderr, "--out") {
|
||||
t.Fatalf("manifest repair missing out rc=%d stderr=%q", rc, stderr)
|
||||
}
|
||||
_, stderr, rc = runWith([]string{"manifest", "repair", "--out", dir, "--checksum", "bad"}, &mockBridge{})
|
||||
if rc != exitErr || !strings.Contains(stderr, "--checksum") {
|
||||
t.Fatalf("manifest repair bad checksum rc=%d stderr=%q", rc, stderr)
|
||||
}
|
||||
_, stderr, rc = runWith([]string{"manifest", "repair", "--out", dir, "--manifest", "bad"}, &mockBridge{})
|
||||
if rc != exitErr || !strings.Contains(stderr, "manifest") {
|
||||
t.Fatalf("manifest repair bad manifest rc=%d stderr=%q", rc, stderr)
|
||||
}
|
||||
fileOut := filepath.Join(dir, "file-out")
|
||||
if err := os.WriteFile(fileOut, []byte("x"), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, stderr, rc = runWith([]string{"manifest", "repair", "--out", fileOut}, &mockBridge{})
|
||||
if rc != exitErr || !strings.Contains(stderr, "error:") {
|
||||
t.Fatalf("manifest repair open append rc=%d stderr=%q", rc, stderr)
|
||||
}
|
||||
badDBDir := t.TempDir()
|
||||
if err := os.WriteFile(manifest.SQLitePath(badDBDir), []byte("not sqlite"), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, stderr, rc = runWith([]string{"manifest", "repair", "--out", badDBDir}, &mockBridge{})
|
||||
if rc != exitErr || !strings.Contains(stderr, "error:") {
|
||||
t.Fatalf("manifest repair open rc=%d stderr=%q", rc, stderr)
|
||||
}
|
||||
saveDir := t.TempDir()
|
||||
sm := manifest.LoadJSONL(saveDir)
|
||||
if err := sm.OpenAppend(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(saveDir, "photo.jpg"), []byte("abc"), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sm.AddEntry(manifest.Entry{ID: "x1", Filename: "photo.jpg", Path: "photo.jpg", Exported: time.Now().Unix()})
|
||||
sm.Close()
|
||||
oldHook := manifest.SetJSONLSaveHook(func() error { return fmt.Errorf("save") })
|
||||
_, stderr, rc = runWith([]string{"manifest", "repair", "--out", saveDir}, &mockBridge{})
|
||||
manifest.SetJSONLSaveHook(oldHook)
|
||||
if rc != exitErr || !strings.Contains(stderr, "save") {
|
||||
t.Fatalf("manifest repair save rc=%d stderr=%q", rc, stderr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestManifestRepairBranches(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
m := manifest.LoadJSONL(dir)
|
||||
if err := m.OpenAppend(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
m.AddEntry(manifest.Entry{ID: "empty"})
|
||||
m.AddEntry(manifest.Entry{ID: "stat-only", Filename: "missing.jpg", Path: "missing.jpg", Checksum: "", Exported: time.Now().Unix()})
|
||||
m.AddEntry(manifest.Entry{ID: "hash-fail", Filename: "hash.jpg", Path: "hash.jpg", Checksum: "", Exported: time.Now().Unix()})
|
||||
m.Close()
|
||||
oldStat := statFunc
|
||||
statFunc = func(path string) (os.FileInfo, error) {
|
||||
if strings.HasSuffix(path, "hash.jpg") {
|
||||
return testFileInfo{size: 3}, nil
|
||||
}
|
||||
return oldStat(path)
|
||||
}
|
||||
out, stderr, rc := runWith([]string{"manifest", "repair", "--out", dir, "--checksum", "sha256", "--dry-run"}, &mockBridge{})
|
||||
statFunc = oldStat
|
||||
if rc != exitOK || stderr != "" || !strings.Contains(out, "skipped\t3") {
|
||||
t.Fatalf("manifest repair branches rc=%d out=%q stderr=%q", rc, out, stderr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifySidecarBranches(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
subdir := filepath.Join(dir, "sub")
|
||||
|
||||
Reference in New Issue
Block a user