From 87acc05a778e337b5705acb3fd03848a1a184081 Mon Sep 17 00:00:00 2001 From: user Date: Tue, 24 Mar 2026 13:40:08 -0700 Subject: [PATCH] fix: add RunDaemon test, remove dead daemonWatcherBatchDelay constant - Add TestRunDaemon_CancelledContext: exercises RunDaemon with a daemon-friendly config, cancels via context, verifies clean return and startup output - Remove unused daemonWatcherBatchDelay constant (batch-settle logic was never implemented; the watcher loop records changes immediately) - Update TestDaemonConstants to remove reference to deleted constant --- internal/vaultik/daemon.go | 5 --- internal/vaultik/daemon_test.go | 57 ++++++++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/internal/vaultik/daemon.go b/internal/vaultik/daemon.go index c9866eb..64e3a1a 100644 --- a/internal/vaultik/daemon.go +++ b/internal/vaultik/daemon.go @@ -19,11 +19,6 @@ import ( // regardless of config, to prevent runaway backup loops. const daemonMinBackupInterval = 1 * time.Minute -// daemonWatcherBatchDelay is the time to wait after the last filesystem event -// before considering the batch of changes "settled." This prevents triggering -// a backup for every individual file write during a burst of activity. -const daemonWatcherBatchDelay = 5 * time.Second - // daemonShutdownTimeout is the maximum time to wait for an in-progress backup // to complete during graceful shutdown before force-exiting. const daemonShutdownTimeout = 5 * time.Minute diff --git a/internal/vaultik/daemon_test.go b/internal/vaultik/daemon_test.go index aef9b1f..f7f0cd2 100644 --- a/internal/vaultik/daemon_test.go +++ b/internal/vaultik/daemon_test.go @@ -1,9 +1,14 @@ package vaultik import ( + "bytes" + "context" + "os" + "path/filepath" "testing" "time" + "git.eeqj.de/sneak/vaultik/internal/config" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -136,6 +141,56 @@ func TestSnapshotsAffectedByChanges(t *testing.T) { func TestDaemonConstants(t *testing.T) { // Verify daemon constants are reasonable values. assert.GreaterOrEqual(t, daemonMinBackupInterval, 1*time.Minute) - assert.GreaterOrEqual(t, daemonWatcherBatchDelay, 1*time.Second) assert.GreaterOrEqual(t, daemonShutdownTimeout, 1*time.Minute) } + +func TestRunDaemon_CancelledContext(t *testing.T) { + // Create a temporary directory to use as a snapshot path. + tmpDir := t.TempDir() + + // Write a file so the watched path is non-empty. + err := os.WriteFile(filepath.Join(tmpDir, "testfile.txt"), []byte("hello"), 0o644) + require.NoError(t, err) + + // Build a minimal Vaultik with daemon-friendly config. + // RunDaemon will fail on the initial backup (no storage configured), + // but it should continue running. We cancel the context to verify + // graceful shutdown. + ctx, cancel := context.WithCancel(context.Background()) + stdout := &bytes.Buffer{} + stderr := &bytes.Buffer{} + + v := &Vaultik{ + Config: &config.Config{ + BackupInterval: 1 * time.Hour, + FullScanInterval: 24 * time.Hour, + MinTimeBetweenRun: 1 * time.Minute, + Snapshots: map[string]config.SnapshotConfig{ + "test": { + Paths: []string{tmpDir}, + }, + }, + }, + ctx: ctx, + cancel: cancel, + Stdout: stdout, + Stderr: stderr, + } + + // Cancel the context shortly after RunDaemon starts so the daemon + // loop exits via its ctx.Done() path. + go func() { + // Wait for the initial backup to fail (it will, since there's no + // storage backend), then cancel. + time.Sleep(200 * time.Millisecond) + cancel() + }() + + err = v.RunDaemon(&SnapshotCreateOptions{}) + // RunDaemon should return nil on context cancellation (graceful shutdown). + assert.NoError(t, err) + + // Verify daemon printed startup messages. + output := stdout.String() + assert.Contains(t, output, "Daemon mode started") +}