perf(report): load side wrinkle layers after report entry
This commit is contained in:
@@ -61,7 +61,12 @@ try {
|
||||
assert.equal(result.report_saved, true);
|
||||
assert(result.vm && result.presentation);
|
||||
assert.equal(result.wrinkle_views.version,'independent-side-wrinkles-v1');
|
||||
assert.equal(result.wrinkle_views.left.status,'ready');assert.equal(result.wrinkle_views.right.status,'ready');
|
||||
assert.equal(result.wrinkle_views.left.status,'pending');assert.equal(result.wrinkle_views.right.status,'pending');
|
||||
assert(result.wrinkle_job.token);
|
||||
const layers = await (await api.post(base+'api/wrinkle-views',{data:{token:result.wrinkle_job.token}})).json();
|
||||
assert.equal(layers.ok,true);assert.equal(layers.wrinkle_views.left.status,'ready');assert.equal(layers.wrinkle_views.right.status,'ready');
|
||||
const cachedLayers = await (await api.post(base+'api/wrinkle-views',{data:{token:result.wrinkle_job.token}})).json();
|
||||
assert.deepEqual(cachedLayers,layers);
|
||||
const expectedTotal = Math.round(result.vm.dims.firm.S * 4 + result.vm.dims.vita.S * 3 + result.vm.dims.stab.S * 3);
|
||||
assert.equal(result.vm.total, expectedTotal);
|
||||
assert.notEqual(expectedTotal, result.result.score_info.total_score, 'fixture distinguishes weighted score from API total');
|
||||
@@ -104,7 +109,8 @@ try {
|
||||
const snapshot = await (await api.get(base + 'api/reports/' + result.record_id)).json();
|
||||
assert.equal(snapshot.report.photos.length, 3);
|
||||
assert.deepEqual(snapshot.report.presentation, result.presentation);
|
||||
assert.deepEqual(snapshot.report.payload.wrinkle_views,result.wrinkle_views);
|
||||
assert.deepEqual(snapshot.report.payload.wrinkle_views,layers.wrinkle_views);
|
||||
assert.equal(snapshot.report.payload.wrinkle_job.status,'complete');
|
||||
assert.deepEqual(snapshot.report.payload.result.face_maps, result.result.face_maps);
|
||||
assert.equal(snapshot.report.photos[0], fixture.photos[0]);
|
||||
assert.equal(snapshot.report.photos[1],fixture.photos[0]); // This HTTP test submits the same fixture for all three views.
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
// Real HTTP + report UI. All provider calls are delayed in-memory fixtures.
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';import os from 'node:os';import path from 'node:path';
|
||||
import {spawn,execSync} from 'node:child_process';
|
||||
import {createRequire} from 'node:module';import {fileURLToPath,pathToFileURL} from 'node:url';
|
||||
const require=createRequire(import.meta.url),h5=fileURLToPath(new URL('../',import.meta.url));
|
||||
const dir=fs.mkdtempSync(path.join(os.tmpdir(),'juzi-wrinkle-async-'));
|
||||
const owner=require('./wechat-fixture.cjs')(path.join(dir,'accounts'))[0];
|
||||
const {chromium}=await import(pathToFileURL(execSync('npm root -g',{encoding:'utf8'}).trim()+'/playwright/index.mjs').href);
|
||||
const child=spawn(process.execPath,['-e',`
|
||||
const fs=require('fs'),vm=require('vm'),path=require('path'),w={};vm.runInNewContext(fs.readFileSync('assets/mock/report-fixture.js','utf8'),{window:w});const f=w.MOCK_FIXTURE;
|
||||
global.fetch=async(url,o)=>{const side=o.body.get('return_maps')==='texture_enhanced_lines';
|
||||
fs.appendFileSync(path.join(process.env.JUZI_DATA_ROOT,'provider-calls.jsonl'),JSON.stringify({side})+'\\n');
|
||||
if(side)await new Promise(r=>setTimeout(r,1000));
|
||||
return{ok:true,status:200,json:async()=>({result:f.result,face_rectangle:f.face_rectangle,left_side_result:f.side.left,right_side_result:f.side.right})};};require('./server.js');
|
||||
`],{cwd:h5,env:{...process.env,HOST:'127.0.0.1',PORT:'18142',JUZI_DATA_ROOT:dir,NODE_ENV:'test',SKIN_API_KEY:'fixture',SKIN_API_SECRET:'fixture',PUBLIC_BASE_URL:'',WECHAT_APP_ID:'',WECHAT_APP_SECRET:''},stdio:['ignore','pipe','pipe']});
|
||||
let browser;
|
||||
try {
|
||||
await new Promise((resolve,reject)=>{const t=setTimeout(()=>reject(Error('startup timeout')),10000);child.stdout.on('data',b=>{if(b.toString().includes('已启动')){clearTimeout(t);resolve();}});child.once('exit',()=>{clearTimeout(t);reject(Error('server exit'));});});
|
||||
const base='http://127.0.0.1:18142/';browser=await chromium.launch({headless:true});const page=await browser.newPage();const errors=[];page.on('pageerror',e=>errors.push(e.message));
|
||||
await page.context().addCookies([{name:'juzi_session',value:owner.token,url:base}]);
|
||||
const source=fs.readFileSync(path.join(h5,'js/app.js'),'utf8');
|
||||
await page.route('**/js/app.js*',r=>r.fulfill({contentType:'text/javascript',body:source.replace(/\}\)\(\);\s*$/,`
|
||||
window.__asyncScreen=showScreen;
|
||||
window.__startAsyncReport=function(){beginCaptureJourney();['front','right','left'].forEach(trackCapture);photos=Array(3).fill(window.MOCK_FIXTURE.photos[0]);camSeq++;showScreen('s-cam');submitPhotos(camSeq);};})();`)}));
|
||||
await page.addInitScript(()=>{const f=window.fetch;window.__annotationStarts=[];window.fetch=function(url,options){if(String(url).includes('api/wrinkle-views'))window.__annotationStarts.push({report:!!document.querySelector('#s-report.active'),scan:!!document.querySelector('#p-analyzing.show')});return f.call(this,url,options);};});
|
||||
await page.goto(base);await page.waitForFunction(()=>AccountClient.user());await page.addScriptTag({url:base+'assets/mock/report-fixture.js'});
|
||||
const primary=page.waitForResponse(r=>r.url().endsWith('/api/skin-analyze'));await page.evaluate(()=>window.__startAsyncReport());
|
||||
const result=await(await primary).json();assert.equal(result.ok,true);assert.equal(result.wrinkle_views.left.status,'pending');
|
||||
assert.equal(fs.readFileSync(path.join(dir,'provider-calls.jsonl'),'utf8').trim().split('\n').length,1,'main response cannot wait for side paid calls');
|
||||
await page.waitForSelector('#s-report.active');const conclusion=await page.locator('#ditem-para2').textContent();
|
||||
await page.waitForFunction(()=>document.querySelector('#wrinkle-layer-note').textContent.includes('加载中'));
|
||||
await page.waitForFunction(()=>window.__annotationStarts.length===1);
|
||||
await page.evaluate(()=>window.__asyncScreen('s-splash'));
|
||||
const api=page.context().request;
|
||||
const cached=await(await api.post(base+'api/wrinkle-views',{data:{token:result.wrinkle_job.token}})).json();assert.equal(cached.ok,true);
|
||||
await page.waitForTimeout(100);
|
||||
assert.equal(await page.locator('#s-splash.active').count(),1,'late annotation response cannot navigate');
|
||||
assert.equal(await page.locator('#wrinkle-left').getAttribute('data-has-annotation'),'false','hidden report is not patched by stale response');
|
||||
await page.evaluate(()=>window.__asyncScreen('s-report'));
|
||||
await page.waitForFunction(()=>['left','right'].every(v=>document.querySelector('#wrinkle-'+v).dataset.hasAnnotation==='true'));
|
||||
assert.equal(await page.locator('#ditem-para2').textContent(),conclusion);
|
||||
assert.deepEqual(await page.evaluate(()=>window.__annotationStarts),[{report:true,scan:false}]);
|
||||
const stored=await(await api.get(base+'api/reports/'+result.record_id)).json();assert.equal(stored.report.payload.wrinkle_job.status,'complete');assert.deepEqual(stored.report.vm,result.vm);assert.deepEqual(stored.report.presentation,result.presentation);
|
||||
const other=await browser.newContext();await other.request.post(base+'api/auth/browser',{data:{consent:true}});assert.equal((await other.request.post(base+'api/wrinkle-views',{data:{token:result.wrinkle_job.token}})).status(),404);await other.close();
|
||||
assert.equal(fs.readFileSync(path.join(dir,'provider-calls.jsonl'),'utf8').trim().split('\n').length,3,'main + exactly two side calls, cache/access denial never repeat');
|
||||
assert.equal((await api.get(base+'data/wrinkle-jobs/'+result.wrinkle_job.token+'.json')).status(),403);
|
||||
await page.route('**/api/wrinkle-views',r=>r.fulfill({status:503,json:{ok:false}}));
|
||||
await page.evaluate(()=>window.__startAsyncReport());await page.waitForSelector('#s-report.active');
|
||||
await page.waitForFunction(()=>document.querySelector('#wrinkle-layer-note').textContent.includes('部分侧脸未获取'));
|
||||
assert.equal(await page.locator('#s-report.active').count(),1,'optional annotation failure keeps report accessible');
|
||||
assert.equal(await page.locator('#ditem-para2').textContent(),conclusion);
|
||||
assert.equal(fs.readFileSync(path.join(dir,'provider-calls.jsonl'),'utf8').trim().split('\n').length,4,'failed optional endpoint adds no provider retry');
|
||||
assert.deepEqual(errors,[]);console.log('PASS: fast main report, side requests only after active report, pending/ready UI, unchanged conclusions/scores, owner isolation, snapshot cache, exactly 3 fixture calls');
|
||||
} finally {if(browser)await browser.close();child.kill();}
|
||||
@@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
const assert=require('node:assert/strict'),fs=require('node:fs'),os=require('node:os'),path=require('node:path');
|
||||
const create=require('../lib/wrinkle-jobs');
|
||||
const dir=fs.mkdtempSync(path.join(os.tmpdir(),'juzi-wrinkle-jobs-'));
|
||||
const owner={id:'owner',kind:'wechat'},guest={id:'guest',kind:'browser'};
|
||||
const ready={version:'independent-side-wrinkles-v1',left:{status:'ready'},right:{status:'ready'}};
|
||||
(async()=>{
|
||||
let calls=0,finish,completed=0;
|
||||
const deps={dir,collect:()=>{calls++;return new Promise(r=>{finish=r;});},load:()=>({}),complete:()=>{completed++;}};
|
||||
const jobs=create(deps),job=jobs.register(owner,'Rtest',{});assert.equal(calls,0,'registration cannot start paid work');
|
||||
assert.equal(await jobs.run({id:'intruder',kind:'wechat'},job.token),null);
|
||||
const a=jobs.run(owner,job.token),b=jobs.run(owner,job.token);assert.equal(calls,1);finish(ready);
|
||||
assert.deepEqual(await a,ready);assert.deepEqual(await b,ready);assert.equal(completed,1);
|
||||
assert.deepEqual(await jobs.run(owner,job.token),ready);assert.equal(calls,1);
|
||||
const restarted=create({...deps,collect:()=>{throw Error('must use persistent cache');}});
|
||||
assert.deepEqual(await restarted.run(owner,job.token),ready);
|
||||
const before=fs.readdirSync(dir);const g=jobs.register(guest,'Rguest',{});
|
||||
const gr=jobs.run(guest,g.token);finish(ready);await gr;
|
||||
assert.deepEqual(fs.readdirSync(dir),before,'guest jobs/photos/results never persist');
|
||||
const pending=jobs.register(owner,'Rpending',{});
|
||||
const reload=create({...deps,collect:async()=>ready});assert.deepEqual(await reload.run(owner,pending.token),ready,'pending restart reloads own saved snapshot');
|
||||
const running=jobs.register(owner,'Rrunning',{});const old=jobs.run(owner,running.token);
|
||||
const afterCrash=create({...deps,collect:()=>{throw Error('must not charge twice');}});
|
||||
assert.equal((await afterCrash.run(owner,running.token)).left.error_code,'WRINKLE_JOB_INTERRUPTED');finish(ready);await old;
|
||||
const failed=create({...deps,collect:async()=>{throw Error('provider failed');}});const f=failed.register(owner,'Rfailed',{});
|
||||
assert.equal((await failed.run(owner,f.token)).left.error_code,'WRINKLE_JOB_FAILED');
|
||||
assert.equal(await failed.run(owner,'../bad'),null);
|
||||
console.log('PASS: lazy owner-bound single-flight jobs, durable success/failure cache, crash no-duplicate-charge policy, pending recovery, guest RAM-only');
|
||||
})().catch(e=>{console.error(e);process.exitCode=1;});
|
||||
Reference in New Issue
Block a user