.docx is accepted for ingest but the format-group switch had no case for it, so every DOCX row stored format_group 'unknown' with is_reflowable=false. Extract the classification switch into a pure classifyFormatGroup helper with a table test, and classify .docx reflowable like the other reflowable text formats (.rtf/.doc stay out until they are readable client-side). Existing rows: dev alice (DOCX) reclassified by hand; production rows reclassify on rescan.
153 lines
4.7 KiB
Go
153 lines
4.7 KiB
Go
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")
|
|
}
|
|
|
|
func TestClassifyFormatGroup(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
ext string
|
|
epubFixed bool
|
|
group string
|
|
reflow bool
|
|
fixed bool
|
|
}{
|
|
{"EPUB reflowable", ".epub", false, "reflowable", true, false},
|
|
{"EPUB fixed-layout", ".epub", true, "fixed_layout", false, true},
|
|
{"MOBI", ".mobi", false, "reflowable", true, false},
|
|
{"FB2", ".fb2", false, "reflowable", true, false},
|
|
{"TXT", ".txt", false, "reflowable", true, false},
|
|
{"DOCX (was unknown before the fix)", ".docx", false, "reflowable", true, false},
|
|
{"PDF", ".pdf", false, "fixed_layout", false, true},
|
|
{"DJVU", ".djvu", false, "fixed_layout", false, true},
|
|
{"CBZ", ".cbz", false, "comic_archive", false, true},
|
|
{"unrecognized extension", ".xyz", false, "unknown", false, false},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
group, reflow, fixed := classifyFormatGroup(tt.ext, tt.epubFixed)
|
|
if group != tt.group || reflow != tt.reflow || fixed != tt.fixed {
|
|
t.Errorf("classifyFormatGroup(%q, %v) = (%q, %v, %v), want (%q, %v, %v)",
|
|
tt.ext, tt.epubFixed, group, reflow, fixed, tt.group, tt.reflow, tt.fixed)
|
|
}
|
|
})
|
|
}
|
|
}
|