import assert from 'node:assert/strict'; import { HeartbeatProcessor } from '../src/processor/heartbeatProcessor.js'; import { RedisIntegration } from '../src/redis/redisIntegration.js'; describe('HeartbeatProcessor smoke', () => { it('decodes JSON buffer into object', () => { const processor = new HeartbeatProcessor( { batchSize: 100, batchTimeout: 1000 }, { insertHeartbeatEvents: async () => {} } ); const payload = { ts_ms: 1700000000123, hotel_id: 1, room_id: 2, device_id: 'd', ip: '127.0.0.1', power_state: 1, guest_type: 0, cardless_state: 0, service_mask: 1, pms_state: 1, carbon_state: 0, device_count: 1, comm_seq: 1 }; const message = { value: Buffer.from(JSON.stringify(payload), 'utf8') }; const decoded = processor.unpackMessage(message); assert.equal(decoded.hotel_id, 1); }); it('accepts camelCase fields via normalizeHeartbeat', () => { const processor = new HeartbeatProcessor( { batchSize: 100, batchTimeout: 1000 }, { insertHeartbeatEvents: async () => {} } ); const payload = { tsMs: 1700000000123, hotelId: 1, roomId: 2, deviceId: 'd', ip: '127.0.0.1', powerState: 1, guestType: 0, cardlessState: 0, serviceMask: 1, pmsState: 1, carbonState: 0, deviceCount: 1, commSeq: 1 }; assert.equal(processor.validateData(payload), true); }); it('parses version field', () => { const processor = new HeartbeatProcessor( { batchSize: 100, batchTimeout: 1000 }, { insertHeartbeatEvents: async () => {} } ); const payload = { version: 10203 }; const normalized = processor.normalizeHeartbeat(payload); assert.equal(normalized.version, 10203); }); it('treats bright_g -1 as undefined', () => { const processor = new HeartbeatProcessor( { batchSize: 100, batchTimeout: 1000 }, { insertHeartbeatEvents: async () => {} } ); const payload = { bright_g: -1 }; const normalized = processor.normalizeHeartbeat(payload); assert.equal(normalized.bright_g, undefined); }); it('treats bright_g normal value as number', () => { const processor = new HeartbeatProcessor( { batchSize: 100, batchTimeout: 1000 }, { insertHeartbeatEvents: async () => {} } ); const payload = { bright_g: 50 }; const normalized = processor.normalizeHeartbeat(payload); assert.equal(normalized.bright_g, 50); }); }); describe('RedisIntegration protocol', () => { it('writes heartbeat to 项目心跳 LIST', async () => { const redis = new RedisIntegration({ enabled: true, projectName: 'BLS主机心跳日志', apiBaseUrl: 'http://127.0.0.1:3000', }); const calls = { rPush: [] }; redis.client = { isReady: true, rPush: async (key, value) => { calls.rPush.push({ key, value }); }, }; const before = Date.now(); await redis.writeHeartbeat(); const after = Date.now(); assert.equal(calls.rPush.length, 1); assert.equal(calls.rPush[0].key, '项目心跳'); const payload = JSON.parse(calls.rPush[0].value); assert.equal(payload.projectName, 'BLS主机心跳日志'); assert.equal(payload.apiBaseUrl, 'http://127.0.0.1:3000'); assert.equal(typeof payload.lastActiveAt, 'number'); assert.ok(payload.lastActiveAt >= before && payload.lastActiveAt <= after); }); it('caches heartbeat when redis is not ready and flushes later', async () => { const redis = new RedisIntegration({ enabled: true, projectName: 'BLS主机心跳日志', apiBaseUrl: 'http://127.0.0.1:3000', }); const calls = { rPush: [] }; redis.client = { isReady: false, connect: async () => {}, rPush: async (key, value) => { calls.rPush.push({ key, value }); }, }; await redis.writeHeartbeat(); assert.ok(redis._pendingHeartbeat); redis.client.isReady = true; await redis.flushPendingHeartbeat(); assert.equal(redis._pendingHeartbeat, null); assert.equal(calls.rPush.length, 1); assert.equal(calls.rPush[0].key, '项目心跳'); const payload = JSON.parse(calls.rPush[0].value); assert.equal(payload.projectName, 'BLS主机心跳日志'); assert.equal(payload.apiBaseUrl, 'http://127.0.0.1:3000'); assert.equal(typeof payload.lastActiveAt, 'number'); }); it('buffers console logs when redis is not ready', async () => { const redis = new RedisIntegration({ enabled: true, projectName: 'BLS主机心跳日志', apiBaseUrl: 'http://127.0.0.1:3000', }); const calls = { rPush: [] }; redis.client = { isReady: false, connect: async () => {}, rPush: async (key, ...values) => { calls.rPush.push({ key, values }); }, }; await redis.info('hello', { module: 'test' }); assert.equal(redis._pendingConsoleLogs.length, 1); redis.client.isReady = true; await redis.flushPendingConsoleLogs(); assert.equal(redis._pendingConsoleLogs.length, 0); assert.equal(calls.rPush.length, 1); assert.equal(calls.rPush[0].key, 'BLS主机心跳日志_项目控制台'); assert.equal(calls.rPush[0].values.length, 1); const entry = JSON.parse(calls.rPush[0].values[0]); assert.equal(entry.level, 'info'); assert.equal(entry.message, 'hello'); assert.equal(entry.metadata.module, 'test'); }); });