ddf861b02f
CI Build & Deploy Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Staging API Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Production API Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Production Web Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Deploy Production (pull_request) Has been skipped
CI Build & Deploy Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI Build & Deploy Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI Build & Deploy Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI Build & Deploy Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 24s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 55s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 56s
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Failing after 1m29s
Auto Merge CI PRs / Auto Merge on CI Green + Approved (pull_request) Successful in 2m5s
AI Code Review / AI Code Review (pull_request) Successful in 2m32s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m36s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 3m8s
Auto Approve CI PRs / Auto Approve on CI Green (pull_request) Successful in 3m42s
- GenerationTaskModel 新增 updated_at 字段(onupdate自动刷新) - 领域模型 GenerationTask 新增 updated_at - repo 新增 cleanup_stale_running 方法:running且updated_at超10分钟→failed - worker启动时(on_worker_ready signal)自动清理 - 任务调度时(generate_video入口)也清理一次 - error_message: 任务执行中断(worker重启/超时) - 配套5个单元测试
127 lines
4.0 KiB
Python
Executable File
127 lines
4.0 KiB
Python
Executable File
"""GenerationTaskRepository - cleanup_stale_running 孤儿任务清理单元测试。"""
|
|
|
|
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)
|
|
|
|
|
|
def test_cleanup_stale_running_no_tasks_returns_zero():
|
|
"""没有任务时返回0。"""
|
|
repo, _, _ = _repository()
|
|
count = repo.cleanup_stale_running(timeout_minutes=10)
|
|
assert count == 0
|
|
|
|
|
|
def test_cleanup_stale_running_recent_running_not_cleaned():
|
|
"""刚启动的 running 任务不清理。"""
|
|
repo, _, _ = _repository()
|
|
task = _make_task()
|
|
repo.create(task)
|
|
task.mark_processing()
|
|
repo.update(task)
|
|
|
|
count = repo.cleanup_stale_running(timeout_minutes=10)
|
|
assert count == 0
|
|
assert repo.get(task.id).status == GenerationTaskStatus.RUNNING
|
|
|
|
|
|
def test_cleanup_stale_running_old_running_marked_failed():
|
|
"""超时的 running 任务被标记为 failed。"""
|
|
repo, _, engine = _repository()
|
|
task = _make_task()
|
|
repo.create(task)
|
|
task.mark_processing()
|
|
repo.update(task)
|
|
|
|
# 手动把 updated_at 改到 30 分钟前
|
|
with engine.connect() as conn:
|
|
conn.execute(
|
|
text("UPDATE generation_tasks SET updated_at = :ts WHERE id = :id"),
|
|
{"ts": datetime.now(timezone.utc) - timedelta(minutes=30), "id": task.id},
|
|
)
|
|
conn.commit()
|
|
|
|
count = repo.cleanup_stale_running(timeout_minutes=10)
|
|
assert count == 1
|
|
|
|
saved = repo.get(task.id)
|
|
assert saved.status == GenerationTaskStatus.FAILED
|
|
assert "任务执行中断" in saved.error_message
|
|
assert saved.error_info.get("error_type") == "WorkerInterrupted"
|
|
assert saved.completed_at is not None
|
|
|
|
|
|
def test_cleanup_stale_running_pending_not_touched():
|
|
"""pending 状态即使超时也不清理。"""
|
|
repo, _, engine = _repository()
|
|
task = _make_task()
|
|
repo.create(task)
|
|
|
|
with engine.connect() as conn:
|
|
conn.execute(
|
|
text("UPDATE generation_tasks SET updated_at = :ts WHERE id = :id"),
|
|
{"ts": datetime.now(timezone.utc) - timedelta(hours=1), "id": task.id},
|
|
)
|
|
conn.commit()
|
|
|
|
count = repo.cleanup_stale_running(timeout_minutes=10)
|
|
assert count == 0
|
|
assert repo.get(task.id).status == GenerationTaskStatus.PENDING
|
|
|
|
|
|
def test_cleanup_stale_running_multiple_orphans():
|
|
"""多个超时 running 任务全部清理。"""
|
|
repo, _, engine = _repository()
|
|
|
|
tasks = []
|
|
for i in range(3):
|
|
t = _make_task(project_id=f"proj-{i}")
|
|
repo.create(t)
|
|
t.mark_processing()
|
|
repo.update(t)
|
|
tasks.append(t)
|
|
|
|
# 前两个超时,第三个是新的
|
|
with engine.connect() as conn:
|
|
for t in tasks[:2]:
|
|
conn.execute(
|
|
text("UPDATE generation_tasks SET updated_at = :ts WHERE id = :id"),
|
|
{"ts": datetime.now(timezone.utc) - timedelta(minutes=20), "id": t.id},
|
|
)
|
|
conn.commit()
|
|
|
|
count = repo.cleanup_stale_running(timeout_minutes=10)
|
|
assert count == 2
|
|
|
|
assert repo.get(tasks[0].id).status == GenerationTaskStatus.FAILED
|
|
assert repo.get(tasks[1].id).status == GenerationTaskStatus.FAILED
|
|
assert repo.get(tasks[2].id).status == GenerationTaskStatus.RUNNING
|