"""Tests for real-time monitor.""" import pytest import time from ayn_antivirus.monitor.realtime import RealtimeMonitor from ayn_antivirus.core.engine import ScanEngine from ayn_antivirus.config import Config @pytest.fixture def monitor(tmp_path): config = Config() engine = ScanEngine(config) m = RealtimeMonitor(config, engine) yield m if m.is_running: m.stop() def test_monitor_init(monitor): assert monitor is not None assert monitor.is_running is False def test_monitor_should_skip(): """Temporary / lock / editor files should be skipped.""" config = Config() engine = ScanEngine(config) m = RealtimeMonitor(config, engine) assert m._should_skip("/tmp/test.tmp") is True assert m._should_skip("/tmp/test.swp") is True assert m._should_skip("/tmp/test.lock") is True assert m._should_skip("/tmp/.#backup") is True assert m._should_skip("/tmp/test.part") is True assert m._should_skip("/tmp/test.txt") is False assert m._should_skip("/tmp/test.py") is False assert m._should_skip("/var/www/index.html") is False def test_monitor_debounce(monitor): """After the first call records the path, an immediate repeat is debounced.""" import time as _time # Prime the path so it's recorded with the current monotonic time. # On fresh processes, monotonic() can be close to 0.0 which is the # default in _recent, so we explicitly set a realistic timestamp. monitor._recent["/tmp/test.txt"] = _time.monotonic() - 10 assert monitor._is_debounced("/tmp/test.txt") is False # Immediate second call should be debounced (within 2s window) assert monitor._is_debounced("/tmp/test.txt") is True def test_monitor_debounce_different_paths(monitor): """Different paths should not debounce each other.""" import time as _time # Prime both paths far enough in the past to avoid the initial-value edge case past = _time.monotonic() - 10 monitor._recent["/tmp/a.txt"] = past monitor._recent["/tmp/b.txt"] = past assert monitor._is_debounced("/tmp/a.txt") is False assert monitor._is_debounced("/tmp/b.txt") is False def test_monitor_start_stop(tmp_path, monitor): monitor.start(paths=[str(tmp_path)], recursive=True) assert monitor.is_running is True time.sleep(0.3) monitor.stop() assert monitor.is_running is False def test_monitor_double_start(tmp_path, monitor): """Starting twice should be harmless.""" monitor.start(paths=[str(tmp_path)]) assert monitor.is_running is True monitor.start(paths=[str(tmp_path)]) # Should log warning, not crash assert monitor.is_running is True monitor.stop() def test_monitor_stop_when_not_running(monitor): """Stopping when not running should be harmless.""" assert monitor.is_running is False monitor.stop() assert monitor.is_running is False def test_monitor_nonexistent_path(monitor): """Non-existent paths should be skipped without crash.""" monitor.start(paths=["/nonexistent/path/xyz123"]) # Should still be running (observer started, just no schedules) assert monitor.is_running is True monitor.stop()