PayrollManagementSystem/backend/tests/unit/test_logging.py

112 lines
4.0 KiB
Python
Raw Normal View History

"""日志脱敏单元测试。
规范第 8 节禁止日志输出密码、手机号、身份证、Token。
脱敏是最容易在重构中悄悄失效的一环,故用参数化用例覆盖各条规则。
"""
import logging
import pytest
from app.core.logging import SensitiveFormatter, scrub
class TestScrub:
"""文本脱敏规则。"""
@pytest.mark.parametrize(
("raw", "leaked"),
[
('{"password": "Admin@123"}', "Admin@123"),
("password=Secret123", "Secret123"),
("pwd: hunter2xyz", "hunter2xyz"),
('{"app_secret_key": "abcdef123456"}', "abcdef123456"),
("token=eyJhbGciOiJIUzI1NiJ9", "eyJhbGciOiJIUzI1NiJ9"),
],
)
def test_凭据值被脱敏(self, raw: str, leaked: str) -> None:
assert leaked not in scrub(raw)
def test_Bearer令牌被脱敏(self) -> None:
result = scrub("Authorization: Bearer eyJhbGciOi.J9.abc-_123")
assert "eyJhbGciOi.J9.abc-_123" not in result
assert "Bearer ***" in result
def test_手机号保留前三后四(self) -> None:
assert scrub("联系电话 13812345678") == "联系电话 138****5678"
def test_身份证保留前六后四(self) -> None:
assert scrub("证件号 110101199003071234") == "证件号 110101********1234"
def test_普通文本不被误伤(self) -> None:
text = "用户 admin 登录成功,耗时 120ms"
assert scrub(text) == text
def _format(msg: str, args: object = None) -> str:
"""用 SensitiveFormatter 渲染一条日志,返回最终文本。"""
record = logging.LogRecord(
name="t",
level=logging.INFO,
pathname="",
lineno=0,
msg=msg,
args=args,
exc_info=None,
)
return SensitiveFormatter("%(message)s").format(record)
class TestSensitiveFormatter:
"""脱敏必须在 %-格式化之后进行。"""
def test_脱敏位置参数中的敏感值(self) -> None:
# 回归用例:早期实现在 filter 阶段改写 record.msg,把 %s 占位符一并抹掉,
# 导致 logging 内部 msg % args 抛 TypeError,整条日志丢失
result = _format("登录失败 password=%s", ("Secret123",))
assert "Secret123" not in result
assert "登录失败" in result
def test_脱敏字典参数中的敏感值(self) -> None:
# LogRecord 要求 args 为元组;字典参数须包成单元素元组,
# 这正是 logger.info(msg, {...}) 的内部形态
result = _format("token=%(token)s", ({"token": "abc123xyz"},))
assert "abc123xyz" not in result
def test_脱敏消息模板内联的敏感值(self) -> None:
"""敏感值直接写在消息模板里(无占位符)时同样要脱敏。"""
result = _format('请求体 {"password": "Admin@123"}')
assert "Admin@123" not in result
def test_普通日志不被改动(self) -> None:
assert (
_format("用户 admin 登录成功,耗时 %d ms", (120,)) == "用户 admin 登录成功,耗时 120 ms"
)
def test_无参数日志正常通过(self) -> None:
assert _format("服务已启动") == "服务已启动"
class TestSetupLogging:
"""setup_logging 必须挂上脱敏格式化器,否则规则形同虚设。"""
def test_所有handler均使用脱敏格式化器(self) -> None:
import logging as _logging
from app.core.logging import setup_logging
setup_logging(level="INFO", log_to_file=False)
handlers = _logging.getLogger().handlers
assert handlers, "根日志器应至少有一个 handler"
assert all(isinstance(h.formatter, SensitiveFormatter) for h in handlers)
def test_重复调用不会累积handler(self) -> None:
import logging as _logging
from app.core.logging import setup_logging
setup_logging(log_to_file=False)
first = len(_logging.getLogger().handlers)
setup_logging(log_to_file=False)
assert len(_logging.getLogger().handlers) == first