mm_os 4.3.7 → 5.0.0

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.
Files changed (232) hide show
  1. package/README.md +101 -504
  2. package/index.js +26 -19
  3. package/lib/loader.js +787 -0
  4. package/lib/os.js +369 -0
  5. package/package.json +55 -84
  6. package/LICENSE +0 -21
  7. package/README_EN.md +0 -505
  8. package/adapter/adapter.js +0 -484
  9. package/adapter/custom_persistence.js +0 -661
  10. package/adapter/mqtt.js +0 -254
  11. package/adapter/socket.js +0 -244
  12. package/adapter/web.js +0 -92
  13. package/adapter/websocket.js +0 -43
  14. package/ai.js +0 -130
  15. package/com/api/README.md +0 -3
  16. package/com/api/com.json +0 -5
  17. package/com/api/config.tpl.json +0 -41
  18. package/com/api/drive.js +0 -732
  19. package/com/api/index.js +0 -201
  20. package/com/api/oauth.js +0 -200
  21. package/com/api/script.tpl.js +0 -32
  22. package/com/cmd/README.md +0 -11
  23. package/com/cmd/com.json +0 -5
  24. package/com/cmd/config.tpl.json +0 -48
  25. package/com/cmd/drive.js +0 -176
  26. package/com/cmd/index.js +0 -287
  27. package/com/cmd/job/config.json +0 -66
  28. package/com/cmd/job/index.js +0 -241
  29. package/com/cmd/job/test.js +0 -188
  30. package/com/cmd/msg.json +0 -48
  31. package/com/cmd/script.tpl.js +0 -12
  32. package/com/db/README.md +0 -3
  33. package/com/db/com.json +0 -5
  34. package/com/db/drive.js +0 -2206
  35. package/com/db/index.js +0 -241
  36. package/com/event/README.md +0 -4
  37. package/com/event/com.json +0 -5
  38. package/com/event/config.tpl.json +0 -20
  39. package/com/event/drive.js +0 -61
  40. package/com/event/index.js +0 -438
  41. package/com/event/script.tpl.js +0 -23
  42. package/com/mqtt/README.md +0 -47
  43. package/com/mqtt/com.json +0 -5
  44. package/com/mqtt/config.tpl.json +0 -42
  45. package/com/mqtt/drive.js +0 -704
  46. package/com/mqtt/index.js +0 -1052
  47. package/com/mqtt/index.test.js +0 -503
  48. package/com/mqtt/script.tpl.js +0 -719
  49. package/com/nav/README.md +0 -2
  50. package/com/nav/com.json +0 -5
  51. package/com/nav/config.tpl.json +0 -84
  52. package/com/nav/drive.js +0 -702
  53. package/com/nav/index.js +0 -228
  54. package/com/nav/tpl/admin_pc/page_channel.vue +0 -305
  55. package/com/nav/tpl/admin_pc/page_config.vue +0 -280
  56. package/com/nav/tpl/admin_pc/page_config_form.vue +0 -194
  57. package/com/nav/tpl/admin_pc/page_default.vue +0 -247
  58. package/com/nav/tpl/admin_pc/page_form.vue +0 -180
  59. package/com/nav/tpl/admin_pc/page_lang.vue +0 -264
  60. package/com/nav/tpl/admin_pc/page_nav.vue +0 -321
  61. package/com/nav/tpl/admin_pc/page_table.vue +0 -296
  62. package/com/nav/tpl/admin_pc/page_type.vue +0 -321
  63. package/com/nav/tpl/admin_pc/page_view.vue +0 -124
  64. package/com/nav/tpl/dev_pc/page_channel.vue +0 -253
  65. package/com/nav/tpl/dev_pc/page_config.vue +0 -253
  66. package/com/nav/tpl/dev_pc/page_default.vue +0 -247
  67. package/com/nav/tpl/dev_pc/page_form.vue +0 -162
  68. package/com/nav/tpl/dev_pc/page_nav.vue +0 -253
  69. package/com/nav/tpl/dev_pc/page_table.vue +0 -247
  70. package/com/nav/tpl/dev_pc/page_type.vue +0 -313
  71. package/com/nav/tpl/home_pc/page_channel.vue +0 -234
  72. package/com/nav/tpl/home_pc/page_default.vue +0 -234
  73. package/com/nav/tpl/home_pc/page_form.vue +0 -137
  74. package/com/nav/tpl/home_pc/page_list.vue +0 -234
  75. package/com/nav/tpl/home_pc/page_nav.vue +0 -221
  76. package/com/nav/tpl/home_pc/page_type.vue +0 -234
  77. package/com/nav/tpl/home_pc/page_view.vue +0 -125
  78. package/com/nav/tpl/home_phone/page_channel.vue +0 -234
  79. package/com/nav/tpl/home_phone/page_default.vue +0 -234
  80. package/com/nav/tpl/home_phone/page_form.vue +0 -137
  81. package/com/nav/tpl/home_phone/page_list.vue +0 -234
  82. package/com/nav/tpl/home_phone/page_nav.vue +0 -237
  83. package/com/nav/tpl/home_phone/page_type.vue +0 -234
  84. package/com/nav/tpl/home_phone/page_view.vue +0 -125
  85. package/com/nav/viewmodel.js +0 -446
  86. package/com/param/README.md +0 -2
  87. package/com/param/com.json +0 -5
  88. package/com/param/config.tpl.json +0 -136
  89. package/com/param/drive.js +0 -502
  90. package/com/param/index.js +0 -158
  91. package/com/param/script.tpl.js +0 -12
  92. package/com/pendant/README.md +0 -3
  93. package/com/pendant/com.json +0 -5
  94. package/com/pendant/config.tpl.json +0 -32
  95. package/com/pendant/drive.js +0 -204
  96. package/com/pendant/index.js +0 -467
  97. package/com/pendant/pendant.html +0 -16
  98. package/com/pendant/script.tpl.js +0 -18
  99. package/com/socket/README.md +0 -47
  100. package/com/socket/com.json +0 -5
  101. package/com/socket/config.tpl.json +0 -12
  102. package/com/socket/drive.js +0 -679
  103. package/com/socket/index.js +0 -402
  104. package/com/socket/script.tpl.js +0 -41
  105. package/com/sql/README.md +0 -3
  106. package/com/sql/com.json +0 -5
  107. package/com/sql/config.tpl.json +0 -74
  108. package/com/sql/drive.js +0 -1328
  109. package/com/sql/index.js +0 -152
  110. package/com/sql/script.tpl.js +0 -47
  111. package/com/static/README.md +0 -2
  112. package/com/static/com.json +0 -5
  113. package/com/static/config.tpl.json +0 -24
  114. package/com/static/drive.js +0 -194
  115. package/com/static/index.js +0 -237
  116. package/com/static/script.tpl.js +0 -28
  117. package/com/task/README.md +0 -3
  118. package/com/task/com.json +0 -5
  119. package/com/task/config.tpl.json +0 -24
  120. package/com/task/drive.js +0 -428
  121. package/com/task/index.js +0 -159
  122. package/com/task/script.tpl.js +0 -40
  123. package/com/template/com.json +0 -5
  124. package/com/template/config.tpl.json +0 -16
  125. package/com/template/drive.js +0 -81
  126. package/com/template/index.js +0 -141
  127. package/com.js +0 -237
  128. package/common/README.md +0 -2
  129. package/common/middleware/web_after/index.js +0 -26
  130. package/common/middleware/web_after/middleware.json +0 -9
  131. package/common/middleware/web_base/index.js +0 -113
  132. package/common/middleware/web_base/middleware.json +0 -19
  133. package/common/middleware/web_before/index.js +0 -33
  134. package/common/middleware/web_before/middleware.json +0 -9
  135. package/common/middleware/web_cors/index.js +0 -87
  136. package/common/middleware/web_cors/middleware.json +0 -24
  137. package/common/middleware/web_error/index.js +0 -131
  138. package/common/middleware/web_error/middleware.json +0 -19
  139. package/common/middleware/web_ip/index.js +0 -15
  140. package/common/middleware/web_ip/middleware.json +0 -14
  141. package/common/middleware/web_logger/index.js +0 -156
  142. package/common/middleware/web_logger/middleware.json +0 -14
  143. package/common/middleware/web_main/index.js +0 -26
  144. package/common/middleware/web_main/middleware.json +0 -9
  145. package/common/middleware/web_static/index.js +0 -73
  146. package/common/middleware/web_static/middleware.json +0 -54
  147. package/common/middleware/web_waf/index.js +0 -395
  148. package/common/middleware/web_waf/middleware.json +0 -13
  149. package/core/app/config.tpl.json +0 -67
  150. package/core/app/index.js +0 -711
  151. package/core/app/script.tpl.js +0 -52
  152. package/core/channel/index.js +0 -899
  153. package/core/channel/matcher.js +0 -585
  154. package/core/com/config.tpl.json +0 -16
  155. package/core/com/index.js +0 -77
  156. package/core/com/script.tpl.js +0 -5
  157. package/core/component/component.js +0 -42
  158. package/core/component/config.tpl.json +0 -63
  159. package/core/component/index.js +0 -273
  160. package/core/component/script.tpl.js +0 -19
  161. package/core/controller/config.tpl.json +0 -14
  162. package/core/controller/index.js +0 -371
  163. package/core/controller/script.tpl.js +0 -27
  164. package/core/factory/config.tpl.json +0 -14
  165. package/core/factory/entity.js +0 -275
  166. package/core/factory/index.js +0 -241
  167. package/core/factory/script.tpl.js +0 -16
  168. package/core/game/bat/index.js +0 -137
  169. package/core/game/bat/world.js +0 -622
  170. package/core/game/config.tpl.json +0 -16
  171. package/core/game/entity_admin.js +0 -230
  172. package/core/game/index.js +0 -186
  173. package/core/handler/config.tpl.json +0 -22
  174. package/core/handler/index.js +0 -181
  175. package/core/handler/script.tpl.js +0 -23
  176. package/core/logic/config.tpl.json +0 -14
  177. package/core/logic/index.js +0 -59
  178. package/core/logic/script.tpl.js +0 -19
  179. package/core/middleware/config.tpl.json +0 -16
  180. package/core/middleware/index.js +0 -117
  181. package/core/middleware/script.tpl.js +0 -37
  182. package/core/mod/config.tpl.json +0 -22
  183. package/core/mod/index.js +0 -136
  184. package/core/mod/script.tpl.js +0 -34
  185. package/core/model/config.tpl.json +0 -219
  186. package/core/model/index.js +0 -272
  187. package/core/model/model.js +0 -27
  188. package/core/model/script.tpl.js +0 -20
  189. package/core/notifier/config.tpl.json +0 -14
  190. package/core/notifier/index.js +0 -77
  191. package/core/notifier/script.tpl.js +0 -20
  192. package/core/plugin/config.tpl.json +0 -36
  193. package/core/plugin/index.js +0 -267
  194. package/core/plugin/script.tpl.js +0 -51
  195. package/core/pusher/config.tpl.json +0 -14
  196. package/core/pusher/index.js +0 -158
  197. package/core/pusher/script.tpl.js +0 -20
  198. package/core/room/bat/index.js +0 -170
  199. package/core/room/bat/room.js +0 -524
  200. package/core/room/config.tpl.json +0 -20
  201. package/core/room/index.js +0 -249
  202. package/core/room/room.js +0 -61
  203. package/core/scene/config.tpl.json +0 -14
  204. package/core/scene/index.js +0 -467
  205. package/core/scene/loop.js +0 -1306
  206. package/core/scene/map.js +0 -28
  207. package/core/scene/script.tpl.js +0 -22
  208. package/core/sender/config.tpl.json +0 -14
  209. package/core/sender/index.js +0 -79
  210. package/core/sender/script.tpl.js +0 -20
  211. package/core/service/config.tpl.json +0 -14
  212. package/core/service/index.js +0 -100
  213. package/core/service/script.tpl.js +0 -25
  214. package/core/store/config.tpl.json +0 -26
  215. package/core/store/index.js +0 -1794
  216. package/core/store/script.tpl.js +0 -22
  217. package/core/system/config.tpl.json +0 -18
  218. package/core/system/index.js +0 -312
  219. package/core/system/script.tpl.js +0 -77
  220. package/core/view/config.tpl.json +0 -14
  221. package/core/view/index.js +0 -91
  222. package/core/view/script.tpl.js +0 -20
  223. package/core/zone/bat/index.js +0 -744
  224. package/core/zone/config.tpl.json +0 -54
  225. package/core/zone/index.js +0 -655
  226. package/core/zone/script.tpl.js +0 -10
  227. package/core/zone/zone_bat.js +0 -136
  228. package/core//345/237/272/347/261/273/346/250/241/345/235/227/346/270/205/345/215/225.md +0 -24
  229. package/os.js +0 -53
  230. package/server.js +0 -602
  231. package/ulits/id_gen.js +0 -134
  232. package/ulits/word.js +0 -206
@@ -1,503 +0,0 @@
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
- });