# -*- coding: utf-8 -*- """Flask 兼容会话编解码的单元测试。 验证 core.session 与 itsdangerous(Flask 会话所用库)**双向互通**: Flask 时代签发的会话 Cookie 必须仍能被本站解析(用户不必重新登录)。 Flask 本身已从依赖中移除,这里直接用 itsdangerous 构造/校验 (Flask 会话即 itsdangerous URLSafeTimedSerializer:盐 cookie-session、HMAC-SHA1)。 """ import hashlib import time import pytest from itsdangerous import URLSafeTimedSerializer from core import session as cs SECRET = 'unit-test-secret-key' def _flask_serializer(): """等价于 Flask 的会话序列化器(同盐、同摘要算法、同 key 派生)。""" return URLSafeTimedSerializer( SECRET, salt='cookie-session', signer_kwargs={'key_derivation': 'hmac', 'digest_method': hashlib.sha1}) def test_itsdangerous_to_core(): """itsdangerous(Flask)签发的会话 Cookie 能被 core.session 解出。""" cookie = _flask_serializer().dumps({'site_uid': 26, 'site_uid_seq': 5}) data = cs.loads(SECRET, cookie) assert data == {'site_uid': 26, 'site_uid_seq': 5} assert cs.get_uid(data) == 26 def test_core_to_itsdangerous(): """core.session 签发的 Cookie 能被 itsdangerous 解出。""" cookie = cs.dumps(SECRET, {'site_uid': 74, 'site_uid_seq': 8}) assert _flask_serializer().loads(cookie) == {'site_uid': 74, 'site_uid_seq': 8} def test_core_timestamp_valid_for_timed_check(): """时间戳编码与 itsdangerous 一致:max_age 校验可用(未过期)。""" cookie = cs.dumps(SECRET, {'site_uid': 1}) assert _flask_serializer().loads(cookie, max_age=3600) == {'site_uid': 1} def test_tampered_returns_none(): cookie = _flask_serializer().dumps({'site_uid': 26}) assert cs.loads(SECRET, cookie[:-3] + 'xyz') is None def test_wrong_key_returns_none(): cookie = _flask_serializer().dumps({'site_uid': 26}) assert cs.loads('wrong-secret', cookie) is None def test_garbage_returns_none(): assert cs.loads(SECRET, 'not-a-cookie') is None assert cs.loads(SECRET, '') is None def test_roundtrip_selfconsistency(): payload = {'site_uid': 9, 'site_uid_seq': 3, 'site_uid_ts': int(time.time())} assert cs.loads(SECRET, cs.dumps(SECRET, payload)) == payload def test_legacy_handwritten_cookie_still_loads(): """旧手写实现签发的 Cookie 仍能被解析(存量会话不必重新登录)。 样本由「替换 itsdangerous 之前」的 core.session.dumps 生成,时间戳固定为 1700000000,故字符串永久有效。它钉住的是**格式**而非实现:旧实现的 payload 用标准 base64 且保留 `=` 填充,签名同理;itsdangerous 用 urlsafe 且去掉填充——两侧必须继续互通,否则线上所有登录态会在部署瞬间失效。 """ legacy = ('eyJzaXRlX3VpZCI6MjYsInNpdGVfdWlkX3NlcSI6NX0=' '.ZVPxAA.hW2ZBQ0BL1OhI4D4eDKFFVvqGO0') data = cs.loads(SECRET, legacy) assert data == {'site_uid': 26, 'site_uid_seq': 5} assert cs.get_uid(data) == 26 def test_max_age_none_accepts_old_timestamp(): """默认不校验签发时间(max_age=None)——与既有行为一致,旧 Cookie 不过期。""" legacy = ('eyJzaXRlX3VpZCI6MjYsInNpdGVfdWlkX3NlcSI6NX0=' '.ZVPxAA.hW2ZBQ0BL1OhI4D4eDKFFVvqGO0') assert cs.loads(SECRET, legacy) is not None # 显式给出 max_age 时,超龄的旧 Cookie 必须被拒绝(供未来启用过期用) assert cs.loads(SECRET, legacy, max_age=60) is None def test_get_uid_none(): assert cs.get_uid(None) is None assert cs.get_uid({}) is None @pytest.mark.parametrize('uid', [1, 26, 99999]) def test_various_uids(uid): assert cs.get_uid(cs.loads(SECRET, cs.dumps(SECRET, {'site_uid': uid}))) == uid