from datetime import datetime, timedelta, timezone from app.db.models import ActivityStatus, HealthState, SyncRunStatus def test_create_two_independent_users(db_session, user_repository) -> None: first = 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", ) second = user_repository.create( name="Anna", enabled=True, health_state=HealthState.HEALTHY, mywhoosh_email_enc="mw-2", mywhoosh_password_enc="mw-pw-2", garmin_email_enc="g-2", garmin_password_enc="g-pw-2", ) disabled = user_repository.create( name="Paused", enabled=False, health_state=HealthState.DISABLED, mywhoosh_email_enc="mw-3", mywhoosh_password_enc="mw-pw-3", garmin_email_enc="g-3", garmin_password_enc="g-pw-3", ) assert first.id != second.id assert {u.name for u in user_repository.list_enabled()} == {"Max", "Anna"} assert disabled.id not in {u.id for u in user_repository.list_enabled()} assert {u.name for u in user_repository.list_all()} == {"Max", "Anna", "Paused"} def test_activity_external_id_is_unique_per_user(user_repository, activity_repository) -> None: user = user_repository.create( name="Max", enabled=True, health_state=HealthState.HEALTHY, mywhoosh_email_enc="a", mywhoosh_password_enc="b", garmin_email_enc="c", garmin_password_enc="d", ) created, inserted = activity_repository.get_or_create_discovered( user_id=user.id, mywhoosh_activity_id="mw-123", activity_name="Morning Ride", activity_timestamp=None, ) same, inserted_again = activity_repository.get_or_create_discovered( user_id=user.id, mywhoosh_activity_id="mw-123", activity_name="Morning Ride", activity_timestamp=None, ) assert inserted is True 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 def test_scheduler_settings_get_or_create_seeds_defaults(scheduler_settings_repository) -> None: row = scheduler_settings_repository.get_or_create(default_minutes=7) assert row.day_interval_minutes == 7 assert row.night_interval_minutes == 7 assert row.day_start_hour == 6 assert row.night_start_hour == 22 def test_scheduler_settings_get_or_create_is_idempotent_after_update(scheduler_settings_repository) -> None: row = scheduler_settings_repository.get_or_create(default_minutes=5) scheduler_settings_repository.update(row, day_interval_minutes=15) reloaded = scheduler_settings_repository.get_or_create(default_minutes=5) assert reloaded.day_interval_minutes == 15 def test_scheduler_settings_update_persists_all_fields(scheduler_settings_repository) -> None: row = scheduler_settings_repository.get_or_create(default_minutes=5) scheduler_settings_repository.update( row, day_start_hour=8, night_start_hour=20, day_interval_minutes=10, night_interval_minutes=45, ) reloaded = scheduler_settings_repository.get_or_create(default_minutes=5) assert reloaded.day_start_hour == 8 assert reloaded.night_start_hour == 20 assert reloaded.day_interval_minutes == 10 assert reloaded.night_interval_minutes == 45 def _make_user(repo, name, *, enabled=True, health_state=HealthState.HEALTHY): return repo.create( name=name, enabled=enabled, health_state=health_state, mywhoosh_email_enc=f"mw-{name}", mywhoosh_password_enc=f"mw-pw-{name}", garmin_email_enc=f"g-{name}", garmin_password_enc=f"g-pw-{name}", ) def test_dashboard_summary_counts_riders_total_and_enabled(user_repository) -> None: _make_user(user_repository, "Alex", enabled=True) _make_user(user_repository, "Jamie", enabled=True) _make_user(user_repository, "Paused", enabled=False) summary = user_repository.dashboard_summary(since=datetime.now(timezone.utc) - timedelta(days=7)) assert summary.rider_total == 3 assert summary.rider_enabled == 2 def test_dashboard_summary_counts_action_required_riders(user_repository) -> None: _make_user(user_repository, "Healthy", health_state=HealthState.HEALTHY) _make_user(user_repository, "Blocked", health_state=HealthState.ACTION_REQUIRED) _make_user(user_repository, "AlsoBlocked", health_state=HealthState.ACTION_REQUIRED) summary = user_repository.dashboard_summary(since=datetime.now(timezone.utc) - timedelta(days=7)) assert summary.action_required_count == 2 def test_dashboard_summary_sums_imported_count_within_window_only(user_repository, sync_run_repository) -> None: user = _make_user(user_repository, "Alex") now = datetime.now(timezone.utc) recent_run = sync_run_repository.start(user.id) recent_run.started_at = now - timedelta(days=1) sync_run_repository.finish( recent_run.id, status=SyncRunStatus.SUCCESS, discovered=5, imported=5, skipped=0, failed=0 ) old_run = sync_run_repository.start(user.id) old_run.started_at = now - timedelta(days=30) sync_run_repository.finish( old_run.id, status=SyncRunStatus.SUCCESS, discovered=3, imported=3, skipped=0, failed=0 ) summary = user_repository.dashboard_summary(since=now - timedelta(days=7)) assert summary.imported_recent == 5 def test_dashboard_summary_computes_success_rate_from_finished_runs_in_window( user_repository, sync_run_repository ) -> None: user = _make_user(user_repository, "Alex") now = datetime.now(timezone.utc) for status in (SyncRunStatus.SUCCESS, SyncRunStatus.PARTIAL, SyncRunStatus.FAILED, SyncRunStatus.FAILED): run = sync_run_repository.start(user.id) run.started_at = now - timedelta(hours=1) sync_run_repository.finish(run.id, status=status, discovered=1, imported=0, skipped=0, failed=1) summary = user_repository.dashboard_summary(since=now - timedelta(days=7)) assert summary.success_rate_recent == 50.0 def test_dashboard_summary_success_rate_is_none_without_finished_runs_in_window(user_repository) -> None: summary = user_repository.dashboard_summary(since=datetime.now(timezone.utc) - timedelta(days=7)) assert summary.success_rate_recent is None