import assert from 'node:assert/strict'; import fs from 'node:fs'; import path from 'node:path'; import test from 'node:test'; import { fileURLToPath } from 'node:url'; import vm from 'node:vm'; import ts from 'typescript'; const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const fromProjectRoot = (...segments) => path.join(projectRoot, ...segments); function loadResources() { const compiled = ts.transpileModule(fs.readFileSync(fromProjectRoot('src/api/resources.ts'), 'utf8'), { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, }).outputText; const resourceModule = { exports: {} }; vm.runInNewContext(compiled, { module: resourceModule, exports: resourceModule.exports }); return resourceModule.exports.resources; } function loadResourceForm() { const source = fs.readFileSync(fromProjectRoot('src/api/resource-form.ts'), 'utf8'); const compiled = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, }).outputText; const resourceModule = { exports: {} }; vm.runInNewContext(compiled, { module: resourceModule, exports: resourceModule.exports }); return resourceModule.exports; } test('资源字段声明保留数字、布尔、时间与文本类型', () => { const resources = loadResources(); const field = (resource, key) => resources.find((item) => item.name === resource).fields.find((item) => item.key === key); assert.equal(field('ec_product', 'price_amount').type, 'number'); assert.equal(field('ec_product_image', 'is_cover').type, 'boolean'); assert.equal(field('delivery_track', 'started_at').type, 'datetime'); assert.equal(field('dev_telemetry', 'payload').type, 'textarea'); }); test('表单载荷构造器省略空的可选关系并转换字段类型', async () => { const resourceFormPath = fromProjectRoot('src/api/resource-form.ts'); assert.ok(fs.existsSync(resourceFormPath), 'resource-form.ts should define the payload boundary'); const resourceForm = loadResourceForm(); const fields = [ { key: 'gas_basic_identity', label: '气站', type: 'identity' }, { key: 'quantity', label: '数量', type: 'number' }, { key: 'selected', label: '选中', type: 'boolean' }, ]; assert.deepEqual( JSON.parse(JSON.stringify(resourceForm.buildResourcePayload(fields, { gas_basic_identity: '', quantity: '2', selected: false, }))), { quantity: 2, selected: false }, ); }); test('安全规则与订单明细的文本字段允许任意文本提交', () => { const resources = loadResources(); const { buildResourcePayload } = loadResourceForm(); const fields = (name) => resources.find((item) => item.name === name).fields; assert.deepEqual( JSON.parse(JSON.stringify(buildResourcePayload(fields('safe_rule'), { rule_code: 'pressure-limit', threshold: '{invalid}', action: 'close-valve', gray_scope: 'north region only', }))), { rule_code: 'pressure-limit', threshold: '{invalid}', action: 'close-valve', gray_scope: 'north region only', }, ); assert.deepEqual( JSON.parse(JSON.stringify(buildResourcePayload(fields('ec_order_item'), { ec_order_identity: 'order-a', ec_product_identity: 'product-a', product_snapshot: 'arbitrary product snapshot text', quantity: 2, sale_amount: 500, }))), { ec_order_identity: 'order-a', ec_product_identity: 'product-a', product_snapshot: 'arbitrary product snapshot text', quantity: 2, sale_amount: 500, }, ); }); test('日期按 RFC3339 提交,密码只在创建时必填并提交', () => { const { buildResourcePayload, isResourceFieldRequired } = loadResourceForm(); const fields = [ { key: 'bill_date', label: '账单日期', type: 'date', required: true }, { key: 'started_at', label: '开始时间', type: 'datetime' }, { key: 'username', label: '用户名', type: 'text', required: true }, { key: 'password', label: '密码', type: 'password', required: true }, ]; assert.equal(isResourceFieldRequired(fields[3], 'create'), true); assert.equal(isResourceFieldRequired(fields[3], 'edit'), false); assert.deepEqual( JSON.parse(JSON.stringify(buildResourcePayload(fields, { bill_date: '2026-07-27', started_at: '2026-07-27T10:30:00Z', username: 'operator', password: 'secret', }, 'create'))), { bill_date: '2026-07-27T00:00:00.000Z', started_at: '2026-07-27T10:30:00.000Z', username: 'operator', password: 'secret', }, ); assert.deepEqual( JSON.parse(JSON.stringify(buildResourcePayload(fields, { bill_date: '2026-07-27', username: 'operator', password: 'replacement-must-not-be-sent', }, 'edit'))), { bill_date: '2026-07-27T00:00:00.000Z', username: 'operator', }, ); }); test('已删除的审批模块不再保留前端资源或页面', () => { const source = fs.readFileSync(fromProjectRoot('src/views/shared/ReadOnlyListPage.vue'), 'utf8'); const resources = fs.readFileSync(fromProjectRoot('src/api/resources.ts'), 'utf8'); assert.doesNotMatch(resources, /audit_approval|audit_operation_log|audit_export_log/); assert.equal(fs.existsSync(fromProjectRoot('src/views/audit')), false); }); test('树页面通过资源归档接口归档节点', () => { const source = fs.readFileSync(fromProjectRoot('src/views/shared/TreePage.vue'), 'utf8'); assert.match(source, /resourceApi\.archive/); assert.match(source, /Modal\.(warning|confirm)/); }); test('route access uses authenticated role and assigned menu codes', () => { const routeDir = fromProjectRoot('src/router/routes/modules'); const routeSource = fs.readdirSync(routeDir) .filter((name) => name.endsWith('.ts')) .map((name) => fs.readFileSync(path.join(routeDir, name), 'utf8')) .join('\n'); const userStore = fs.readFileSync(fromProjectRoot('src/store/modules/user/index.ts'), 'utf8'); const permission = fs.readFileSync(fromProjectRoot('src/hooks/permission.ts'), 'utf8'); assert.doesNotMatch(routeSource, /roles:\s*\[\s*['"]\*['"]\s*\]/); assert.match(routeSource, /menuCode:\s*['"]finance['"]/); assert.match(userStore, /profile\.role_code/); assert.match(userStore, /menuCodes/); assert.match(permission, /menuCode/); }); test('platform role UI replaces assigned menu identities through the singular contract URL', () => { const resources = fs.readFileSync(fromProjectRoot('src/api/resources.ts'), 'utf8'); const crudPage = fs.readFileSync(fromProjectRoot('src/views/shared/CrudListPage.vue'), 'utf8'); const platformApi = fs.readFileSync(fromProjectRoot('src/api/platform.ts'), 'utf8'); assert.match(resources, /platform_role[\s\S]*\/platform\/platform_role\/:identity\/menu/); assert.match(resources, /menu-identities/); assert.match(crudPage, /multiple/); assert.match(crudPage, /menu_identities/); assert.match(platformApi, /\/platform\/platform_role\/\$\{identity\}\/menu/); assert.match(platformApi, /method:\s*['"]PUT['"]/); }); test('platform role UI permits an empty menu selection to revoke every assignment', () => { const resources = loadResources(); const { buildResourcePayload, isMissingField } = loadResourceForm(); const role = resources.find((item) => item.name === 'platform_role'); const menuField = role.detailActions .flatMap((action) => action.fields) .find((field) => field.key === 'menu_identities'); assert.equal(menuField.required, undefined); assert.equal(isMissingField([]), true); assert.deepEqual( JSON.parse(JSON.stringify(buildResourcePayload([menuField], { menu_identities: [], }))), { menu_identities: [] }, ); });