mm_os 4.3.7 → 5.0.1

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