import fs from 'node:fs'; import path from 'node:path'; import crypto from 'node:crypto'; import assert from 'node:assert/strict'; import {report,work} from './scene-editor-closure-live.mjs'; import {sceneUnitPlan} from './scene-editor-evidence-bindings.mjs'; const readLog=name=>{const b=fs.readFileSync(path.join(work,name));return b.subarray(0,2).equals(Buffer.from([255,254]))?b.toString('utf16le').slice(1):b.toString('utf8').replace(/^\uFEFF/,'')}; const read=name=>JSON.parse(fs.readFileSync(path.join(report,name),'utf8')); const sha=file=>crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex'); const write=(name,value)=>fs.writeFileSync(path.join(report,name),JSON.stringify(value,null,2)); const results=read('results.json'); function tests(suites){return suites.flatMap(s=>[...(s.specs||[]).flatMap(p=>p.tests.map(t=>({title:p.title,status:t.status,result:t.results.at(-1)}))),...tests(s.suites||[])])} const cases=tests(results.suites); assert.ok(cases.length>0);assert.equal(results.errors.length,0);assert.ok(cases.every(t=>t.status==='expected'&&t.result.status==='passed'&&t.result.retry===0),'One full no-retry browser run must pass'); const runtimeLog=readLog('node-tests-final.log'),apiLog=readLog('scene-api-tests-final.log'); const totals=text=>Object.fromEntries(['tests','pass','fail','skipped'].map(key=>[key,Number(new RegExp(`^# ${key} (\\d+)`,'m').exec(text)?.[1])])); const runtime=totals(runtimeLog),client=totals(apiLog); for(const group of [runtime,client]){assert.ok(group.tests>0);assert.equal(group.pass,group.tests);assert.equal(group.fail,0);assert.equal(group.skipped,0)} const build=readLog('web-build-final.log');assert.match(build,/built in/);assert.match(build,/legacy model iframe verified/); const apiBuild=readLog('api-build.log');assert.match(apiBuild,/BUILD SUCCESS/); const apiReports=path.resolve(report,'../../../unreal_tran_api/ut_tran/target/surefire-reports'); const backend={tests:0,failures:0,errors:0,skipped:0,suites:0}; for(const file of fs.readdirSync(apiReports).filter(f=>/^TEST-.*\.xml$/.test(f))){ const head=fs.readFileSync(path.join(apiReports,file),'utf8').match(/]*>/)?.[0];assert.ok(head); backend.suites++;for(const key of ['tests','failures','errors','skipped'])backend[key]+=Number(new RegExp(`\\b${key}="(\\d+)"`).exec(head)?.[1]||0); } assert.ok(backend.tests>0);assert.equal(backend.failures+backend.errors+backend.skipped,0); const runId=`scene-editor-${results.stats.startTime}`; const coverage=sceneUnitPlan.map(([file,title,featureIds])=>{ assert.ok(`${runtimeLog}\n${apiLog}`.includes(`- ${title}\n`)||`${runtimeLog}\n${apiLog}`.includes(`- ${title}\r\n`),`Executed unit proof: ${title}`); return{kind:'unit',status:'PASS',runId,testTitle:title,featureIds,source:file,note:'实际执行真实方法/纯函数合同,未冒称浏览器操作'}; }); write('validation.json',{time:new Date().toISOString(),runId,overallStatus:'PASS',finalRun:true, scope:'默认路由的119项功能;浏览器证据与聚焦单元合同分别计数。较大功能扩展未实施。', e2e:{tests:cases.length,pass:cases.length,fail:0,retries:0,startTime:results.stats.startTime,durationMs:results.stats.duration,complete:true}, priorFullRun:{pass:32,fail:1,reason:'关闭保存失败用例的提示定位同时匹配成功和失败消息,已限定目标错误;另补列表搜索遮罩消失后截图的等待条件。',privateArchived:true,countedAsFinal:false}, secondFullRun:{pass:32,fail:1,reason:'并行加载同源模型时恢复顺序不稳定;已保留文档对象顺序,并以实例编号验证部件隔离和重开。',privateArchived:true,countedAsFinal:false}, frontend:{status:'PASS',command:'pnpm run build',legacyModelPayloadUnchanged:true}, backend:{status:'PASS',command:'mvn -pl ut_tran -am test package',...backend}, runtime:{status:'PASS',...runtime},client:{status:'PASS',...client},coverage, database:{ddlChanged:false,reason:'未新增或修改表结构;仅注册并清理测试副本、账号和角色。'}, logSha256:Object.fromEntries(['web-build-final.log','api-build.log','node-tests-final.log','scene-api-tests-final.log'].map(file=>[file,sha(path.join(work,file))]))}); const captions=read('screenshot-captions.json'); const titles=cases.flatMap(t=>(t.result.attachments||[]).map(a=>({name:a.name,title:t.title}))); for(const file of fs.readdirSync(path.join(report,'screenshots'))){ if(!captions[file]){const base=file.replace(/\.[^.]+$/,'');const found=titles.find(a=>a.name===base||a.name===file);assert.ok(found,`Current screenshot attachment ${file}`);captions[file]=`${found.title}:${base}`;} } write('screenshot-captions.json',captions); console.log(JSON.stringify({e2e:cases.length,backend:backend.tests,node:runtime.tests+client.tests,unitProofs:coverage.length,runId}));