- Add unified compression/encryption package in internal/blobgen - Update DATAMODEL.md to reflect current schema implementation - Refactor snapshot cleanup into well-named methods for clarity - Add snapshot_id to uploads table to track new blobs per snapshot - Fix blob count reporting for incremental backups - Add DeleteOrphaned method to BlobChunkRepository - Fix cleanup order to respect foreign key constraints - Update tests to reflect schema changes
164 lines
4.1 KiB
Go
164 lines
4.1 KiB
Go
package backup
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"git.eeqj.de/sneak/vaultik/internal/config"
|
|
"git.eeqj.de/sneak/vaultik/internal/database"
|
|
"git.eeqj.de/sneak/vaultik/internal/log"
|
|
)
|
|
|
|
const (
|
|
// Test age public key for encryption
|
|
testAgeRecipient = "age1ezrjmfpwsc95svdg0y54mums3zevgzu0x0ecq2f7tp8a05gl0sjq9q9wjg"
|
|
)
|
|
|
|
func TestCleanSnapshotDBEmptySnapshot(t *testing.T) {
|
|
// Initialize logger
|
|
log.Initialize(log.Config{})
|
|
|
|
ctx := context.Background()
|
|
|
|
// Create a test database
|
|
tempDir := t.TempDir()
|
|
dbPath := filepath.Join(tempDir, "test.db")
|
|
db, err := database.New(ctx, dbPath)
|
|
if err != nil {
|
|
t.Fatalf("failed to create database: %v", err)
|
|
}
|
|
|
|
repos := database.NewRepositories(db)
|
|
|
|
// Create an empty snapshot
|
|
snapshot := &database.Snapshot{
|
|
ID: "empty-snapshot",
|
|
Hostname: "test-host",
|
|
}
|
|
|
|
err = repos.WithTx(ctx, func(ctx context.Context, tx *sql.Tx) error {
|
|
return repos.Snapshots.Create(ctx, tx, snapshot)
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("failed to create snapshot: %v", err)
|
|
}
|
|
|
|
// Create some files and chunks not associated with any snapshot
|
|
file := &database.File{Path: "/orphan/file.txt", Size: 1000}
|
|
chunk := &database.Chunk{ChunkHash: "orphan-chunk", Size: 500}
|
|
|
|
err = repos.WithTx(ctx, func(ctx context.Context, tx *sql.Tx) error {
|
|
if err := repos.Files.Create(ctx, tx, file); err != nil {
|
|
return err
|
|
}
|
|
return repos.Chunks.Create(ctx, tx, chunk)
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("failed to create orphan data: %v", err)
|
|
}
|
|
|
|
// Close the database
|
|
if err := db.Close(); err != nil {
|
|
t.Fatalf("failed to close database: %v", err)
|
|
}
|
|
|
|
// Copy database
|
|
tempDBPath := filepath.Join(tempDir, "temp.db")
|
|
if err := copyFile(dbPath, tempDBPath); err != nil {
|
|
t.Fatalf("failed to copy database: %v", err)
|
|
}
|
|
|
|
// Create a mock config for testing
|
|
cfg := &config.Config{
|
|
CompressionLevel: 3,
|
|
AgeRecipients: []string{testAgeRecipient},
|
|
}
|
|
// Clean the database
|
|
sm := &SnapshotManager{config: cfg}
|
|
if _, err := sm.cleanSnapshotDB(ctx, tempDBPath, snapshot.ID); err != nil {
|
|
t.Fatalf("failed to clean snapshot database: %v", err)
|
|
}
|
|
|
|
// Verify the cleaned database
|
|
cleanedDB, err := database.New(ctx, tempDBPath)
|
|
if err != nil {
|
|
t.Fatalf("failed to open cleaned database: %v", err)
|
|
}
|
|
defer func() {
|
|
if err := cleanedDB.Close(); err != nil {
|
|
t.Errorf("failed to close database: %v", err)
|
|
}
|
|
}()
|
|
|
|
cleanedRepos := database.NewRepositories(cleanedDB)
|
|
|
|
// Verify snapshot exists
|
|
verifySnapshot, err := cleanedRepos.Snapshots.GetByID(ctx, snapshot.ID)
|
|
if err != nil {
|
|
t.Fatalf("failed to get snapshot: %v", err)
|
|
}
|
|
if verifySnapshot == nil {
|
|
t.Error("snapshot should exist")
|
|
}
|
|
|
|
// Verify orphan file is gone
|
|
f, err := cleanedRepos.Files.GetByPath(ctx, file.Path)
|
|
if err != nil {
|
|
t.Fatalf("failed to check file: %v", err)
|
|
}
|
|
if f != nil {
|
|
t.Error("orphan file should not exist")
|
|
}
|
|
|
|
// Verify orphan chunk is gone
|
|
c, err := cleanedRepos.Chunks.GetByHash(ctx, chunk.ChunkHash)
|
|
if err != nil {
|
|
t.Fatalf("failed to check chunk: %v", err)
|
|
}
|
|
if c != nil {
|
|
t.Error("orphan chunk should not exist")
|
|
}
|
|
}
|
|
|
|
func TestCleanSnapshotDBNonExistentSnapshot(t *testing.T) {
|
|
// Initialize logger
|
|
log.Initialize(log.Config{})
|
|
|
|
ctx := context.Background()
|
|
|
|
// Create a test database
|
|
tempDir := t.TempDir()
|
|
dbPath := filepath.Join(tempDir, "test.db")
|
|
db, err := database.New(ctx, dbPath)
|
|
if err != nil {
|
|
t.Fatalf("failed to create database: %v", err)
|
|
}
|
|
|
|
// Close immediately
|
|
if err := db.Close(); err != nil {
|
|
t.Fatalf("failed to close database: %v", err)
|
|
}
|
|
|
|
// Copy database
|
|
tempDBPath := filepath.Join(tempDir, "temp.db")
|
|
if err := copyFile(dbPath, tempDBPath); err != nil {
|
|
t.Fatalf("failed to copy database: %v", err)
|
|
}
|
|
|
|
// Create a mock config for testing
|
|
cfg := &config.Config{
|
|
CompressionLevel: 3,
|
|
AgeRecipients: []string{testAgeRecipient},
|
|
}
|
|
// Try to clean with non-existent snapshot
|
|
sm := &SnapshotManager{config: cfg}
|
|
_, err = sm.cleanSnapshotDB(ctx, tempDBPath, "non-existent-snapshot")
|
|
|
|
// Should not error - it will just delete everything
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|