""" Comprehensive tests for BackgroundJobScheduler class. Tests scheduling, user sessions, document processing, and configuration. """ import pytest from unittest.mock import MagicMock, Mock, patch from datetime import datetime import threading class TestNewsSchedulerSingleton: """Tests for BackgroundJobScheduler singleton pattern.""" def test_returns_same_instance(self): """Test returns same instance on multiple calls.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, ) # Reset singleton for test BackgroundJobScheduler._instance = None s1 = BackgroundJobScheduler() s2 = BackgroundJobScheduler() assert s1 is s2 def test_thread_safe_singleton(self): """Test singleton is thread-safe.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, ) BackgroundJobScheduler._instance = None instances = [] def create_instance(): instances.append(BackgroundJobScheduler()) threads = [threading.Thread(target=create_instance) for _ in range(10)] for t in threads: t.start() for t in threads: t.join() # All instances should be the same assert all(i is instances[0] for i in instances) class TestNewsSchedulerInit: """Tests for BackgroundJobScheduler initialization.""" def test_initializes_user_sessions_dict(self): """Test user_sessions initialized as empty dict.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, ) BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() assert scheduler.user_sessions == {} def test_initializes_lock(self): """Test lock is initialized.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, ) BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() assert isinstance(scheduler.lock, type(threading.Lock())) def test_initializes_background_scheduler(self): """Test BackgroundScheduler is initialized.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, ) from apscheduler.schedulers.background import BackgroundScheduler BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() assert isinstance(scheduler.scheduler, BackgroundScheduler) def test_is_running_initially_false(self): """Test is_running is False initially.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, ) BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() assert scheduler.is_running is False class TestLoadDefaultConfig: """Tests for _load_default_config method.""" def test_returns_dict(self): """Test returns a dictionary.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, ) BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() config = scheduler._load_default_config() assert isinstance(config, dict) def test_includes_enabled_key(self): """Test config includes enabled key.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, ) BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() config = scheduler._load_default_config() assert "enabled" in config def test_includes_retention_hours(self): """Test config includes retention_hours.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, ) BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() config = scheduler._load_default_config() assert "retention_hours" in config assert config["retention_hours"] == 48 def test_includes_max_jitter_seconds(self): """Test config includes max_jitter_seconds.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, ) BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() config = scheduler._load_default_config() assert "max_jitter_seconds" in config class TestInitializeWithSettings: """Tests for initialize_with_settings method.""" def test_stores_settings_manager(self): """Test stores settings manager reference.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, ) BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() mock_settings = Mock() mock_settings.get_setting.return_value = True scheduler.initialize_with_settings(mock_settings) assert scheduler.settings_manager is mock_settings def test_loads_config_from_settings(self): """Test loads config from settings manager.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, ) BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() mock_settings = Mock() mock_settings.get_setting.side_effect = lambda k, default: { "news.scheduler.enabled": False, "news.scheduler.retention_hours": 24, }.get(k, default) scheduler.initialize_with_settings(mock_settings) assert scheduler.config["enabled"] is False assert scheduler.config["retention_hours"] == 24 def test_handles_settings_exception(self): """Test handles exception during settings load.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, ) BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() mock_settings = Mock() mock_settings.get_setting.side_effect = Exception("Settings error") # Should not raise scheduler.initialize_with_settings(mock_settings) class TestGetSetting: """Tests for _get_setting method.""" def test_returns_default_without_settings_manager(self): """Test returns default when no settings manager.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, ) BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() result = scheduler._get_setting("some.key", default=42) assert result == 42 def test_calls_settings_manager(self): """Test calls settings manager get_setting.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, ) BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() mock_settings = Mock() mock_settings.get_setting.return_value = 100 scheduler.settings_manager = mock_settings _ = scheduler._get_setting("some.key", default=42) mock_settings.get_setting.assert_called_with("some.key", default=42) class TestDocumentSchedulerSettings: """Tests for DocumentSchedulerSettings dataclass.""" def test_is_frozen(self): """Test dataclass is frozen (immutable).""" from local_deep_research.scheduler.background import ( DocumentSchedulerSettings, ) settings = DocumentSchedulerSettings() with pytest.raises(Exception): # FrozenInstanceError settings.enabled = False def test_default_enabled_is_true(self): """Test default enabled is True.""" from local_deep_research.scheduler.background import ( DocumentSchedulerSettings, ) settings = DocumentSchedulerSettings() assert settings.enabled is True def test_default_interval_seconds(self): """Test default interval_seconds is 1800.""" from local_deep_research.scheduler.background import ( DocumentSchedulerSettings, ) settings = DocumentSchedulerSettings() assert settings.interval_seconds == 1800 def test_defaults_factory_method(self): """Test defaults() factory method.""" from local_deep_research.scheduler.background import ( DocumentSchedulerSettings, ) defaults = DocumentSchedulerSettings.defaults() assert defaults.enabled is True assert defaults.interval_seconds == 1800 class TestGetDocumentSchedulerSettings: """Tests for _get_document_scheduler_settings method.""" def test_returns_cached_value(self): """Test returns cached value when available.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, DocumentSchedulerSettings, ) BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() cached_settings = DocumentSchedulerSettings( enabled=False, interval_seconds=600 ) scheduler._settings_cache["testuser"] = cached_settings result = scheduler._get_document_scheduler_settings("testuser") assert result is cached_settings def test_returns_defaults_without_session(self): """Test returns defaults when no user session.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, DocumentSchedulerSettings, ) BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() result = scheduler._get_document_scheduler_settings("unknown_user") defaults = DocumentSchedulerSettings.defaults() assert result.enabled == defaults.enabled def test_force_refresh_bypasses_cache(self): """Test force_refresh bypasses cache.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, DocumentSchedulerSettings, ) BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() cached_settings = DocumentSchedulerSettings(enabled=False) scheduler._settings_cache["testuser"] = cached_settings # Without session, should return defaults even with cache result = scheduler._get_document_scheduler_settings( "testuser", force_refresh=True ) # Returns defaults since no session assert result.enabled is True class TestInvalidateUserSettingsCache: """Tests for invalidate_user_settings_cache method.""" def test_removes_user_from_cache(self): """Test removes user from cache.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, DocumentSchedulerSettings, ) BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() scheduler._settings_cache["testuser"] = DocumentSchedulerSettings() scheduler.invalidate_user_settings_cache("testuser") assert "testuser" not in scheduler._settings_cache def test_returns_true_if_found(self): """Test returns True when user was in cache.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, DocumentSchedulerSettings, ) BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() scheduler._settings_cache["testuser"] = DocumentSchedulerSettings() result = scheduler.invalidate_user_settings_cache("testuser") assert result is True def test_returns_false_if_not_found(self): """Test returns False when user not in cache.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, ) BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() result = scheduler.invalidate_user_settings_cache("unknown_user") assert result is False class TestInvalidateAllSettingsCache: """Tests for invalidate_all_settings_cache method.""" def test_clears_all_entries(self): """Test clears all cache entries.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, DocumentSchedulerSettings, ) BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() scheduler._settings_cache["user1"] = DocumentSchedulerSettings() scheduler._settings_cache["user2"] = DocumentSchedulerSettings() scheduler.invalidate_all_settings_cache() assert len(scheduler._settings_cache) == 0 def test_returns_count_cleared(self): """Test returns count of cleared entries.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, DocumentSchedulerSettings, ) BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() scheduler._settings_cache["user1"] = DocumentSchedulerSettings() scheduler._settings_cache["user2"] = DocumentSchedulerSettings() scheduler._settings_cache["user3"] = DocumentSchedulerSettings() count = scheduler.invalidate_all_settings_cache() assert count == 3 class TestStartScheduler: """Tests for start method.""" def test_sets_is_running_true(self): """Test sets is_running to True.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, ) BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() scheduler.set_app(MagicMock()) with patch.object(scheduler.scheduler, "start"): scheduler.start() assert scheduler.is_running is True def test_starts_background_scheduler(self): """Test starts the background scheduler.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, ) BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() scheduler.set_app(MagicMock()) with patch.object(scheduler.scheduler, "start") as mock_start: scheduler.start() mock_start.assert_called_once() def test_does_not_start_if_already_running(self): """Test does not start if already running.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, ) BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() scheduler.is_running = True with patch.object(scheduler.scheduler, "start") as mock_start: scheduler.start() mock_start.assert_not_called() class TestStopScheduler: """Tests for stop method.""" def test_sets_is_running_false(self): """Test sets is_running to False.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, ) BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() scheduler.is_running = True with patch.object(scheduler.scheduler, "shutdown"): scheduler.stop() assert scheduler.is_running is False def test_shuts_down_scheduler(self): """Test shuts down the background scheduler.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, ) BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() scheduler.is_running = True with patch.object(scheduler.scheduler, "shutdown") as mock_shutdown: scheduler.stop() mock_shutdown.assert_called_once() class TestUpdateUserInfo: """Tests for update_user_info method.""" def test_creates_new_session(self): """Test creates new session for new user.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, ) BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() scheduler.is_running = True # Enable scheduler with patch.object(scheduler, "_schedule_user_subscriptions"): scheduler.update_user_info("newuser", "password123") assert "newuser" in scheduler.user_sessions assert "password" not in scheduler.user_sessions["newuser"] assert scheduler._credential_store.retrieve("newuser") == "password123" def test_updates_last_activity(self): """Test updates last_activity timestamp.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, ) from datetime import timezone BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() scheduler.is_running = True before = datetime.now(timezone.utc) with patch.object(scheduler, "_schedule_user_subscriptions"): scheduler.update_user_info("testuser", "password") after = datetime.now(timezone.utc) last_activity = scheduler.user_sessions["testuser"]["last_activity"] assert before <= last_activity <= after def test_initializes_scheduled_jobs_set(self): """Test initializes empty scheduled_jobs set.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, ) BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() scheduler.is_running = True with patch.object(scheduler, "_schedule_user_subscriptions"): scheduler.update_user_info("testuser", "password") assert scheduler.user_sessions["testuser"]["scheduled_jobs"] == set() class TestUnregisterUser: """Tests for unregister_user method.""" def test_removes_user_from_sessions(self): """Test removes user from sessions.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, ) BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() scheduler.user_sessions["testuser"] = { "last_activity": datetime.now(), "scheduled_jobs": set(), } scheduler._credential_store.store("testuser", "test") scheduler.unregister_user("testuser") assert "testuser" not in scheduler.user_sessions def test_invalidates_settings_cache(self): """Test invalidates settings cache for user.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, DocumentSchedulerSettings, ) BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() scheduler.user_sessions["testuser"] = { "last_activity": datetime.now(), "scheduled_jobs": set(), } scheduler._credential_store.store("testuser", "test") scheduler._settings_cache["testuser"] = DocumentSchedulerSettings() scheduler.unregister_user("testuser") assert "testuser" not in scheduler._settings_cache def test_handles_nonexistent_user(self): """Test handles unregistering nonexistent user.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, ) BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() # Should not raise scheduler.unregister_user("nonexistent_user") class TestGetDocumentSchedulerStatus: """Tests for get_document_scheduler_status method.""" def test_returns_dict(self): """Test returns a dictionary.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, ) BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() result = scheduler.get_document_scheduler_status("testuser") assert isinstance(result, dict) def test_includes_enabled_flag(self): """Test includes enabled flag.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, ) BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() result = scheduler.get_document_scheduler_status("testuser") assert "enabled" in result def test_returns_disabled_for_unknown_user(self): """Test returns disabled for unknown user.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, ) BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() result = scheduler.get_document_scheduler_status("unknown") # Default settings should have enabled=True assert "enabled" in result class TestTriggerDocumentProcessing: """Tests for trigger_document_processing method.""" def test_returns_false_if_no_session(self): """Test returns False if no user session.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, ) BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() result = scheduler.trigger_document_processing("unknown_user") assert result is False def test_returns_false_if_not_running(self): """Test returns False if scheduler not running.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, ) BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() scheduler.is_running = False scheduler.user_sessions["testuser"] = { "last_activity": datetime.now(), "scheduled_jobs": set(), } scheduler._credential_store.store("testuser", "test") result = scheduler.trigger_document_processing("testuser") assert result is False class TestSchedulerThreadSafety: """Tests for scheduler thread safety.""" def test_lock_used_for_user_sessions(self): """Test lock is used when modifying user_sessions.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, ) BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() # The lock should be accessible assert hasattr(scheduler, "lock") assert isinstance(scheduler.lock, type(threading.Lock())) def test_settings_cache_lock_exists(self): """Test settings cache lock exists.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, ) BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() assert hasattr(scheduler, "_settings_cache_lock") assert isinstance( scheduler._settings_cache_lock, type(threading.Lock()) ) class TestSchedulerConfigDefaults: """Tests for scheduler configuration defaults.""" def test_default_retention_hours_is_48(self): """Test default retention_hours is 48.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, ) BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() assert scheduler.config["retention_hours"] == 48 def test_default_cleanup_interval_is_1_hour(self): """Test default cleanup_interval_hours is 1.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, ) BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() assert scheduler.config["cleanup_interval_hours"] == 1 def test_default_max_jitter_is_300_seconds(self): """Test default max_jitter_seconds is 300.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, ) BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() assert scheduler.config["max_jitter_seconds"] == 300 def test_default_max_concurrent_jobs_is_10(self): """Test default max_concurrent_jobs is 10.""" from local_deep_research.scheduler.background import ( BackgroundJobScheduler, ) BackgroundJobScheduler._instance = None scheduler = BackgroundJobScheduler() assert scheduler.config["max_concurrent_jobs"] == 10