文件
shanghaipeiwan/server/tests/nav-category.spec.ts

162 行
6.3 KiB
TypeScript

// 已读取AGENTS.md
import { NavCategoryService } from '../src/core/nav/nav-category.service';
import * as fs from 'fs';
import * as path from 'path';
describe('NavCategory Management System (TDD 首页金刚区导航品类后台配置与小程序联动)', () => {
let service: NavCategoryService;
const rootDir = path.resolve(__dirname, '../../');
const serverIndexPath = path.join(rootDir, 'server/src/index.ts');
const mysqlPath = path.join(rootDir, 'server/src/db/mysql.ts');
const repoPath = path.join(rootDir, 'server/src/db/repository.ts');
const mobileIndexPath = path.join(rootDir, 'pwuniapp/pages/index/index.vue');
beforeEach(() => {
service = new NavCategoryService();
});
describe('1. 核心服务 NavCategoryService 业务校验', () => {
it('默认加载 10 项经典品类种子且按 sortOrder 正序排序', () => {
const active = service.getActiveCategories();
expect(active.length).toBe(10);
expect(active[0].name).toBe('电子竞技');
expect(active[1].name).toBe('美食探店');
expect(active[2].name).toBe('KTV嗨唱');
expect(active[9].name).toBe('全部活动');
expect(active.every((c) => c.status === 1)).toBe(true);
});
it('成功新增金刚区品类导航并支持角标高亮', () => {
const item = service.createCategory({
code: 'camping',
name: '露营烧烤',
icon: 'warmFlame',
bgClass: 'bg-emerald',
cat: 'offline_play',
targetUrl: '/pages/activity/list?category=ALL',
badgeText: '新玩法',
sortOrder: 0, // 置顶
status: 1
});
expect(item.id).toBe(11);
expect(item.name).toBe('露营烧烤');
expect(item.badgeText).toBe('新玩法');
const active = service.getActiveCategories();
expect(active.length).toBe(11);
expect(active[0].id).toBe(11); // 置顶生效
});
it('创建品类时空名称或空图标应被拦截', () => {
expect(() => {
service.createCategory({
name: '',
icon: 'utensils'
});
}).toThrow('品类名称不能为空');
expect(() => {
service.createCategory({
name: '桌游体验',
icon: ' '
});
}).toThrow('品类图标不能为空');
});
it('品类名称或角标中含有违规敏感词应触发微信内容安全拦截', () => {
expect(() => {
service.createCategory({
name: '美女上门特殊服务招嫖',
icon: 'utensils'
});
}).toThrow();
expect(() => {
service.createCategory({
name: '私下转账免手续费活动',
icon: 'utensils'
});
}).toThrow();
});
it('支持快速上下线切换并在移动端列表中实时反映', () => {
const offline = service.toggleStatus(2, 0);
expect(offline.status).toBe(0);
// 移动端不再展示美食探店
const active = service.getActiveCategories();
expect(active.some((c) => c.id === 2)).toBe(false);
// 后台全量仍存在
expect(service.getAllCategories().some((c) => c.id === 2)).toBe(true);
// 重新上线
const online = service.toggleStatus(2, 1);
expect(online.status).toBe(1);
expect(service.getActiveCategories().some((c) => c.id === 2)).toBe(true);
});
it('更新品类配置与排序权重', () => {
const updated = service.updateCategory(3, {
name: '夜店派对',
badgeText: '超嗨',
sortOrder: 1
});
expect(updated.name).toBe('夜店派对');
expect(updated.badgeText).toBe('超嗨');
expect(updated.sortOrder).toBe(1);
});
it('删除品类导航项', () => {
const ok = service.deleteCategory(10);
expect(ok).toBe(true);
expect(service.getAllCategories().some((c) => c.id === 10)).toBe(false);
expect(() => {
service.deleteCategory(999);
}).toThrow('品类导航不存在或已被删除');
});
});
describe('2. 架构持久化与数据门禁校验', () => {
it('MySQL 数据库必须包含 nav_categories 表定义与 10 条种子数据', () => {
const mysqlContent = fs.readFileSync(mysqlPath, 'utf-8');
expect(mysqlContent).toContain('CREATE TABLE IF NOT EXISTS nav_categories');
expect(mysqlContent).toContain('sort_order INT DEFAULT 0');
expect(mysqlContent).toContain('status TINYINT DEFAULT 1');
expect(mysqlContent).toContain('INSERT INTO nav_categories');
});
it('Repository 数据仓储层必须实现完整的 nav_categories CRUD 方法', () => {
const repoContent = fs.readFileSync(repoPath, 'utf-8');
expect(repoContent).toContain('async getNavCategories(): Promise<any[]>');
expect(repoContent).toContain('async getAdminNavCategories(): Promise<any[]>');
expect(repoContent).toContain('async createNavCategory(');
expect(repoContent).toContain('async updateNavCategory(');
expect(repoContent).toContain('async updateNavCategoryStatus(');
expect(repoContent).toContain('async deleteNavCategory(');
expect(repoContent).toContain('WHERE status = 1');
expect(repoContent).toContain('ORDER BY sort_order ASC');
});
it('server/src/index.ts 必须注册完整的金刚区导航 API 路由与敏感词检查', () => {
const indexContent = fs.readFileSync(serverIndexPath, 'utf-8');
expect(indexContent).toContain("app.get('/api/nav-categories'");
expect(indexContent).toContain("app.get('/api/admin/nav-categories'");
expect(indexContent).toContain("app.post('/api/admin/nav-categories'");
expect(indexContent).toContain("app.put('/api/admin/nav-categories/:id'");
expect(indexContent).toContain("app.post('/api/admin/nav-categories/:id/status'");
expect(indexContent).toContain("app.delete('/api/admin/nav-categories/:id'");
});
it('服务文件与测试套件必须严格满足零系统 Emoji 规范', () => {
const emojiRegex = /[\u{1F300}-\u{1F9FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/u;
const serviceContent = fs.readFileSync(path.join(rootDir, 'server/src/core/nav/nav-category.service.ts'), 'utf-8');
const testContent = fs.readFileSync(__filename, 'utf-8');
expect(emojiRegex.test(serviceContent)).toBe(false);
expect(emojiRegex.test(testContent)).toBe(false);
});
});
});