test_mask.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. # -*- coding:utf-8 -*-
  2. import pytest
  3. from dw_base.tracking.mask import apply_mask, apply_method, load_mask_conf
  4. # ---- apply_method:6 种方法语义(对齐 datax/mask.py) ----
  5. def test_method_md5():
  6. assert apply_method('md5', 'abc') == '900150983cd24fb0d6963f7d28e17f72'
  7. def test_method_mask_middle():
  8. assert apply_method('mask_middle', '17301839727') == '173****9727'
  9. def test_method_mask_middle_short_unchanged():
  10. assert apply_method('mask_middle', '1234567') == '1234567' # len<8 不脱敏
  11. def test_method_keep_first():
  12. assert apply_method('keep_first_2', 'abcdef') == 'ab****'
  13. def test_method_keep_last():
  14. assert apply_method('keep_last_4', '17301839727') == '****9727'
  15. def test_method_month_trunc():
  16. assert apply_method('month_trunc', '2024-05-13 10:00:00') == '2024-05'
  17. def test_method_month_trunc_non_date_unchanged():
  18. assert apply_method('month_trunc', 'not-a-date') == 'not-a-date'
  19. def test_method_none_passthrough():
  20. assert apply_method('md5', None) is None
  21. def test_method_unknown_raises():
  22. with pytest.raises(ValueError, match='未知脱敏方法'):
  23. apply_method('bogus', 'x')
  24. # ---- apply_mask:drop / mask / 兜底 / 优先级 / 嵌套 ----
  25. CONF = {
  26. 'PayOrder': {'drop': ['receiverName', 'receiverTelephone']},
  27. 'Login': {'mask': {'phone': 'keep_last_4'}},
  28. 'Mixed': {'drop': ['x'], 'mask': {'x': 'md5'}},
  29. }
  30. def test_drop_in_params_layer():
  31. props = {'storeId': '31', 'params': {'receiverName': 'Ethan', 'orderNo': 'A1'}}
  32. out = apply_mask('PayOrder', props, CONF)
  33. assert 'receiverName' not in out['params']
  34. assert out['params']['orderNo'] == 'A1'
  35. def test_apply_mask_does_not_mutate_input():
  36. props = {'params': {'receiverName': 'Ethan'}}
  37. apply_mask('PayOrder', props, CONF)
  38. assert props['params']['receiverName'] == 'Ethan'
  39. def test_undeclared_event_passthrough():
  40. props = {'params': {'whatever': '1'}}
  41. assert apply_mask('$AppStart', props, CONF) == props
  42. def test_mask_top_level_field():
  43. out = apply_mask('Login', {'phone': '17301839727'}, CONF)
  44. assert out['phone'] == '****9727'
  45. def test_drop_beats_mask_same_field():
  46. out = apply_mask('Mixed', {'x': 'secret'}, CONF)
  47. assert 'x' not in out # drop 优先:字段消失而非被 md5
  48. # ---- load_mask_conf:解析 / fail-fast ----
  49. def test_load_conf_parses_drop_and_mask(tmp_path):
  50. p = tmp_path / 'tracking-mask.ini'
  51. p.write_text(
  52. '[event:PayOrder]\n'
  53. 'drop = receiverName, receiverTelephone\n'
  54. 'mask = receiverArea:mask_middle\n',
  55. encoding='utf-8')
  56. conf = load_mask_conf(str(p))
  57. assert conf['PayOrder']['drop'] == ['receiverName', 'receiverTelephone']
  58. assert conf['PayOrder']['mask'] == {'receiverArea': 'mask_middle'}
  59. def test_load_conf_ignores_non_event_sections(tmp_path):
  60. p = tmp_path / 'm.ini'
  61. p.write_text('[DEFAULT]\nfoo = bar\n[event:X]\ndrop = a\n', encoding='utf-8')
  62. assert set(load_mask_conf(str(p))) == {'X'}
  63. def test_load_conf_missing_file_raises(tmp_path):
  64. with pytest.raises(FileNotFoundError):
  65. load_mask_conf(str(tmp_path / 'nope.ini'))
  66. def test_load_conf_unknown_method_raises(tmp_path):
  67. p = tmp_path / 'm.ini'
  68. p.write_text('[event:X]\nmask = f:bogus\n', encoding='utf-8')
  69. with pytest.raises(ValueError, match='未知脱敏方法'):
  70. load_mask_conf(str(p))
  71. def test_load_conf_bad_mask_format_raises(tmp_path):
  72. p = tmp_path / 'm.ini'
  73. p.write_text('[event:X]\nmask = noColon\n', encoding='utf-8')
  74. with pytest.raises(ValueError, match='field:method'):
  75. load_mask_conf(str(p))