const assert = require('assert'); const { EventEmitter } = require('events'); const { AliyunService } = require('../aliyun-service'); class MockIotDevice extends EventEmitter { constructor({ autoConnect = true } = {}) { super(); this.lastPayload = null; if (autoConnect) { setImmediate(() => { this.emit('connect'); }); } } postProps(payload, callback) { this.lastPayload = payload; callback({ message: 'success' }); } } function createLogger() { return { infoMessages: [], warnMessages: [], errorMessages: [], info(message) { this.infoMessages.push(message); }, warn(message) { this.warnMessages.push(message); }, error(message) { this.errorMessages.push(message); }, }; } function createService({ logger = console, deviceFactory, fetchImpl } = {}) { return new AliyunService({ enabled: true, tupleApiBaseUrl: 'https://things.icoldchain.cn', tupleApiPath: '/device/info/getAliyunDeviceSecret', autoRegister: true, registerRetryMs: 60000, connectTimeoutMs: 5000, closeLogThrottleMs: 60000, }, logger, { fetchImpl: fetchImpl || (async (_url, options = {}) => ({ ok: true, text: async () => JSON.stringify({ data: { productKey: 'pk-demo', deviceName: 'JHM-001', deviceSecret: 'secret-demo', requestBody: options.body || '', }, }), })), aliyunSdk: { device: deviceFactory || (() => new MockIotDevice()), }, }); } describe('AliyunService', () => { it('adds suedtime into aliyun payload', () => { const service = createService(); const payload = service.buildPayload({ F: 37.5 }); assert.strictEqual(payload.F, 37.5); assert.match(payload.suedtime, /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/); }); it('uses deviceId to request tuple and publish the minimal payload', async () => { let requestBody = ''; let mockDevice = null; const service = new AliyunService({ enabled: true, tupleApiBaseUrl: 'https://things.icoldchain.cn', tupleApiPath: '/device/info/getAliyunDeviceSecret', autoRegister: true, registerRetryMs: 60000, connectTimeoutMs: 5000, }, console, { fetchImpl: async (_url, options) => { requestBody = options.body; return { ok: true, text: async () => JSON.stringify({ data: { productKey: 'pk-demo', deviceName: 'JHM-001', deviceSecret: 'secret-demo', }, }), }; }, aliyunSdk: { device: () => { mockDevice = new MockIotDevice(); return mockDevice; }, }, }); service.start(); const result = await service.publish({ deviceId: 'JHM-001', ip: '127.0.0.1' }, { F: 37.5 }); assert.strictEqual(result.ok, true); assert.match(requestBody, /deviceName=JHM-001/); assert.strictEqual(mockDevice.lastPayload.F, 37.5); assert.match(mockDevice.lastPayload.suedtime, /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/); }); it('suppresses repeated close logs and formats sdk errors clearly', async () => { let mockDevice = null; const logger = createLogger(); const service = createService({ logger, deviceFactory: () => { mockDevice = new MockIotDevice(); return mockDevice; }, }); service.start(); await service.publish({ deviceId: 'JHM-001', ip: '127.0.0.1' }, { F: 37.5 }); mockDevice.emit('close'); mockDevice.emit('close'); mockDevice.emit('error', [new Error('socket reset')]); assert.strictEqual(logger.infoMessages.filter((message) => message.includes('连接关闭')).length, 1); assert.strictEqual(logger.errorMessages.some((message) => message.includes('socket reset')), true); assert.strictEqual(logger.errorMessages.some((message) => message.includes('undefined')), false); }); it('reuses the same sdk device after reconnect', async () => { let createCount = 0; let mockDevice = null; const logger = createLogger(); const service = createService({ logger, deviceFactory: () => { createCount += 1; mockDevice = new MockIotDevice(); return mockDevice; }, }); service.start(); await service.publish({ deviceId: 'JHM-001', ip: '127.0.0.1' }, { F: 37.5 }); mockDevice.emit('close'); mockDevice.emit('reconnect'); mockDevice.emit('connect'); const result = await service.publish({ deviceId: 'JHM-001', ip: '127.0.0.1' }, { G: 0 }); assert.strictEqual(result.ok, true); assert.strictEqual(createCount, 1); assert.strictEqual(logger.infoMessages.some((message) => message.includes('正在重连')), true); assert.strictEqual(logger.infoMessages.some((message) => message.includes('重新连接成功')), true); }); it('does not create duplicate sdk connections while waiting for reconnect', async () => { let createCount = 0; let mockDevice = null; const logger = createLogger(); const service = createService({ logger, deviceFactory: () => { createCount += 1; mockDevice = new MockIotDevice(); return mockDevice; }, }); service.start(); await service.publish({ deviceId: 'JHM-001', ip: '127.0.0.1' }, { F: 37.5 }); mockDevice.emit('close'); const publishPromise1 = service.publish({ deviceId: 'JHM-001', ip: '127.0.0.1' }, { B: 0.25 }); const publishPromise2 = service.publish({ deviceId: 'JHM-001', ip: '127.0.0.1' }, { D: 320 }); await new Promise((resolve) => setTimeout(resolve, 30)); assert.strictEqual(createCount, 1); assert.strictEqual(logger.infoMessages.some((message) => message.includes('等待现有设备连接恢复')), true); mockDevice.emit('connect'); const [result1, result2] = await Promise.all([publishPromise1, publishPromise2]); assert.strictEqual(result1.ok, true); assert.strictEqual(result2.ok, true); assert.strictEqual(createCount, 1); }); it('deduplicates tuple requests and initial device creation during the first burst', async () => { let fetchCount = 0; let createCount = 0; let mockDevice = null; const logger = createLogger(); const service = createService({ logger, fetchImpl: async () => { fetchCount += 1; await new Promise((resolve) => setTimeout(resolve, 50)); return { ok: true, text: async () => JSON.stringify({ data: { productKey: 'pk-demo', deviceName: 'JHM-001', deviceSecret: 'secret-demo', }, }), }; }, deviceFactory: () => { createCount += 1; mockDevice = new MockIotDevice({ autoConnect: false }); return mockDevice; }, }); service.start(); const publishPromise1 = service.publish({ deviceId: 'JHM-001', ip: '127.0.0.1' }, { H: 1 }); const publishPromise2 = service.publish({ deviceId: 'JHM-001', ip: '127.0.0.1' }, { o: 1 }); const publishPromise3 = service.publish({ deviceId: 'JHM-001', ip: '127.0.0.1' }, { J: 62 }); await new Promise((resolve) => setTimeout(resolve, 120)); assert.strictEqual(fetchCount, 1); assert.strictEqual(createCount, 1); assert.strictEqual(logger.infoMessages.some((message) => message.includes('复用三元组请求中的会话')), true); assert.strictEqual(logger.infoMessages.filter((message) => message.includes('开始创建设备连接')).length, 1); mockDevice.emit('connect'); const results = await Promise.all([publishPromise1, publishPromise2, publishPromise3]); assert.deepStrictEqual(results.map((item) => item.ok), [true, true, true]); assert.strictEqual(fetchCount, 1); assert.strictEqual(createCount, 1); }); });