Files
kefu/wechat_rpa/test_message_plaintext.py
T
2026-09-21 10:34:06 +08:00

146 lines
7.2 KiB
Python

"""Synthetic message fixtures: no real contact or customer content."""
import unittest
from archive_content_parser import parse_message_content
def varint(number):
result = bytearray()
while number > 127:
result.append((number & 127) | 128)
number >>= 7
result.append(number)
return bytes(result)
def field(number, value):
if isinstance(value, int):
return varint(number << 3) + varint(value)
if isinstance(value, str):
value = value.encode('utf-8')
return varint((number << 3) | 2) + varint(len(value)) + value
def text_message(text):
return field(1, field(1, 0) + field(2, field(1, text)))
class MessagePlaintextTests(unittest.TestCase):
def assert_decoded(self, value, expected, content_type=2):
self.assertEqual(parse_message_content(value, content_type),
{'text': expected, 'status': 'decoded', 'kind': 'text'})
def test_native_short_text_is_decoded_without_loss(self):
for text in ('1', '0', '14.1', '好', '?', '??', 'OK', 'abc中文',
'3盒', '👍', '👩\u200d⚕️', 'line 1\nline 2'):
for content_type in (0, 1, 2, '文本', 'text'):
with self.subTest(text=text, content_type=content_type):
self.assert_decoded(text_message(text), text, content_type)
def test_repeated_native_text_segments_preserve_order(self):
self.assert_decoded(text_message('合成正文') + text_message('👍'), '合成正文👍')
self.assert_decoded(text_message('hello ') + text_message('world'), 'hello world')
self.assertEqual(parse_message_content(text_message('ok') + field(1, b'\xff'), 2)['status'], 'unsupported')
def test_flat_text_envelope_is_decoded(self):
self.assert_decoded(field(1, '13字符abcdefghij'), '13字符abcdefghij')
self.assert_decoded(field(1, 'abcdefghijklm'), 'abcdefghijklm')
def test_utf8_text_and_literal_hex_remain_text(self):
for value in ('1', '你好', 'OK', '1234567890abcdef', 'https://example.test/a',
'aGVsbG8=', '👩\u200d⚕️'):
with self.subTest(value=value):
self.assert_decoded(value, value)
self.assert_decoded(value.encode(), value)
def test_memoryview_and_bytearray_supported(self):
self.assert_decoded(memoryview(text_message('1')), '1')
self.assert_decoded(bytearray(text_message('OK')), 'OK')
def test_no_arbitrary_protobuf_string_extraction(self):
raw = field(50, 'this is metadata not the message')
self.assertEqual(parse_message_content(raw, 2)['status'], 'unsupported')
def test_malformed_text_never_becomes_hex(self):
for raw in (b'\xff\x00', b'\x0a\x06a', field(1, b'\x08\x00\x12\xff'),
text_message('hello') + b'\x00', b'\x00', b''):
with self.subTest(raw=raw):
parsed = parse_message_content(raw, 2)
self.assertEqual(parsed['text'], '[文本暂无法解析]')
self.assertEqual(parsed['status'], 'unsupported')
def test_size_type_and_unicode_bounds(self):
for raw in (b'a' * (128 * 1024 + 1), '\ud800', None, 123, {'content': 'hello'}):
self.assertEqual(parse_message_content(raw, 2)['status'], 'unsupported')
nested = field(1, 'hello')
for _ in range(20):
nested = field(1, nested)
self.assertEqual(parse_message_content(nested, 2)['status'], 'unsupported')
def test_opaque_reference_is_not_guessed_as_text(self):
token = '*1*' + 'AbCd0123456789+/' * 50
self.assertEqual(parse_message_content(token, 2)['status'], 'unsupported')
self.assertEqual(parse_message_content(text_message(token), 2)['status'], 'unsupported')
def test_moments_use_caption_not_longer_media_reference(self):
token = '*1*' + 'AbCd0123456789+/' * 50
raw = field(1, 2) + field(2, '分享朋友圈') + field(4,
field(5, '合成朋友圈正文') + field(6, field(2, token)) + field(19, 1))
self.assertEqual(parse_message_content(raw, 529),
{'text': '[朋友圈] 合成朋友圈正文', 'status': 'summary', 'kind': 'moments'})
def test_file_only_exposes_filename_and_size(self):
token = '*1*' + 'AbCd0123456789+/' * 50
raw = field(1, token) + field(2, r'C:\private\report.pdf') + field(4, 1024) + field(10, 'A' * 32)
parsed = parse_message_content(raw, 20)
self.assertEqual(parsed['kind'], 'file')
self.assertIn('report.pdf', parsed['text'])
self.assertIn('1.0 KB', parsed['text'])
for private in (token, 'C:', 'private', 'A' * 32):
self.assertNotIn(private, parsed['text'])
def test_media_has_typed_summary_instead_of_paths(self):
raw = field(1, '/storage/emulated/0/private/file.silk') + field(2, '*1*' + 'A' * 500)
for content_type, expected in ((3, '[图片]'), (14, '[图片]'), (123, '[图片]'),
(4, '[语音待转文字]'), (16, '[语音待转文字]'),
(8, '[视频]'), (5, '[表情]'), (111, '[文件]')):
self.assertEqual(parse_message_content(raw, content_type)['text'], expected)
def test_mini_program_summary_excludes_references(self):
card = (field(1, 'gh_opaque') + field(2, 'wx123') + field(3, '/pages/private?token=SECRET')
+ field(6, 'https://example.test/token') + field(7, '示例程序') + field(10, '示例应用'))
parsed = parse_message_content(field(1, card), 78)
self.assertEqual(parsed['text'], '[小程序] 示例程序\n应用:示例应用')
self.assertNotIn('SECRET', parsed['text'])
def test_link_title_excludes_url(self):
raw = field(1, field(1, '合成标题') + field(2, 'https://example.test/token')) + field(2, 0)
self.assertEqual(parse_message_content(raw, 31)['text'], '[链接] 合成标题')
def test_contact_status_system_notice_is_retained(self):
notice = '对方开启了朋友验证,请先发送朋友验证请求'
self.assertEqual(parse_message_content(notice.encode(), 1011)['text'], notice)
def test_card_does_not_return_json_or_metadata(self):
raw = field(1, '合成通知') + field(2, '{"private_token": "SECRET"}')
self.assertEqual(parse_message_content(raw, 573)['text'], '[应用消息] 合成通知')
def test_extra_content_never_becomes_customer_message(self):
parsed = parse_message_content(b'\xff', 2, extra_content=text_message('metadata SECRET'))
self.assertNotIn('SECRET', parsed['text'])
self.assertEqual(parsed['status'], 'unsupported')
def test_summary_fields_reject_paths_and_opaque_values(self):
for secret in ('*1*' + 'A' * 500, '/storage/emulated/0/private',
'https://example.test/SECRET', '{"secret":"SECRET"}'):
raw = field(4, field(5, secret))
self.assertEqual(parse_message_content(raw, 529)['text'], '[朋友圈]')
def test_unknown_type_cannot_surface_binary_metadata(self):
parsed = parse_message_content(field(1, 'SECRET'), 999999)
self.assertEqual(parsed, {'text': '[暂不支持的消息类型]', 'status': 'unsupported', 'kind': 'unknown'})
if __name__ == '__main__':
unittest.main()