from app.db.models import ActivityStatus, SyncRunStatus def test_failure_retains_last_completed_stage(activity_repository, seeded_activity) -> None: activity_repository.mark_downloaded(seeded_activity.id, "/data/activities/1/a/source.fit") activity_repository.mark_failed(seeded_activity.id, "Garmin timeout", retryable=True) activity = activity_repository.get(seeded_activity.id) assert activity.status == ActivityStatus.FAILED assert activity.last_completed_stage == ActivityStatus.DOWNLOADED assert activity.retryable is True def test_converted_activity_is_pending_until_terminal(activity_repository, seeded_activity) -> None: activity_repository.mark_converted(seeded_activity.id, "/data/activities/1/a/converted.fit") ids = [item.id for item in activity_repository.list_pending_for_user(seeded_activity.user_id)] assert seeded_activity.id in ids def test_list_pending_excludes_imported(activity_repository, seeded_activity) -> None: activity_repository.mark_downloaded(seeded_activity.id, "/data/activities/1/a/source.fit") activity_repository.mark_converted(seeded_activity.id, "/data/activities/1/a/converted.fit") activity_repository.mark_imported(seeded_activity.id, "garmin-123") ids = [item.id for item in activity_repository.list_pending_for_user(seeded_activity.user_id)] assert seeded_activity.id not in ids def test_list_pending_excludes_duplicate(activity_repository, seeded_activity) -> None: activity_repository.mark_downloaded(seeded_activity.id, "/data/activities/1/a/source.fit") activity_repository.mark_duplicate(seeded_activity.id) ids = [item.id for item in activity_repository.list_pending_for_user(seeded_activity.user_id)] assert seeded_activity.id not in ids def test_list_pending_excludes_non_retryable_failed(activity_repository, seeded_activity) -> None: activity_repository.mark_downloaded(seeded_activity.id, "/data/activities/1/a/source.fit") activity_repository.mark_failed(seeded_activity.id, "Cannot retry", retryable=False) ids = [item.id for item in activity_repository.list_pending_for_user(seeded_activity.user_id)] assert seeded_activity.id not in ids def test_list_pending_includes_retryable_failed(activity_repository, seeded_activity) -> None: activity_repository.mark_downloaded(seeded_activity.id, "/data/activities/1/a/source.fit") activity_repository.mark_failed(seeded_activity.id, "Temporary error", retryable=True) ids = [item.id for item in activity_repository.list_pending_for_user(seeded_activity.user_id)] assert seeded_activity.id in ids def test_sync_run_start_and_finish(sync_run_repository, user_repository) -> None: user = user_repository.create( name="Test User", enabled=True, health_state="healthy", mywhoosh_email_enc="test@example.com", mywhoosh_password_enc="password", garmin_email_enc="test@garmin.com", garmin_password_enc="garmin_password", ) sync_run = sync_run_repository.start(user.id) assert sync_run.status == SyncRunStatus.RUNNING assert sync_run.discovered_count == 0 assert sync_run.imported_count == 0 assert sync_run.skipped_count == 0 assert sync_run.failed_count == 0 finished = sync_run_repository.finish( sync_run.id, status=SyncRunStatus.SUCCESS, discovered=5, imported=3, skipped=1, failed=1, ) assert finished.status == SyncRunStatus.SUCCESS assert finished.discovered_count == 5 assert finished.imported_count == 3 assert finished.skipped_count == 1 assert finished.failed_count == 1 assert finished.finished_at is not None # Reload from DB to verify persisted reloaded = sync_run_repository.get(sync_run.id) assert reloaded.status == SyncRunStatus.SUCCESS assert reloaded.discovered_count == 5 assert reloaded.imported_count == 3 def test_sync_run_finish_with_error(sync_run_repository, user_repository) -> None: user = user_repository.create( name="Test User", enabled=True, health_state="healthy", mywhoosh_email_enc="test@example.com", mywhoosh_password_enc="password", garmin_email_enc="test@garmin.com", garmin_password_enc="garmin_password", ) sync_run = sync_run_repository.start(user.id) error_msg = "Connection timeout" finished = sync_run_repository.finish( sync_run.id, status=SyncRunStatus.FAILED, discovered=0, imported=0, skipped=0, failed=0, summary_error=error_msg, ) assert finished.summary_error == error_msg # Verify truncation works long_error = "x" * 5000 finished_long = sync_run_repository.finish( sync_run.id, status=SyncRunStatus.FAILED, discovered=0, imported=0, skipped=0, failed=0, summary_error=long_error, ) assert len(finished_long.summary_error) == 2000 assert finished_long.summary_error == long_error[:2000]