Integrate afero filesystem abstraction library

- Add afero.Fs field to Vaultik struct for filesystem operations
- Vaultik now owns and manages the filesystem instance
- SnapshotManager receives filesystem via SetFilesystem() setter
- Update blob packer to use afero for temporary files
- Convert all filesystem operations to use afero abstraction
- Remove filesystem module - Vaultik manages filesystem directly
- Update tests: remove symlink test (unsupported by afero memfs)
- Fix TestMultipleFileChanges to handle scanner examining directories

This enables full end-to-end testing without touching disk by using
memory-backed filesystems. Database operations continue using real
filesystem as SQLite requires actual files.
This commit is contained in:
2025-07-26 15:33:18 +02:00
parent e29a995120
commit bb38f8c5d6
9 changed files with 78 additions and 146 deletions

View File

@@ -3,12 +3,14 @@ package snapshot
import (
"context"
"database/sql"
"io"
"path/filepath"
"testing"
"git.eeqj.de/sneak/vaultik/internal/config"
"git.eeqj.de/sneak/vaultik/internal/database"
"git.eeqj.de/sneak/vaultik/internal/log"
"github.com/spf13/afero"
)
const (
@@ -16,11 +18,30 @@ const (
testAgeRecipient = "age1ezrjmfpwsc95svdg0y54mums3zevgzu0x0ecq2f7tp8a05gl0sjq9q9wjg"
)
// copyFile is a test helper to copy files using afero
func copyFile(fs afero.Fs, src, dst string) error {
sourceFile, err := fs.Open(src)
if err != nil {
return err
}
defer func() { _ = sourceFile.Close() }()
destFile, err := fs.Create(dst)
if err != nil {
return err
}
defer func() { _ = destFile.Close() }()
_, err = io.Copy(destFile, sourceFile)
return err
}
func TestCleanSnapshotDBEmptySnapshot(t *testing.T) {
// Initialize logger
log.Initialize(log.Config{})
ctx := context.Background()
fs := afero.NewOsFs()
// Create a test database
tempDir := t.TempDir()
@@ -66,7 +87,7 @@ func TestCleanSnapshotDBEmptySnapshot(t *testing.T) {
// Copy database
tempDBPath := filepath.Join(tempDir, "temp.db")
if err := copyFile(dbPath, tempDBPath); err != nil {
if err := copyFile(fs, dbPath, tempDBPath); err != nil {
t.Fatalf("failed to copy database: %v", err)
}
@@ -75,8 +96,11 @@ func TestCleanSnapshotDBEmptySnapshot(t *testing.T) {
CompressionLevel: 3,
AgeRecipients: []string{testAgeRecipient},
}
// Clean the database
sm := &SnapshotManager{config: cfg}
// Create SnapshotManager with filesystem
sm := &SnapshotManager{
config: cfg,
fs: fs,
}
if _, err := sm.cleanSnapshotDB(ctx, tempDBPath, snapshot.ID); err != nil {
t.Fatalf("failed to clean snapshot database: %v", err)
}
@@ -127,6 +151,7 @@ func TestCleanSnapshotDBNonExistentSnapshot(t *testing.T) {
log.Initialize(log.Config{})
ctx := context.Background()
fs := afero.NewOsFs()
// Create a test database
tempDir := t.TempDir()
@@ -143,7 +168,7 @@ func TestCleanSnapshotDBNonExistentSnapshot(t *testing.T) {
// Copy database
tempDBPath := filepath.Join(tempDir, "temp.db")
if err := copyFile(dbPath, tempDBPath); err != nil {
if err := copyFile(fs, dbPath, tempDBPath); err != nil {
t.Fatalf("failed to copy database: %v", err)
}
@@ -153,7 +178,7 @@ func TestCleanSnapshotDBNonExistentSnapshot(t *testing.T) {
AgeRecipients: []string{testAgeRecipient},
}
// Try to clean with non-existent snapshot
sm := &SnapshotManager{config: cfg}
sm := &SnapshotManager{config: cfg, fs: fs}
_, err = sm.cleanSnapshotDB(ctx, tempDBPath, "non-existent-snapshot")
// Should not error - it will just delete everything