139 lines
7.3 KiB
Python
139 lines
7.3 KiB
Python
"""Recipient discovery regression: synthetic databases, no native calls or sends."""
|
|
import contextlib
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import sqlite3
|
|
import tempfile
|
|
import unittest
|
|
from unittest import mock
|
|
|
|
import review_assistant_contacts as contacts
|
|
|
|
ACCOUNT = '168800000000001'
|
|
ACTIVE = {'ready': True, 'account_id': ACCOUNT, 'account_name': 'Test', 'reason': ''}
|
|
|
|
|
|
class ContactDirectoryTest(unittest.TestCase):
|
|
def setUp(self):
|
|
self.temp = tempfile.TemporaryDirectory()
|
|
self.addCleanup(self.temp.cleanup)
|
|
self.root = Path(self.temp.name)
|
|
self.folder = self.root / ACCOUNT
|
|
self.folder.mkdir()
|
|
with contextlib.closing(sqlite3.connect(self.folder / 'message.db')) as db, db:
|
|
db.execute('CREATE TABLE message_table(conversation_id TEXT,send_time REAL,content TEXT)')
|
|
db.executemany('INSERT INTO message_table VALUES(?,?,?)', [
|
|
('M:101', 1700000000, 'MESSAGE_BODY_NOT_READ'),
|
|
('S:1_2', 1700000010, 'internal'), ('R:10', 1700000020, 'group')])
|
|
self.write_user('''CREATE TABLE user_table(id TEXT,name TEXT,real_name TEXT,account TEXT);
|
|
INSERT INTO user_table VALUES('168800000000001','托管账号','','');
|
|
CREATE TABLE wechat_contactV1(wxid TEXT,gene INTEGER,name TEXT);
|
|
INSERT INTO wechat_contactV1 VALUES('wxid_one',101,'同名助理');
|
|
INSERT INTO wechat_contactV1 VALUES('wxid_two',999,'同名助理');
|
|
CREATE TABLE wx_friend(user_id TEXT,nick_name TEXT,wx_id TEXT);
|
|
CREATE TABLE external_user_relation_v3(user_id TEXT,remarks TEXT,real_remarks TEXT);''')
|
|
self.patch = mock.patch.object(contacts, 'active_context', return_value=ACTIVE)
|
|
self.identity = self.patch.start()
|
|
self.addCleanup(self.patch.stop)
|
|
contacts._CACHE.clear()
|
|
|
|
def write_user(self, sql):
|
|
with contextlib.closing(sqlite3.connect(self.folder / 'user.db')) as db, db:
|
|
db.executescript(sql)
|
|
|
|
def listing(self, *args, **kwargs):
|
|
return contacts.list_recipients(ACCOUNT, *args, cache_roots=[self.root], **kwargs)
|
|
|
|
def test_directory_contacts_are_visible_but_never_fabricate_send_targets(self):
|
|
result = self.listing()
|
|
self.assertTrue(result['ready'])
|
|
self.assertEqual((result['total'], result['selectable_total'], result['contact_total']), (3, 1, 2))
|
|
self.assertEqual({i['id'] for i in result['items'] if i['selectable']}, {'M:101'})
|
|
self.assertNotIn('M:999', {i['id'] for i in result['items']})
|
|
self.assertNotIn('MESSAGE_BODY_NOT_READ', json.dumps(result))
|
|
for item in result['items']:
|
|
if not item['selectable']:
|
|
self.assertIn('刷新消息库', item['unavailableReason'])
|
|
self.assertFalse(contacts.validate_recipient(ACCOUNT,item['id'],cache_roots=[self.root])['ok'])
|
|
|
|
def test_directory_search_and_pagination_keep_same_names_separate(self):
|
|
result = self.listing('同名助理', limit=1)
|
|
self.assertEqual(result['total'],2)
|
|
self.assertTrue(result['has_more'])
|
|
second = self.listing('同名助理',limit=1,offset=1)
|
|
self.assertNotEqual(result['items'][0]['id'],second['items'][0]['id'])
|
|
self.assertEqual(self.listing('wxid_two')['items'][0]['id'],'contact:wxid_two')
|
|
|
|
def test_explicit_unique_friend_relation_supplies_name_and_search_aliases(self):
|
|
self.write_user("INSERT INTO wx_friend VALUES('101','助理备注','wxid_one');")
|
|
result = self.listing()
|
|
self.assertEqual(result['total'],2)
|
|
target = next(i for i in result['items'] if i['id']=='M:101')
|
|
self.assertEqual(target['name'],'助理备注')
|
|
self.assertTrue(target['selectable'])
|
|
self.assertEqual(self.listing('wxid_one')['items'][0]['id'],'M:101')
|
|
self.assertTrue(contacts.validate_recipient(ACCOUNT,'M:101',cache_roots=[self.root])['ok'])
|
|
|
|
def test_ambiguous_friend_mapping_does_not_merge_contacts(self):
|
|
for rows in ["('101','','wxid_one'),('202','','wxid_one')",
|
|
"('101','','wxid_one'),('101','','wxid_two')"]:
|
|
with self.subTest(rows=rows):
|
|
self.write_user('DELETE FROM wx_friend;INSERT INTO wx_friend VALUES'+rows+';')
|
|
contacts._CACHE.clear()
|
|
result=self.listing()
|
|
self.assertEqual(result['contact_total'],2)
|
|
self.assertEqual(next(i for i in result['items'] if i['id']=='M:101')['name'],'微信联系人')
|
|
|
|
def test_friend_relation_without_existing_message_does_not_enable_sending(self):
|
|
self.write_user("INSERT INTO wx_friend VALUES('999','助理','wxid_two');")
|
|
result=self.listing('wxid_two')
|
|
self.assertFalse(result['items'][0]['selectable'])
|
|
self.assertFalse(contacts.validate_recipient(ACCOUNT,'M:999',cache_roots=[self.root])['ok'])
|
|
|
|
def test_remark_and_nickname_both_searchable_for_exact_existing_uid(self):
|
|
self.write_user("INSERT INTO user_table VALUES('101','助理昵称','','');INSERT INTO external_user_relation_v3 VALUES('101','助理备注','');")
|
|
for query in ['助理昵称','助理备注','M:101']:
|
|
self.assertEqual(self.listing(query)['items'][0]['id'],'M:101')
|
|
self.assertEqual(self.listing('助理备注')['items'][0]['name'],'助理备注')
|
|
|
|
def test_legacy_database_without_addressbook_tables_is_supported(self):
|
|
self.write_user('DROP TABLE wechat_contactV1;DROP TABLE wx_friend;DROP TABLE external_user_relation_v3;')
|
|
self.assertEqual(self.listing()['total'],1)
|
|
|
|
def test_refresh_invalidates_cache_after_database_change(self):
|
|
self.assertEqual(self.listing()['total'],3)
|
|
self.write_user("INSERT INTO wechat_contactV1 VALUES('wxid_new',123,'新助理');")
|
|
self.assertEqual(self.listing('新助理')['total'],1)
|
|
|
|
def test_latest_wal_revision_wins_between_account_caches(self):
|
|
other=self.root/'other'/ACCOUNT
|
|
other.mkdir(parents=True)
|
|
from shutil import copyfile
|
|
copyfile(self.folder/'message.db',other/'message.db')
|
|
copyfile(self.folder/'user.db',other/'user.db')
|
|
os.utime(self.folder/'user.db',ns=(100,100))
|
|
os.utime(other/'user.db',ns=(200,200))
|
|
wal=Path(str(self.folder/'user.db')+'-wal')
|
|
wal.write_bytes(b'file-time-only')
|
|
os.utime(wal,ns=(300,300))
|
|
self.assertEqual(contacts._databases(ACCOUNT,[self.root,self.root/'other'])['user.db'],self.folder/'user.db')
|
|
|
|
def test_default_roots_include_protocol_cache_before_archived_copy(self):
|
|
with mock.patch.object(contacts,'application_data_dir',return_value=self.root):
|
|
self.assertEqual(contacts._roots()[0],self.root/'wxwork_reply_cache')
|
|
|
|
def test_other_account_directory_is_never_read(self):
|
|
self.identity.return_value={**ACTIVE,'account_id':'168800000000002'}
|
|
with mock.patch.object(contacts,'_contacts') as read:
|
|
self.assertFalse(self.listing()['ready'])
|
|
read.assert_not_called()
|
|
|
|
def test_lookup_does_not_change_database_files(self):
|
|
before={p.name:p.read_bytes() for p in self.folder.glob('*.db')}
|
|
self.listing(); contacts.validate_recipient(ACCOUNT,'M:101',cache_roots=[self.root])
|
|
self.assertEqual(before,{p.name:p.read_bytes() for p in self.folder.glob('*.db')})
|
|
|
|
|
|
if __name__=='__main__':
|
|
unittest.main() |