log
This commit is contained in:
@@ -9,7 +9,7 @@ from sqlalchemy.pool import StaticPool
|
||||
|
||||
from app.config import Settings
|
||||
from app.db.models import Activity, Base, HealthState
|
||||
from app.db.repositories import ActivityRepository, SyncRunRepository, UserRepository
|
||||
from app.db.repositories import ActivityRepository, SyncRunRepository, SystemLogRepository, UserRepository
|
||||
from app.main import create_app
|
||||
|
||||
|
||||
@@ -44,6 +44,11 @@ def sync_run_repository(db_session: Session) -> SyncRunRepository:
|
||||
return SyncRunRepository(db_session)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def system_log_repository(db_session: Session) -> SystemLogRepository:
|
||||
return SystemLogRepository(db_session)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app(tmp_path: Path):
|
||||
settings = Settings(
|
||||
|
||||
@@ -64,3 +64,32 @@ def test_activity_external_id_is_unique_per_user(user_repository, activity_repos
|
||||
assert inserted_again is False
|
||||
assert created.id == same.id
|
||||
assert same.status == ActivityStatus.DISCOVERED
|
||||
|
||||
|
||||
def test_system_log_lists_most_recent_first(system_log_repository, user_repository) -> None:
|
||||
user = user_repository.create(
|
||||
name="Max",
|
||||
enabled=True,
|
||||
health_state=HealthState.HEALTHY,
|
||||
mywhoosh_email_enc="mw-1",
|
||||
mywhoosh_password_enc="mw-pw-1",
|
||||
garmin_email_enc="g-1",
|
||||
garmin_password_enc="g-pw-1",
|
||||
)
|
||||
system_log_repository.add(source="email_notification", message="first failure", user_id=user.id)
|
||||
system_log_repository.add(source="email_notification", message="second failure", user_id=user.id)
|
||||
|
||||
entries = system_log_repository.list_recent()
|
||||
|
||||
assert [entry.message for entry in entries] == ["second failure", "first failure"]
|
||||
assert entries[0].source == "email_notification"
|
||||
assert entries[0].user_id == user.id
|
||||
|
||||
|
||||
def test_system_log_respects_limit(system_log_repository) -> None:
|
||||
for i in range(5):
|
||||
system_log_repository.add(source="test", message=f"entry {i}")
|
||||
|
||||
entries = system_log_repository.list_recent(limit=2)
|
||||
|
||||
assert len(entries) == 2
|
||||
|
||||
@@ -4,7 +4,7 @@ import pytest
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.db.models import ActivityStatus, HealthState, SyncRun, SyncRunStatus, SyncUser
|
||||
from app.db.repositories import UserRepository
|
||||
from app.db.repositories import SystemLogRepository, UserRepository
|
||||
from app.garmin.uploader import UploadResult
|
||||
from app.mywhoosh.client import MyWhooshDeviceConflictError
|
||||
from app.mywhoosh.models import MyWhooshActivity
|
||||
@@ -305,6 +305,44 @@ async def test_does_not_renotify_for_unresolved_unchanged_reason(session_factory
|
||||
assert len(notifier.sent) == 1
|
||||
|
||||
|
||||
class FailingNotifier:
|
||||
def send(self, *, to_address: str, subject: str, body: str) -> None:
|
||||
raise RuntimeError("SMTP connection refused")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_email_send_failure_is_recorded_in_system_log_and_does_not_break_sync(
|
||||
session_factory, cipher, settings
|
||||
) -> None:
|
||||
with session_factory() as session:
|
||||
user = _create_user(session, cipher)
|
||||
user.notify_email_enabled = True
|
||||
user.notification_email = "alerts@example.com"
|
||||
session.commit()
|
||||
user_id = user.id
|
||||
|
||||
manager = SyncManager(
|
||||
session_factory=session_factory,
|
||||
credential_cipher=cipher,
|
||||
settings=settings,
|
||||
mywhoosh_factory=lambda token_store: DeviceConflictMyWhooshClient(),
|
||||
garmin_factory=lambda email, password, tokenstore: FakeGarminUploader(),
|
||||
fit_converter=FakeFitConverter(),
|
||||
notifier=FailingNotifier(),
|
||||
)
|
||||
|
||||
outcome = await manager.sync_user(user_id)
|
||||
assert outcome.status == "failed"
|
||||
|
||||
with session_factory() as session:
|
||||
entries = SystemLogRepository(session).list_recent()
|
||||
assert len(entries) == 1
|
||||
assert entries[0].source == "email_notification"
|
||||
assert entries[0].user_id == user_id
|
||||
assert "SMTP connection refused" in entries[0].message
|
||||
assert "alerts@example.com" in entries[0].message
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mfa_code_reaches_real_garmin_uploader_via_sync_manager(
|
||||
session_factory, cipher, settings, seeded_user: SyncUser
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from app.db.repositories import SystemLogRepository
|
||||
|
||||
|
||||
def test_manual_sync_calls_shared_manager(authenticated_client, fake_sync_manager) -> None:
|
||||
response = authenticated_client.post(
|
||||
@@ -60,3 +62,30 @@ def test_system_page_shows_scheduler_state(app, authenticated_client) -> None:
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user