文件
shanghaipeiwan/server/tests/performance-audit.spec.ts

82 行
4.5 KiB
TypeScript

// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第一百二十批闸门:小程序与服务端全栈性能深度优化与稳定性守护门禁
*/
describe('第一百二十批全栈性能深度优化与稳定性守护门禁', () => {
const rootDir = path.resolve(__dirname, '../../');
const repoPath = path.join(rootDir, 'server/src/db/repository.ts');
const indexPath = path.join(rootDir, 'server/src/index.ts');
const privacyModalPath = path.join(rootDir, 'pwuniapp/components/PrivacyModal.vue');
it('repository.ts 中 getActivities 必须带 WHERE p.activity_id IN (?) 精确范围查询,杜绝全表扫描 participants', () => {
const content = fs.readFileSync(repoPath, 'utf-8');
expect(content).toContain('WHERE p.activity_id IN (?)');
// 确保没有直接从 participants 无条件 SELECT *
expect(content).not.toMatch(/FROM participants p\s+LEFT JOIN users u\s*`\s*\);/);
});
it('repository.ts 中 getMyActivities 必须通过索引查用户参与和发起的活动,禁止全量查出后内存 filter', () => {
const content = fs.readFileSync(repoPath, 'utf-8');
expect(content).toContain('SELECT DISTINCT activity_id FROM participants WHERE user_id = ?');
expect(content).toContain('this.getActivities({ creatorId: uid })');
expect(content).not.toContain('allActivities.filter((a: any) => Number(a.creatorId) === Number(userId))');
});
it('repository.ts 中 getPosts 必须将点赞与报名状态限定在当前页 post_id IN (?) 范围内', () => {
const content = fs.readFileSync(repoPath, 'utf-8');
expect(content).toContain('SELECT post_id FROM post_likes WHERE user_id = ? AND post_id IN (?)');
expect(content).toContain('SELECT post_id, applicant_id, status FROM post_applies WHERE post_id IN (?)');
});
it('高频查询业务函数中严禁在每次读写请求中执行 ALTER TABLE 产生独占锁,建表必须有单例初始化防护', () => {
const content = fs.readFileSync(repoPath, 'utf-8');
// 确保 getCompanions, applyCompanion, auditCompanion, getOrderReviews, replyOrderReview 中不包含 ALTER TABLE
const getCompanionsSection = content.substring(content.indexOf('async getCompanions('), content.indexOf('async getCompanionById('));
expect(getCompanionsSection).not.toContain('ALTER TABLE');
const getOrderReviewsSection = content.substring(content.indexOf('async getOrderReviews('), content.indexOf('async getOrderReviewById('));
expect(getOrderReviewsSection).not.toContain('ALTER TABLE');
// 确保 ensureExpertServicesTable 具备静态单例标志防护
expect(content).toContain('private static expertServicesTableInitialized = false');
expect(content).toContain('if (MysqlRepository.expertServicesTableInitialized) return');
});
it('服务端必须包含定时 sweep 淘汰器,防止防爆破与缓存 Map 无界内存泄漏', () => {
const content = fs.readFileSync(indexPath, 'utf-8');
expect(content).toContain('securitySweepTimer');
expect(content).toContain('loginFailureMap.delete(k)');
expect(content).toContain('geocodeCache.delete(k)');
});
it('服务端高频静态读接口必须提供内存 TTL 缓存及写时主动淘汰机制', () => {
const content = fs.readFileSync(indexPath, 'utf-8');
expect(content).toContain('STATIC_CACHE_TTL_MS');
expect(content).toContain('cachedBanners');
expect(content).toContain('cachedNavCategories');
expect(content).toContain('cachedPlatformConfigs');
expect(content).toContain('invalidateBannersCache()');
expect(content).toContain('invalidateNavCategoriesCache()');
expect(content).toContain('invalidatePlatformConfigsCache()');
});
it('小程序前端 PrivacyModal 组件必须在 onUnmounted 中成对使用 uni.$off 注销全局事件监听', () => {
const content = fs.readFileSync(privacyModalPath, 'utf-8');
expect(content).toContain("uni.$on('theme-change', onThemeChanged)");
expect(content).toContain("uni.$off('theme-change', onThemeChanged)");
expect(content).toContain('onUnmounted');
});
it('本次性能优化涉及的文件与新增测试严禁引入任何系统 Emoji', () => {
const emojiRegex = /[\u{1F300}-\u{1F5FF}\u{1F600}-\u{1F64F}\u{1F680}-\u{1F6FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}\u{1F900}-\u{1F9FF}\u{1F018}-\u{1F270}]/u;
const filesToCheck = [repoPath, indexPath, privacyModalPath, __filename];
for (const f of filesToCheck) {
const text = fs.readFileSync(f, 'utf-8');
expect(emojiRegex.test(text)).toBe(false);
}
});
});