文件
OmniCard/admin/tests/platform-management.test.ts
wikiboss 62e0ae4435 feat: 同步管理端、用户端与后端业务,并接通线上 Web 接口
补齐平台订阅、外部登录、CRM 同步与用户端页面;Nginx 转发用户端接口,登录页使用 Google 与 Apple 标志。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-23 18:01:47 +08:00

141 行
6.9 KiB
TypeScript

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 假数据'
);
});