import assert from 'node:assert/strict'; import { StatsCounters, StatsReporter } from '../src/stats/statsManager.js'; import { HeartbeatProcessor } from '../src/processor/heartbeatProcessor.js'; describe('StatsCounters', () => { it('snapshots and resets minute counters atomically', () => { const stats = new StatsCounters(); stats.incDbWritten(3); stats.incFiltered(2); stats.incKafkaPulled(5); stats.incDbWriteFailed(4); stats.incG5Written(6); stats.incG5WriteFailed(1); const first = stats.snapshotAndResetMinute(); assert.equal(first.dbWritten, 3n); assert.equal(first.filtered, 2n); assert.equal(first.kafkaPulled, 5n); assert.equal(first.dbWriteFailed, 4n); assert.equal(first.g5Written, 6n); assert.equal(first.g5WriteFailed, 1n); const second = stats.snapshotAndResetMinute(); assert.equal(second.dbWritten, 0n); assert.equal(second.filtered, 0n); assert.equal(second.kafkaPulled, 0n); assert.equal(second.dbWriteFailed, 0n); assert.equal(second.g5Written, 0n); assert.equal(second.g5WriteFailed, 0n); }); }); describe('StatsReporter', () => { it('writes all [STATS] info logs to redis console', () => { const stats = new StatsCounters(); stats.incDbWritten(7); stats.incFiltered(8); stats.incKafkaPulled(9); stats.incDbWriteFailed(2); stats.incG5Written(5); stats.incG5WriteFailed(1); const calls = { push: [] }; const redis = { isEnabled: () => true, pushConsoleLog: ({ level, message, metadata }) => { calls.push.push({ level, message, metadata }); }, }; const reporter = new StatsReporter({ redis, stats }); reporter.flushOnce(); assert.equal(calls.push.length, 11); for (const c of calls.push) assert.equal(c.level, 'info'); assert.match(calls.push[0].message, /Legacy写入量: 7条$/); assert.match(calls.push[1].message, /Legacy写入失败量: 2条$/); assert.match(calls.push[2].message, /G4Hot写入量: 0条$/); assert.match(calls.push[3].message, /G4Hot写入失败量: 0条$/); assert.match(calls.push[4].message, /G5写入量: 5条$/); assert.match(calls.push[5].message, /G5写入失败量: 1条$/); assert.match(calls.push[6].message, /RoomStatus写入量: 0条$/); assert.match(calls.push[7].message, /RoomStatus失败量: 0条$/); assert.match(calls.push[8].message, /G4Hot错误表插入量: 0条$/); assert.match(calls.push[9].message, /数据过滤量: 8条$/); assert.match(calls.push[10].message, /Kafka拉取量: 9条$/); }); }); describe('HeartbeatProcessor db write error logging', () => { it('emits [ERROR] warn log with raw data', () => { const calls = { warn: [] }; const redis = { isEnabled: () => true, pushConsoleLog: ({ level, message }) => { if (level === 'warn') calls.warn.push(message); }, }; const processor = new HeartbeatProcessor({ batchSize: 1, batchTimeout: 10 }, {}, { redis }); processor._emitDbWriteError(new Error('boom'), [{ a: 1 }]); assert.equal(calls.warn.length >= 1, true); assert.match(calls.warn[0], /^\[ERROR\] \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3} db_write_failed: /); assert.match(calls.warn[0], /"errorId":"db_write_failed"/); assert.match(calls.warn[0], /"rawData":\{"a":1\}/); }); });