import test from 'node:test'; import assert from 'node:assert/strict'; import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; const adminRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const routesPath = path.join(adminRoot, 'src/routes.ts'); const layoutPath = path.join(adminRoot, 'src/layout.tsx'); const localePath = path.join(adminRoot, 'src/locale/index.ts'); const navBarPath = path.join(adminRoot, 'src/components/NavBar/index.tsx'); const subscribersPagePath = path.join(adminRoot, 'src/pages/platform/subscribers/index.tsx'); const renewalsPagePath = path.join(adminRoot, 'src/pages/platform/renewals/index.tsx'); const renewalsLocalePath = path.join( adminRoot, 'src/pages/platform/renewals/locale/index.ts' ); const subscriptionsPagePath = path.join(adminRoot, 'src/pages/platform/subscriptions/index.tsx'); const EMOJI_RE = /[\u{1F300}-\u{1F9FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/u; test('1. routes.ts 必须配置 platform 平台总控与会员/续费/订阅三大路由', () => { const content = fs.readFileSync(routesPath, 'utf-8'); assert.ok(content.includes("key: 'platform'"), 'routes.ts 必须包含 platform 模块'); assert.ok(content.includes("key: 'platform/subscribers'"), 'routes.ts 必须包含 会员列表 路由'); assert.ok(content.includes("key: 'platform/renewals'"), 'routes.ts 必须包含 续费列表 路由'); assert.ok(content.includes("key: 'platform/subscriptions'"), 'routes.ts 必须包含 订阅管理 路由'); }); test('2. layout.tsx 必须为 platform 模块配置专属图标', () => { const content = fs.readFileSync(layoutPath, 'utf-8'); assert.ok(content.includes("case 'platform':"), 'layout.tsx 必须处理 platform 模块的图标'); }); test('3. locale/index.ts 必须包含平台总控相关中英文词条', () => { const content = fs.readFileSync(localePath, 'utf-8'); assert.ok(content.includes("'menu.platform':"), '缺少 menu.platform 国际化词条'); assert.ok(content.includes("'menu.platform.subscribers':"), '缺少 会员列表 国际化词条'); assert.ok(content.includes("'menu.platform.renewals':"), '缺少 续费列表 国际化词条'); assert.ok(content.includes("'menu.platform.subscriptions':"), '缺少 订阅管理 国际化词条'); }); test('4. NavBar 组件必须展示超级管理员 [最高权限] 身份标签', () => { const content = fs.readFileSync(navBarPath, 'utf-8'); assert.ok( content.includes('超级管理员') || content.includes('Platform Super Admin') || content.includes('最高权限'), 'NavBar 必须展示超级管理员或最高权限身份标识' ); }); test('5. 平台总控三大页面文件必须存在且包含核心 Arco 组件与 API 路由', () => { assert.ok(fs.existsSync(subscribersPagePath), '必须存在 platform/subscribers/index.tsx'); assert.ok(fs.existsSync(renewalsPagePath), '必须存在 platform/renewals/index.tsx'); assert.ok(fs.existsSync(subscriptionsPagePath), '必须存在 platform/subscriptions/index.tsx'); const subContent = fs.readFileSync(subscribersPagePath, 'utf-8'); assert.ok(subContent.includes('/api/admin/team/platform/subscribers'), '会员列表必须调用对应后端接口'); assert.ok(subContent.includes('Table'), '会员列表必须使用 Arco Table'); const renewContent = fs.readFileSync(renewalsPagePath, 'utf-8'); assert.ok(renewContent.includes('/api/admin/team/platform/renewals'), '续费列表必须调用对应后端接口'); assert.ok(renewContent.includes('Table'), '续费列表必须使用 Arco Table'); const planContent = fs.readFileSync(subscriptionsPagePath, 'utf-8'); assert.ok(planContent.includes('/api/admin/team/platform/plans'), '订阅管理必须调用对应后端接口'); assert.ok(planContent.includes('Card'), '订阅管理必须使用 Arco Card'); }); test('6. P39: 续费列表状态与到期日必须来自订阅接口,禁止演示假数据', () => { assert.ok(fs.existsSync(renewalsLocalePath), '必须存在 platform/renewals/locale/index.ts'); const content = fs.readFileSync(renewalsPagePath, 'utf-8'); const localeContent = fs.readFileSync(renewalsLocalePath, 'utf-8'); // 状态 / 到期日来自订阅会员接口 assert.ok( content.includes('/api/admin/team/platform/subscribers/page'), '状态与到期日必须请求 /api/admin/team/platform/subscribers/page' ); assert.ok(content.includes('expireDate'), '列表必须绑定接口 expireDate'); assert.ok( content.includes('dataIndex: \'status\'') || content.includes('dataIndex: "status"'), '列表必须绑定接口 status' ); assert.ok(content.includes('SubscriberItem'), '必须使用 SubscriberItem 类型'); assert.ok(content.includes('code === 1000') || content.includes('code !== 1000'), '必须按 Cool-Admin code 校验'); // 代续费仍走续费接口 assert.ok( content.includes('/api/admin/team/platform/renewals/manual-renew'), '代续费必须调用 renewals/manual-renew' ); // 禁止写死日期 / 演示行 / 假租户 assert.ok(!content.includes('即将到期'), '禁止硬编码「即将到期」演示行'); assert.ok(!content.includes('Vanguard'), '禁止硬编码演示租户名'); assert.ok(!/\b2026-\d{2}-\d{2}\b/.test(content), '禁止硬编码到期日 YYYY-MM-DD'); assert.ok(!/\b2099-\d{2}-\d{2}\b/.test(content), '禁止硬编码演示到期日'); assert.ok(!content.includes('ORD-2026'), '禁止硬编码演示订单号'); // 失败必须空态,不得保留假列表 assert.ok( content.includes('setData([])') || content.includes('setData([] as'), '接口失败必须 setData([]) 清空列表' ); assert.ok( content.includes('setTotal(0)') || content.includes('setTotal(0 as'), '接口失败必须 setTotal(0)' ); // 文案走 locale assert.ok(content.includes("from './locale'") || content.includes('from "./locale"'), '必须引入页面 locale'); assert.ok(content.includes('useLocale(locale)'), '必须 useLocale(locale)'); const localeKeys = [ 'menu.platform.renewals', 'platformRenewals.request.fail', 'platformRenewals.col.status', 'platformRenewals.col.expireDate', 'platformRenewals.empty', 'platformRenewals.status.active', 'platformRenewals.status.past_due', ]; for (const key of localeKeys) { assert.ok(localeContent.includes(key), `语言包必须包含: ${key}`); assert.ok(content.includes(key), `页面必须引用 locale key: ${key}`); } assert.ok(localeContent.includes("'en-US'"), 'locale 必须含 en-US'); assert.ok(localeContent.includes("'zh-CN'"), 'locale 必须含 zh-CN'); assert.ok(localeContent.includes("'ar-SA'"), 'locale 必须含 ar-SA'); assert.ok(!/\bany\b/.test(content), '严禁 any'); assert.ok(!EMOJI_RE.test(content), '页面严禁 emoji'); assert.ok(!EMOJI_RE.test(localeContent), '语言包严禁 emoji'); assert.ok( !content.includes('Mock.js') && !content.includes("from 'mockjs'"), '禁止用 Mock.js 假数据' ); });