Files
live-hub-py/tests/test_douyu_cookie_visibility.py
T
yml2213 64c399617a fix(douyu): 账号只要还持有 CK 就不再从工作台消失
线上事故:分配给客服的账号(如「远行者103」)在斗鱼工作台与 CK 列表里
查不到,只有账号管理里还能搜到。原因是可见性判定写成了
LoginTask.status == "success",而按设计保留旧 CK 的三种状态都不再是
success:

- 普通登录批次启动时会把复用的成功记录置为 pending/running(旧 CK 保留),
  批次被停止或失败后一直停在 pending,账号永久消失;
- CK 重登失败置为 relogin_failed(旧 CK 保留),账号从工作台消失;
- 普通登录失败置为 failed(复用记录的旧 CK 未清空),同样消失。

线上现状:12545 个账号持有 CK,但只有 11703 个能出现在工作台,842 个
(644 重登失败 + 176 排队中 + …)对客服和管理员都不可见。

改动:
- douyu_service 新增 cookie_holding_filter()/latest_cookie_login_task(),
  以「记录里还留着 Cookie」作为唯一判据,并抽出 LOGIN_IN_FLIGHT_STATUSES
  与 cookie_operable_filter() 供检测/重登入口复用;
- 斗鱼工作台账号列表、任务账号校验、账号管理 has_cookie 筛选、分配校验与
  概览、CK 管理/操作列表、首页 CK 统计统一改用该判据;
- 工作台取 CK 从 latest_success_login_task 换成 latest_cookie_login_task,
  保证重登失败/排队中的账号仍能被任务正常执行;
- 新增 cleanup_orphan_login_tasks():进程重启后把遗留的 pending/running
  收敛为 failed 并保留旧 CK,避免页面永久显示排队中。

新增回归测试覆盖上述状态下的可见性、可执行性与启动清理。
2026-10-01 18:18:02 +08:00

167 lines
6.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""回归:只要账号还留着 CK,就不得因为登录批次状态而从工作台消失。
线上事故(2026-10-01):账号「远行者103」被分配给客服后,客服与管理员在
斗鱼工作台都看不到它,只有账号管理里还能搜到。原因是可见性判定写成了
``LoginTask.status == "success"``,而普通登录批次启动时会把复用的成功记录
置为 ``pending``,CK 重登失败会置为 ``relogin_failed`` —— 两种状态都保留旧
Cookie,却让账号从工作台与 CK 列表里凭空消失。
"""
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from web.backend.database import Base
from web.backend.models import Account, LoginTask, User
from web.backend.routers.cookies import list_cookies
from web.backend.routers.douyu import list_task_account_ids, list_task_accounts
from web.backend.services.account_service import cookie_account_ids_query
from web.backend.services.douyu_service import (
create_douyu_planned_tasks,
latest_cookie_login_task,
visible_douyu_task_accounts,
)
class TestDouyuCookieVisibility:
def setup_method(self):
self.engine = create_engine("sqlite://")
Base.metadata.create_all(self.engine)
self.session = sessionmaker(bind=self.engine)()
self.admin = User(username="admin", password_hash="hash", role="super_admin")
self.support = User(username="kf_7", password_hash="hash", role="support")
self.session.add_all([self.admin, self.support])
self.session.commit()
# relogin_failed:重登失败但旧 CK 已按设计保留
self.relogin_account = self._make_account("远行者103", self.support.id)
self.session.add(
LoginTask(
batch_id="relogin-failed",
account_id=self.relogin_account.id,
created_by=self.support.id,
status="relogin_failed",
cookie="acf_uid=481802039",
ck_check_status="valid",
)
)
# pending:普通登录批次排队中,复用了成功记录,旧 CK 仍在
self.pending_account = self._make_account("排队中账号", self.support.id)
self.session.add(
LoginTask(
batch_id="queued",
account_id=self.pending_account.id,
created_by=self.admin.id,
status="pending",
cookie="acf_uid=2",
)
)
# 从未登录成功:没有 CK,必须继续隐藏
self.empty_account = self._make_account("无CK账号", self.support.id)
self.session.add(
LoginTask(
batch_id="failed",
account_id=self.empty_account.id,
created_by=self.admin.id,
status="failed",
cookie="",
)
)
self.session.commit()
def _make_account(self, username: str, assigned_to: int) -> Account:
account = Account(
username=username,
password="password",
email=f"{username}@example.com",
email_password="mail-password",
assigned_to=assigned_to,
)
self.session.add(account)
self.session.flush()
return account
def teardown_method(self):
self.session.close()
Base.metadata.drop_all(self.engine)
self.engine.dispose()
def _task_account_names(self, current: User) -> set[str]:
result = list_task_accounts(
search="",
tag="",
ids="",
account_names="",
page=None,
page_size=50,
db=self.session,
current=current,
)
return {item.username for item in result}
@pytest.mark.parametrize("current_attr", ["admin", "support"])
def test_workbench_lists_accounts_that_still_hold_cookie(self, current_attr):
current = getattr(self, current_attr)
names = self._task_account_names(current)
assert {"远行者103", "排队中账号"} <= names
assert "无CK账号" not in names
def test_workbench_account_ids_keep_cookie_holders(self):
result = list_task_account_ids(
search="",
tag="",
account_names="",
db=self.session,
current=self.admin,
)
assert set(result["account_ids"]) == {
self.relogin_account.id,
self.pending_account.id,
}
def test_latest_cookie_login_task_ignores_batch_status(self):
task = latest_cookie_login_task(self.session, self.relogin_account.id)
assert task is not None
assert task.cookie == "acf_uid=481802039"
def test_visible_task_accounts_include_retained_cookie(self):
accounts = visible_douyu_task_accounts(
self.session,
[self.relogin_account.id, self.empty_account.id],
)
assert [account.id for account in accounts] == [self.relogin_account.id]
def test_account_has_cookie_filter_matches_workbench(self):
ids = {
account_id for (account_id,) in cookie_account_ids_query(self.session).all()
}
assert ids == {self.relogin_account.id, self.pending_account.id}
def test_cookie_management_page_lists_retained_cookie(self):
items = list_cookies(
search="",
tag="",
account_names="",
page=None,
page_size=50,
include_cookie=True,
db=self.session,
current=self.admin,
)
assert {item["account_username"] for item in items} == {
"远行者103",
"排队中账号",
}
def test_retained_cookie_account_can_still_create_task(self):
batch_id, created = create_douyu_planned_tasks(
self.session,
[self.relogin_account.id],
"create_gold_qr",
"elite",
self.admin.id,
)
assert batch_id
assert created == 1