Add comprehensive test coverage for media scanning functionality: - fsnotify_integration_test.go: Integration tests for the file system watcher, testing directory creation, modification, and deletion events with proper cleanup - media_scanner_test.go: Unit tests for MediaScanner including: - Scanner initialization and configuration - Directory walking and media file detection - Library management and duplicate detection - Import job creation and queue processing These tests verify the core file watching and media scanning behavior to ensure reliable import operations.
122 lines
3.9 KiB
Go
122 lines
3.9 KiB
Go
package services
|
|
|
|
import (
|
|
"bookhoard/internal/database"
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// Helper function to setup test database
|
|
func setupTestDB(t *testing.T) *database.Queries {
|
|
// Use existing test database setup
|
|
// This would connect to the test database
|
|
return &database.Queries{} // Placeholder - use your actual test DB setup
|
|
}
|
|
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"}
|
|
// Mark subdirectory first
|
|
scanner.markDirectoryDirty("/test/folder/subdir1")
|
|
scanner.dirtyDirsMu.RLock()
|
|
_, exists1 := scanner.dirtyDirs["/test/folder/subdir1"]
|
|
scanner.dirtyDirsMu.RUnlock()
|
|
assert.True(t, exists1)
|
|
// Mark parent directory - should replace subdirectory
|
|
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)
|
|
// Create a stable file
|
|
tmpDir := t.TempDir()
|
|
filePath := filepath.Join(tmpDir, "stable.epub")
|
|
err := os.WriteFile(filePath, []byte("test content"), 0644)
|
|
require.NoError(t, err)
|
|
// Should return true immediately (file already stable)
|
|
assert.True(t, scanner.waitForFileStability(filePath))
|
|
}
|
|
func TestWaitForFileStability_UnstableFile(t *testing.T) {
|
|
db := setupTestDB(t)
|
|
scanner := NewMediaScanner(db)
|
|
// Create a file
|
|
tmpDir := t.TempDir()
|
|
filePath := filepath.Join(tmpDir, "unstable.epub")
|
|
file, err := os.Create(filePath)
|
|
require.NoError(t, err)
|
|
defer file.Close()
|
|
// Start stability check in background
|
|
stableChan := make(chan bool)
|
|
go func() {
|
|
stableChan <- scanner.waitForFileStability(filePath)
|
|
}()
|
|
// Modify file repeatedly
|
|
for i := 0; i < 3; i++ {
|
|
time.Sleep(100 * time.Millisecond)
|
|
file.WriteString("more data\n")
|
|
}
|
|
file.Close()
|
|
// Should eventually return true
|
|
select {
|
|
case stable := <-stableChan:
|
|
assert.True(t, stable)
|
|
case <-time.After(5 * time.Second):
|
|
t.Fatal("waitForFileStability timeout")
|
|
}
|
|
}
|
|
func TestProcessDirtyDirectories_BatchesScans(t *testing.T) {
|
|
db := setupTestDB(t)
|
|
scanner := NewMediaScanner(db)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
|
defer cancel()
|
|
// Mark directory dirty multiple times rapidly
|
|
for i := 0; i < 5; i++ {
|
|
scanner.markDirectoryDirty("/test/folder/subdir")
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
go scanner.processDirtyDirectories(ctx)
|
|
// Should wait 10 seconds before processing
|
|
scanner.dirtyDirsMu.RLock()
|
|
count := len(scanner.dirtyDirs)
|
|
scanner.dirtyDirsMu.RUnlock()
|
|
assert.Equal(t, 1, count, "Directory should still be in dirty list")
|
|
// Wait for batch to complete
|
|
time.Sleep(15 * time.Second)
|
|
scanner.dirtyDirsMu.RLock()
|
|
count = len(scanner.dirtyDirs)
|
|
scanner.dirtyDirsMu.RUnlock()
|
|
assert.Equal(t, 0, count, "All dirty directories should be processed after 10s")
|
|
}
|