import base64,json,os,sqlite3,subprocess,threading,time,uuid,re from http.server import BaseHTTPRequestHandler,ThreadingHTTPServer from pathlib import Path from PIL import Image import requests root=Path('D:/web/dj');data=root/'integration-work/hd-test-tracking-2';data.mkdir(exist_ok=True) assert not (data/'huobao.sqlite3').exists() stage=root/'integration-work/stage-4.0.11/app';counts={};posts=[] def jid(n):return base64.urlsafe_b64encode(('gpu3:'+str(uuid.UUID(int=n))).encode()).decode().rstrip('=') ids={jid(n):n for n in range(1,10)} with sqlite3.connect(root/'huobao-data/huobao.sqlite3') as src,sqlite3.connect(data/'huobao.sqlite3') as db: src.backup(db);db.execute('DELETE FROM sys_task') db.execute("UPDATE ai_service_configs SET base_url='http://127.0.0.1:18890',api_key='test-only'") db.execute("UPDATE characters SET image_url='static/images/newer.png' WHERE id=26") for n in range(1,10): image=n in [4,5,8];status='failed' if n in [1,3,4,7] else 'completed' if n==5 else 'processing';code='generation_failed' if n==3 else 'user_cancelled' if n==7 else None db.execute('INSERT INTO sys_task(id,type,drama_id,character_id,provider,model,config_id,config_base_url,task_id,status,stage,error_code,error_msg,params,created_at,updated_at) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)', (n,'image' if image else 'video',6,26 if n in [4,5] else None,'openai' if image else 'minimax','qwen_image_2_1' if image else 'MiniMax-H3',6 if image else 4,'http://127.0.0.1:18890',jid(n),status,'polling',code,'Timeout: polling attempts exhausted' if n in [1,4] else None,'{}','2026-11-01T00:00:00Z','2026-11-01T00:00:00Z')) (data/'static/images').mkdir(parents=True);Image.new('RGB',(64,64),(0,180,0)).save(data/'static/images/newer.png');Image.new('RGB',(64,64),(180,0,0)).save(data/'old-result.png') source=(root/'huobao-source/desktop/build/backend.mjs').read_text(encoding='utf-8') source=source.replace('const require = __hbCreateRequire(import.meta.url)',"const require = __hbCreateRequire("+json.dumps(str(stage/'resources/app.asar/build/backend.mjs'))+")",1) source=re.sub(r'^import (\w+) from "(better-sqlite3|sharp)";',r'const \1 = require("\2");',source,flags=re.M) needle='url2.hostname === "192.168.110.111" && ["18792", "18794"].includes(url2.port)';assert source.count(needle)==1 source=source.replace(needle,'url2.hostname === "127.0.0.1" && ["18890"].includes(url2.port)') needle='if (local) return { attempts: Infinity, intervalMs: 1e4, maxDurationMs: null };';assert source.count(needle)==1 source=source.replace(needle,'if (local) return { attempts: Infinity, intervalMs: 5, maxDurationMs: null };') (data/'backend.mjs').write_text(source,encoding='utf-8') class Mock(BaseHTTPRequestHandler): def log_message(self,*args):pass def do_POST(self):posts.append(self.path);self.send_error(500) def do_GET(self): if self.path in ['/old.png','/video.mp4']: p=data/'old-result.png' if self.path=='/old.png' else root/'integration-work/hd-test-regenerate/static/videos/hd-test-original.mp4' raw=p.read_bytes();self.send_response(200);self.send_header('Content-Type','image/png' if self.path.endswith('png') else 'video/mp4');self.send_header('Content-Length',str(len(raw)));self.end_headers();self.wfile.write(raw);return n=ids[self.path.split('/')[-1]];counts[n]=counts.get(n,0)+1 if n==6 and counts[n]<3:self.send_error(503);return state='missing' if n==2 else 'queued' if n==9 or counts[n]<360 else 'succeeded' if n==4:state='succeeded' if counts[n]>150 else 'queued' if n==8:state='failed' if n in [4,8]:payload={'status':'completed' if state=='succeeded' else 'failed' if state=='failed' else 'processing','worker_state':state,'image_url':'http://127.0.0.1:18890/old.png','error':{'message':'actual failure'}} else:payload={'task':{'status':'succeeded' if state=='succeeded' else 'running','worker_state':state,'duration':4,'content':{'url':'http://127.0.0.1:18890/video.mp4'}}} raw=json.dumps(payload).encode();self.send_response(200);self.send_header('Content-Type','application/json');self.end_headers();self.wfile.write(raw) server=ThreadingHTTPServer(('127.0.0.1',18890),Mock);threading.Thread(target=server.serve_forever,daemon=True).start() env={**os.environ,'ELECTRON_RUN_AS_NODE':'1','PORT':'18889','HUOBAO_DATA_DIR':str(data),'SQLITE_PATH':str(data/'huobao.sqlite3'),'FRONTEND_DIST':str(stage/'resources/frontend'),'WORKSPACE_PATH':str(stage/'resources/workspace-template'),'FFMPEG_BIN':str(stage/'resources/bin/ffmpeg.exe'),'FFPROBE_BIN':str(stage/'resources/bin/ffprobe.exe')} log=(data/'backend.log').open('w',encoding='utf-8');process=subprocess.Popen([str(stage/'HuobaoDrama.exe'),str(data/'backend.mjs')],env=env,cwd=data,stdout=log,stderr=log,creationflags=subprocess.CREATE_NO_WINDOW) try: for _ in range(450): if process.poll() is not None:raise AssertionError('Test backend exited; inspect backend.log') with sqlite3.connect(data/'huobao.sqlite3') as db:states=dict(db.execute('SELECT id,status FROM sys_task')) if states.get(1)==states.get(4)==states.get(6)=='completed' and counts.get(9,0)>360:break time.sleep(.1) else:raise AssertionError((states,counts)) with sqlite3.connect(data/'huobao.sqlite3') as db: assert db.execute('SELECT error_code FROM sys_task WHERE id=2').fetchone()[0]=='upstream_missing' assert db.execute('SELECT error_code FROM sys_task WHERE id=8').fetchone()[0]=='generation_failed' assert db.execute('SELECT image_url FROM characters WHERE id=26').fetchone()[0]=='static/images/newer.png' assert db.execute('SELECT local_path FROM sys_task WHERE id=4').fetchone()[0] assert db.execute('SELECT status,stage FROM sys_task WHERE id=9').fetchone()==('processing','queued') assert dict(db.execute('SELECT id,task_id FROM sys_task'))=={n:jid(n) for n in range(1,10)} assert counts.get(3,0)==counts.get(5,0)==counts.get(7,0)==0 and not posts result={'passed':True,'same_ids':True,'no_submissions':True,'counts':counts,'late_image_preserves_newer':True};(data/'result.json').write_text(json.dumps(result,indent=2));print(json.dumps(result)) finally: process.terminate();process.wait(timeout=10);server.shutdown();log.close()