7e505a04c7
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Lint (push) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (push) Has been cancelled
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
176 lines
6.1 KiB
Python
Executable File
176 lines
6.1 KiB
Python
Executable File
"""Password Handler 单元测试."""
|
||
|
||
from __future__ import annotations
|
||
|
||
import pytest
|
||
|
||
from packages.application.auth.password_handler import (
|
||
PasswordHandler,
|
||
configure_password_handler,
|
||
get_password_handler,
|
||
)
|
||
|
||
|
||
@pytest.fixture
|
||
def password_handler():
|
||
return PasswordHandler(rounds=4) # 用低rounds加速测试
|
||
|
||
|
||
class TestPasswordHandler:
|
||
"""PasswordHandler 测试"""
|
||
|
||
def test_hash_password_returns_string(self, password_handler):
|
||
"""哈希密码返回非空字符串"""
|
||
hashed = password_handler.hash_password("MyP@ssw0rd!")
|
||
|
||
assert isinstance(hashed, str)
|
||
assert len(hashed) > 0
|
||
assert hashed != "MyP@ssw0rd!"
|
||
|
||
def test_hash_password_different_each_time(self, password_handler):
|
||
"""同一密码每次哈希结果不同(加盐)"""
|
||
h1 = password_handler.hash_password("TestPass123")
|
||
h2 = password_handler.hash_password("TestPass123")
|
||
|
||
assert h1 != h2
|
||
|
||
def test_verify_password_correct(self, password_handler):
|
||
"""正确密码验证通过"""
|
||
hashed = password_handler.hash_password("CorrectPass1!")
|
||
assert password_handler.verify_password("CorrectPass1!", hashed) is True
|
||
|
||
def test_verify_password_wrong(self, password_handler):
|
||
"""错误密码验证失败"""
|
||
hashed = password_handler.hash_password("RightPass1!")
|
||
assert password_handler.verify_password("WrongPass1!", hashed) is False
|
||
|
||
def test_verify_password_empty_string(self, password_handler):
|
||
"""空字符串密码也能正确验证(不匹配)"""
|
||
hashed = password_handler.hash_password("SomePass1!")
|
||
assert password_handler.verify_password("", hashed) is False
|
||
|
||
def test_hash_empty_password_raises(self, password_handler):
|
||
"""空密码哈希抛出 ValueError"""
|
||
with pytest.raises(ValueError):
|
||
password_handler.hash_password("")
|
||
|
||
def test_needs_rehash_with_different_rounds(self):
|
||
"""不同 rounds 的哈希需要重新计算"""
|
||
handler_low = PasswordHandler(rounds=4)
|
||
handler_high = PasswordHandler(rounds=5)
|
||
|
||
hashed = handler_low.hash_password("TestPass1!")
|
||
assert handler_low.needs_rehash(hashed) is False
|
||
assert handler_high.needs_rehash(hashed) is True
|
||
|
||
def test_validate_strength_strong_password(self, password_handler):
|
||
"""强密码通过强度验证"""
|
||
valid, error = password_handler.validate_strength("Str0ngP@ss!")
|
||
|
||
assert valid is True
|
||
assert error is None
|
||
|
||
def test_validate_strength_too_short(self, password_handler):
|
||
"""密码太短不通过"""
|
||
valid, error = password_handler.validate_strength("Sh0rt!")
|
||
|
||
assert valid is False
|
||
assert error is not None
|
||
assert "长度" in error or "length" in error.lower() or "8" in error
|
||
|
||
def test_validate_strength_no_uppercase(self, password_handler):
|
||
"""没有大写字母不通过"""
|
||
valid, error = password_handler.validate_strength("lowercase1!")
|
||
|
||
assert valid is False
|
||
assert error is not None
|
||
|
||
def test_validate_strength_no_lowercase(self, password_handler):
|
||
"""没有小写字母不通过"""
|
||
valid, error = password_handler.validate_strength("UPPERCASE1!")
|
||
|
||
assert valid is False
|
||
assert error is not None
|
||
|
||
def test_validate_strength_no_digit(self, password_handler):
|
||
"""没有数字不通过"""
|
||
valid, error = password_handler.validate_strength("NoDigitPass!")
|
||
|
||
assert valid is False
|
||
assert error is not None
|
||
|
||
def test_validate_strength_special_not_required(self, password_handler):
|
||
"""默认不要求特殊字符"""
|
||
valid, error = password_handler.validate_strength("NoSpecial1")
|
||
|
||
# 没有特殊字符也应该通过(require_special=False)
|
||
assert valid is True
|
||
assert error is None
|
||
|
||
def test_validate_strength_empty_string(self, password_handler):
|
||
"""空字符串验证失败"""
|
||
valid, error = password_handler.validate_strength("")
|
||
|
||
assert valid is False
|
||
assert error is not None
|
||
|
||
def test_hash_and_verify_roundtrip(self, password_handler):
|
||
"""哈希-验证完整往返"""
|
||
passwords = [
|
||
"Simple12",
|
||
"C0mpl3x!Pass",
|
||
"12345678aA",
|
||
"user@example.com1",
|
||
]
|
||
for pwd in passwords:
|
||
hashed = password_handler.hash_password(pwd)
|
||
assert password_handler.verify_password(pwd, hashed)
|
||
assert not password_handler.verify_password(pwd + "x", hashed)
|
||
|
||
|
||
class TestGlobalPasswordHandler:
|
||
"""全局密码处理器配置测试"""
|
||
|
||
def test_get_password_handler_default(self):
|
||
"""未配置时 get_password_handler 返回默认实例"""
|
||
import packages.application.auth.password_handler as pw_module
|
||
|
||
pw_module._default_handler = None
|
||
|
||
handler = get_password_handler()
|
||
assert isinstance(handler, PasswordHandler)
|
||
|
||
def test_configure_creates_handler(self):
|
||
"""configure_password_handler 创建并返回 handler"""
|
||
import packages.application.auth.password_handler as pw_module
|
||
|
||
pw_module._default_handler = None
|
||
|
||
handler = configure_password_handler(rounds=4)
|
||
|
||
assert isinstance(handler, PasswordHandler)
|
||
assert get_password_handler() is handler
|
||
|
||
def test_configure_overwrites_existing(self):
|
||
"""重新配置会覆盖之前的 handler"""
|
||
import packages.application.auth.password_handler as pw_module
|
||
|
||
pw_module._default_handler = None
|
||
|
||
handler1 = configure_password_handler(rounds=4)
|
||
handler2 = configure_password_handler(rounds=5)
|
||
|
||
assert handler1 is not handler2
|
||
assert get_password_handler() is handler2
|
||
|
||
def test_get_password_handler_lazy_init(self):
|
||
"""未配置时首次调用 get_password_handler 会懒初始化"""
|
||
import packages.application.auth.password_handler as pw_module
|
||
|
||
pw_module._default_handler = None
|
||
|
||
assert pw_module._default_handler is None
|
||
handler = get_password_handler()
|
||
assert pw_module._default_handler is not None
|
||
assert pw_module._default_handler is handler
|