Fix every finding surfaced by the canonical .golangci.yml with golangci-lint v2.12.2 (refs #61), behavior-preserving throughout: - err113: dynamic errors replaced with package-level sentinels and %w wrapping; direct comparisons converted to errors.Is - goprintffuncname: printf-style helpers renamed with an f suffix (ui.Writer message methods, cli.ReportErrorf, database.Fatalf, vaultik stdoutf) and all call sites updated - revive: stuttering type names renamed (blob.Handler, blob.WithReader, blob.ChunkPosition, storage.URL, storage.Info), doc comments added, unused parameters blanked, package comments added - contextcheck/noctx: ctx threaded through blob.Packer (AddChunk/Flush/FinalizeBlob/PackChunks) and scanner call sites; context-aware exec and sql variants used - funlen/cyclop/gocognit/nestif/dupl: oversized or duplicated functions split into focused helpers across production and test code - paralleltest/tparallel/thelper/usetesting/testpackage: tests parallelized where safe (global log.Initialize kept in the serial phase), helpers marked, t.TempDir adopted, external test packages where only exported API is used - gosec: integer conversions clamped or justified, header timeouts added, remaining findings suppressed with per-site justifications - mnd/goconst/lll/wsl_v5/nlreturn/noinlineerr/errcheck and other mechanical findings fixed directly Remove the deprecated log.LogOptions alias (callers migrated to log.Options). make check is green.
290 lines
7.8 KiB
Go
290 lines
7.8 KiB
Go
package vaultik_test
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"database/sql"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"sneak.berlin/go/vaultik/internal/database"
|
|
"sneak.berlin/go/vaultik/internal/log"
|
|
"sneak.berlin/go/vaultik/internal/types"
|
|
"sneak.berlin/go/vaultik/internal/vaultik"
|
|
)
|
|
|
|
// Snapshot IDs reused across the purge tests.
|
|
const (
|
|
snapSystemT0 = "testhost_system_2026-01-01T00:00:00Z"
|
|
snapHomeT0 = "testhost_home_2026-01-01T00:00:00Z"
|
|
snapHomeT1 = "testhost_home_2026-01-01T01:00:00Z"
|
|
snapHomeT3 = "testhost_home_2026-01-01T03:00:00Z"
|
|
)
|
|
|
|
// setupPurgeTest creates a Vaultik instance with an in-memory database and mock
|
|
// storage pre-populated with the given snapshot IDs. Each snapshot is marked as
|
|
// completed. Remote metadata stubs are created so syncWithRemote keeps them.
|
|
func setupPurgeTest(t *testing.T, snapshotIDs []string) *vaultik.Vaultik {
|
|
t.Helper()
|
|
|
|
ctx := context.Background()
|
|
db, err := database.New(ctx, ":memory:")
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() { _ = db.Close() })
|
|
|
|
repos := database.NewRepositories(db)
|
|
mockStorage := NewMockStorer()
|
|
|
|
// Insert each snapshot into the DB and create remote metadata stubs.
|
|
// Use timestamps parsed from snapshot IDs for realistic ordering.
|
|
for _, id := range snapshotIDs {
|
|
// Parse timestamp from the snapshot ID
|
|
parts := strings.Split(id, "_")
|
|
timestampStr := parts[len(parts)-1]
|
|
startedAt, err := time.Parse(time.RFC3339, timestampStr)
|
|
require.NoError(t, err, "parsing timestamp from snapshot ID %q", id)
|
|
|
|
completedAt := startedAt.Add(5 * time.Minute)
|
|
snap := &database.Snapshot{
|
|
ID: types.SnapshotID(id),
|
|
Hostname: "testhost",
|
|
VaultikVersion: testLabel,
|
|
StartedAt: startedAt,
|
|
CompletedAt: &completedAt,
|
|
}
|
|
err = repos.WithTx(ctx, func(ctx context.Context, tx *sql.Tx) error {
|
|
return repos.Snapshots.Create(ctx, tx, snap)
|
|
})
|
|
require.NoError(t, err, "creating snapshot %s", id)
|
|
|
|
// Create remote metadata stub so syncWithRemote keeps it
|
|
metadataKey := "metadata/" + id + "/manifest.json.zst"
|
|
err = mockStorage.Put(ctx, metadataKey, strings.NewReader("stub"))
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
stdout := &bytes.Buffer{}
|
|
stderr := &bytes.Buffer{}
|
|
stdin := &bytes.Buffer{}
|
|
|
|
v := &vaultik.Vaultik{
|
|
Storage: mockStorage,
|
|
Repositories: repos,
|
|
DB: db,
|
|
Stdout: stdout,
|
|
Stderr: stderr,
|
|
Stdin: stdin,
|
|
}
|
|
v.SetContext(ctx)
|
|
|
|
return v
|
|
}
|
|
|
|
// listRemainingSnapshots returns IDs of all completed snapshots in the database.
|
|
func listRemainingSnapshots(t *testing.T, v *vaultik.Vaultik) []string {
|
|
t.Helper()
|
|
|
|
ctx := context.Background()
|
|
dbSnaps, err := v.Repositories.Snapshots.ListRecent(ctx, 10000)
|
|
require.NoError(t, err)
|
|
|
|
var ids []string
|
|
|
|
for _, s := range dbSnaps {
|
|
if s.CompletedAt != nil {
|
|
ids = append(ids, s.ID.String())
|
|
}
|
|
}
|
|
|
|
return ids
|
|
}
|
|
|
|
func TestPurgeKeepLatest_PerName(t *testing.T) {
|
|
log.Initialize(log.Config{})
|
|
t.Parallel()
|
|
|
|
// Create snapshots for two different names: "home" and "system".
|
|
// With per-name --keep-latest, the latest of each should be kept.
|
|
snapshotIDs := []string{
|
|
snapSystemT0,
|
|
snapHomeT1,
|
|
"testhost_system_2026-01-01T02:00:00Z",
|
|
snapHomeT3,
|
|
"testhost_system_2026-01-01T04:00:00Z",
|
|
}
|
|
|
|
v := setupPurgeTest(t, snapshotIDs)
|
|
|
|
err := v.PurgeSnapshotsWithOptions(&vaultik.SnapshotPurgeOptions{
|
|
KeepLatest: true,
|
|
Force: true,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
remaining := listRemainingSnapshots(t, v)
|
|
|
|
// Should keep the latest of each name
|
|
assert.Len(t, remaining, 2, "should keep exactly 2 snapshots (one per name)")
|
|
assert.Contains(t, remaining, "testhost_system_2026-01-01T04:00:00Z",
|
|
"should keep latest system")
|
|
assert.Contains(t, remaining, snapHomeT3, "should keep latest home")
|
|
}
|
|
|
|
func TestPurgeKeepLatest_SingleName(t *testing.T) {
|
|
log.Initialize(log.Config{})
|
|
t.Parallel()
|
|
|
|
// All snapshots have the same name — keep-latest should keep exactly one.
|
|
snapshotIDs := []string{
|
|
snapHomeT0,
|
|
snapHomeT1,
|
|
"testhost_home_2026-01-01T02:00:00Z",
|
|
}
|
|
|
|
v := setupPurgeTest(t, snapshotIDs)
|
|
|
|
err := v.PurgeSnapshotsWithOptions(&vaultik.SnapshotPurgeOptions{
|
|
KeepLatest: true,
|
|
Force: true,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
remaining := listRemainingSnapshots(t, v)
|
|
assert.Len(t, remaining, 1)
|
|
assert.Contains(t, remaining, "testhost_home_2026-01-01T02:00:00Z",
|
|
"should keep the newest")
|
|
}
|
|
|
|
func TestPurgeKeepLatest_WithNameFilter(t *testing.T) {
|
|
log.Initialize(log.Config{})
|
|
t.Parallel()
|
|
|
|
// Use --name to filter purge to only "home" snapshots.
|
|
// "system" snapshots should be untouched.
|
|
snapshotIDs := []string{
|
|
snapSystemT0,
|
|
snapHomeT1,
|
|
"testhost_system_2026-01-01T02:00:00Z",
|
|
snapHomeT3,
|
|
"testhost_home_2026-01-01T04:00:00Z",
|
|
}
|
|
|
|
v := setupPurgeTest(t, snapshotIDs)
|
|
|
|
err := v.PurgeSnapshotsWithOptions(&vaultik.SnapshotPurgeOptions{
|
|
KeepLatest: true,
|
|
Force: true,
|
|
Names: []string{"home"},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
remaining := listRemainingSnapshots(t, v)
|
|
|
|
// 2 system snapshots untouched + 1 latest home = 3
|
|
assert.Len(t, remaining, 3)
|
|
assert.Contains(t, remaining, snapSystemT0)
|
|
assert.Contains(t, remaining, "testhost_system_2026-01-01T02:00:00Z")
|
|
assert.Contains(t, remaining, "testhost_home_2026-01-01T04:00:00Z")
|
|
}
|
|
|
|
func TestPurgeKeepLatest_NoSnapshots(t *testing.T) {
|
|
log.Initialize(log.Config{})
|
|
t.Parallel()
|
|
|
|
v := setupPurgeTest(t, nil)
|
|
|
|
err := v.PurgeSnapshotsWithOptions(&vaultik.SnapshotPurgeOptions{
|
|
KeepLatest: true,
|
|
Force: true,
|
|
})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestPurgeKeepLatest_NameFilterNoMatch(t *testing.T) {
|
|
log.Initialize(log.Config{})
|
|
t.Parallel()
|
|
|
|
snapshotIDs := []string{
|
|
snapSystemT0,
|
|
"testhost_system_2026-01-01T01:00:00Z",
|
|
}
|
|
|
|
v := setupPurgeTest(t, snapshotIDs)
|
|
|
|
err := v.PurgeSnapshotsWithOptions(&vaultik.SnapshotPurgeOptions{
|
|
KeepLatest: true,
|
|
Force: true,
|
|
Names: []string{"nonexistent"},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// All snapshots should remain — the name filter matched nothing
|
|
remaining := listRemainingSnapshots(t, v)
|
|
assert.Len(t, remaining, 2)
|
|
}
|
|
|
|
func TestPurgeOlderThan_WithNameFilter(t *testing.T) {
|
|
log.Initialize(log.Config{})
|
|
t.Parallel()
|
|
|
|
// Snapshots with different names and timestamps.
|
|
// --older-than should apply only to the named subset when --name is used.
|
|
snapshotIDs := []string{
|
|
"testhost_system_2020-01-01T00:00:00Z",
|
|
"testhost_home_2020-01-01T00:00:00Z",
|
|
snapSystemT0,
|
|
snapHomeT0,
|
|
}
|
|
|
|
v := setupPurgeTest(t, snapshotIDs)
|
|
|
|
// Purge only "home" snapshots older than 365 days
|
|
err := v.PurgeSnapshotsWithOptions(&vaultik.SnapshotPurgeOptions{
|
|
OlderThan: "365d",
|
|
Force: true,
|
|
Names: []string{"home"},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
remaining := listRemainingSnapshots(t, v)
|
|
|
|
// Old system stays (not filtered by name), old home deleted, recent ones stay
|
|
assert.Len(t, remaining, 3)
|
|
assert.Contains(t, remaining, "testhost_system_2020-01-01T00:00:00Z")
|
|
assert.Contains(t, remaining, snapSystemT0)
|
|
assert.Contains(t, remaining, snapHomeT0)
|
|
}
|
|
|
|
func TestPurgeKeepLatest_ThreeNames(t *testing.T) {
|
|
log.Initialize(log.Config{})
|
|
t.Parallel()
|
|
|
|
// Three different snapshot names with multiple snapshots each.
|
|
snapshotIDs := []string{
|
|
snapHomeT0,
|
|
"testhost_system_2026-01-01T01:00:00Z",
|
|
"testhost_media_2026-01-01T02:00:00Z",
|
|
"testhost_home_2026-01-01T03:00:00Z",
|
|
"testhost_system_2026-01-01T04:00:00Z",
|
|
"testhost_media_2026-01-01T05:00:00Z",
|
|
"testhost_home_2026-01-01T06:00:00Z",
|
|
}
|
|
|
|
v := setupPurgeTest(t, snapshotIDs)
|
|
|
|
err := v.PurgeSnapshotsWithOptions(&vaultik.SnapshotPurgeOptions{
|
|
KeepLatest: true,
|
|
Force: true,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
remaining := listRemainingSnapshots(t, v)
|
|
assert.Len(t, remaining, 3, "should keep one per name")
|
|
assert.Contains(t, remaining, "testhost_home_2026-01-01T06:00:00Z")
|
|
assert.Contains(t, remaining, "testhost_system_2026-01-01T04:00:00Z")
|
|
assert.Contains(t, remaining, "testhost_media_2026-01-01T05:00:00Z")
|
|
}
|