Files
Profeto/tests/test_p1_e_stale_ingest_jobs.py
T
shangfangjian b30ad56319 fix(P1-E): 启动时标 failed 残留 pending/running ingest_jobs
lifespan 加 _fail_stale_ingest_jobs():UPDATE ingest_jobs SET status=failed
WHERE status IN ('pending','running');异常仅记 warning 不阻断启动。
顺手修复 0021(op.text→字符串)/0023(索引名 leason→league)/0024(type_unique→type_)
三个待执行迁移 bug。

测试 test_p1_e_stale_ingest_jobs(2/2 mock);全量 315 通过。
2026-09-22 08:49:30 +08:00

81 lines
2.4 KiB
Python

"""P1-E 回归测试: 启动时将过期 pending/running ingest_jobs 标 failed。
运行: pytest tests/test_p1_e_stale_ingest_jobs.py -v
(使用 mock session,验证 UPDATE 过滤条件与 SET 值。)
"""
from __future__ import annotations
from unittest.mock import MagicMock, patch
import pytest
import src.api.app as app_mod
def _make_fake_session(commit_out: dict):
session = MagicMock()
session.__aenter__ = MagicMock(return_value=session)
session.__aexit__ = MagicMock(return_value=False)
async def execute(stmt):
commit_out["sql"] = str(stmt)
result = MagicMock()
result.rowcount = 2
return result
async def commit():
commit_out["commit"] = commit_out.get("commit", 0) + 1
session.execute = execute
session.commit = commit
return session
@pytest.mark.asyncio
async def test_fail_stale_updates_pending_and_running_only():
"""P1-E: UPDATE 必须过滤 status IN ('pending','running'),SET status=failed。"""
out = {}
fake = _make_fake_session(out)
class FakeSessionLocal:
def __init__(self):
self._s = fake
async def __aenter__(self):
return self._s
async def __aexit__(self, *a):
return None
# patch src.db.base.AsyncSessionLocal(函数内 from-import 每次调用从此取)
import src.db.base as _base
with patch.object(_base, "AsyncSessionLocal", FakeSessionLocal):
await app_mod._fail_stale_ingest_jobs()
sql = out.get("sql", "").lower()
# WHERE 子句过滤 status IN (绑定参数,SQLAlchemy 用 __[postcompile_x] 占位)
assert "status in" in sql, f"SQL 缺少 status IN 过滤: {sql}"
# SET status=failed
assert "status=:status" in sql or "status=" in sql, f"SQL 缺少 status 更新: {sql}"
# 应清理成功(commit 被调用)
assert out.get("commit", 0) >= 1, f"应已提交,实际 commit 调用: {out}"
@pytest.mark.asyncio
async def test_fail_stale_does_not_raise_on_db_error():
"""P1-E: DB 异常不得阻断启动(仅记 warning)。"""
class BoomSessionLocal:
async def __aenter__(self):
raise RuntimeError("DB down")
async def __aexit__(self, *a):
return None
import src.db.base as _base
with patch.object(_base, "AsyncSessionLocal", BoomSessionLocal):
# 不应抛异常
await app_mod._fail_stale_ingest_jobs()
assert True