from pathlib import Path import hashlib,json,os,sys,tempfile,types from unittest import mock from PyInstaller.archive.readers import CArchiveReader root=Path('C:/wechat_rpa') folder=Path('C:/kefu/deploy/plaintext-chat-20260918') app=root/'dist/ZhenAI-WeCom-Assistant-v1.4.29' exe=app/'ZhenAI-WeCom-Assistant-v1.4.29.exe' manifest=json.loads((folder/'build-source-manifest.json').read_text(encoding='utf-8')) assert sys.version_info[:2]==(3,12) assert all(hashlib.sha256((root/name).read_bytes()).hexdigest()==digest for name,digest in manifest.items()) archive=CArchiveReader(str(exe));pyz=archive.open_embedded_archive('PYZ.pyz') assert pyz.extract('review_assistant') assert pyz.extract('review_assistant_contacts') def code_fingerprint(code): if isinstance(code, types.CodeType): return (code.co_code, code.co_names, code.co_varnames, code.co_freevars, code.co_cellvars, code.co_argcount, code.co_posonlyargcount, code.co_kwonlyargcount, code.co_flags, code.co_exceptiontable, tuple(code_fingerprint(item) for item in code.co_consts)) if isinstance(code, tuple): return tuple(code_fingerprint(item) for item in code) return code plan=json.loads((folder/'plaintext-chat-release-plan.json').read_text(encoding='utf-8-sig')) assert plan.get('finalized') is True and plan.get('version')=='1.4.29' checked_modules=plan['frozenModules'] assert {'review_assistant_contacts','review_assistant','review_state','reply_session_policy','protocol_engine','wechat_bot','reply_database','wxwork_db','voice_messages','voice_transcription','wechat_gui_qt','app_version','contact_reply_guard','contact_relation'} <= set(checked_modules) for module_name in checked_modules: source = root / (module_name + '.py') compiled = compile(source.read_text(encoding='utf-8-sig'), str(source), 'exec', optimize=1) assert code_fingerprint(pyz.extract(module_name)) == code_fingerprint(compiled), module_name sender=pyz.extract('wecom_native_sender') functions={item.co_name:item for item in sender.co_consts if isinstance(item,types.CodeType)} needed=['_file_identity','_path_file_identity','_hash_client_file'] assert all(name in functions for name in needed) assert 'stat' not in functions['_path_file_identity'].co_names assert 'fstat' in functions['_path_file_identity'].co_names assert '_path_file_identity' in functions['_hash_client_file'].co_names env={'Path':Path,'os':os,'hashlib':hashlib,'NativeUnavailable':type('NativeUnavailable',(RuntimeError,),{})} for name in needed:env[name]=types.FunctionType(functions[name],env) with tempfile.TemporaryDirectory(prefix='frozen-identity-',dir=folder) as temp: p=Path(temp)/'fixture.bin';content=b'packaged file identity regression\x00'*4096;p.write_bytes(content) os.utime(p,ns=(1_600_000_000_000_000_000,1_600_000_000_000_000_000)) with mock.patch.object(Path,'stat',side_effect=AssertionError('Path.stat must not be used for handle identity')): actual,identity=env['_hash_client_file'](p) assert actual==hashlib.sha256(content).hexdigest() assert identity[2]==len(content) private={'wxwork_keys.json','protocol_sends.sqlite3','pending_replies.protocol.json','pending_replies.json','conversations.json','backend_connection.json','runtime_settings.json','desktop_account.json','app_settings.json','review_assistant.sqlite3'} private.update({'contacts.json','customers.json','customer_contacts.json','wxwork_contacts.json', 'review_assistant_recipients.json','backend_runtime.json','ai_settings.json', 'wxwork_database_config.json','wxwork_decrypt_state.json'}) private_suffixes=('.db','.db-wal','.db-shm','.db-journal','.sqlite','.sqlite-wal','.sqlite-shm', '.sqlite-journal','.sqlite3','.sqlite3-wal','.sqlite3-shm','.sqlite3-journal','.key') excluded=[] for p in app.rglob('*'): if p.is_file() and (p.name.lower() in private or p.name.lower().endswith(private_suffixes)): excluded.append(str(p.relative_to(app))) assert not excluded, 'Private contact/database/credential files found in package: '+repr(excluded) for relative in ['assets/ui/console-app.js','assets/ui/zhenyang-ai-console.html','assets/ui/console-review.css','assets/ui/console-responsive.css']: assert (app/'_internal'/relative).read_bytes()==(root/relative).read_bytes(),relative assert (app/'_internal/python312.dll').is_file() report={'version':'1.4.29','sourceManifestMatches':True,'compiledModulesMatchSource':checked_modules,'frozenFileIdentityFixPresent':True,'frozenHashSyntheticRegression':'passed','python312Bundled':True,'privateFilesExcluded':True,'contactDatabasesExcluded':True,'credentialFilesExcluded':True,'uiAssetsMatchSource':True,'runtime':sys.version,'realMessagesSent':0,'installed':False} (folder/'frozen-release-qa.json').write_text(json.dumps(report,ensure_ascii=False,indent=2),encoding='utf-8') print(json.dumps(report,ensure_ascii=False))