diff --git a/tests/test_automation_ck_relogin.py b/tests/test_automation_ck_relogin.py index 8baca93..4339027 100644 --- a/tests/test_automation_ck_relogin.py +++ b/tests/test_automation_ck_relogin.py @@ -14,10 +14,7 @@ from datetime import UTC, datetime, timedelta -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker - -from web.backend.database import Base +from tests.support.db import get_test_engine, make_test_session from web.backend.models import Account, AutomationJob, LoginTask, User from web.backend.services.automation import register_default_jobs, reset_registry from web.backend.services.automation.jobs import ck_relogin @@ -25,9 +22,8 @@ from web.backend.services.automation.jobs import ck_relogin class _Base: def setup_method(self): - self.engine = create_engine("sqlite://") - Base.metadata.create_all(self.engine) - self.session = sessionmaker(bind=self.engine)() + self.engine = get_test_engine() + self.session = make_test_session(self.engine) # 作业注册表是模块级全局,测试之间会互相污染;重置成生产启动时的状态, # 否则 list_jobs() 里的 ensure_jobs() 建不出作业行。 reset_registry() @@ -401,3 +397,19 @@ class TestLinkageWithHealthJob(_Base): assert payload["pending_relogin"] == 1 assert any(job["key"] == ck_relogin.JOB_KEY for job in payload["jobs"]) assert ready.id is not None + + def test_pending_count_is_not_capped_by_batch_size(self): + """积压数不能被批次上限截断。 + + 一开始复用了作业的候选查询来取 count,而那个查询带 LIMIT batch_size, + 页面上于是恒显示 10(批次上限),完全看不出真实积压 —— 线上就是这样 + 显示了「待重登 10」而实际有一千多。 + """ + from web.backend.routers import automation as api + + for index in range(25): + self.make_task(f"acc{index}") + payload = api.list_jobs(db=self.session, current=self.admin) + relogin = next(j for j in payload["jobs"] if j["key"] == ck_relogin.JOB_KEY) + assert relogin["batch_size"] == 10 + assert payload["pending_relogin"] == 25 # 不被 limit 10 截断 diff --git a/web/backend/routers/automation.py b/web/backend/routers/automation.py index 10ac038..3dc056b 100644 --- a/web/backend/routers/automation.py +++ b/web/backend/routers/automation.py @@ -89,14 +89,9 @@ def list_jobs( # 巡检与重登是两个作业,但通过数据库联动:巡检把行标成 invalid,重登作业的 # 候选条件正是这个字段。这里把「当前有多少条等着重登」一并返回,页面才能把 # 这层上下游关系显示出来,而不是让人以为两个开关互不相干。 - from ..services.automation.jobs.ck_relogin import build_select_query + from ..services.automation.jobs.ck_relogin import count_pending_relogin - pending_relogin = 0 - relogin_job = next((job for job in jobs if job.key == "ck_relogin"), None) - if relogin_job is not None: - pending_relogin = build_select_query( - db, relogin_job, datetime.now(UTC), max_attempts=10**6 - ).count() + pending_relogin = count_pending_relogin(db) return { "scheduler": scheduler_status(), diff --git a/web/backend/services/automation/jobs/ck_relogin.py b/web/backend/services/automation/jobs/ck_relogin.py index b399295..b95c9ee 100644 --- a/web/backend/services/automation/jobs/ck_relogin.py +++ b/web/backend/services/automation/jobs/ck_relogin.py @@ -114,6 +114,30 @@ def build_select_query( ).limit(max(int(job.batch_size or 0), 1)) +def count_pending_relogin(db: Session, *, max_attempts: int = 2) -> int: + """待自动重登的**积压**条数(给页面显示用)。 + + 刻意不复用 ``build_select_query``:那个查询带 ``LIMIT batch_size``,用它取 + count 只会得到批次上限(页面上显示恒等于 10),完全看不出积压。这里也**不 + 按退避窗口过滤** —— 退避是「什么时候试」的细节,不影响「还有多少要处理」。 + """ + return int( + db.query(func.count(LoginTask.id)) + .join(Account, LoginTask.account_id == Account.id) + .filter(LoginTask.ck_check_status == "invalid") + .filter(cookie_holding_filter()) + .filter(Account.auto_managed.is_(True)) + .filter(LoginTask.relogin_attempts < max_attempts) + .filter( + func.coalesce(LoginTask.relogin_failure_kind, "").notin_( + tuple(MANUAL_KINDS) + ) + ) + .scalar() + or 0 + ) + + def _system_user_id(db: Session) -> int | None: """自动重登要用一个真实用户身份落 ``created_by``(外键非空,且便于追责)。