check / check (push) Waiting to run
Every backup now writes vaultik-destination.txt at the destination root. While the local index holds snapshots, a destination without it is not present: snapshot create refuses before writing, prune and snapshot purge fail, and snapshot list and snapshot remove warn. remote nuke deletes the marker last. The file:// backend no longer creates its destination directory and checks for it on every write; that protects a directory on a removable volume, not the mount point itself. Tests back up into existing directories; fixtures standing for a written destination carry the marker. Judgement call: the marker is rewritten on every backup. Deviation: for the complexity limit, snapshot name selection left CreateSnapshot and the --force/--json test moved into confirmRemoveSnapshot, both unchanged. Model: opus-5-5
73 lines
2.1 KiB
Go
73 lines
2.1 KiB
Go
package vaultik_test
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/spf13/afero"
|
|
"github.com/stretchr/testify/require"
|
|
"sneak.berlin/go/vaultik/internal/database"
|
|
"sneak.berlin/go/vaultik/internal/log"
|
|
"sneak.berlin/go/vaultik/internal/storage"
|
|
"sneak.berlin/go/vaultik/internal/vaultik"
|
|
)
|
|
|
|
// A file rewritten with its size unchanged and a new mtime in the same
|
|
// second as the mtime the index holds must still be backed up. See
|
|
// https://git.eeqj.de/sneak/vaultik/issues/226.
|
|
func TestBackupOfSameSecondRewriteRestoresNewContent(t *testing.T) {
|
|
log.Initialize(log.Config{})
|
|
t.Parallel()
|
|
|
|
fs := afero.NewOsFs()
|
|
tempDir := t.TempDir()
|
|
dataDir := filepath.Join(tempDir, "src")
|
|
storeDir := t.TempDir()
|
|
restoreDir := filepath.Join(tempDir, "restored")
|
|
dbPath := filepath.Join(tempDir, "index.sqlite")
|
|
rewrittenPath := filepath.Join(dataDir, "small.txt")
|
|
|
|
ctx := context.Background()
|
|
files := writeFaultSourceTree(t, fs, dataDir)
|
|
cfg := changedFileConfig(dataDir, dbPath)
|
|
|
|
firstMTime := time.Date(2026, time.January, 2, 3, 4, 5, 0, time.UTC).
|
|
Add(100 * time.Millisecond)
|
|
secondMTime := firstMTime.Add(800 * time.Millisecond)
|
|
|
|
require.NoError(t, fs.Chtimes(rewrittenPath, firstMTime, firstMTime))
|
|
|
|
store, err := storage.NewFileStorer(storeDir)
|
|
require.NoError(t, err)
|
|
|
|
db, err := database.New(ctx, dbPath)
|
|
require.NoError(t, err)
|
|
|
|
repos := database.NewRepositories(db)
|
|
v := newBackupVaultik(ctx, cfg, store, repos, db, fs)
|
|
|
|
require.NoError(t, backUp(v, "first"))
|
|
|
|
// Upper-casing ASCII text keeps its size.
|
|
files[rewrittenPath] = bytes.ToUpper(files[rewrittenPath])
|
|
require.NoError(t, afero.WriteFile(fs, rewrittenPath, files[rewrittenPath], 0o644))
|
|
require.NoError(t, fs.Chtimes(rewrittenPath, secondMTime, secondMTime))
|
|
|
|
require.NoError(t, backUp(v, "second"))
|
|
|
|
id := localSnapshotID(ctx, t, repos, "second")
|
|
require.NoError(t, db.Close())
|
|
|
|
reader := newReaderVaultik(ctx, cfg, store, nil, fs)
|
|
require.NoError(t, reader.Restore(&vaultik.RestoreOptions{
|
|
SnapshotID: id,
|
|
TargetDir: restoreDir,
|
|
Verify: true,
|
|
}))
|
|
|
|
assertRestoredTree(t, fs, restoreDir, files)
|
|
}
|