Files
DjangoAdmin_FastAPI_EleVue/tests/unit/utils/test_captcha.py
T
2026-09-12 17:39:13 +08:00

87 lines
2.9 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.
"""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)