Files
gongxue-base/scripts/taro-student-product-guardrails-test.js

81 lines
2.9 KiB
JavaScript

import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
const repoRoot = process.cwd();
const studentPagesDir = path.join(repoRoot, 'apps', 'taro', 'src', 'pages', 'student');
const appConfigPath = path.join(repoRoot, 'apps', 'taro', 'src', 'app.config.ts');
const studentProfileServicePath = path.join(repoRoot, 'apps', 'taro', 'src', 'services', 'profile.ts');
function readText(filePath) {
return fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
}
function walkFiles(dir) {
const entries = fs.readdirSync(dir, { withFileTypes: true });
return entries.flatMap(entry => {
const entryPath = path.join(dir, entry.name);
if (entry.isDirectory()) return walkFiles(entryPath);
if (!/\.(ts|tsx|css|json)$/.test(entry.name)) return [];
return [entryPath];
});
}
function relative(filePath) {
return path.relative(repoRoot, filePath).replace(/\\/g, '/');
}
const appConfig = readText(appConfigPath);
assert.ok(
!/pages\/student\/(?:leaderboard|rank|ranking)\b/i.test(appConfig),
'Student routes must not include a default leaderboard/ranking page. Leaderboard is optional and tenant-gated.',
);
const studentFiles = walkFiles(studentPagesDir);
const leaderboardViolations = [];
const avatarUploadViolations = [];
const avatarUrlViolations = [];
for (const filePath of studentFiles) {
const text = readText(filePath);
if (/\bloadLeaderboard\b|\/api\/learning\/leaderboard|learning\/leaderboard|leaderboard|排行榜/.test(text)) {
leaderboardViolations.push(relative(filePath));
}
if (/\bchooseImage\b|\buploadFile\b|sign-upload|avatar\s*upload|头像上传|上传头像|裁剪头像/.test(text)) {
avatarUploadViolations.push(relative(filePath));
}
if (/\bavatarUrl\b|\bavatar_url\b|\bheadimgurl\b|\bheadImgUrl\b|\bfigureurl\b/i.test(text)) {
avatarUrlViolations.push(relative(filePath));
}
}
assert.deepEqual(
[...new Set(leaderboardViolations)].sort(),
[],
'Student pages must not request or render leaderboard by default. Add a tenant-gated activity page only after explicit enablement and load testing.',
);
assert.deepEqual(
[...new Set(avatarUploadViolations)].sort(),
[],
'Student pages must not add avatar upload/crop/storage flows. Only avatarPreset=male/female is supported.',
);
assert.deepEqual(
[...new Set(avatarUrlViolations)].sort(),
[],
'Student pages must not read or write avatarUrl/headimgurl/figureurl fields. Use avatarPreset only.',
);
const profileService = readText(studentProfileServicePath);
assert.ok(
/avatarPreset\?: 'male' \| 'female'/.test(profileService),
'Student profile service must expose avatarPreset=male/female.',
);
assert.ok(
!/\bavatarUrl\b|\bavatar_url\b|\bheadimgurl\b|\bheadImgUrl\b|\bfigureurl\b/i.test(profileService),
'Student profile service must not accept or expose avatar URL fields.',
);
console.log('[PASS] Taro student product guardrails');