Files
xiaoxia-saas/tests/unit/test_generation_task_pending_cleanup.py
xiaoxia 5a5c653d2c
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 1m5s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m13s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 2m8s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 2m17s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m35s
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 2m19s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 3m41s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 3m4s
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
PR Automation / Auto Approve on CI Green (pull_request) Successful in 4m13s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 31s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 2m38s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 6m29s
CI/CD Pipeline / Validate - Code Quality (pull_request) Successful in 7m10s
CI/CD Pipeline / Build Staging API Image (push) Successful in 7m18s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 6m46s
CI/CD Pipeline / Integration Tests (push) Successful in 2m51s
AI Code Review / AI Code Review (pull_request) Successful in 8m5s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 3m5s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 3m9s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 1m2s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m3s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m12s
CI/CD Pipeline / Unit Tests (push) Successful in 13m12s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Successful in 12m19s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 6s
feat(cleanup): pending 任务自动清理,防止队列占满 (#1463)
2026-08-23 12:55:39 +08:00

152 lines
4.9 KiB
Python

"""GenerationTaskRepository - cleanup_stale_pending 超时 pending 清理单元测试。"""
import sys
from datetime import datetime, timedelta, timezone
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "apps" / "api"))
from sqlalchemy import create_engine, text
from sqlalchemy.orm import sessionmaker
from packages.adapters.sqlalchemy_impl.generation_task_repository import (
SQLAlchemyGenerationTaskRepository,
)
from packages.adapters.sqlalchemy_impl.models import Base
from packages.domain import GenerationTask, GenerationTaskStatus
def _repository():
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
session = sessionmaker(bind=engine)()
return SQLAlchemyGenerationTaskRepository(session), session, engine
def _make_task(**kwargs) -> GenerationTask:
defaults = dict(
project_id="proj-1",
asset_library_id="lib-1",
created_by_user_id="user-1",
)
defaults.update(kwargs)
return GenerationTask.create(**defaults)
# ---------------------------------------------------------------------------
# cleanup_stale_pending 基本测试
# ---------------------------------------------------------------------------
def test_cleanup_stale_pending_no_tasks_returns_zero():
"""没有任务时返回 0。"""
repo, _, _ = _repository()
count = repo.cleanup_stale_pending(timeout_minutes=30)
assert count == 0
def test_cleanup_stale_pending_recent_pending_not_cleaned():
"""30 分钟内的 pending 任务不被清理。"""
repo, _, _ = _repository()
task = _make_task()
repo.create(task)
# 刚创建的 pending 任务不应被清理
count = repo.cleanup_stale_pending(timeout_minutes=30)
assert count == 0
assert repo.get(task.id).status == GenerationTaskStatus.PENDING
def test_cleanup_stale_pending_old_pending_marked_failed():
"""超过 30 分钟的 pending 任务被标记为 failed。"""
repo, _, engine = _repository()
task = _make_task()
repo.create(task)
# 手动把 created_at 改到 1 小时前
with engine.connect() as conn:
conn.execute(
text("UPDATE generation_tasks SET created_at = :ts WHERE id = :id"),
{"ts": datetime.now(timezone.utc) - timedelta(hours=1), "id": task.id},
)
conn.commit()
count = repo.cleanup_stale_pending(timeout_minutes=30)
assert count == 1
saved = repo.get(task.id)
assert saved.status == GenerationTaskStatus.FAILED
assert saved.error_message == "pending timeout: auto cleanup"
assert saved.error_info.get("error_type") == "PendingTimeout"
assert "30" in saved.error_info["message"]
assert "failed_at" in saved.error_info
assert saved.completed_at is not None
def test_cleanup_stale_pending_running_not_touched():
"""running 任务不受影响,只清理 pending。"""
repo, _, engine = _repository()
task = _make_task()
repo.create(task)
task.mark_processing()
repo.update(task)
# 回写 created_at 到 1 小时前
with engine.connect() as conn:
conn.execute(
text("UPDATE generation_tasks SET created_at = :ts WHERE id = :id"),
{"ts": datetime.now(timezone.utc) - timedelta(hours=1), "id": task.id},
)
conn.commit()
count = repo.cleanup_stale_pending(timeout_minutes=30)
assert count == 0
assert repo.get(task.id).status == GenerationTaskStatus.RUNNING
def test_cleanup_stale_pending_custom_timeout():
"""自定义超时时间生效。"""
repo, _, engine = _repository()
task = _make_task()
repo.create(task)
# 回写 created_at 到 20 分钟前
with engine.connect() as conn:
conn.execute(
text("UPDATE generation_tasks SET created_at = :ts WHERE id = :id"),
{"ts": datetime.now(timezone.utc) - timedelta(minutes=20), "id": task.id},
)
conn.commit()
# 30 分钟超时:不清理
count_30 = repo.cleanup_stale_pending(timeout_minutes=30)
assert count_30 == 0
# 15 分钟超时:清理
count_15 = repo.cleanup_stale_pending(timeout_minutes=15)
assert count_15 == 1
assert repo.get(task.id).status == GenerationTaskStatus.FAILED
def test_cleanup_stale_pending_multiple():
"""批量清理多个超时的 pending 任务。"""
repo, _, engine = _repository()
tasks = []
for i in range(5):
t = _make_task(project_id=f"proj-{i}")
repo.create(t)
tasks.append(t)
# 全部回写 created_at 到 2 小时前
with engine.connect() as conn:
for t in tasks:
conn.execute(
text("UPDATE generation_tasks SET created_at = :ts WHERE id = :id"),
{"ts": datetime.now(timezone.utc) - timedelta(hours=2), "id": t.id},
)
conn.commit()
count = repo.cleanup_stale_pending(timeout_minutes=30)
assert count == 5
for t in tasks:
assert repo.get(t.id).status == GenerationTaskStatus.FAILED