"""Local SQLite -> desktop decoder -> gateway request, with no live services.""" import io import json import sqlite3 import tempfile from pathlib import Path import unittest from unittest import mock import ai_chat from wxwork_db import WXWorkDB from wxwork_message_browser import _content_label def varint(value): output = bytearray() while value > 127: output.append((value & 127) | 128) value >>= 7 output.append(value) return bytes(output) def field(number, value): if isinstance(value, str): value = value.encode('utf-8') return varint(number << 3 | 2) + varint(len(value)) + value def text_record(text): return field(1, b'\x08\x00' + field(2, field(1, text))) class DesktopPlaintextFlowTest(unittest.TestCase): def setUp(self): temporary = tempfile.TemporaryDirectory() self.addCleanup(temporary.cleanup) self.root = Path(temporary.name) path = self.root / '100' / 'Data' path.mkdir(parents=True) self.connection = sqlite3.connect(path / 'message.db') self.addCleanup(self.connection.close) self.connection.execute('CREATE TABLE message_table(sender_id TEXT, conversation_id TEXT, ' 'content_type INTEGER, send_time REAL, content BLOB, server_id TEXT)') def add(self, sender, kind, content): index = self.connection.execute('SELECT count(*) FROM message_table').fetchone()[0] + 1 self.connection.execute('INSERT INTO message_table VALUES(?,?,?,?,?,?)', (sender, 'M:200', kind, 1789000000 + index, content, str(index))) self.connection.commit() def reader(self): reader = WXWorkDB(str(self.root), {}, str(self.root / 'cache')) self.addCleanup(reader.close) return reader def test_raw_database_records_become_readable_before_gateway_transport(self): self.add('200', 0, text_record('在')) self.add('100', 0, text_record('收到')) secret_reference = '*1*' + 'SyntheticMediaReferenceOnly' * 30 self.add('200', 529, field(2, '朋友圈分享') + field(4, field(5, '合成动态正文') + field(6, field(2, secret_reference)))) self.add('100', 0, text_record('请提供读数')) self.add('200', 0, text_record('14.1')) reader = self.reader() context = reader.get_conversation_context(reader._conv_display_name('100', 'M:200'), account='100', conv_id='M:200', limit=100) self.assertEqual(context['messages'][0]['content'], '在') self.assertEqual(context['last_message']['content'], '14.1') self.assertIn('合成动态正文', context['text']) self.assertNotIn(secret_reference, context['text']) self.assertNotIn(text_record('14.1').hex(), context['text']) requests = [] def urlopen(request, timeout=None): requests.append(json.loads(request.data.decode('utf-8'))) return io.BytesIO(json.dumps({'reply': '合成测试完成'}).encode('utf-8')) provider = ai_chat.Provider(kind='gateway', base_url='https://synthetic.invalid/v1/answer', api_key='synthetic-token', model='synthetic-model') with mock.patch('urllib.request.urlopen', side_effect=urlopen), \ mock.patch('backend_client.device_id', return_value='synthetic-device'), \ mock.patch('ai_chat._system_prompt', return_value='合成测试规则'), \ mock.patch('ai_chat._finalize_reply', side_effect=lambda value, *a: value): ai_chat.call_ai_text(context['text'], [], provider) payload = requests[0] self.assertEqual(payload['customer_text'], '14.1') self.assertIn('请提供读数', payload['messages'][-1]['content']) self.assertNotIn(secret_reference, json.dumps(payload)) self.assertNotIn('【本轮', payload['customer_text']) def test_browser_keeps_short_text_but_never_displays_image_transport_paths(self): self.assertEqual(_content_label(text_record('?'), 0), '?') self.assertEqual(_content_label('123456', 0), '123456') raw = field(1, '/storage/emulated/0/Android/data/synthetic/cache/photo.jpg') label = _content_label(raw, 3) self.assertIn('图片', label) self.assertNotIn('storage', label) def test_unreadable_text_is_explicit_and_cannot_trigger_protocol_model_call(self): self.add('200', 0, b'\xff\xfe\x00\x01') message = self.reader().get_new_messages(0)[0] self.assertEqual(message['content_status'], 'unsupported') self.assertNotIn('fffe', message['content']) try: import protocol_engine except ModuleNotFoundError: return # The separate visual-only workspace has no protocol engine. self.assertFalse(protocol_engine._is_text_message(message)) self.assertEqual(protocol_engine._unanswered_manual_reason({'messages': [message]}), protocol_engine.MANUAL_DECODE_REASON) def test_guard_payload_remains_a_guard_prompt(self): sent = [] def urlopen(request, timeout=None): sent.append(json.loads(request.data)) return io.BytesIO(b'{"reply":"ok"}') prompt = '界面识别合成测试' provider = ai_chat.Provider(kind='gateway', base_url='https://synthetic.invalid/v1/answer', api_key='synthetic-token', model='synthetic-model') with mock.patch('urllib.request.urlopen', side_effect=urlopen), \ mock.patch('backend_client.device_id', return_value='synthetic-device'): ai_chat._gateway_completion([{'role': 'user', 'content': prompt}], [], provider, purpose='guard') self.assertEqual(sent[0]['customer_text'], prompt) self.assertEqual(sent[0]['purpose'], 'guard')