180 lines
5.6 KiB
Python
180 lines
5.6 KiB
Python
"""Wave 1:信息脱敏模块单元测试(CS-C-12,纯函数无需 DB)。"""
|
|||
|
|
|
||
|
|
from app.utils.data_masker import (
|
||
|
|
mask_bank_card,
|
||
|
|
mask_dict_fields,
|
||
|
|
mask_id_card,
|
||
|
|
mask_name,
|
||
|
|
mask_phone,
|
||
|
|
mask_text,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
# 手机号
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
def test_mask_phone_standard():
|
||
|
|
assert mask_phone("13812345678") == "138****5678"
|
||
|
|
|
||
|
|
|
||
|
|
def test_mask_phone_already_masked_passthrough():
|
||
|
|
assert mask_phone("139****1001") == "139****1001"
|
||
|
|
|
||
|
|
|
||
|
|
def test_mask_phone_none_and_empty():
|
||
|
|
assert mask_phone(None) is None
|
||
|
|
assert mask_phone("") == ""
|
||
|
|
|
||
|
|
|
||
|
|
def test_mask_phone_invalid_format_untouched():
|
||
|
|
assert mask_phone("12345") == "12345" # 非 11 位手机号不强行处理
|
||
|
|
|
||
|
|
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
# 身份证
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
def test_mask_id_card_18_digits():
|
||
|
|
assert mask_id_card("110101199003071234") == "110***********1234"
|
||
|
|
|
||
|
|
|
||
|
|
def test_mask_id_card_18_with_tail_x():
|
||
|
|
assert mask_id_card("11010119900307123X") == "110***********123X"
|
||
|
|
assert mask_id_card("11010119900307123x") == "110***********123X" # 小写 x 归一
|
||
|
|
|
||
|
|
|
||
|
|
def test_mask_id_card_15_digits_legacy():
|
||
|
|
# 15 位老证:前 3 + 8 个 * + 后 4
|
||
|
|
assert mask_id_card("110101900307123") == "110********7123"
|
||
|
|
|
||
|
|
|
||
|
|
def test_mask_id_card_already_masked_passthrough():
|
||
|
|
assert mask_id_card("310101199903XXXXXX") == "310101199903XXXXXX"
|
||
|
|
|
||
|
|
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
# 姓名(只保留姓)
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
def test_mask_name_two_chars():
|
||
|
|
assert mask_name("张三") == "张*"
|
||
|
|
assert mask_name("李四") == "李*"
|
||
|
|
|
||
|
|
|
||
|
|
def test_mask_name_three_chars():
|
||
|
|
assert mask_name("张明明") == "张**"
|
||
|
|
|
||
|
|
|
||
|
|
def test_mask_name_four_or_more():
|
||
|
|
assert mask_name("欧阳修先生") == "欧****"
|
||
|
|
|
||
|
|
|
||
|
|
def test_mask_name_already_masked_passthrough():
|
||
|
|
assert mask_name("客户·王**") == "客户·王**"
|
||
|
|
assert mask_name("陈**兰") == "陈**兰"
|
||
|
|
|
||
|
|
|
||
|
|
def test_mask_name_none_and_single():
|
||
|
|
assert mask_name(None) is None
|
||
|
|
assert mask_name("张") == "张"
|
||
|
|
|
||
|
|
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
# 银行卡
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
def test_mask_bank_card_continuous():
|
||
|
|
assert mask_bank_card("6222021234567890") == "************7890"
|
||
|
|
|
||
|
|
|
||
|
|
def test_mask_bank_card_with_spaces():
|
||
|
|
assert mask_bank_card("6222 0212 3456 7890") == "**** **** **** 7890"
|
||
|
|
|
||
|
|
|
||
|
|
def test_mask_bank_card_with_hyphens():
|
||
|
|
assert mask_bank_card("6222-0212-3456-7890") == "****-****-****-7890"
|
||
|
|
|
||
|
|
|
||
|
|
def test_mask_bank_card_too_short_untouched():
|
||
|
|
assert mask_bank_card("12345") == "12345"
|
||
|
|
|
||
|
|
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
# 自由文本兜底扫描
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
def test_mask_text_mixed_pii():
|
||
|
|
text = "我的手机13812345678,身份证110101199003071234,卡号6222021234567890"
|
||
|
|
out = mask_text(text)
|
||
|
|
assert "138****5678" in out
|
||
|
|
assert "110***********1234" in out
|
||
|
|
assert "************7890" in out
|
||
|
|
|
||
|
|
|
||
|
|
def test_mask_text_spaced_bank_card():
|
||
|
|
out = mask_text("收款卡号 6222 0212 3456 7890 请查收")
|
||
|
|
assert "**** **** **** 7890" in out
|
||
|
|
|
||
|
|
|
||
|
|
def test_mask_text_legacy_id_card():
|
||
|
|
out = mask_text("老证号 110101900307123 已登记")
|
||
|
|
assert "110********7123" in out
|
||
|
|
|
||
|
|
|
||
|
|
def test_mask_text_no_pii_untouched():
|
||
|
|
text = "今天想了解一下货币基金的赎回规则"
|
||
|
|
assert mask_text(text) == text
|
||
|
|
|
||
|
|
|
||
|
|
def test_mask_text_amount_and_date_not_falsely_masked():
|
||
|
|
# 金额(小数点分隔)与日期不应被当作卡号/身份证
|
||
|
|
assert mask_text("金额520000.00元") == "金额520000.00元"
|
||
|
|
assert mask_text("到期日2026-09-08") == "到期日2026-09-08"
|
||
|
|
|
||
|
|
|
||
|
|
def test_mask_text_phone_not_cut_from_card():
|
||
|
|
# 16 位卡号中即便包含 1[3-9] 开头的片段,也不应被手机号规则误切
|
||
|
|
out = mask_text("6222138123456789")
|
||
|
|
assert out == "************6789"
|
||
|
|
|
||
|
|
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
# 结构化数据批量脱敏
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
def test_mask_dict_fields_single_row():
|
||
|
|
row = {
|
||
|
|
"customer_id": "CUST-1001",
|
||
|
|
"display_name": "李四",
|
||
|
|
"phone_mask": "139****1001", # 库中已脱敏 → 放行
|
||
|
|
"payer_name": "王五",
|
||
|
|
"amount": 5000.00, # 非字符串原样保留
|
||
|
|
}
|
||
|
|
out = mask_dict_fields(row)
|
||
|
|
assert out["display_name"] == "李*"
|
||
|
|
assert out["phone_mask"] == "139****1001"
|
||
|
|
assert out["payer_name"] == "王*"
|
||
|
|
assert out["amount"] == 5000.00
|
||
|
|
assert out["customer_id"] == "CUST-1001"
|
||
|
|
# 不入参修改
|
||
|
|
assert row["display_name"] == "李四"
|
||
|
|
|
||
|
|
|
||
|
|
def test_mask_dict_fields_list_of_rows():
|
||
|
|
rows = [
|
||
|
|
{"product_name": "现金宝货币", "counterparty_name": "张三"},
|
||
|
|
{"product_name": "稳健债基", "counterparty_name": "李四"},
|
||
|
|
]
|
||
|
|
out = mask_dict_fields(rows)
|
||
|
|
assert out[0]["counterparty_name"] == "张*"
|
||
|
|
assert out[1]["counterparty_name"] == "李*"
|
||
|
|
assert out[0]["product_name"] == "现金宝货币"
|
||
|
|
|
||
|
|
|
||
|
|
def test_mask_dict_fields_custom_mapping():
|
||
|
|
row = {"contact_mobile": "13812345678", "remark": "ok"}
|
||
|
|
out = mask_dict_fields(row, {"contact_mobile": "phone"})
|
||
|
|
assert out["contact_mobile"] == "138****5678"
|
||
|
|
assert out["remark"] == "ok"
|