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

146 行
5.2 KiB
TypeScript

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

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/subscribers/index.tsx');
const localePath = path.join(adminRoot, 'src/pages/platform/subscribers/locale/index.ts');
const EMOJI_RE = /[\u{1F300}-\u{1F9FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/u;
const HARDCODED_SUCCESS = [
'会员配置已更新',
'延长有效期',
'会员状态已切换',
];
function readPage(): string {
assert.ok(fs.existsSync(pagePath), '必须存在 platform/subscribers/index.tsx');
return fs.readFileSync(pagePath, 'utf-8');
}
function readLocale(): string {
assert.ok(fs.existsSync(localePath), '必须存在 platform/subscribers/locale/index.ts');
return fs.readFileSync(localePath, 'utf-8');
}
test('1. R20: 必须有页面 locale,且含成功/失败词条与三语', () => {
const localeContent = readLocale();
const keys = [
'platformSubscribers.update.success',
'platformSubscribers.update.fail',
'platformSubscribers.extend.success',
'platformSubscribers.extend.fail',
'platformSubscribers.status.success',
'platformSubscribers.status.fail',
];
for (const key of keys) {
assert.ok(localeContent.includes(key), `locale 必须包含: ${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(!EMOJI_RE.test(localeContent), '语言包严禁 emoji');
assert.ok(!/\bany\b/.test(localeContent), 'locale 禁止 any');
});
test('2. R20: 页面必须引入 locale,成功文案走 t[...]', () => {
const content = readPage();
assert.ok(
content.includes("from './locale'") || content.includes('from "./locale"'),
'必须引入页面 locale'
);
assert.ok(content.includes('useLocale(locale)'), '必须 useLocale(locale)');
const successKeys = [
'platformSubscribers.update.success',
'platformSubscribers.extend.success',
'platformSubscribers.status.success',
];
for (const key of successKeys) {
assert.ok(content.includes(key), `页面必须引用成功 locale key: ${key}`);
}
const failKeys = [
'platformSubscribers.update.fail',
'platformSubscribers.extend.fail',
'platformSubscribers.status.fail',
];
for (const key of failKeys) {
assert.ok(content.includes(key), `页面必须引用失败 locale key: ${key}`);
}
});
test('3. R20: 延期/改配置/切换状态仅在 code===1000 时 Message.success', () => {
const content = readPage();
assert.ok(
content.includes('/api/admin/team/platform/subscribers/update'),
'操作必须走 subscribers/update'
);
const successMatches = [...content.matchAll(/Message\.success\s*\(/g)];
assert.ok(successMatches.length >= 3, '改配置/延期/切状态成功时仍应 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(-100)}`
);
}
});
test('4. R20: 失败不得报延期成功或状态已切换;catch/非1000 不得 Message.success', () => {
const content = readPage();
assert.ok(
content.includes('code !== 1000') || content.includes('code === 1000'),
'必须按 Cool-Admin code 校验'
);
// 硬编码中文成功句不得再出现在页面源码(尤其 Message.success)
for (const phrase of HARDCODED_SUCCESS) {
assert.ok(
!content.includes(phrase),
`页面禁止硬编码成功句: ${phrase}`
);
}
const failBlocks = content.match(
/else\s*\{[\s\S]{0,220}\}|catch\s*(?:\([^)]*\))?\s*\{[\s\S]{0,220}\}/g
);
if (failBlocks) {
for (const block of failBlocks) {
assert.ok(
!block.includes('Message.success'),
`失败分支内不得 Message.success: ${block.slice(0, 120)}`
);
}
}
// 非 1000 分支须 Message.error(正分支 return 后、或 !== 1000 早退)
assert.ok(
/code\s*!==\s*1000[\s\S]{0,160}Message\.error/.test(content) ||
/if\s*\(\s*res\.data\?\.code\s*!==\s*1000[\s\S]{0,200}Message\.error/.test(
content
) ||
(/code\s*===\s*1000[\s\S]{0,400}Message\.error/.test(content) &&
content.includes('Message.error')),
'code 非 1000 时必须 Message.error'
);
assert.ok(content.includes('Message.error'), '失败须 Message.error');
});
test('5. R20: 禁止造假租户;操作不写演示企业名', () => {
const content = readPage();
assert.ok(!content.includes('Vanguard'), '禁止硬编码演示租户名 Vanguard');
assert.ok(!content.includes('Acme Corp'), '禁止硬编码演示租户名');
assert.ok(!content.includes('演示企业'), '禁止硬编码演示企业');
assert.ok(!content.includes('Mock.js') && !content.includes("from 'mockjs'"), '禁止 Mock.js');
assert.ok(!/\bany\b/.test(content), '严禁 any');
assert.ok(!EMOJI_RE.test(content), '页面严禁 emoji');
});