"""密码哈希单元测试。 重点覆盖 bcrypt 5.0 的 72 字节硬限制 —— 此前是静默截断,现在是抛 ValueError, 若不在边界上把关,用户设一个长密码就会在登录时炸出 500。 """ import pytest from app.core.constants import PASSWORD_MAX_BYTES from app.core.security import hash_password, verify_password class TestHashPassword: """哈希生成。""" def test_输出为BCrypt格式且定长60字符(self) -> None: hashed = hash_password("Admin@2026") assert hashed.startswith("$2") assert len(hashed) == 60 def test_相同明文两次哈希结果不同(self) -> None: """BCrypt 内含随机盐,相同明文每次结果不同 —— 这是防彩虹表的基础。""" assert hash_password("SamePwd@1") != hash_password("SamePwd@1") def test_恰好72字节时允许(self) -> None: password = "a" * PASSWORD_MAX_BYTES assert len(password.encode("utf-8")) == PASSWORD_MAX_BYTES assert len(hash_password(password)) == 60 def test_超过72字节时抛ValueError(self) -> None: with pytest.raises(ValueError): hash_password("a" * (PASSWORD_MAX_BYTES + 1)) @pytest.mark.parametrize( ("text", "count", "should_pass"), [("中", 24, True), ("中", 25, False)], ) def test_中文按字节而非字符计算边界(self, text: str, count: int, should_pass: bool) -> None: """一个汉字 3 字节:24 个正好 72 字节,25 个即 75 字节超限。""" password = text * count if should_pass: assert len(hash_password(password)) == 60 else: with pytest.raises(ValueError): hash_password(password) class TestVerifyPassword: """哈希校验。""" def test_正确密码校验通过(self) -> None: assert verify_password("Admin@2026", hash_password("Admin@2026")) is True def test_错误密码校验失败(self) -> None: assert verify_password("WrongPwd@1", hash_password("Admin@2026")) is False def test_超长密码返回False而不抛异常(self) -> None: """校验路径不得因输入长度报错 —— 否则会形成「密码过长」与「密码错误」 可区分的侧信道,且会让恶意长输入直接打出 500。""" assert verify_password("a" * 200, hash_password("Admin@2026")) is False def test_损坏的哈希串返回False而不抛异常(self) -> None: assert verify_password("Admin@2026", "not-a-bcrypt-hash") is False def test_空哈希串返回False(self) -> None: assert verify_password("Admin@2026", "") is False