102 lines
3.6 KiB
Python
102 lines
3.6 KiB
Python
import unittest
|
|
|
|
import numpy as np
|
|
|
|
from wechat_bot import WeChatBot
|
|
|
|
|
|
class SessionFingerprintTest(unittest.TestCase):
|
|
@staticmethod
|
|
def _bot():
|
|
bot = WeChatBot.__new__(WeChatBot)
|
|
bot.scale = 1.0
|
|
bot.session_item_h = 64
|
|
bot._known_fps = set()
|
|
return bot
|
|
|
|
@staticmethod
|
|
def _row(name_variant: int) -> np.ndarray:
|
|
img = np.full((64, 230, 3), 238, dtype=np.uint8)
|
|
|
|
# 两个客户使用完全相同的高对比头像。
|
|
for y in range(20, 44):
|
|
for x in range(12, 42):
|
|
tone = 45 if ((x // 5) + (y // 4)) % 2 else 205
|
|
img[y, x, :] = tone
|
|
|
|
# 在昵称首行画两组分布明显不同的合成字形。
|
|
if name_variant == 1:
|
|
for x in (62, 68, 76, 84, 96, 108, 122):
|
|
img[12:28, x:x + 2, :] = 35
|
|
img[13:15, 62:126, :] = 35
|
|
img[21:23, 68:110, :] = 35
|
|
else:
|
|
for y in (12, 16, 21, 26):
|
|
img[y:y + 2, 62:132, :] = 35
|
|
for offset in range(16):
|
|
img[11 + offset, 136 + offset:138 + offset, :] = 35
|
|
return img
|
|
|
|
def test_same_avatar_different_name_patterns_are_isolated(self):
|
|
bot = self._bot()
|
|
first = bot._session_fingerprint(self._row(1), 32)
|
|
second = bot._session_fingerprint(self._row(2), 32)
|
|
|
|
self.assertEqual(16, len(first))
|
|
self.assertEqual(16, len(second))
|
|
self.assertEqual(first[:8], second[:8], "测试前提:头像哈希必须相同")
|
|
self.assertNotEqual(first, second)
|
|
|
|
def test_small_render_noise_and_label_inversion_are_normalized(self):
|
|
bot = self._bot()
|
|
original = self._row(1)
|
|
canonical = bot._session_fingerprint(original, 32)
|
|
|
|
rng = np.random.default_rng(20260723)
|
|
noise = rng.integers(-2, 3, size=original.shape, dtype=np.int16)
|
|
variant = np.clip(original.astype(np.int16) + noise, 0, 255).astype(np.uint8)
|
|
# 模拟选中态:文字区前景/背景同时反相,绝对边缘强度仍应稳定。
|
|
variant[9:30, 58:190, :] = 255 - variant[9:30, 58:190, :]
|
|
|
|
self.assertEqual(canonical, bot._session_fingerprint(variant, 32))
|
|
|
|
def test_new_composite_fingerprint_never_merges_into_legacy_avatar_key(self):
|
|
bot = self._bot()
|
|
composite = bot._session_fingerprint(self._row(1), 32)
|
|
legacy = composite[:8]
|
|
|
|
bot._known_fps = {legacy}
|
|
self.assertEqual(composite, bot._canonical_fp(composite))
|
|
self.assertIn(composite, bot._known_fps)
|
|
|
|
def test_identical_visible_rows_use_isolated_fallbacks(self):
|
|
bot = self._bot()
|
|
first_row = self._row(1)
|
|
second_row = first_row.copy()
|
|
# 消息预览属于可见行兜底摘要,但不参与稳定昵称签名。
|
|
first_row[38:45, 70:100, :] = 60
|
|
second_row[38:45, 120:160, :] = 60
|
|
image = np.vstack((first_row, second_row))
|
|
|
|
first = bot._session_fingerprint(image, 32)
|
|
second = bot._session_fingerprint(image, 96)
|
|
self.assertEqual(16, len(first))
|
|
self.assertEqual(16, len(second))
|
|
self.assertNotEqual(first, second)
|
|
|
|
def test_out_of_bounds_fallback_is_16_bytes_and_image_specific(self):
|
|
bot = self._bot()
|
|
first_img = np.zeros((8, 10, 3), dtype=np.uint8)
|
|
second_img = first_img.copy()
|
|
second_img[0, 0, 0] = 1
|
|
|
|
first = bot._session_fingerprint(first_img, 0)
|
|
second = bot._session_fingerprint(second_img, 0)
|
|
self.assertEqual(16, len(first))
|
|
self.assertEqual(16, len(second))
|
|
self.assertNotEqual(first, second)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|