"""utils/captcha.py 验证码校验:原子消费(get→del→compare)杜绝重放与无限试错。 fakeredis 不支持 Lua eval,这里用 Python 复刻 Lua 语义(原子 get→del→compare, 无论对错都消费验证码)作为 fake,验证业务层行为;另断言生产代码确实调用 _CAPTCHA_VERIFY_LUA 脚本。 """ import pytest import fakeredis.aioredis @pytest.fixture def _captcha_redis(monkeypatch): from core import redis_client as rc redis = fakeredis.aioredis.FakeRedis(decode_responses=True) async def fake_eval(script, numkeys, key, user_input): # 复刻 _CAPTCHA_VERIFY_LUA 语义:原子 get→del→compare,无论对错都消费 stored = await redis.get(key) if not stored: return 0 await redis.delete(key) if str(stored).upper().strip() == str(user_input).upper().strip(): return 1 return 0 monkeypatch.setattr(redis, 'eval', fake_eval) monkeypatch.setattr(rc, 'get_redis', lambda: redis) return redis @pytest.mark.asyncio async def test_verify_captcha_correct_then_consumed(_captcha_redis): from utils.captcha import verify_captcha await _captcha_redis.set('k1', 'Ab3X') # 大小写不敏感比较通过 assert await verify_captcha('k1', 'ab3x') is True # 已消费:同一验证码无法再次使用(防重放) assert await verify_captcha('k1', 'Ab3X') is False @pytest.mark.asyncio async def test_verify_captcha_wrong_input_burns_captcha(_captcha_redis): from utils.captcha import verify_captcha await _captcha_redis.set('k2', 'Ab3X') # 错误输入 → False,且验证码被消费(失败即失效,防无限试错) assert await verify_captcha('k2', 'wrong') is False # 随后即使用正确输入也无法通过 assert await verify_captcha('k2', 'Ab3X') is False assert await _captcha_redis.get('k2') is None @pytest.mark.asyncio async def test_verify_captcha_missing_key(_captcha_redis): from utils.captcha import verify_captcha assert await verify_captcha('missing', 'any') is False @pytest.mark.asyncio async def test_verify_captcha_empty_input(_captcha_redis): from utils.captcha import verify_captcha assert await verify_captcha('k', '') is False assert await verify_captcha('', 'x') is False @pytest.mark.asyncio async def test_verify_captcha_uses_atomic_lua_script(_captcha_redis, monkeypatch): from utils.captcha import verify_captcha, _CAPTCHA_VERIFY_LUA await _captcha_redis.set('k1', 'Ab3X') calls = [] original = _captcha_redis.eval async def spy_eval(script, numkeys, key, user_input): calls.append((script, numkeys)) return await original(script, numkeys, key, user_input) monkeypatch.setattr(_captcha_redis, 'eval', spy_eval) assert await verify_captcha('k1', 'Ab3X') is True # 生产代码调用原子 Lua 脚本(numkeys=1) assert calls and calls[0] == (_CAPTCHA_VERIFY_LUA, 1)