PayrollManagementSystem/backend/tests/unit/test_config.py

84 lines
3.2 KiB
Python
Raw Permalink Normal View History

"""配置模块单元测试。
用 `_env_file=None` 绕过 backend/.env,使断言不受开发者本地配置影响。
"""
import pytest
from pydantic import ValidationError
from app.core.config import Settings
VALID_SECRET = "x" * 32
def _settings(**overrides: object) -> Settings:
"""构造一个不读取 .env 的配置实例。"""
params: dict[str, object] = {"_env_file": None, "app_secret_key": VALID_SECRET}
params.update(overrides)
return Settings(**params) # type: ignore[arg-type]
class TestSecretKey:
"""密钥强度校验。"""
def test_缺失密钥时拒绝构造(self) -> None:
with pytest.raises(ValidationError):
Settings(_env_file=None) # type: ignore[call-arg]
@pytest.mark.parametrize("length", [0, 1, 31])
def test_密钥短于32字符时拒绝(self, length: int) -> None:
with pytest.raises(ValidationError):
_settings(app_secret_key="x" * length)
def test_密钥恰好32字符时通过(self) -> None:
assert _settings(app_secret_key="x" * 32).app_secret_key == "x" * 32
class TestSecurityBounds:
"""安全边界:规范要求 JWT ≤120 分钟、验证码 300 秒。"""
def test_jwt有效期超过120分钟时拒绝(self) -> None:
with pytest.raises(ValidationError):
_settings(jwt_expire_minutes=121)
def test_jwt有效期为120分钟时通过(self) -> None:
assert _settings(jwt_expire_minutes=120).jwt_expire_minutes == 120
def test_验证码有效期超过300秒时拒绝(self) -> None:
with pytest.raises(ValidationError):
_settings(captcha_expire_seconds=301)
def test_验证码有效期为300秒时通过(self) -> None:
assert _settings(captcha_expire_seconds=300).captcha_expire_seconds == 300
class TestConnectionUrl:
"""连接 URL 构造:密码含特殊字符时不可手工拼接。"""
def test_数据库URL使用同步驱动(self) -> None:
"""运行期与迁移共用同步连接串,不存在同步/异步两套配置。"""
url = _settings().database_url
assert url.drivername == "mysql+pymysql"
assert url.database == "payroll"
assert url.query.get("charset") == "utf8mb4"
def test_密码含特殊字符时被原样保存于URL对象(self) -> None:
url = _settings(mysql_password="p@ss:w/ord#1").database_url
# URL.create 以结构化方式保存各组件,不做编码 —— 编码发生在驱动建连时,
# 这正是手工拼接字符串会出错(如把 : 误认为端口分隔符)而它不会的原因
assert url.password == "p@ss:w/ord#1"
assert url.username == "payroll"
assert url.port == 3306
def test_渲染URL时默认隐藏密码(self) -> None:
"""异常或日志中打印 URL 时不得泄露密码。"""
url = _settings(mysql_password="p@ss:w/ord#1").database_url
assert "p@ss:w/ord#1" not in url.render_as_string()
def test_Redis无密码时不拼接认证段(self) -> None:
assert _settings(redis_password="").redis_url == "redis://127.0.0.1:6379/0"
def test_Redis有密码时拼接认证段(self) -> None:
url = _settings(redis_password="secret").redis_url
assert url == "redis://:secret@127.0.0.1:6379/0"