package watcher import ( "os" "path/filepath" "testing" "time" ) func TestWatcherDetectsChange(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "test.json") if err := os.WriteFile(path, []byte(`{"version": 1}`), 0644); err != nil { t.Fatalf("failed to write test file: %v", err) } changeDetected := make(chan bool, 1) onChange := func() { select { case changeDetected <- true: default: } } fw := NewFileWatcher(path, 100*time.Millisecond, onChange) fw.Start() defer fw.Stop() // Wait for initial hash to be computed time.Sleep(200 * time.Millisecond) // Modify the file if err := os.WriteFile(path, []byte(`{"version": 2}`), 0644); err != nil { t.Fatalf("failed to modify test file: %v", err) } // Wait for change detection select { case detected := <-changeDetected: if !detected { t.Error("expected change detection, got false") } case <-time.After(2 * time.Second): t.Error("timeout waiting for file change detection") } } func TestWatcherNoChange(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "test.json") if err := os.WriteFile(path, []byte(`{"version": 1}`), 0644); err != nil { t.Fatalf("failed to write test file: %v", err) } changeDetected := make(chan bool, 1) onChange := func() { select { case changeDetected <- true: default: } } fw := NewFileWatcher(path, 100*time.Millisecond, onChange) fw.Start() defer fw.Stop() // Wait without modifying the file time.Sleep(300 * time.Millisecond) // Should not detect a change select { case <-changeDetected: t.Error("unexpected change detection without file modification") default: // Expected: no change detected } } func TestWatcherMultipleChanges(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "test.json") if err := os.WriteFile(path, []byte(`{"v": 1}`), 0644); err != nil { t.Fatalf("failed to write test file: %v", err) } changeCount := 0 var mu chan struct{} onChange := func() { changeCount++ if mu != nil { mu <- struct{}{} } } fw := NewFileWatcher(path, 50*time.Millisecond, onChange) fw.Start() defer fw.Stop() // Wait for initial hash time.Sleep(100 * time.Millisecond) // Make 3 changes for i := 2; i <= 4; i++ { if err := os.WriteFile(path, []byte(`{"v": `+string(rune('0'+i))+`}`), 0644); err != nil { t.Fatalf("failed to modify file: %v", err) } time.Sleep(150 * time.Millisecond) } if changeCount < 2 { t.Errorf("expected at least 2 change detections, got %d", changeCount) } } func TestWatcherStop(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "test.json") if err := os.WriteFile(path, []byte(`{"v": 1}`), 0644); err != nil { t.Fatalf("failed to write test file: %v", err) } fw := NewFileWatcher(path, 50*time.Millisecond, func() {}) fw.Start() time.Sleep(100 * time.Millisecond) fw.Stop() // Should not panic on double stop or further operations time.Sleep(100 * time.Millisecond) } func TestWatcherNonexistentFile(t *testing.T) { onChange := func() { t.Error("onChange should not be called for nonexistent file") } fw := NewFileWatcher("/nonexistent/file.json", 100*time.Millisecond, onChange) fw.Start() defer fw.Stop() // Wait — should not call onChange since file doesn't exist time.Sleep(300 * time.Millisecond) } func TestWatcherReproducible(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "test.json") if err := os.WriteFile(path, []byte(`{"data": "value"}`), 0644); err != nil { t.Fatalf("failed to write test file: %v", err) } // Create two watchers on the same file var detected1, detected2 bool fw1 := NewFileWatcher(path, 50*time.Millisecond, func() { detected1 = true }) fw2 := NewFileWatcher(path, 50*time.Millisecond, func() { detected2 = true }) fw1.Start() fw2.Start() defer fw1.Stop() defer fw2.Stop() time.Sleep(100 * time.Millisecond) // Modify file if err := os.WriteFile(path, []byte(`{"data": "changed"}`), 0644); err != nil { t.Fatalf("failed to modify file: %v", err) } time.Sleep(300 * time.Millisecond) if !detected1 { t.Error("watcher 1 did not detect change") } if !detected2 { t.Error("watcher 2 did not detect change") } }