|
- import fs from 'node:fs';
- import path from 'node:path';
- import assert from 'node:assert/strict';
- import { call, login, fixture, writeFixture, report, work, hash } from './scene-editor-closure-live.mjs';
- const data=fixture(), session=await login('root'), token=session.accessToken;
- const result={time:new Date().toISOString(),status:'PENDING',originals:[],projects:[],users:[],roles:[]};
- const originalSnapshots=new Map();
- function authoredSnapshot(detail) {
- const project={...detail.project};
- // Dependency creation/removal refreshes these two derived bookkeeping fields.
- // Keep every authored field, version, asset and dependency in the comparison.
- delete project.usageCount;delete project.updateTime;
- return {...detail,project};
- }
- try {
- const me=(await call('auth/v1/auth/me',token)).data;
- const originals=new Set(Object.values(data.originals).map(o=>String(o.id)));
- const owners=new Set([String(me.id),String(data.user?.id),...(data.extraUsers||[]).map(u=>String(u.id))]);
- for(const [key,original] of Object.entries(data.originals)) {
- const baseline=JSON.parse(fs.readFileSync(path.join(work,`original-${key}.json`),'utf8'));
- assert.equal(hash(baseline),original.hash,`Original snapshot integrity ${original.id}`);
- originalSnapshots.set(String(original.id),baseline);
- const current=await call(`tran/v1/content/projects/${original.id}`,token);
- assert.equal(current.status,200);assert.equal(hash(authoredSnapshot(current.data)),hash(authoredSnapshot(baseline)),`Original ${original.id} authored data unchanged`);
- result.originals.push({id:original.id,name:original.name,version:original.version,unchanged:true,scope:'全部创作字段、版本、资产、依赖;引用统计与其触发的更新时间单独核对',
- before:{usageCount:baseline.project.usageCount,updateTime:baseline.project.updateTime}});
- }
- // Training copies can depend on scene copies; remove dependents first.
- const registrations=[...data.projects].sort((a,b)=>Number(b.id)-Number(a.id));
- for(const registered of registrations) {
- const id=String(registered.id);assert.ok(!originals.has(id));
- const found=await call(`tran/v1/content/projects/${id}`,token);
- if(found.status===404){result.projects.push({id,alreadyAbsent:true});continue;}
- assert.equal(found.status,200);let p=found.data.project;
- assert.equal(p.code,registered.code,`Registered project identity ${id}`);
- assert.ok(p.name.startsWith('场景闭环-'),`Review-owned name ${id}`);
- assert.ok(owners.has(String(p.ownerUserId)),`Review-owned actor ${id}`);
- assert.ok(p.addTime>=Math.floor(Date.parse(data.createdAt)/1000),`Review creation time ${id}`);
- let withdrawn=false;
- if(p.status==='PUBLISHED'){
- const revoked=await call(`tran/v1/content/projects/${id}/review`,token,{action:'REVOKE',version:p.version,comment:'场景编辑器闭环验收完成,清理独立测试副本'});
- assert.equal(revoked.status,200,`Revoke isolated ${id}: ${revoked.message}`);p=revoked.data.project;withdrawn=true;
- }
- const removed=await call(`tran/v1/content/projects/${id}?version=${p.version}`,token,undefined,'DELETE');
- assert.equal(removed.status,200,`Remove isolated ${id}: ${removed.message}`);
- result.projects.push({id,name:p.name,withdrawn,removed:true});
- }
- for(const user of [data.user,...(data.extraUsers||[])].filter(Boolean)){
- assert.ok(/^(rv_scene_|scene_perm_)/.test(user.username),'Only registered review users');
- const current=await call(`auth/v1/users/${user.id}`,token);
- if(current.status===404){result.users.push({id:user.id,alreadyAbsent:true});continue;}
- assert.equal(current.status,200);assert.equal(current.data.username,user.username);assert.equal(current.data.isProtected,false);
- const removed=await call(`auth/v1/users/${user.id}`,token,{version:current.data.dataVersion??current.data.version},'DELETE');
- assert.equal(removed.status,200,`Remove isolated user: ${removed.message}`);result.users.push({id:user.id,removed:true});
- }
- for(const id of data.roleIds||[]){
- const current=await call(`auth/v1/roles/${id}`,token);
- if(current.status===404){result.roles.push({id,alreadyAbsent:true});continue;}
- assert.equal(current.status,200);assert.ok(String(current.data.code).startsWith('scene_'));
- const removed=await call(`auth/v1/roles/${id}`,token,{version:current.data.dataVersion??current.data.version},'DELETE');
- assert.equal(removed.status,200,`Remove isolated role: ${removed.message}`);result.roles.push({id,removed:true});
- }
- for(const original of result.originals){
- const baseline=originalSnapshots.get(String(original.id)),current=await call(`tran/v1/content/projects/${original.id}`,token);
- assert.equal(current.status,200);assert.equal(hash(authoredSnapshot(current.data)),hash(authoredSnapshot(baseline)));
- assert.equal(current.data.project.usageCount,baseline.project.usageCount,`Original ${original.id} reference count restored`);
- original.after={usageCount:current.data.project.usageCount,updateTime:current.data.project.updateTime};
- original.fullRecordUnchanged=hash(current.data)===hash(baseline);
- original.referenceCountRestored=true;
- }
- result.note='测试副本增删会刷新原件引用统计及更新时间;创作内容、版本、资产和依赖未改写,引用数量已恢复,未回填或伪造原更新时间。';
- result.status='PASS';data.cleanedAt=result.time;writeFixture(data);
- console.log(JSON.stringify({status:'PASS',originals:result.originals.length,projects:result.projects.length,users:result.users.length,roles:result.roles.length}));
- }catch(error){result.status='FAIL';result.error=error.message;process.exitCode=1;console.error(error.message);}
- finally{fs.writeFileSync(path.join(report,'cleanup.json'),JSON.stringify(result,null,2));await call('auth/v1/auth/logout',token,{}).catch(()=>{});}
|