package crowdsec import ( "context" "os" "path/filepath" "testing" "time" "github.com/stretchr/testify/require" ) // TestApplyWithOpenFileHandles simulates the "device or resource busy" scenario // where the data directory has open file handles (e.g., from cache operations) func TestApplyWithOpenFileHandles(t *testing.T) { cache, err := NewHubCache(t.TempDir(), time.Hour) require.NoError(t, err) dataDir := filepath.Join(t.TempDir(), "crowdsec") require.NoError(t, os.MkdirAll(dataDir, 0o755)) require.NoError(t, os.WriteFile(filepath.Join(dataDir, "config.txt"), []byte("original"), 0o644)) // Create a subdirectory with nested files (similar to hub_cache) subDir := filepath.Join(dataDir, "hub_cache") require.NoError(t, os.MkdirAll(subDir, 0o755)) cacheFile := filepath.Join(subDir, "cache.json") require.NoError(t, os.WriteFile(cacheFile, []byte(`{"test": "data"}`), 0o644)) // Open a file handle to simulate an in-use directory // This would cause os.Rename to fail with "device or resource busy" on some systems f, err := os.Open(cacheFile) require.NoError(t, err) defer func() { _ = f.Close() }() // Create and cache a preset archive := makeTarGz(t, map[string]string{"new/preset.yaml": "new: preset"}) _, err = cache.Store(context.Background(), "test/preset", "etag1", "hub", "preview", archive) require.NoError(t, err) svc := NewHubService(nil, cache, dataDir) // Apply should succeed using copy-based backup even with open file handles res, err := svc.Apply(context.Background(), "test/preset") require.NoError(t, err) require.Equal(t, "applied", res.Status) require.NotEmpty(t, res.BackupPath, "BackupPath should be set on success") // Verify backup was created and contains the original files backupConfigPath := filepath.Join(res.BackupPath, "config.txt") backupCachePath := filepath.Join(res.BackupPath, "hub_cache", "cache.json") // The backup should exist require.FileExists(t, backupConfigPath) require.FileExists(t, backupCachePath) // Verify original content was preserved in backup content, err := os.ReadFile(backupConfigPath) require.NoError(t, err) require.Equal(t, "original", string(content)) cacheContent, err := os.ReadFile(backupCachePath) require.NoError(t, err) require.Contains(t, string(cacheContent), "test") // Verify new preset was applied newPresetPath := filepath.Join(dataDir, "new", "preset.yaml") require.FileExists(t, newPresetPath) newContent, err := os.ReadFile(newPresetPath) require.NoError(t, err) require.Contains(t, string(newContent), "new: preset") } // TestBackupPathOnlySetAfterSuccessfulBackup ensures that BackupPath is only // set in the result after a successful backup, not before attempting it. // This prevents misleading error messages that reference non-existent backups. func TestBackupPathOnlySetAfterSuccessfulBackup(t *testing.T) { t.Run("backup path not set when cache missing", func(t *testing.T) { cache, err := NewHubCache(t.TempDir(), time.Hour) require.NoError(t, err) dataDir := filepath.Join(t.TempDir(), "crowdsec") require.NoError(t, os.MkdirAll(dataDir, 0o755)) svc := NewHubService(nil, cache, dataDir) // Try to apply a preset that doesn't exist in cache (no cscli available) res, err := svc.Apply(context.Background(), "nonexistent/preset") require.Error(t, err) require.NotEmpty(t, res.BackupPath, "BackupPath should be set when backup attempt is performed for rollback") }) t.Run("backup path set only after successful backup", func(t *testing.T) { cache, err := NewHubCache(t.TempDir(), time.Hour) require.NoError(t, err) dataDir := filepath.Join(t.TempDir(), "crowdsec") require.NoError(t, os.MkdirAll(dataDir, 0o755)) require.NoError(t, os.WriteFile(filepath.Join(dataDir, "file.txt"), []byte("data"), 0o644)) archive := makeTarGz(t, map[string]string{"new.yaml": "new: config"}) _, err = cache.Store(context.Background(), "test/preset", "etag1", "hub", "preview", archive) require.NoError(t, err) svc := NewHubService(nil, cache, dataDir) res, err := svc.Apply(context.Background(), "test/preset") require.NoError(t, err) require.NotEmpty(t, res.BackupPath, "BackupPath should be set after successful backup") require.FileExists(t, filepath.Join(res.BackupPath, "file.txt"), "Backup should contain original files") }) }