from fastapi.testclient import TestClient from app.db.repositories import SchedulerSettingsRepository, SystemLogRepository def test_manual_sync_calls_shared_manager(authenticated_client, fake_sync_manager) -> None: response = authenticated_client.post( "/users/1/sync", data={"csrf_token": authenticated_client.csrf_token}, ) assert response.status_code == 200 assert fake_sync_manager.user_calls == [1] def test_manual_sync_reports_already_running(authenticated_client, fake_sync_manager) -> None: fake_sync_manager.raise_already_running = True response = authenticated_client.post( "/users/1/sync", data={"csrf_token": authenticated_client.csrf_token}, ) assert response.status_code == 409 assert "already running" in response.text.lower() def test_sync_all_calls_shared_manager(authenticated_client, fake_sync_manager) -> None: response = authenticated_client.post( "/sync-all", data={"csrf_token": authenticated_client.csrf_token}, ) assert response.status_code == 200 assert fake_sync_manager.all_calls == 1 def test_manual_sync_requires_admin(client: TestClient) -> None: response = client.post( "/users/1/sync", data={"csrf_token": "whatever"}, follow_redirects=False, ) assert response.status_code == 303 assert response.headers["location"] == "/login" def test_manual_sync_rejects_invalid_csrf(authenticated_client) -> None: response = authenticated_client.post( "/users/1/sync", data={"csrf_token": "invalid-token"}, ) assert response.status_code == 403 def test_system_page_shows_scheduler_state(app, authenticated_client) -> None: class FakeScheduler: def __init__(self) -> None: self.last_tick = None self.next_tick = None app.state.scheduler = FakeScheduler() response = authenticated_client.get("/system") assert response.status_code == 200 assert "1.0.0" in response.text assert "5" in response.text # sync_interval_minutes assert "0" in response.text # user_count / activity_count fresh DB class _FakeScheduler: def __init__(self) -> None: self.last_tick = None self.next_tick = None def test_system_page_shows_empty_log_state(app, authenticated_client) -> None: app.state.scheduler = _FakeScheduler() response = authenticated_client.get("/system") assert response.status_code == 200 assert "No system log entries" in response.text def test_system_page_shows_recorded_log_entries(app, authenticated_client) -> None: app.state.scheduler = _FakeScheduler() with app.state.session_factory() as session: SystemLogRepository(session).add( source="email_notification", message="Failed to email alerts@example.com: SMTP timeout" ) response = authenticated_client.get("/system") assert response.status_code == 200 assert "email_notification" in response.text assert "SMTP timeout" in response.text def _extract_csrf(html: str) -> str: marker = 'name="csrf_token" value="' start = html.index(marker) + len(marker) end = html.index('"', start) return html[start:end] def test_scheduler_settings_page_prefills_defaults(app, authenticated_client) -> None: app.state.scheduler = _FakeScheduler() response = authenticated_client.get("/system") assert response.status_code == 200 assert 'id="day_start_hour"' in response.text assert 'value="6"' in response.text assert 'value="22"' in response.text def test_update_scheduler_settings_persists_and_takes_effect_next_tick(app, authenticated_client) -> None: app.state.scheduler = _FakeScheduler() page = authenticated_client.get("/system") csrf = _extract_csrf(page.text) response = authenticated_client.post( "/system/scheduler-settings", data={ "csrf_token": csrf, "day_start_hour": "8", "night_start_hour": "20", "day_interval_minutes": "3", "night_interval_minutes": "45", }, follow_redirects=False, ) assert response.status_code == 303 assert response.headers["location"] == "/system" with app.state.session_factory() as session: row = SchedulerSettingsRepository(session).get_or_create(default_minutes=5) assert row.day_start_hour == 8 assert row.night_start_hour == 20 assert row.day_interval_minutes == 3 assert row.night_interval_minutes == 45 def test_update_scheduler_settings_rejects_out_of_range_hour(app, authenticated_client) -> None: app.state.scheduler = _FakeScheduler() page = authenticated_client.get("/system") csrf = _extract_csrf(page.text) response = authenticated_client.post( "/system/scheduler-settings", data={ "csrf_token": csrf, "day_start_hour": "24", "night_start_hour": "22", "day_interval_minutes": "5", "night_interval_minutes": "5", }, ) assert response.status_code == 400 def test_update_scheduler_settings_rejects_non_positive_interval(app, authenticated_client) -> None: app.state.scheduler = _FakeScheduler() page = authenticated_client.get("/system") csrf = _extract_csrf(page.text) response = authenticated_client.post( "/system/scheduler-settings", data={ "csrf_token": csrf, "day_start_hour": "6", "night_start_hour": "22", "day_interval_minutes": "0", "night_interval_minutes": "5", }, ) assert response.status_code == 400 def test_update_scheduler_settings_requires_admin(client: TestClient) -> None: response = client.post( "/system/scheduler-settings", data={ "csrf_token": "whatever", "day_start_hour": "6", "night_start_hour": "22", "day_interval_minutes": "5", "night_interval_minutes": "5", }, follow_redirects=False, ) assert response.status_code == 303 assert response.headers["location"] == "/login" def test_update_scheduler_settings_rejects_invalid_csrf(app, authenticated_client) -> None: app.state.scheduler = _FakeScheduler() response = authenticated_client.post( "/system/scheduler-settings", data={ "csrf_token": "invalid-token", "day_start_hour": "6", "night_start_hour": "22", "day_interval_minutes": "5", "night_interval_minutes": "5", }, ) assert response.status_code == 403