Files
firewall_containers/network-go/watcher/watcher_test.go
gyurix bf94206849
continuous-integration/drone/push Build is passing
feat: Add POSTROUTING MASQUERADE and periodic state reconciliation
- Add POSTROUTING MASQUERADE rule alongside DNAT rules to ensure return
  traffic from container targets can route back through the same interface,
  matching legacy shell script behavior
- Enhance FileWatcher to trigger periodic state reconciliation every tick
  regardless of config file changes, ensuring desired state is maintained
  after container restarts or iptables flushes
2026-06-15 22:40:43 +02:00

175 lines
4.2 KiB
Go

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)
}
// With periodic reconciliation, onChange will be called every period.
// Count how many times it's called within the wait period.
callCount := 0
onChange := func() {
callCount++
}
fw := NewFileWatcher(path, 100*time.Millisecond, onChange)
fw.Start()
defer fw.Stop()
// Wait without modifying the file
time.Sleep(350 * time.Millisecond)
// onChange should have been called ~3 times (0s, 0.1s, 0.2s, 0.3s) for periodic reconciliation
if callCount < 1 {
t.Errorf("expected at least 1 periodic reconciliation call, got %d", callCount)
}
}
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")
}
}