204 lines
6.5 KiB
Python
204 lines
6.5 KiB
Python
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
|