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 通过。
81 lines
2.4 KiB
Python
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
|