# -*- coding: utf-8 -*- """运营账号扫码登录的完成判据。 这里守的是一个具体的故障:判据原先读页面里的 `window.__INITIAL_STATE__`,而那是 **页面加载那一刻的快照** —— 扫码是加载之后才登录的,快照不会翻转,于是登录明明 成功了,界面却永远停在二维码上。 现在改成拿 cookie 去问创作者后台"我是谁"。重要的是**游客也有 a1**(所以签名算得 出来),所以"有 a1"什么都不能证明,只有后台认了才算数。 """ from unittest.mock import AsyncMock, MagicMock import pytest from api.creator import login as creator_login from api.creator.client import CreatorApiError class _FakeContext: def __init__(self, cookies): self._cookies = cookies self.closed = False async def cookies(self): return self._cookies async def close(self): self.closed = True class _FakePage: def __init__(self): self.closed = False async def close(self): self.closed = True def _session(cookies): context = _FakeContext(cookies) page = _FakePage() session = creator_login.AccountLoginSession(context, page) # 跳过节流,让每次 refresh 都真的去问一次。 session._last_login_check = 0.0 return session, context, page def _patch_client(monkeypatch, behaviour): """behaviour(cookie) -> dict 或抛异常。""" class _Client: def __init__(self, cookie, **kwargs): self.cookie = cookie async def fetch_user_info(self): return behaviour(self.cookie) monkeypatch.setattr(creator_login, "CreatorClient", _Client) GUEST_COOKIES = [ {"name": "a1", "value": "guest-a1"}, {"name": "web_session", "value": "guest-session"}, ] @pytest.mark.asyncio async def test_a_guest_session_never_completes(monkeypatch): """游客也有 a1,但后台回 401 —— 必须继续等,不能当成登录成功。""" def behaviour(_cookie): raise CreatorApiError("登录态无效或已过期", status=401) _patch_client(monkeypatch, behaviour) session, _context, _page = _session(GUEST_COOKIES) await session.refresh() assert session.status == creator_login.STATUS_WAITING assert session.cookie == "" @pytest.mark.asyncio async def test_a_recognised_identity_completes_the_login(monkeypatch): _patch_client( monkeypatch, lambda _cookie: {"user_id": "u123", "nickname": "小明", "red_id": "1"}, ) session, _context, _page = _session(GUEST_COOKIES) await session.refresh() assert session.status == creator_login.STATUS_SUCCESS assert "小明" in session.message assert "a1=guest-a1" in session.cookie assert session.account["user_id"] == "u123" @pytest.mark.asyncio async def test_an_empty_identity_does_not_complete(monkeypatch): """接口返回 200 但没有账号标识 —— 同样不能算成功。""" _patch_client(monkeypatch, lambda _cookie: {"user_id": "", "nickname": ""}) session, _context, _page = _session(GUEST_COOKIES) await session.refresh() assert session.status == creator_login.STATUS_WAITING @pytest.mark.asyncio async def test_the_api_is_not_hit_on_every_poll(monkeypatch): """前端每 2 秒轮询一次,但每次轮询都打一次后台接口是浪费。""" calls = [] def behaviour(cookie): calls.append(cookie) raise CreatorApiError("还没登录", status=401) _patch_client(monkeypatch, behaviour) session, _context, _page = _session(GUEST_COOKIES) await session.refresh() # 第一次之后 _last_login_check 已经是"现在",后面两次应落在节流窗口内。 await session.refresh() await session.refresh() assert len(calls) == 1 @pytest.mark.asyncio async def test_expiry_beats_a_successful_scan(monkeypatch): _patch_client(monkeypatch, lambda _cookie: {"user_id": "u1", "nickname": "x"}) session, _context, _page = _session(GUEST_COOKIES) session.started_at -= creator_login.QR_TTL_SECONDS + 1 await session.refresh() assert session.status == creator_login.STATUS_EXPIRED @pytest.mark.asyncio async def test_a_closed_window_is_reported(monkeypatch): session, context, _page = _session(GUEST_COOKIES) context.cookies = AsyncMock(side_effect=RuntimeError("Target closed")) await session.refresh() assert session.status == creator_login.STATUS_ERROR @pytest.mark.asyncio async def test_closing_discards_the_temporary_context(): """临时上下文是这个设计的关键 —— 用完必须关掉,否则会挂在操作者的 Chrome 里。""" session, context, page = _session(GUEST_COOKIES) await session.close() assert context.closed is True assert page.closed is True