package services import ( "os" "path/filepath" "testing" "time" "bookhoard/internal/database" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func setupTestDB(t *testing.T) *database.Queries { return &database.Queries{} } func TestMarkDirectoryDirty(t *testing.T) { db := setupTestDB(t) scanner := NewMediaScanner(db) scanner.folders = []string{"/test/folder"} scanner.markDirectoryDirty("/test/folder/subdir") scanner.dirtyDirsMu.RLock() _, exists := scanner.dirtyDirs["/test/folder/subdir"] scanner.dirtyDirsMu.RUnlock() assert.True(t, exists, "Directory should be marked dirty") } func TestMarkDirectoryDirty_IgnoresNonWatchedPaths(t *testing.T) { db := setupTestDB(t) scanner := NewMediaScanner(db) scanner.folders = []string{"/test/folder"} scanner.markDirectoryDirty("/other/folder") scanner.dirtyDirsMu.RLock() _, exists := scanner.dirtyDirs["/other/folder"] scanner.dirtyDirsMu.RUnlock() assert.False(t, exists, "Non-watched directory should be ignored") } func TestMarkDirectoryDirty_SmartEventMerging(t *testing.T) { db := setupTestDB(t) scanner := NewMediaScanner(db) scanner.folders = []string{"/test/folder"} scanner.markDirectoryDirty("/test/folder/subdir1") scanner.dirtyDirsMu.RLock() _, exists1 := scanner.dirtyDirs["/test/folder/subdir1"] scanner.dirtyDirsMu.RUnlock() assert.True(t, exists1) scanner.markDirectoryDirty("/test/folder") scanner.dirtyDirsMu.RLock() _, parentExists := scanner.dirtyDirs["/test/folder"] _, childExists := scanner.dirtyDirs["/test/folder/subdir1"] scanner.dirtyDirsMu.RUnlock() assert.True(t, parentExists, "Parent should exist") assert.False(t, childExists, "Child should be removed (consolidated)") } func TestWaitForFileStability_StableFile(t *testing.T) { db := setupTestDB(t) scanner := NewMediaScanner(db) tmpDir := t.TempDir() filePath := filepath.Join(tmpDir, "stable.epub") err := os.WriteFile(filePath, []byte("test content"), 0644) require.NoError(t, err) assert.True(t, scanner.waitForFileStability(filePath)) } func TestWaitForFileStability_UnstableFile(t *testing.T) { db := setupTestDB(t) scanner := NewMediaScanner(db) tmpDir := t.TempDir() filePath := filepath.Join(tmpDir, "unstable.epub") file, err := os.Create(filePath) require.NoError(t, err) defer file.Close() stableChan := make(chan bool) go func() { stableChan <- scanner.waitForFileStability(filePath) }() for i := 0; i < 3; i++ { time.Sleep(100 * time.Millisecond) file.WriteString("more data\n") } file.Close() select { case stable := <-stableChan: assert.True(t, stable) case <-time.After(7 * time.Second): t.Fatal("waitForFileStability timeout") } } func TestProcessDirtyDirectories_CollectsReadyDirs(t *testing.T) { db := setupTestDB(t) scanner := NewMediaScanner(db) scanner.folders = []string{"/test/folder"} scanner.dirtyDirsMu.Lock() scanner.dirtyDirs["/test/folder/subdir"] = time.Now().Add(-15 * time.Second) scanner.dirtyDirsMu.Unlock() scanner.dirtyDirsMu.Lock() now := time.Now() readyDirs := make([]string, 0) for dirPath, lastChange := range scanner.dirtyDirs { if now.Sub(lastChange) >= 10*time.Second { readyDirs = append(readyDirs, dirPath) delete(scanner.dirtyDirs, dirPath) } } scanner.dirtyDirsMu.Unlock() assert.Equal(t, 1, len(readyDirs), "Should find one ready directory") assert.Equal(t, "/test/folder/subdir", readyDirs[0]) scanner.dirtyDirsMu.RLock() count := len(scanner.dirtyDirs) scanner.dirtyDirsMu.RUnlock() assert.Equal(t, 0, count, "Ready directory should be removed from dirty list") }