mm_os 4.2.4 → 4.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/README_EN.md +2 -2
- package/adapter/adapter.js +105 -52
- package/adapter/custom_persistence.js +32 -31
- package/adapter/mqtt.js +59 -79
- package/adapter/socket.js +188 -58
- package/adapter/web.js +92 -68
- package/adapter/websocket.js +43 -146
- package/com/api/drive.js +732 -708
- package/com/api/index.js +196 -198
- package/com/api/script.tpl.js +24 -24
- package/com/cmd/config.tpl.json +48 -122
- package/com/cmd/drive.js +176 -1548
- package/com/cmd/index.js +287 -1066
- package/com/cmd/job/config.json +66 -0
- package/com/cmd/job/index.js +241 -0
- package/com/cmd/job/test.js +188 -0
- package/com/cmd/script.tpl.js +12 -33
- package/com/db/drive.js +2206 -2019
- package/com/db/index.js +241 -242
- package/com/event/config.tpl.json +3 -1
- package/com/event/drive.js +3 -1
- package/com/event/index.js +73 -46
- package/com/event/script.tpl.js +22 -22
- package/com/mqtt/config.tpl.json +16 -8
- package/com/mqtt/drive.js +696 -676
- package/com/mqtt/index.js +1012 -822
- package/com/mqtt/index.test.js +503 -0
- package/com/mqtt/mm_mqtt.js +446 -420
- package/com/mqtt/script.tpl.js +3 -7
- package/com/nav/drive.js +7 -7
- package/com/nav/index.js +228 -231
- package/com/param/index.js +19 -17
- package/com/pendant/index.js +467 -441
- package/com/socket/drive.js +679 -651
- package/com/socket/index.js +94 -48
- package/com/sql/drive.js +1283 -1259
- package/com/sql/index.js +152 -150
- package/com/static/drive.js +1 -1
- package/com/static/index.js +232 -226
- package/com/static/script.tpl.js +25 -25
- package/com/task/drive.js +428 -405
- package/com/task/index.js +154 -148
- package/com/task/script.tpl.js +37 -34
- package/com/template/drive.js +1 -1
- package/com/template/index.js +141 -138
- package/com.js +87 -6
- package/common/middleware/web_after/index.js +5 -11
- package/common/middleware/web_base/index.js +1 -1
- package/common/middleware/web_cors/index.js +1 -1
- package/common/middleware/web_error/index.js +131 -119
- package/common/middleware/web_error/middleware.json +2 -1
- package/common/middleware/web_logger/index.js +1 -1
- package/common/middleware/web_main/index.js +2 -0
- package/common/middleware/web_static/index.js +2 -2
- package/common/middleware/web_waf/index.js +1 -1
- package/core/app/config.tpl.json +4 -4
- package/core/app/index.js +710 -534
- package/core/middleware/index.js +1 -9
- package/core/mod/config.tpl.json +1 -1
- package/core/model/config.tpl.json +1 -1
- package/core/plugin/config.tpl.json +15 -3
- package/core/plugin/index.js +31 -0
- package/core/pusher/index.js +158 -161
- package/core/scene/loop.js +1306 -1255
- package/core/store/index.js +1794 -1755
- package/core/store/sql.js +1488 -1464
- package/core/zone/bat/index.js +744 -728
- package/core/zone/config.tpl.json +3 -3
- package/core/zone/index.js +655 -616
- package/os.js +0 -4
- package/package.json +16 -16
- package/server.js +583 -427
- package/com/cmd/nlp.js +0 -525
- package/core/app/index copy.js +0 -611
- package/software.js +0 -194
- package/tcp.js +0 -279
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MQTT模块单元测试
|
|
3
|
+
* 测试修复的线程阻塞点:match缓存、dict_msg O(1)查找、并行处理
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Mock 框架扩展方法
|
|
7
|
+
String.prototype.fullname = function (base) {
|
|
8
|
+
return base ? base + '/' + this : this;
|
|
9
|
+
};
|
|
10
|
+
String.prototype.hasDir = function () {
|
|
11
|
+
return false;
|
|
12
|
+
};
|
|
13
|
+
Array.prototype.getObj = function () {
|
|
14
|
+
return null;
|
|
15
|
+
};
|
|
16
|
+
Array.prototype.to2D = function (size) {
|
|
17
|
+
var result = [];
|
|
18
|
+
for (var i = 0; i < this.length; i += size) {
|
|
19
|
+
result.push(this.slice(i, i + size));
|
|
20
|
+
}
|
|
21
|
+
return result;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Mock global $ 对象
|
|
25
|
+
global.$ = {
|
|
26
|
+
sleep: jest.fn(() => Promise.resolve()),
|
|
27
|
+
pool: {},
|
|
28
|
+
val: { scope: 'test' },
|
|
29
|
+
admin: {
|
|
30
|
+
mqtt: null,
|
|
31
|
+
sql: jest.fn(() => ({
|
|
32
|
+
db: jest.fn(() => ({
|
|
33
|
+
get: jest.fn(() => Promise.resolve([])),
|
|
34
|
+
set: jest.fn(() => Promise.resolve()),
|
|
35
|
+
})),
|
|
36
|
+
})),
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Mock mm_machine Manager
|
|
41
|
+
const mockGetMods = jest.fn(() => ({}));
|
|
42
|
+
const mockGetInfos = jest.fn(() => []);
|
|
43
|
+
const mockGetMod = jest.fn(() => null);
|
|
44
|
+
const mockLog = jest.fn();
|
|
45
|
+
|
|
46
|
+
jest.mock('mm_machine', () => {
|
|
47
|
+
class MockManager {
|
|
48
|
+
constructor(config, parent) {
|
|
49
|
+
this.config = config;
|
|
50
|
+
this.parent = parent;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
MockManager.prototype.getMods = mockGetMods;
|
|
54
|
+
MockManager.prototype.getInfos = mockGetInfos;
|
|
55
|
+
MockManager.prototype.getMod = mockGetMod;
|
|
56
|
+
MockManager.prototype.log = mockLog;
|
|
57
|
+
MockManager.prototype.getEventer = jest.fn(() => ({}));
|
|
58
|
+
MockManager.prototype.getParentKey = jest.fn(() => 'Mqtt_test');
|
|
59
|
+
MockManager.prototype.getBaseDir = jest.fn(() => __dirname);
|
|
60
|
+
MockManager.prototype.getTplDir = jest.fn(() => __dirname);
|
|
61
|
+
MockManager.prototype.getDirName = jest.fn(() => 'mqtt_test');
|
|
62
|
+
return { Manager: MockManager, Drive: class MockDrive {}, MODE: { HOT_RELOAD: 3 } };
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
jest.mock('mqtt', () => ({
|
|
66
|
+
connect: jest.fn(() => ({
|
|
67
|
+
on: jest.fn(),
|
|
68
|
+
subscribe: jest.fn(),
|
|
69
|
+
unsubscribe: jest.fn(),
|
|
70
|
+
publish: jest.fn(),
|
|
71
|
+
end: jest.fn(),
|
|
72
|
+
removeAllListeners: jest.fn(),
|
|
73
|
+
options: { clientId: 'test_client' },
|
|
74
|
+
})),
|
|
75
|
+
}));
|
|
76
|
+
|
|
77
|
+
const { Mqtt } = require('./index');
|
|
78
|
+
|
|
79
|
+
describe('Mqtt 类', () => {
|
|
80
|
+
let mqtt;
|
|
81
|
+
|
|
82
|
+
beforeEach(() => {
|
|
83
|
+
jest.clearAllMocks();
|
|
84
|
+
mqtt = new Mqtt({ name: 'test' });
|
|
85
|
+
mqtt._preset();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('match() 正则缓存', () => {
|
|
89
|
+
test('完全匹配应返回true', () => {
|
|
90
|
+
expect(mqtt.match('face/device1/status', 'face/device1/status')).toBe(true);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('+通配符匹配', () => {
|
|
94
|
+
expect(mqtt.match('face/device1/status', 'face/+/status')).toBe(true);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('#通配符匹配', () => {
|
|
98
|
+
expect(mqtt.match('face/device1/status/extra', 'face/device1/#')).toBe(true);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('不匹配应返回false', () => {
|
|
102
|
+
expect(mqtt.match('other/topic', 'face/+/status')).toBe(false);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('正则缓存生效:相同模式复用RegExp', () => {
|
|
106
|
+
mqtt.match('face/device1/status', 'face/+/status');
|
|
107
|
+
mqtt.match('face/device2/status', 'face/+/status');
|
|
108
|
+
// 缓存中应有该模式
|
|
109
|
+
expect(mqtt._regex_cache['face/+/status']).toBeDefined();
|
|
110
|
+
expect(mqtt._regex_cache['face/+/status'] instanceof RegExp).toBe(true);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe('dict_msg O(1)查找', () => {
|
|
115
|
+
test('req() 应同步维护 dict_msg', () => {
|
|
116
|
+
mqtt.client = {
|
|
117
|
+
options: { clientId: 'test' },
|
|
118
|
+
publish: jest.fn(),
|
|
119
|
+
on: jest.fn(),
|
|
120
|
+
};
|
|
121
|
+
mqtt.send = jest.fn();
|
|
122
|
+
var callback = jest.fn();
|
|
123
|
+
mqtt.req('test/topic', 'testMethod', { key: 'val' }, callback);
|
|
124
|
+
|
|
125
|
+
expect(mqtt.list_msg.length).toBe(1);
|
|
126
|
+
expect(mqtt.dict_msg[mqtt.list_msg[0].id]).toBeDefined();
|
|
127
|
+
expect(mqtt.dict_msg[mqtt.list_msg[0].id].func).toBe(callback);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('_handleResponse() O(1) 查找并执行回调', () => {
|
|
131
|
+
var callback = jest.fn();
|
|
132
|
+
var msg_id = 'test_id_123';
|
|
133
|
+
mqtt.dict_msg[msg_id] = {
|
|
134
|
+
id: msg_id,
|
|
135
|
+
func: callback,
|
|
136
|
+
timer: null,
|
|
137
|
+
};
|
|
138
|
+
mqtt.list_msg.push({ id: msg_id, func: callback });
|
|
139
|
+
|
|
140
|
+
mqtt._handleResponse({ id: msg_id, result: 'ok' });
|
|
141
|
+
|
|
142
|
+
expect(callback).toHaveBeenCalledWith({ id: msg_id, result: 'ok' });
|
|
143
|
+
// 消息应从dict_msg中删除
|
|
144
|
+
expect(mqtt.dict_msg[msg_id]).toBeUndefined();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('_handleResponse() 无id时应安全返回', () => {
|
|
148
|
+
expect(() => mqtt._handleResponse(null)).not.toThrow();
|
|
149
|
+
expect(() => mqtt._handleResponse({})).not.toThrow();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test('_delMsgById() 同步清理 dict_msg 和 list_msg', () => {
|
|
153
|
+
var msg_id = 'del_test_123';
|
|
154
|
+
mqtt.dict_msg[msg_id] = { id: msg_id, timer: null };
|
|
155
|
+
mqtt.list_msg.push({ id: msg_id });
|
|
156
|
+
|
|
157
|
+
mqtt._delMsgById(msg_id);
|
|
158
|
+
|
|
159
|
+
expect(mqtt.dict_msg[msg_id]).toBeUndefined();
|
|
160
|
+
expect(mqtt.list_msg.length).toBe(0);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test('_delMsgById() 清除关联定时器', () => {
|
|
164
|
+
var clear_fn = jest.spyOn(global, 'clearTimeout');
|
|
165
|
+
var msg_id = 'timer_test_123';
|
|
166
|
+
var mock_timer = { _id: 999 };
|
|
167
|
+
mqtt.dict_msg[msg_id] = { id: msg_id, timer: mock_timer };
|
|
168
|
+
mqtt.list_msg.push({ id: msg_id });
|
|
169
|
+
|
|
170
|
+
mqtt._delMsgById(msg_id);
|
|
171
|
+
|
|
172
|
+
expect(clear_fn).toHaveBeenCalledWith(mock_timer);
|
|
173
|
+
clear_fn.mockRestore();
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe('_collectTopicPromises() 收集匹配主题', () => {
|
|
178
|
+
test('应收集所有匹配主题的handler Promise', () => {
|
|
179
|
+
var mockHandle = jest.fn(() => Promise.resolve('handled'));
|
|
180
|
+
var mockMod = {
|
|
181
|
+
config: { state: 1, topic: ['face/+/status', 'face/+/event'] },
|
|
182
|
+
call: jest.fn((method, ...args) => {
|
|
183
|
+
if (method === 'handle') {
|
|
184
|
+
return mockHandle(...args);
|
|
185
|
+
}
|
|
186
|
+
return Promise.resolve();
|
|
187
|
+
}),
|
|
188
|
+
};
|
|
189
|
+
mockGetMods.mockReturnValue({ mod1: mockMod });
|
|
190
|
+
|
|
191
|
+
var result = mqtt._collectTopicPromises('face/device1/status', { data: 'test' });
|
|
192
|
+
|
|
193
|
+
expect(result.promises.length).toBe(1);
|
|
194
|
+
expect(result.topic).toBe('face/+/event');
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test('应跳过 state !== 1 的mod', () => {
|
|
198
|
+
var mockMod = {
|
|
199
|
+
config: { state: 0, topic: ['face/+/status'] },
|
|
200
|
+
call: jest.fn(),
|
|
201
|
+
};
|
|
202
|
+
mockGetMods.mockReturnValue({ mod1: mockMod });
|
|
203
|
+
|
|
204
|
+
var result = mqtt._collectTopicPromises('face/device1/status', {});
|
|
205
|
+
|
|
206
|
+
expect(result.promises.length).toBe(0);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test('应跳过无topic的mod', () => {
|
|
210
|
+
var mockMod = {
|
|
211
|
+
config: { state: 1 },
|
|
212
|
+
call: jest.fn(),
|
|
213
|
+
};
|
|
214
|
+
mockGetMods.mockReturnValue({ mod1: mockMod });
|
|
215
|
+
|
|
216
|
+
var result = mqtt._collectTopicPromises('face/device1/status', {});
|
|
217
|
+
|
|
218
|
+
expect(result.promises.length).toBe(0);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe('_processTopics() 并行处理', () => {
|
|
223
|
+
test('应并行执行多个handler', async () => {
|
|
224
|
+
var call_order = [];
|
|
225
|
+
var mockMod1 = {
|
|
226
|
+
config: { state: 1, topic: ['face/+/status'] },
|
|
227
|
+
call: jest.fn((method) => {
|
|
228
|
+
if (method === 'handle') {
|
|
229
|
+
return new Promise((resolve) => {
|
|
230
|
+
setTimeout(() => {
|
|
231
|
+
call_order.push('mod1');
|
|
232
|
+
resolve('result1');
|
|
233
|
+
}, 50);
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
return Promise.resolve();
|
|
237
|
+
}),
|
|
238
|
+
};
|
|
239
|
+
var mockMod2 = {
|
|
240
|
+
config: { state: 1, topic: ['face/+/status'] },
|
|
241
|
+
call: jest.fn((method) => {
|
|
242
|
+
if (method === 'handle') {
|
|
243
|
+
return new Promise((resolve) => {
|
|
244
|
+
setTimeout(() => {
|
|
245
|
+
call_order.push('mod2');
|
|
246
|
+
resolve('result2');
|
|
247
|
+
}, 10);
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
return Promise.resolve();
|
|
251
|
+
}),
|
|
252
|
+
};
|
|
253
|
+
mockGetMods.mockReturnValue({ mod1: mockMod1, mod2: mockMod2 });
|
|
254
|
+
|
|
255
|
+
var result = await mqtt._processTopics('face/device1/status', {});
|
|
256
|
+
|
|
257
|
+
// mod2(10ms)应在mod1(50ms)之前完成,证明是并行执行的
|
|
258
|
+
expect(call_order[0]).toBe('mod2');
|
|
259
|
+
expect(call_order[1]).toBe('mod1');
|
|
260
|
+
// Promise.allSettled 保持输入顺序,从后往前取最后一个非空结果
|
|
261
|
+
// 输入顺序: [mod1, mod2],所以 result = 'result2'
|
|
262
|
+
expect(result.result).toBe('result2');
|
|
263
|
+
expect(result.topic).toBe('face/+/status');
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test('一个handler失败不应影响其他', async () => {
|
|
267
|
+
var mod2_called = false;
|
|
268
|
+
var mockMod1 = {
|
|
269
|
+
config: { state: 1, topic: ['face/+/status'] },
|
|
270
|
+
call: jest.fn((method) => {
|
|
271
|
+
if (method === 'handle') {
|
|
272
|
+
return Promise.reject(new Error('mod1 failed'));
|
|
273
|
+
}
|
|
274
|
+
return Promise.resolve();
|
|
275
|
+
}),
|
|
276
|
+
};
|
|
277
|
+
var mockMod2 = {
|
|
278
|
+
config: { state: 1, topic: ['face/+/status'] },
|
|
279
|
+
call: jest.fn((method) => {
|
|
280
|
+
if (method === 'handle') {
|
|
281
|
+
mod2_called = true;
|
|
282
|
+
return Promise.resolve('result2');
|
|
283
|
+
}
|
|
284
|
+
return Promise.resolve();
|
|
285
|
+
}),
|
|
286
|
+
};
|
|
287
|
+
mockGetMods.mockReturnValue({ mod1: mockMod1, mod2: mockMod2 });
|
|
288
|
+
|
|
289
|
+
var result = await mqtt._processTopics('face/device1/status', {});
|
|
290
|
+
|
|
291
|
+
expect(mod2_called).toBe(true);
|
|
292
|
+
expect(result.result).toBe('result2');
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
describe('updateAfter() 并行初始化', () => {
|
|
297
|
+
test('应并行执行所有mod的init', async () => {
|
|
298
|
+
var init_order = [];
|
|
299
|
+
var createMod = (name, delay) => ({
|
|
300
|
+
config: { name },
|
|
301
|
+
do: jest.fn((method) => {
|
|
302
|
+
if (method === 'init') {
|
|
303
|
+
return new Promise((resolve) => {
|
|
304
|
+
setTimeout(() => {
|
|
305
|
+
init_order.push(name);
|
|
306
|
+
resolve();
|
|
307
|
+
}, delay);
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
return Promise.resolve();
|
|
311
|
+
}),
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
mockGetInfos.mockReturnValue([
|
|
315
|
+
{ name: 'mod1', state: 1 },
|
|
316
|
+
{ name: 'mod2', state: 1 },
|
|
317
|
+
]);
|
|
318
|
+
mockGetMod.mockImplementation((name) => {
|
|
319
|
+
if (name === 'mod1') return createMod('mod1', 50);
|
|
320
|
+
if (name === 'mod2') return createMod('mod2', 10);
|
|
321
|
+
return null;
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
await mqtt.updateAfter();
|
|
325
|
+
|
|
326
|
+
// mod2(10ms)应在mod1(50ms)之前完成
|
|
327
|
+
expect(init_order[0]).toBe('mod2');
|
|
328
|
+
expect(init_order[1]).toBe('mod1');
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
describe('_updateDeviceStatus() 并行DB写入', () => {
|
|
333
|
+
test('应并行执行所有saveOnline调用', async () => {
|
|
334
|
+
mqtt.saveOnline = jest.fn(() => Promise.resolve());
|
|
335
|
+
|
|
336
|
+
var list_offline = ['dev1', 'dev2', 'dev3', 'dev4'];
|
|
337
|
+
var list_online = ['dev5', 'dev6'];
|
|
338
|
+
|
|
339
|
+
await mqtt._updateDeviceStatus(list_offline, list_online);
|
|
340
|
+
|
|
341
|
+
// saveOnline 应被调用(批次不固定,取决于to2D分组)
|
|
342
|
+
expect(mqtt.saveOnline).toHaveBeenCalled();
|
|
343
|
+
var calls = mqtt.saveOnline.mock.calls;
|
|
344
|
+
// 验证离线设备传入online=0
|
|
345
|
+
var off_calls = calls.filter((c) => c[1] === 0);
|
|
346
|
+
expect(off_calls.length).toBeGreaterThan(0);
|
|
347
|
+
// 验证在线设备传入online=1
|
|
348
|
+
var on_calls = calls.filter((c) => c[1] === 1);
|
|
349
|
+
expect(on_calls.length).toBeGreaterThan(0);
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
describe('start() 定时器启动', () => {
|
|
354
|
+
test('start() 不应包含setTimeout延迟', () => {
|
|
355
|
+
var setTimeout_spy = jest.spyOn(global, 'setTimeout');
|
|
356
|
+
mockGetInfos.mockReturnValue([]);
|
|
357
|
+
|
|
358
|
+
// start() 内部会调用_createClient,但connect会失败
|
|
359
|
+
// 我们只验证没有setTimeout调用
|
|
360
|
+
mqtt._createClient = jest.fn(() => {
|
|
361
|
+
mqtt.client = { on: true };
|
|
362
|
+
});
|
|
363
|
+
mqtt._setupClientEvents = jest.fn(() => Promise.resolve('connected'));
|
|
364
|
+
|
|
365
|
+
mqtt.start();
|
|
366
|
+
|
|
367
|
+
// 验证start()内部没有调用setTimeout
|
|
368
|
+
var start_calls = setTimeout_spy.mock.calls.filter(
|
|
369
|
+
(c) => typeof c[1] === 'number' && c[1] === 2000,
|
|
370
|
+
);
|
|
371
|
+
expect(start_calls.length).toBe(0);
|
|
372
|
+
|
|
373
|
+
setTimeout_spy.mockRestore();
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
describe('多厂商并行处理场景 (模拟 example/app/face/mqtt/)', () => {
|
|
378
|
+
/**
|
|
379
|
+
* 模拟真实场景:多个设备协议mod同时订阅同一主题
|
|
380
|
+
* 例如 face/+/status 主题被 DA、TS、EF 三个厂商mod同时匹配
|
|
381
|
+
* 每个mod的handler可能执行DB查询、HTTP请求、日志记录等耗时操作
|
|
382
|
+
*/
|
|
383
|
+
test('多个厂商mod应并行处理同一条消息', async () => {
|
|
384
|
+
var call_order = [];
|
|
385
|
+
|
|
386
|
+
// 创建模拟mod的工厂函数
|
|
387
|
+
var createMod = (name, delay) => ({
|
|
388
|
+
config: {
|
|
389
|
+
name,
|
|
390
|
+
state: 1,
|
|
391
|
+
topic: ['face/+/status', 'face/+/event/recognition'],
|
|
392
|
+
},
|
|
393
|
+
call: jest.fn((method, push_topic, msg, topic, n) => {
|
|
394
|
+
if (method === 'handle') {
|
|
395
|
+
return new Promise((resolve) => {
|
|
396
|
+
setTimeout(() => {
|
|
397
|
+
call_order.push(name);
|
|
398
|
+
// 模拟真实场景:handler返回处理结果
|
|
399
|
+
resolve({ result: 'ok', mod: name });
|
|
400
|
+
}, delay);
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
return Promise.resolve();
|
|
404
|
+
}),
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
// 模拟5个厂商mod,各自有不同的处理耗时
|
|
408
|
+
mockGetMods.mockReturnValue({
|
|
409
|
+
da: createMod('DA', 80), // RV1109云对接协议 - 较慢
|
|
410
|
+
ts: createMod('TS', 30), // 自定设备 - 中等
|
|
411
|
+
ef: createMod('EF', 60), // 另一个厂商
|
|
412
|
+
df: createMod('DF', 10), // 快速
|
|
413
|
+
zs: createMod('ZS', 110), // 最慢(可能涉及HTTP请求)
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
var result = await mqtt._processTopics('face/device1/status', {
|
|
417
|
+
cmd: 'reportVisitRecord',
|
|
418
|
+
param: { CustomID: 'test_user', Name: '测试用户' },
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
// 验证并行执行:最快的DF(10ms)先完成,ZS(110ms)最后完成
|
|
422
|
+
expect(call_order[0]).toBe('DF');
|
|
423
|
+
expect(call_order[call_order.length - 1]).toBe('ZS');
|
|
424
|
+
// 所有5个mod都被调用了
|
|
425
|
+
expect(call_order.length).toBe(5);
|
|
426
|
+
// 有返回结果
|
|
427
|
+
expect(result.result).toBeDefined();
|
|
428
|
+
expect(result.result.mod).toBeDefined();
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
test('一个厂商handler失败不应影响其他厂商', async () => {
|
|
432
|
+
var called = [];
|
|
433
|
+
|
|
434
|
+
var createMod = (name, should_fail) => ({
|
|
435
|
+
config: {
|
|
436
|
+
name,
|
|
437
|
+
state: 1,
|
|
438
|
+
topic: ['face/+/event/recognition'],
|
|
439
|
+
},
|
|
440
|
+
call: jest.fn((method) => {
|
|
441
|
+
if (method === 'handle') {
|
|
442
|
+
if (should_fail) {
|
|
443
|
+
return Promise.reject(new Error(name + ' handler failed'));
|
|
444
|
+
}
|
|
445
|
+
called.push(name);
|
|
446
|
+
return Promise.resolve({ result: 'ok', mod: name });
|
|
447
|
+
}
|
|
448
|
+
return Promise.resolve();
|
|
449
|
+
}),
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
mockGetMods.mockReturnValue({
|
|
453
|
+
da: createMod('DA', false),
|
|
454
|
+
ts: createMod('TS', true), // TS 厂商handler失败
|
|
455
|
+
ef: createMod('EF', false),
|
|
456
|
+
df: createMod('DF', false),
|
|
457
|
+
zs: createMod('ZS', false),
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
var result = await mqtt._processTopics('face/device1/event/recognition', {
|
|
461
|
+
cmd: 'reportVisitRecord',
|
|
462
|
+
param: { CustomID: 'test_user' },
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
// TS失败但其他4个厂商都被调用了
|
|
466
|
+
expect(called.length).toBe(4);
|
|
467
|
+
expect(called).toContain('DA');
|
|
468
|
+
expect(called).toContain('EF');
|
|
469
|
+
expect(called).toContain('DF');
|
|
470
|
+
expect(called).toContain('ZS');
|
|
471
|
+
expect(called).not.toContain('TS');
|
|
472
|
+
// 有返回结果
|
|
473
|
+
expect(result.result).toBeDefined();
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
test('全部厂商handler失败应返回空结果', async () => {
|
|
477
|
+
var createMod = (name) => ({
|
|
478
|
+
config: {
|
|
479
|
+
name,
|
|
480
|
+
state: 1,
|
|
481
|
+
topic: ['face/+/status'],
|
|
482
|
+
},
|
|
483
|
+
call: jest.fn((method) => {
|
|
484
|
+
if (method === 'handle') {
|
|
485
|
+
return Promise.reject(new Error(name + ' failed'));
|
|
486
|
+
}
|
|
487
|
+
return Promise.resolve();
|
|
488
|
+
}),
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
mockGetMods.mockReturnValue({
|
|
492
|
+
da: createMod('DA'),
|
|
493
|
+
ts: createMod('TS'),
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
var result = await mqtt._processTopics('face/device1/status', {});
|
|
497
|
+
|
|
498
|
+
// 所有mod都失败,result应为undefined
|
|
499
|
+
expect(result.result).toBeUndefined();
|
|
500
|
+
expect(result.topic).toBeDefined();
|
|
501
|
+
});
|
|
502
|
+
});
|
|
503
|
+
});
|