71 lines
1.9 KiB
Python
71 lines
1.9 KiB
Python
|
|
"""Push retry job outcome classification."""
|
||
|
|
|
||
|
|
from datetime import datetime, timezone
|
||
|
|
from types import SimpleNamespace
|
||
|
|
|
||
|
|
from server.background.retry_runner import apply_push_retry_outcome
|
||
|
|
|
||
|
|
|
||
|
|
def _job(**kwargs):
|
||
|
|
defaults = {
|
||
|
|
"retry_count": 1,
|
||
|
|
"max_retries": 3,
|
||
|
|
"status": "pending",
|
||
|
|
"last_error": None,
|
||
|
|
"next_retry_at": None,
|
||
|
|
}
|
||
|
|
defaults.update(kwargs)
|
||
|
|
return SimpleNamespace(**defaults)
|
||
|
|
|
||
|
|
|
||
|
|
def test_success_marks_completed():
|
||
|
|
job = _job(retry_count=1)
|
||
|
|
apply_push_retry_outcome(
|
||
|
|
job,
|
||
|
|
{"total": 1, "completed": 1, "failed": 0, "cancelled": 0},
|
||
|
|
now=datetime.now(timezone.utc),
|
||
|
|
)
|
||
|
|
assert job.status == "completed"
|
||
|
|
|
||
|
|
|
||
|
|
def test_cancelled_only_marks_failed_not_pending():
|
||
|
|
job = _job(retry_count=1)
|
||
|
|
apply_push_retry_outcome(
|
||
|
|
job,
|
||
|
|
{"total": 1, "completed": 0, "failed": 0, "cancelled": 1},
|
||
|
|
now=datetime.now(timezone.utc),
|
||
|
|
)
|
||
|
|
assert job.status == "failed"
|
||
|
|
assert "取消" in (job.last_error or "")
|
||
|
|
|
||
|
|
|
||
|
|
def test_sync_error_schedules_backoff_when_under_max():
|
||
|
|
job = _job(retry_count=1, max_retries=3)
|
||
|
|
now = datetime.now(timezone.utc)
|
||
|
|
apply_push_retry_outcome(job, {"error": "no source", "total": 0}, now=now)
|
||
|
|
assert job.status == "pending"
|
||
|
|
assert job.next_retry_at is not None
|
||
|
|
assert job.next_retry_at > now
|
||
|
|
|
||
|
|
|
||
|
|
def test_exhausted_retries_marks_failed():
|
||
|
|
job = _job(retry_count=3, max_retries=3)
|
||
|
|
apply_push_retry_outcome(
|
||
|
|
job,
|
||
|
|
{"total": 1, "completed": 0, "failed": 1, "cancelled": 0},
|
||
|
|
now=datetime.now(timezone.utc),
|
||
|
|
)
|
||
|
|
assert job.status == "failed"
|
||
|
|
|
||
|
|
|
||
|
|
def test_running_job_returns_to_pending_after_retryable_failure():
|
||
|
|
job = _job(retry_count=1, status="running")
|
||
|
|
now = datetime.now(timezone.utc)
|
||
|
|
apply_push_retry_outcome(
|
||
|
|
job,
|
||
|
|
{"total": 1, "completed": 0, "failed": 1, "cancelled": 0},
|
||
|
|
now=now,
|
||
|
|
)
|
||
|
|
assert job.status == "pending"
|
||
|
|
assert job.next_retry_at > now
|