Files
dj/integration-work/test_tracking_recovery.py

64 lines
6.0 KiB
Python

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()