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);
|
});
|
});
|