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

@@ -159,118 +159,6 @@ func TestScannerSimpleDirectory(t *testing.T) {
}
}
func TestScannerWithSymlinks(t *testing.T) {
// Initialize logger for tests
log.Initialize(log.Config{})
// Create in-memory filesystem
fs := afero.NewMemMapFs()
// Create test files
if err := fs.MkdirAll("/source", 0755); err != nil {
t.Fatal(err)
}
if err := afero.WriteFile(fs, "/source/target.txt", []byte("target content"), 0644); err != nil {
t.Fatal(err)
}
if err := afero.WriteFile(fs, "/outside/file.txt", []byte("outside content"), 0644); err != nil {
t.Fatal(err)
}
// Create symlinks (if supported by the filesystem)
linker, ok := fs.(afero.Symlinker)
if !ok {
t.Skip("filesystem does not support symlinks")
}
// Symlink to file in source
if err := linker.SymlinkIfPossible("target.txt", "/source/link1.txt"); err != nil {
t.Fatal(err)
}
// Symlink to file outside source
if err := linker.SymlinkIfPossible("/outside/file.txt", "/source/link2.txt"); err != nil {
t.Fatal(err)
}
// Create test database
db, err := database.NewTestDB()
if err != nil {
t.Fatalf("failed to create test database: %v", err)
}
defer func() {
if err := db.Close(); err != nil {
t.Errorf("failed to close database: %v", err)
}
}()
repos := database.NewRepositories(db)
// Create scanner
scanner := snapshot.NewScanner(snapshot.ScannerConfig{
FS: fs,
ChunkSize: 1024 * 16,
Repositories: repos,
MaxBlobSize: int64(1024 * 1024),
CompressionLevel: 3,
AgeRecipients: []string{"age1ezrjmfpwsc95svdg0y54mums3zevgzu0x0ecq2f7tp8a05gl0sjq9q9wjg"}, // Test public key
})
// Create a snapshot record for testing
ctx := context.Background()
snapshotID := "test-snapshot-001"
err = repos.WithTx(ctx, func(ctx context.Context, tx *sql.Tx) error {
snapshot := &database.Snapshot{
ID: snapshotID,
Hostname: "test-host",
VaultikVersion: "test",
StartedAt: time.Now(),
CompletedAt: nil,
FileCount: 0,
ChunkCount: 0,
BlobCount: 0,
TotalSize: 0,
BlobSize: 0,
CompressionRatio: 1.0,
}
return repos.Snapshots.Create(ctx, tx, snapshot)
})
if err != nil {
t.Fatalf("failed to create snapshot: %v", err)
}
// Scan the directory
var result *snapshot.ScanResult
result, err = scanner.Scan(ctx, "/source", snapshotID)
if err != nil {
t.Fatalf("scan failed: %v", err)
}
// Should have scanned 3 files (target + 2 symlinks)
if result.FilesScanned != 3 {
t.Errorf("expected 3 files scanned, got %d", result.FilesScanned)
}
// Check symlinks in database
link1, err := repos.Files.GetByPath(ctx, "/source/link1.txt")
if err != nil {
t.Fatalf("failed to get link1.txt: %v", err)
}
if link1.LinkTarget != "target.txt" {
t.Errorf("expected link1.txt target 'target.txt', got %q", link1.LinkTarget)
}
link2, err := repos.Files.GetByPath(ctx, "/source/link2.txt")
if err != nil {
t.Fatalf("failed to get link2.txt: %v", err)
}
if link2.LinkTarget != "/outside/file.txt" {
t.Errorf("expected link2.txt target '/outside/file.txt', got %q", link2.LinkTarget)
}
}
func TestScannerLargeFile(t *testing.T) {
// Initialize logger for tests
log.Initialize(log.Config{})