Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 

76 строки
5.7 KiB

  1. import fs from 'node:fs';
  2. import path from 'node:path';
  3. import assert from 'node:assert/strict';
  4. import { call, login, fixture, writeFixture, report, work, hash } from './scene-editor-closure-live.mjs';
  5. const data=fixture(), session=await login('root'), token=session.accessToken;
  6. const result={time:new Date().toISOString(),status:'PENDING',originals:[],projects:[],users:[],roles:[]};
  7. const originalSnapshots=new Map();
  8. function authoredSnapshot(detail) {
  9. const project={...detail.project};
  10. // Dependency creation/removal refreshes these two derived bookkeeping fields.
  11. // Keep every authored field, version, asset and dependency in the comparison.
  12. delete project.usageCount;delete project.updateTime;
  13. return {...detail,project};
  14. }
  15. try {
  16. const me=(await call('auth/v1/auth/me',token)).data;
  17. const originals=new Set(Object.values(data.originals).map(o=>String(o.id)));
  18. const owners=new Set([String(me.id),String(data.user?.id),...(data.extraUsers||[]).map(u=>String(u.id))]);
  19. for(const [key,original] of Object.entries(data.originals)) {
  20. const baseline=JSON.parse(fs.readFileSync(path.join(work,`original-${key}.json`),'utf8'));
  21. assert.equal(hash(baseline),original.hash,`Original snapshot integrity ${original.id}`);
  22. originalSnapshots.set(String(original.id),baseline);
  23. const current=await call(`tran/v1/content/projects/${original.id}`,token);
  24. assert.equal(current.status,200);assert.equal(hash(authoredSnapshot(current.data)),hash(authoredSnapshot(baseline)),`Original ${original.id} authored data unchanged`);
  25. result.originals.push({id:original.id,name:original.name,version:original.version,unchanged:true,scope:'全部创作字段、版本、资产、依赖;引用统计与其触发的更新时间单独核对',
  26. before:{usageCount:baseline.project.usageCount,updateTime:baseline.project.updateTime}});
  27. }
  28. // Training copies can depend on scene copies; remove dependents first.
  29. const registrations=[...data.projects].sort((a,b)=>Number(b.id)-Number(a.id));
  30. for(const registered of registrations) {
  31. const id=String(registered.id);assert.ok(!originals.has(id));
  32. const found=await call(`tran/v1/content/projects/${id}`,token);
  33. if(found.status===404){result.projects.push({id,alreadyAbsent:true});continue;}
  34. assert.equal(found.status,200);let p=found.data.project;
  35. assert.equal(p.code,registered.code,`Registered project identity ${id}`);
  36. assert.ok(p.name.startsWith('场景闭环-'),`Review-owned name ${id}`);
  37. assert.ok(owners.has(String(p.ownerUserId)),`Review-owned actor ${id}`);
  38. assert.ok(p.addTime>=Math.floor(Date.parse(data.createdAt)/1000),`Review creation time ${id}`);
  39. let withdrawn=false;
  40. if(p.status==='PUBLISHED'){
  41. const revoked=await call(`tran/v1/content/projects/${id}/review`,token,{action:'REVOKE',version:p.version,comment:'场景编辑器闭环验收完成,清理独立测试副本'});
  42. assert.equal(revoked.status,200,`Revoke isolated ${id}: ${revoked.message}`);p=revoked.data.project;withdrawn=true;
  43. }
  44. const removed=await call(`tran/v1/content/projects/${id}?version=${p.version}`,token,undefined,'DELETE');
  45. assert.equal(removed.status,200,`Remove isolated ${id}: ${removed.message}`);
  46. result.projects.push({id,name:p.name,withdrawn,removed:true});
  47. }
  48. for(const user of [data.user,...(data.extraUsers||[])].filter(Boolean)){
  49. assert.ok(/^(rv_scene_|scene_perm_)/.test(user.username),'Only registered review users');
  50. const current=await call(`auth/v1/users/${user.id}`,token);
  51. if(current.status===404){result.users.push({id:user.id,alreadyAbsent:true});continue;}
  52. assert.equal(current.status,200);assert.equal(current.data.username,user.username);assert.equal(current.data.isProtected,false);
  53. const removed=await call(`auth/v1/users/${user.id}`,token,{version:current.data.dataVersion??current.data.version},'DELETE');
  54. assert.equal(removed.status,200,`Remove isolated user: ${removed.message}`);result.users.push({id:user.id,removed:true});
  55. }
  56. for(const id of data.roleIds||[]){
  57. const current=await call(`auth/v1/roles/${id}`,token);
  58. if(current.status===404){result.roles.push({id,alreadyAbsent:true});continue;}
  59. assert.equal(current.status,200);assert.ok(String(current.data.code).startsWith('scene_'));
  60. const removed=await call(`auth/v1/roles/${id}`,token,{version:current.data.dataVersion??current.data.version},'DELETE');
  61. assert.equal(removed.status,200,`Remove isolated role: ${removed.message}`);result.roles.push({id,removed:true});
  62. }
  63. for(const original of result.originals){
  64. const baseline=originalSnapshots.get(String(original.id)),current=await call(`tran/v1/content/projects/${original.id}`,token);
  65. assert.equal(current.status,200);assert.equal(hash(authoredSnapshot(current.data)),hash(authoredSnapshot(baseline)));
  66. assert.equal(current.data.project.usageCount,baseline.project.usageCount,`Original ${original.id} reference count restored`);
  67. original.after={usageCount:current.data.project.usageCount,updateTime:current.data.project.updateTime};
  68. original.fullRecordUnchanged=hash(current.data)===hash(baseline);
  69. original.referenceCountRestored=true;
  70. }
  71. result.note='测试副本增删会刷新原件引用统计及更新时间;创作内容、版本、资产和依赖未改写,引用数量已恢复,未回填或伪造原更新时间。';
  72. result.status='PASS';data.cleanedAt=result.time;writeFixture(data);
  73. console.log(JSON.stringify({status:'PASS',originals:result.originals.length,projects:result.projects.length,users:result.users.length,roles:result.roles.length}));
  74. }catch(error){result.status='FAIL';result.error=error.message;process.exitCode=1;console.error(error.message);}
  75. finally{fs.writeFileSync(path.join(report,'cleanup.json'),JSON.stringify(result,null,2));await call('auth/v1/auth/logout',token,{}).catch(()=>{});}