diff --git a/tests/test_login_attempt_isolation.py b/tests/test_login_attempt_isolation.py index 196327b..b156302 100644 --- a/tests/test_login_attempt_isolation.py +++ b/tests/test_login_attempt_isolation.py @@ -7,6 +7,7 @@ 停止、失败、进程重启都不可能破坏账号已有的 CK。 """ +from datetime import UTC, datetime from types import SimpleNamespace from sqlalchemy.orm import sessionmaker @@ -171,6 +172,37 @@ class TestLoginAttemptIsolation: assert account.cookie_updated_at is not None assert account.cookie_source == "web" + def test_relogin_success_writes_ck_verdict_on_the_account(self, monkeypatch): + """重登成功后的复检必须写到账号上,不能写到登录器的临时对象上。 + + 2026-10-05 生产:登录已经成功,但 `on_ck_verdict` 收到的是 + SimpleNamespace,读 `ck_check_status` 抛异常,新 Cookie 被丢弃, + 页面上全部是「重新登录异常(旧 Cookie 已保留)」。 + """ + self.account.ck_check_status = "invalid" + self.session.commit() + monkeypatch.setattr( + login_service, + "check_douyu_cookie", + lambda _cookie: { + "valid": True, + "message": "ok", + "fish_ball": 1, + "nickname": "n", + "level": 1, + "checked_at": datetime.now(UTC), + }, + ) + + self._run_batch(monkeypatch, mode="relogin") + + task = self._fresh(self.task_id) + account = self._fresh_account() + assert task.attempt_status == "success" + assert "SimpleNamespace" not in (task.attempt_message or "") + assert account.cookie == "new-cookie" + assert account.ck_check_status == "valid" + def test_stopped_batch_keeps_existing_cookie(self, monkeypatch): self._run_batch(monkeypatch, stop=True) diff --git a/web/backend/services/login_service.py b/web/backend/services/login_service.py index 19fd360..cd7ce1e 100644 --- a/web/backend/services/login_service.py +++ b/web/backend/services/login_service.py @@ -412,7 +412,11 @@ class LoginBatchRunner: ) return - account = cast( + # 只给登录器用的凭据快照。不能覆盖上面的 ORM `account`: + # 重登成功后 `on_ck_verdict` 要写账号的 ck_check_*, + # SimpleNamespace 没有这些字段,会把整次成功登录判成异常, + # 新 Cookie 也就写不回去。 + login_account = cast( AccountLike, SimpleNamespace( username=acc_info["username"], @@ -426,7 +430,7 @@ class LoginBatchRunner: ) loginer = DouyuLogin( - account, + login_account, proxy=proxy_dict, max_login_retries=self.max_login_retries, max_total_time=self.max_total_time,