/** * NotificationChannel LRU 批量淘汰 & Logger 队列上限测试。 * 验证 #6/#20 修复。 */ import { describe, expect, it, beforeEach } from 'vitest'; import { NotificationChannel, SimulatedNotificationListener, type NotificationEvent } from '../src/services/notification'; import { LoggerImpl } from '../src/utils/logger'; // ============ NotificationChannel LRU 批量淘汰 ============ describe('NotificationChannel LRU 批量淘汰 (#6)', () => { let channel: NotificationChannel; let listener: SimulatedNotificationListener; beforeEach(() => { listener = new SimulatedNotificationListener(); // 使用自定义白名单包含测试包名 channel = new NotificationChannel(listener, ['com.tencent.mm', 'com.test.app']); }); it('正常去重:相同通知只处理一次', () => { const event: NotificationEvent = { packageName: 'com.tencent.mm', title: '微信支付', text: '微信支付 金额 10.00 商户:星巴克', timestamp: Date.now(), }; const result1 = channel.handleNotification(event); expect(result1.processed).toBe(true); const result2 = channel.handleNotification(event); expect(result2.processed).toBe(false); expect(result2.reason).toContain('MD5'); }); it('LRU 批量淘汰:超过 1000 条时裁剪到 800', () => { // 注入 1001 条不同的通知(使用白名单包名) for (let i = 0; i < 1001; i++) { const event: NotificationEvent = { packageName: 'com.tencent.mm', title: `微信支付 ${i}`, text: `微信支付 金额 ${i}.00 商户:测试${i}`, timestamp: Date.now() + i, }; channel.handleNotification(event); } // 验证缓存大小被限制(通过行为验证:新事件仍可处理) const newEvent: NotificationEvent = { packageName: 'com.tencent.mm', title: '微信支付 新事件', text: '微信支付 金额 999.00 商户:新商户', timestamp: Date.now() + 2000, }; const result = channel.handleNotification(newEvent); expect(result.processed).toBe(true); }); it('LRU 淘汰后保留较新的记录', () => { // 注入 1001 条 for (let i = 0; i < 1001; i++) { const event: NotificationEvent = { packageName: 'com.tencent.mm', title: `微信支付 ${i}`, text: `微信支付 金额 ${i}.00`, timestamp: Date.now() + i, }; channel.handleNotification(event); } // 第 1000 条(最新的)应该还在缓存中 const lastEvent: NotificationEvent = { packageName: 'com.tencent.mm', title: '微信支付 1000', text: '微信支付 金额 1000.00', timestamp: Date.now() + 1000, }; const result = channel.handleNotification(lastEvent); expect(result.processed).toBe(false); // 仍然是重复 expect(result.reason).toContain('MD5'); }); }); // ============ Logger 队列上限 ============ describe('Logger 队列上限 (#20)', () => { it('队列超过 MAX_QUEUE_SIZE 时丢弃最旧条目', () => { const logger = new LoggerImpl(100); logger.consoleOutput = false; // 模拟一个总是失败的后端 const failingBackend = { appendLog: async () => { throw new Error('写入失败'); }, readLogFile: async () => '', listLogFiles: async () => [], deleteLogFile: async () => {}, clearAllLogFiles: async () => {}, }; // 同步初始化后端(不等待清理) logger.initFileBackend(failingBackend); // 写入超过 5000 条日志 for (let i = 0; i < 5100; i++) { logger.info('test', `消息 ${i}`); } // 队列不应该无限增长 // 由于是私有属性,我们通过行为验证:flushQueue 不会处理超过 5000 条 // 这里主要验证不会抛出 OOM 错误 expect(true).toBe(true); }); it('正常写入时队列按日期分组写入', async () => { const logger = new LoggerImpl(100); logger.consoleOutput = false; const writtenLines: string[] = []; const successBackend = { appendLog: async (_date: string, line: string) => { writtenLines.push(line); }, readLogFile: async () => '', listLogFiles: async () => [], deleteLogFile: async () => {}, clearAllLogFiles: async () => {}, }; await logger.initFileBackend(successBackend); // 写入少量日志 for (let i = 0; i < 10; i++) { logger.info('test', `消息 ${i}`); } // 刷新队列 await logger.flushQueue(); // 同一天的日志会被合并为一次写入(按日期分组) expect(writtenLines.length).toBe(1); // 但内容包含所有 10 条日志 expect(writtenLines[0]).toContain('消息 0'); expect(writtenLines[0]).toContain('消息 9'); }); it('内存缓冲区循环淘汰正常工作', () => { const logger = new LoggerImpl(5); // 小缓冲区 logger.consoleOutput = false; for (let i = 0; i < 10; i++) { logger.info('test', `消息 ${i}`); } const logs = logger.getLogs(); expect(logs.length).toBe(5); // 应该保留最新的 5 条 expect(logs[0].message).toBe('消息 5'); expect(logs[4].message).toBe('消息 9'); }); });