Files
cairnquire/apps/server/internal/app/watcher_test.go
2026-05-28 08:35:50 -04:00

85 lines
2.5 KiB
Go

package app
import (
"log/slog"
"path/filepath"
"testing"
"time"
"github.com/fsnotify/fsnotify"
)
func TestFileWatcherFiltersProcessableEvents(t *testing.T) {
t.Parallel()
root := t.TempDir()
watcher := &FileWatcher{rootDir: root, logger: slog.Default()}
tests := []struct {
name string
path string
op fsnotify.Op
want bool
}{
{name: "markdown write", path: "guide.md", op: fsnotify.Write, want: true},
{name: "markdown create", path: "nested/guide.md", op: fsnotify.Create, want: true},
{name: "case insensitive markdown", path: "guide.MD", op: fsnotify.Write, want: true},
{name: "markdown delete", path: "guide.md", op: fsnotify.Remove, want: true},
{name: "non markdown write", path: "logo.webp", op: fsnotify.Write, want: false},
{name: "non markdown delete", path: "logo.webp", op: fsnotify.Remove, want: false},
{name: "hidden markdown file", path: ".guide.md", op: fsnotify.Write, want: false},
{name: "hidden directory", path: ".cache/guide.md", op: fsnotify.Write, want: false},
{name: "backup file", path: "guide.md~", op: fsnotify.Write, want: false},
{name: "vim swap", path: "guide.md.swp", op: fsnotify.Write, want: false},
{name: "emacs lock file", path: "#guide.md#", op: fsnotify.Write, want: false},
{name: "chmod only", path: "guide.md", op: fsnotify.Chmod, want: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
event := fsnotify.Event{Name: filepath.Join(root, tt.path), Op: tt.op}
if got := watcher.isProcessableEvent(event); got != tt.want {
t.Fatalf("isProcessableEvent() = %v, want %v", got, tt.want)
}
})
}
}
func TestFileWatcherDebouncesPendingSyncs(t *testing.T) {
t.Parallel()
root := t.TempDir()
watcher := &FileWatcher{
rootDir: root,
logger: slog.Default(),
debounce: 10 * time.Millisecond,
}
var changed []string
watcher.SetOnChange(func(path string) {
changed = append(changed, path)
})
pending := make(map[string]struct{})
var timer *time.Timer
var timerC <-chan time.Time
defer watcher.stopTimer(timer)
watcher.scheduleSync(filepath.Join(root, "one.md"), pending, &timer, &timerC)
watcher.scheduleSync(filepath.Join(root, "two.md"), pending, &timer, &timerC)
select {
case <-timerC:
watcher.flushPending(pending)
case <-time.After(250 * time.Millisecond):
t.Fatal("debounce timer did not fire")
}
if len(changed) != 1 {
t.Fatalf("onChange calls = %d, want 1", len(changed))
}
if len(pending) != 0 {
t.Fatalf("pending changes = %d, want 0", len(pending))
}
}