mirror of
https://github.com/elisiariocouto/leggen.git
synced 2025-12-11 17:22:18 +00:00
198 lines
7.2 KiB
Python
198 lines
7.2 KiB
Python
"""Tests for background scheduler."""
|
|
|
|
from datetime import datetime
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from leggen.background.scheduler import BackgroundScheduler
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestBackgroundScheduler:
|
|
"""Test background job scheduler."""
|
|
|
|
@pytest.fixture
|
|
def mock_config(self):
|
|
"""Mock configuration for scheduler tests."""
|
|
return {"sync": {"enabled": True, "hour": 3, "minute": 0, "cron": None}}
|
|
|
|
@pytest.fixture
|
|
def scheduler(self):
|
|
"""Create scheduler instance for testing."""
|
|
with (
|
|
patch("leggen.background.scheduler.SyncService"),
|
|
patch("leggen.background.scheduler.config") as mock_config,
|
|
):
|
|
mock_config.scheduler_config = {
|
|
"sync": {"enabled": True, "hour": 3, "minute": 0}
|
|
}
|
|
|
|
# Create scheduler and replace its AsyncIO scheduler with a mock
|
|
scheduler = BackgroundScheduler()
|
|
mock_scheduler = MagicMock()
|
|
mock_scheduler.running = False
|
|
mock_scheduler.get_jobs.return_value = []
|
|
scheduler.scheduler = mock_scheduler
|
|
return scheduler
|
|
|
|
def test_scheduler_start_default_config(self, scheduler, mock_config):
|
|
"""Test starting scheduler with default configuration."""
|
|
with patch("leggen.utils.config.config") as mock_config_obj:
|
|
mock_config_obj.scheduler_config = mock_config
|
|
|
|
# Mock the job that gets added
|
|
mock_job = MagicMock()
|
|
mock_job.id = "daily_sync"
|
|
scheduler.scheduler.get_jobs.return_value = [mock_job]
|
|
|
|
scheduler.start()
|
|
|
|
# Verify scheduler.start() was called
|
|
scheduler.scheduler.start.assert_called_once()
|
|
# Verify add_job was called
|
|
scheduler.scheduler.add_job.assert_called_once()
|
|
|
|
def test_scheduler_start_disabled(self, scheduler):
|
|
"""Test scheduler behavior when sync is disabled."""
|
|
disabled_config = {"sync": {"enabled": False}}
|
|
|
|
with (
|
|
patch.object(scheduler, "scheduler") as mock_scheduler,
|
|
patch("leggen.background.scheduler.config") as mock_config_obj,
|
|
):
|
|
mock_config_obj.scheduler_config = disabled_config
|
|
mock_scheduler.running = False
|
|
|
|
scheduler.start()
|
|
|
|
# Verify scheduler.start() was called
|
|
mock_scheduler.start.assert_called_once()
|
|
# Verify add_job was NOT called for disabled sync
|
|
mock_scheduler.add_job.assert_not_called()
|
|
|
|
def test_scheduler_start_with_cron(self, scheduler):
|
|
"""Test starting scheduler with custom cron expression."""
|
|
cron_config = {
|
|
"sync": {
|
|
"enabled": True,
|
|
"cron": "0 6 * * 1-5", # 6 AM on weekdays
|
|
}
|
|
}
|
|
|
|
with patch("leggen.utils.config.config") as mock_config_obj:
|
|
mock_config_obj.scheduler_config = cron_config
|
|
|
|
scheduler.start()
|
|
|
|
# Verify scheduler.start() and add_job were called
|
|
scheduler.scheduler.start.assert_called_once()
|
|
scheduler.scheduler.add_job.assert_called_once()
|
|
# Verify job was added with correct ID
|
|
call_args = scheduler.scheduler.add_job.call_args
|
|
assert call_args.kwargs["id"] == "daily_sync"
|
|
|
|
def test_scheduler_start_invalid_cron(self, scheduler):
|
|
"""Test handling of invalid cron expressions."""
|
|
invalid_cron_config = {"sync": {"enabled": True, "cron": "invalid cron"}}
|
|
|
|
with (
|
|
patch.object(scheduler, "scheduler") as mock_scheduler,
|
|
patch("leggen.background.scheduler.config") as mock_config_obj,
|
|
):
|
|
mock_config_obj.scheduler_config = invalid_cron_config
|
|
mock_scheduler.running = False
|
|
|
|
scheduler.start()
|
|
|
|
# With invalid cron, scheduler.start() should not be called due to early return
|
|
# and add_job should not be called
|
|
mock_scheduler.start.assert_not_called()
|
|
mock_scheduler.add_job.assert_not_called()
|
|
|
|
def test_scheduler_shutdown(self, scheduler):
|
|
"""Test scheduler shutdown."""
|
|
scheduler.scheduler.running = True
|
|
|
|
scheduler.shutdown()
|
|
|
|
scheduler.scheduler.shutdown.assert_called_once()
|
|
|
|
def test_reschedule_sync(self, scheduler, mock_config):
|
|
"""Test rescheduling sync job."""
|
|
scheduler.scheduler.running = True
|
|
|
|
# Reschedule with new config
|
|
new_config = {"enabled": True, "hour": 6, "minute": 30}
|
|
|
|
scheduler.reschedule_sync(new_config)
|
|
|
|
# Verify remove_job and add_job were called
|
|
scheduler.scheduler.remove_job.assert_called_once_with("daily_sync")
|
|
scheduler.scheduler.add_job.assert_called_once()
|
|
|
|
def test_reschedule_sync_disable(self, scheduler, mock_config):
|
|
"""Test disabling sync via reschedule."""
|
|
scheduler.scheduler.running = True
|
|
|
|
# Disable sync
|
|
disabled_config = {"enabled": False}
|
|
scheduler.reschedule_sync(disabled_config)
|
|
|
|
# Job should be removed but not re-added
|
|
scheduler.scheduler.remove_job.assert_called_once_with("daily_sync")
|
|
scheduler.scheduler.add_job.assert_not_called()
|
|
|
|
def test_get_next_sync_time(self, scheduler, mock_config):
|
|
"""Test getting next scheduled sync time."""
|
|
mock_job = MagicMock()
|
|
mock_job.next_run_time = datetime(2025, 9, 2, 3, 0)
|
|
scheduler.scheduler.get_job.return_value = mock_job
|
|
|
|
next_time = scheduler.get_next_sync_time()
|
|
|
|
assert next_time is not None
|
|
assert isinstance(next_time, datetime)
|
|
scheduler.scheduler.get_job.assert_called_once_with("daily_sync")
|
|
|
|
def test_get_next_sync_time_no_job(self, scheduler):
|
|
"""Test getting next sync time when no job is scheduled."""
|
|
scheduler.scheduler.get_job.return_value = None
|
|
|
|
next_time = scheduler.get_next_sync_time()
|
|
|
|
assert next_time is None
|
|
scheduler.scheduler.get_job.assert_called_once_with("daily_sync")
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_run_sync_success(self, scheduler):
|
|
"""Test successful sync job execution."""
|
|
mock_sync_service = AsyncMock()
|
|
scheduler.sync_service = mock_sync_service
|
|
|
|
await scheduler._run_sync()
|
|
|
|
mock_sync_service.sync_all_accounts.assert_called_once()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_run_sync_failure(self, scheduler):
|
|
"""Test sync job execution with failure."""
|
|
mock_sync_service = AsyncMock()
|
|
mock_sync_service.sync_all_accounts.side_effect = Exception("Sync failed")
|
|
scheduler.sync_service = mock_sync_service
|
|
|
|
# Should not raise exception, just log error
|
|
await scheduler._run_sync()
|
|
|
|
mock_sync_service.sync_all_accounts.assert_called_once()
|
|
|
|
def test_scheduler_job_max_instances(self, scheduler, mock_config):
|
|
"""Test that sync jobs have max_instances=1."""
|
|
with patch("leggen.utils.config.config") as mock_config_obj:
|
|
mock_config_obj.scheduler_config = mock_config
|
|
scheduler.start()
|
|
|
|
# Verify add_job was called with max_instances=1
|
|
call_args = scheduler.scheduler.add_job.call_args
|
|
assert call_args.kwargs["max_instances"] == 1
|