// 已读取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'); expect(repoContent).toContain('async getAdminNavCategories(): Promise'); 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); }); }); });