文件
OmniCard/admin/tests/platform-subscriptions.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

206 行
7.5 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 pagePath = path.join(
adminRoot,
'src/pages/platform/subscriptions/index.tsx'
);
const localePath = path.join(
adminRoot,
'src/pages/platform/subscriptions/locale/index.ts'
);
const EMOJI_RE = /[\u{1F300}-\u{1F9FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/u;
const HARDCODED_SUCCESS_RE = /规格配置已更新|配置已更新/;
const HARDCODED_PRICE_RE =
/monthlyPrice\s*[:=]\s*\d+|yearlyPrice\s*[:=]\s*\d+|\$\s*(9|19|29|49|99|199|299)\b/;
function readPage(): string {
assert.ok(fs.existsSync(pagePath), '必须存在 platform/subscriptions/index.tsx');
return fs.readFileSync(pagePath, 'utf-8');
}
function readLocale(): string {
assert.ok(
fs.existsSync(localePath),
'必须存在 platform/subscriptions/locale/index.ts'
);
return fs.readFileSync(localePath, 'utf-8');
}
test('1. R19: 订阅规格页必须请求 plans list/update,并按 code===1000 才成功提示', () => {
const content = readPage();
assert.ok(
content.includes('/api/admin/team/platform/plans/list'),
'必须请求 plans/list'
);
assert.ok(
content.includes('/api/admin/team/platform/plans/update'),
'必须请求 plans/update'
);
assert.ok(content.includes('axios'), '必须使用 axios');
assert.ok(
content.includes('code === 1000') || content.includes('code ===1000'),
'必须按 Cool-Admin code === 1000 判断保存成功'
);
assert.ok(content.includes('Message.success'), '真实保存成功仍应 Message.success');
assert.ok(content.includes('Message.error'), '失败必须 Message.error');
assert.ok(content.includes('PlanConfigItem'), '必须使用 PlanConfigItem 类型');
assert.ok(content.includes("from '@/types/omni'"), '必须复用 omni 类型');
assert.ok(!/\bany\b/.test(content), '严禁 any');
assert.ok(
!content.includes('Mock.js') && !content.includes("from 'mockjs'"),
'禁止 Mock.js'
);
});
test('2. R19: Message.success 仅允许出现在 code===1000 分支,失败不得报已更新', () => {
const content = readPage();
const successMatches = [...content.matchAll(/Message\.success\s*\(/g)];
assert.ok(successMatches.length > 0, '必须保留保存成功 Message.success');
for (const match of successMatches) {
const index = match.index ?? 0;
const preceding = content.slice(Math.max(0, index - 280), index);
assert.ok(
/code\s*===\s*1000/.test(preceding),
`Message.success 必须位于 code === 1000 分支内,附近: ${preceding.slice(-80)}`
);
}
const failBlocks = content.match(
/else\s*\{[\s\S]{0,200}\}|catch\s*(?:\([^)]*\))?\s*\{[\s\S]{0,200}\}/g
);
if (failBlocks) {
for (const block of failBlocks) {
assert.ok(
!block.includes('Message.success'),
`失败分支内不得 Message.success: ${block.slice(0, 120)}`
);
assert.ok(
!HARDCODED_SUCCESS_RE.test(block),
'失败分支不得硬编码已更新文案'
);
}
}
assert.ok(
content.includes("t['platformSubscriptions.request.updateSuccess']") ||
content.includes('t["platformSubscriptions.request.updateSuccess"]'),
'成功提示必须走 locale updateSuccess'
);
assert.ok(
content.includes("t['platformSubscriptions.request.saveFail']") ||
content.includes('t["platformSubscriptions.request.saveFail"]') ||
content.includes("t['platformSubscriptions.request.updateFail']") ||
content.includes('t["platformSubscriptions.request.updateFail"]'),
'保存失败必须走 locale 失败文案'
);
assert.ok(
content.includes("t['platformSubscriptions.request.fail']") ||
content.includes('t["platformSubscriptions.request.fail"]'),
'加载失败必须走 locale request.fail'
);
});
test('3. R19: 成功/失败文案走 locale,禁止硬编码成功句与套餐价格', () => {
const content = readPage();
const localeContent = readLocale();
assert.ok(
content.includes("from './locale'") || content.includes('from "./locale"'),
'必须引入页面 locale'
);
assert.ok(content.includes('useLocale(locale)'), '必须 useLocale(locale)');
assert.ok(!HARDCODED_SUCCESS_RE.test(content), '页面禁止硬编码「规格配置已更新」');
assert.ok(
!HARDCODED_SUCCESS_RE.test(localeContent) ||
localeContent.includes('platformSubscriptions.request.updateSuccess'),
'成功句只能作为 locale 词条值,不得散落在脚本模板'
);
// 页面脚本不得把成功句写死在模板字符串里
assert.ok(
!/`[^`]*规格配置已更新[^`]*`/.test(content) &&
!/'[^']*规格配置已更新[^']*'/.test(content) &&
!/"[^"]*规格配置已更新[^"]*"/.test(content),
'页面不得硬编码规格配置已更新'
);
assert.ok(!content.includes('获取套餐配置失败'), '加载失败不得硬编码中文');
const localeKeys = [
'menu.platform.subscriptions',
'platformSubscriptions.request.fail',
'platformSubscriptions.request.updateSuccess',
'platformSubscriptions.request.saveFail',
];
for (const key of localeKeys) {
assert.ok(localeContent.includes(key), `语言包必须包含: ${key}`);
}
assert.ok(
content.includes('platformSubscriptions.request.updateSuccess'),
'页面必须引用 updateSuccess'
);
assert.ok(
content.includes('platformSubscriptions.request.saveFail') ||
content.includes('platformSubscriptions.request.updateFail'),
'页面必须引用保存失败 key'
);
assert.ok(
content.includes('platformSubscriptions.request.fail'),
'页面必须引用 request.fail'
);
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');
// 价格来自接口 plan.monthlyPrice / yearlyPrice,禁止写死套餐价
assert.ok(
content.includes('plan.monthlyPrice') || content.includes('monthlyPrice'),
'月价必须绑定接口字段'
);
assert.ok(
content.includes('plan.yearlyPrice') || content.includes('yearlyPrice'),
'年价必须绑定接口字段'
);
assert.ok(!HARDCODED_PRICE_RE.test(content), '页面禁止写死套餐价格');
assert.ok(!HARDCODED_PRICE_RE.test(localeContent), 'locale 禁止写死套餐价格');
assert.ok(!EMOJI_RE.test(content), '页面严禁 emoji');
assert.ok(!EMOJI_RE.test(localeContent), '语言包严禁 emoji');
assert.ok(!/\bany\b/.test(localeContent), 'locale 严禁 any');
// 自审:模板/脚本不得把成功句写死;Message.success 只能引用 locale key
const successCalls = [
...content.matchAll(/Message\.success\s*\(\s*([^)]+)\s*\)/g),
];
assert.ok(successCalls.length > 0, '必须有 Message.success 调用');
for (const match of successCalls) {
const arg = match[1] ?? '';
assert.ok(
/platformSubscriptions\.request\.updateSuccess/.test(arg),
`Message.success 参数必须引用 updateSuccess locale key,实际: ${arg}`
);
assert.ok(
!HARDCODED_SUCCESS_RE.test(arg),
'Message.success 参数不得内联硬编码成功句'
);
}
assert.ok(
!content.includes('规格配置已更新'),
'页面脚本/模板自审:不得出现硬编码成功句「规格配置已更新」'
);
assert.ok(
/'platformSubscriptions\.request\.updateSuccess'\s*:\s*'/.test(
localeContent
),
'成功句只能存在于 locale 词条值'
);
});