Não pode escolher mais do que 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 

86 linhas
5.0 KiB

  1. import fs from 'node:fs';
  2. import path from 'node:path';
  3. import crypto from 'node:crypto';
  4. import assert from 'node:assert/strict';
  5. import { fileURLToPath } from 'node:url';
  6. import { login, root } from './role-permissions-live.mjs';
  7. export { login };
  8. export const work = path.resolve(root, '../.codex-tmp/scene-closure-20260906');
  9. export const report = path.join(root, 'ute2e/reports/scene-editor-closure-20260906');
  10. export const fixturePath = path.join(work, 'fixtures.json');
  11. fs.mkdirSync(work, { recursive: true }); fs.mkdirSync(report, { recursive: true });
  12. export const fixture = () => JSON.parse(fs.readFileSync(fixturePath, 'utf8'));
  13. export function writeFixture(value) {
  14. const lock = `${fixturePath}.lock`;
  15. let handle;
  16. for (let attempt=0; attempt<100; attempt++) {
  17. try { handle=fs.openSync(lock,'wx'); break; }
  18. catch (error) { if(error.code!=='EEXIST')throw error; Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,20); }
  19. }
  20. if(handle===undefined)throw new Error('Scene fixture registry is busy');
  21. try {
  22. const existing=fs.existsSync(fixturePath)?fixture():{};
  23. const merged={...existing,...value};
  24. for(const key of ['projects','extraUsers','roleIds']) {
  25. const entries=[...(existing[key]||[]),...(value[key]||[])];
  26. if(entries.length)merged[key]=[...new Map(entries.map(item=>[typeof item==='object'?String(item.id):String(item),item])).values()];
  27. }
  28. fs.writeFileSync(fixturePath,JSON.stringify(merged,null,2));
  29. } finally { fs.closeSync(handle); fs.unlinkSync(lock); }
  30. }
  31. export const hash = value => crypto.createHash('sha256').update(JSON.stringify(value)).digest('hex');
  32. export async function call(endpoint, token, body, method = body === undefined ? 'GET' : 'POST') {
  33. const response = await fetch(`http://127.0.0.1:6180/api/${endpoint}`, {
  34. method, headers: { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}) },
  35. body: body === undefined ? undefined : JSON.stringify(body), signal: AbortSignal.timeout(120000),
  36. });
  37. const value = await response.json();
  38. return { status: response.status, code: value.code, data: value.data, message: value.message };
  39. }
  40. export async function teacherLogin() {
  41. const user = fixture().user;
  42. const response = await call('auth/v1/auth/login', null, { userId: user.id, password: user.password, roleCode: 'teacher', departmentId: user.departmentId, rememberMe: false });
  43. assert.equal(response.status, 200, 'Scene review teacher signs in');
  44. return response.data;
  45. }
  46. export function registerProject(project) {
  47. const f = fixture();
  48. if (!f.projects.some(p => p.id === project.id)) f.projects.push({ id: project.id, name: project.name, code: project.code, ownerId: project.ownerId });
  49. writeFixture(f);
  50. }
  51. export async function freshScene(token, key = 'fox', suffix = '') {
  52. const f = fixture(), source = f.originals[key];
  53. const result = await call(`tran/v1/content/projects/${source.id}/copy`, token, { name: `场景闭环-${key}-${suffix}-${Date.now()}`, version: source.version });
  54. assert.equal(result.status, 200, `Copy scene: ${result.message}`);
  55. registerProject(result.data.project);
  56. return result.data;
  57. }
  58. async function prepare() {
  59. if (fs.existsSync(fixturePath) && !fixture().cleanedAt && fixture().user) throw new Error('Scene fixtures already active; reuse existing fixtures');
  60. const session = await login('root'), token = session.accessToken;
  61. const f = { createdAt: new Date().toISOString(), originals: {}, projects: [], user: null };
  62. writeFixture(f);
  63. try {
  64. for (const [key, id] of [['fox', '160'], ['truck', '159'], ['foxModel', '157'], ['truckModel', '156']]) {
  65. const original = await call(`tran/v1/content/projects/${id}`, token);
  66. assert.equal(original.status, 200);
  67. f.originals[key] = { id, name: original.data.project.name, version: original.data.project.version, hash: hash(original.data) };
  68. fs.writeFileSync(path.join(work, `original-${key}.json`), JSON.stringify(original.data, null, 2));
  69. writeFixture(f);
  70. }
  71. const me = (await call('auth/v1/auth/me', token)).data;
  72. const roles = (await call('auth/v1/roles/all', token)).data;
  73. const teacher = (roles.records || roles).find(r => r.code === 'teacher');
  74. const password = `Sc!${crypto.randomBytes(7).toString('hex')}`;
  75. const made = await call('auth/v1/users', token, {
  76. username: `rv_scene_${crypto.randomBytes(4).toString('hex')}`, displayName: '场景编辑器闭环验收',
  77. departmentId: me.departmentId, roleIds: [teacher.id], defaultRoleId: teacher.id,
  78. enabled: true, password, remark: 'UTE2E-SCENE-CLOSURE-20260906',
  79. });
  80. assert.equal(made.status, 200, `Temporary teacher: ${made.message}`);
  81. f.user = { ...made.data, password }; writeFixture(f);
  82. console.log(JSON.stringify({ prepared: true, originals: Object.fromEntries(Object.entries(f.originals).map(([k,v])=>[k,v.id])) }));
  83. } finally { await call('auth/v1/auth/logout', token, {}).catch(()=>{}); }
  84. }
  85. if (process.argv[1] === fileURLToPath(import.meta.url)) prepare().catch(e => { console.error(e.message); process.exitCode = 1; });