dolphin-server-modules 2.9.4 → 2.9.6

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 (78) hide show
  1. package/README.md +45 -83
  2. package/TUTORIAL_NEPALI.md +65 -31
  3. package/dist/ai/dolphin-agent/agent.d.ts +25 -0
  4. package/dist/ai/dolphin-agent/agent.js +362 -0
  5. package/dist/ai/dolphin-agent/agent.js.map +1 -0
  6. package/dist/ai/dolphin-agent/config.d.ts +9 -0
  7. package/dist/ai/dolphin-agent/config.js +52 -0
  8. package/dist/ai/dolphin-agent/config.js.map +1 -0
  9. package/dist/ai/dolphin-agent/index.d.ts +5 -0
  10. package/dist/ai/dolphin-agent/index.js +13 -0
  11. package/dist/ai/dolphin-agent/index.js.map +1 -0
  12. package/dist/bin/cli.js +39 -125
  13. package/dist/bin/cli.js.map +1 -1
  14. package/dist/services/ai-service.d.ts +6 -0
  15. package/dist/services/ai-service.js +142 -23
  16. package/dist/services/ai-service.js.map +1 -1
  17. package/package.json +1 -1
  18. package/dist/adapters/mongoose/index.test.d.ts +0 -1
  19. package/dist/adapters/mongoose/index.test.js +0 -145
  20. package/dist/adapters/mongoose/index.test.js.map +0 -1
  21. package/dist/adapters/mongoose/integration.test.d.ts +0 -5
  22. package/dist/adapters/mongoose/integration.test.js +0 -217
  23. package/dist/adapters/mongoose/integration.test.js.map +0 -1
  24. package/dist/auth/auth.test.d.ts +0 -1
  25. package/dist/auth/auth.test.js +0 -286
  26. package/dist/auth/auth.test.js.map +0 -1
  27. package/dist/authController/authController.test.d.ts +0 -1
  28. package/dist/authController/authController.test.js +0 -359
  29. package/dist/authController/authController.test.js.map +0 -1
  30. package/dist/controller/controller.test.d.ts +0 -1
  31. package/dist/controller/controller.test.js +0 -37
  32. package/dist/controller/controller.test.js.map +0 -1
  33. package/dist/curd/crud.test.d.ts +0 -1
  34. package/dist/curd/crud.test.js +0 -104
  35. package/dist/curd/crud.test.js.map +0 -1
  36. package/dist/demo-server.d.ts +0 -1
  37. package/dist/demo-server.js +0 -191
  38. package/dist/demo-server.js.map +0 -1
  39. package/dist/djson/djson.test.d.ts +0 -1
  40. package/dist/djson/djson.test.js +0 -200
  41. package/dist/djson/djson.test.js.map +0 -1
  42. package/dist/dolphin-bench.d.ts +0 -1
  43. package/dist/dolphin-bench.js +0 -63
  44. package/dist/dolphin-bench.js.map +0 -1
  45. package/dist/hard-performance-test.d.ts +0 -1
  46. package/dist/hard-performance-test.js +0 -97
  47. package/dist/hard-performance-test.js.map +0 -1
  48. package/dist/middleware/zod.test.d.ts +0 -1
  49. package/dist/middleware/zod.test.js +0 -74
  50. package/dist/middleware/zod.test.js.map +0 -1
  51. package/dist/performance-test.d.ts +0 -1
  52. package/dist/performance-test.js +0 -92
  53. package/dist/performance-test.js.map +0 -1
  54. package/dist/real-test-mongoose.d.ts +0 -1
  55. package/dist/real-test-mongoose.js +0 -104
  56. package/dist/real-test-mongoose.js.map +0 -1
  57. package/dist/realtime/realtime.test.d.ts +0 -1
  58. package/dist/realtime/realtime.test.js +0 -623
  59. package/dist/realtime/realtime.test.js.map +0 -1
  60. package/dist/router/router.test.d.ts +0 -1
  61. package/dist/router/router.test.js +0 -45
  62. package/dist/router/router.test.js.map +0 -1
  63. package/dist/server/server.test.d.ts +0 -1
  64. package/dist/server/server.test.js +0 -299
  65. package/dist/server/server.test.js.map +0 -1
  66. package/dist/signaling/signaling.test.d.ts +0 -1
  67. package/dist/signaling/signaling.test.js +0 -112
  68. package/dist/signaling/signaling.test.js.map +0 -1
  69. package/dist/swagger/swagger.test.d.ts +0 -1
  70. package/dist/swagger/swagger.test.js +0 -38
  71. package/dist/swagger/swagger.test.js.map +0 -1
  72. package/dist/test-2fa-real.d.ts +0 -1
  73. package/dist/test-2fa-real.js +0 -105
  74. package/dist/test-2fa-real.js.map +0 -1
  75. package/dist/test-dolphin.d.ts +0 -1
  76. package/dist/test-dolphin.js +0 -98
  77. package/dist/test-dolphin.js.map +0 -1
  78. package/dist/tsconfig.tsbuildinfo +0 -1
@@ -1,623 +0,0 @@
1
- /// <reference types="jest" />
2
- import { TopicTrie } from './trie';
3
- import { RealtimeCore } from './core';
4
- import * as fs from 'fs';
5
- import * as path from 'path';
6
- class MockWebSocket {
7
- readyState = 1;
8
- messages = [];
9
- closed = false;
10
- pingCalled = false;
11
- send(data) {
12
- this.messages.push(data);
13
- }
14
- ping() {
15
- this.pingCalled = true;
16
- }
17
- close() {
18
- this.closed = true;
19
- this.readyState = 3;
20
- }
21
- }
22
- describe('Realtime Module v2 - Tests', () => {
23
- let trie;
24
- let realtime;
25
- let createdInstances = [];
26
- let testFileId;
27
- let testFilePath;
28
- beforeAll(() => {
29
- // Create a test file for file transfer tests
30
- testFileId = 'test-file-001';
31
- testFilePath = path.join(__dirname, 'test-temp-file.bin');
32
- const testData = Buffer.alloc(1024 * 500, 'A'); // 500KB test file (needs 8 chunks of 64KB to allow chunk index 6 resume test)
33
- fs.writeFileSync(testFilePath, testData);
34
- });
35
- afterAll(() => {
36
- // Clean up test file
37
- if (fs.existsSync(testFilePath)) {
38
- fs.unlinkSync(testFilePath);
39
- }
40
- });
41
- beforeEach(() => {
42
- trie = new TopicTrie();
43
- realtime = new RealtimeCore({
44
- maxMessageSize: 1024 * 1024,
45
- enableJSONCache: true,
46
- useBinaryProtocol: false,
47
- debug: false,
48
- enableP2P: true,
49
- maxBufferPerTopic: 100,
50
- defaultChunkSize: 64 * 1024
51
- });
52
- createdInstances.push(realtime);
53
- });
54
- afterEach(async () => {
55
- jest.clearAllMocks();
56
- jest.restoreAllMocks();
57
- // Clean up all created instances
58
- for (const instance of createdInstances) {
59
- if (instance && typeof instance.destroy === 'function') {
60
- await instance.destroy();
61
- }
62
- }
63
- createdInstances = [];
64
- });
65
- afterAll(async () => {
66
- await new Promise(resolve => setTimeout(resolve, 200));
67
- });
68
- // ============================================
69
- // TopicTrie Tests (Existing)
70
- // ============================================
71
- describe('TopicTrie', () => {
72
- it('should match exact topics', () => {
73
- const fn = jest.fn();
74
- trie.add('sensors/temp', fn);
75
- trie.match('sensors/temp', (f) => f());
76
- expect(fn).toHaveBeenCalledTimes(1);
77
- });
78
- it('should match single level wildcard (+)', () => {
79
- const fn = jest.fn();
80
- trie.add('sensors/+', fn);
81
- trie.match('sensors/temp', (f) => f());
82
- trie.match('sensors/hum', (f) => f());
83
- trie.match('actuators/led', (f) => f());
84
- expect(fn).toHaveBeenCalledTimes(2);
85
- });
86
- it('should match multi-level wildcard (#)', () => {
87
- const fn = jest.fn();
88
- trie.add('sensors/#', fn);
89
- trie.match('sensors/temp', (f) => f());
90
- trie.match('sensors/temp/value', (f) => f());
91
- trie.match('sensors/hum/status', (f) => f());
92
- trie.match('actuators/led', (f) => f());
93
- expect(fn).toHaveBeenCalledTimes(3);
94
- });
95
- it('should handle multiple subscribers on same topic', () => {
96
- const fn1 = jest.fn();
97
- const fn2 = jest.fn();
98
- trie.add('device/status', fn1);
99
- trie.add('device/status', fn2);
100
- trie.match('device/status', (f) => f());
101
- expect(fn1).toHaveBeenCalledTimes(1);
102
- expect(fn2).toHaveBeenCalledTimes(1);
103
- });
104
- });
105
- // ============================================
106
- // Pub/Sub Tests (Existing)
107
- // ============================================
108
- describe('Publish/Subscribe', () => {
109
- it('should publish and receive messages', (done) => {
110
- const testTopic = 'test/topic';
111
- const testPayload = { message: 'hello world' };
112
- realtime.subscribe(testTopic, (payload) => {
113
- expect(payload).toEqual(testPayload);
114
- done();
115
- });
116
- realtime.publish(testTopic, testPayload);
117
- });
118
- it('should handle retained messages', () => {
119
- const fn = jest.fn();
120
- const testTopic = 'retained/topic';
121
- const testPayload = { data: 'retained message' };
122
- realtime.publish(testTopic, testPayload, { retain: true, ttl: 10000 });
123
- realtime.subscribe(testTopic, fn);
124
- expect(fn).toHaveBeenCalledTimes(1);
125
- expect(fn).toHaveBeenCalledWith(testPayload);
126
- });
127
- });
128
- // ============================================
129
- // v2 NEW: pubPush / subPull Tests
130
- // ============================================
131
- describe('High-Frequency: pubPush / subPull', () => {
132
- it('should push binary data with pubPush', () => {
133
- const fn = jest.fn();
134
- const topic = 'sensor/live';
135
- const binaryData = Buffer.from([0x01, 0x02, 0x03, 0x04]);
136
- realtime.subscribe(topic, fn);
137
- realtime.pubPush(topic, binaryData);
138
- expect(fn).toHaveBeenCalledTimes(1);
139
- expect(fn).toHaveBeenCalledWith(binaryData);
140
- });
141
- it('should pull buffered data with subPull', async () => {
142
- const deviceId = 'test-device-001';
143
- const mockSocket = new MockWebSocket();
144
- const topic = 'sensor/history';
145
- realtime.register(deviceId, mockSocket);
146
- // Push multiple data points
147
- for (let i = 1; i <= 25; i++) {
148
- realtime.pubPush(topic, Buffer.from([i]));
149
- }
150
- // Pull last 10 items
151
- realtime.subPull(deviceId, topic, 10);
152
- await new Promise(resolve => setTimeout(resolve, 50));
153
- expect(mockSocket.messages.length).toBeGreaterThan(0);
154
- const lastMessage = JSON.parse(mockSocket.messages[mockSocket.messages.length - 1]);
155
- expect(lastMessage.type).toBe('PULL_RESPONSE');
156
- expect(lastMessage.topic).toBe(topic);
157
- expect(lastMessage.count).toBeLessThanOrEqual(10);
158
- });
159
- it('should handle empty buffer in subPull', () => {
160
- const deviceId = 'test-device-002';
161
- const mockSocket = new MockWebSocket();
162
- const topic = 'empty/topic';
163
- realtime.register(deviceId, mockSocket);
164
- realtime.subPull(deviceId, topic, 10);
165
- const lastMessage = JSON.parse(mockSocket.messages[0]);
166
- expect(lastMessage.type).toBe('PULL_EMPTY');
167
- expect(lastMessage.message).toBe('No data available');
168
- });
169
- });
170
- // ============================================
171
- // v2 NEW: File Transfer Tests
172
- // ============================================
173
- describe('File Transfer: pubFile / subFile', () => {
174
- it('should publish a file with pubFile', () => {
175
- const fileId = 'test-pub-file';
176
- const metadata = realtime.pubFile(fileId, testFilePath);
177
- expect(metadata).not.toBeNull();
178
- expect(metadata?.name).toBe('test-temp-file.bin');
179
- expect(metadata?.size).toBe(1024 * 500);
180
- expect(metadata?.totalChunks).toBeGreaterThan(0);
181
- });
182
- it('should return null for non-existent file', () => {
183
- const result = realtime.pubFile('missing-file', '/path/to/nonexistent.bin');
184
- expect(result).toBeNull();
185
- });
186
- it('should get file info', () => {
187
- const fileId = 'test-info-file';
188
- realtime.pubFile(fileId, testFilePath);
189
- const info = realtime.getFileInfo(fileId);
190
- expect(info).toBeDefined();
191
- expect(info?.size).toBe(1024 * 500);
192
- });
193
- it('should list all available files', () => {
194
- realtime.pubFile('file-1', testFilePath);
195
- realtime.pubFile('file-2', testFilePath);
196
- const files = realtime.listFiles();
197
- expect(files.length).toBeGreaterThanOrEqual(2);
198
- expect(files[0]).toHaveProperty('fileId');
199
- expect(files[0]).toHaveProperty('name');
200
- expect(files[0]).toHaveProperty('size');
201
- });
202
- it('should download file chunks with subFile', async () => {
203
- const deviceId = 'download-device';
204
- const mockSocket = new MockWebSocket();
205
- const fileId = 'test-download-file';
206
- realtime.register(deviceId, mockSocket);
207
- realtime.pubFile(fileId, testFilePath);
208
- // Download first chunk
209
- const result = await realtime.subFile(deviceId, fileId, 0);
210
- expect(result).toBe(true);
211
- expect(mockSocket.messages.length).toBeGreaterThan(0);
212
- const message = JSON.parse(mockSocket.messages[0]);
213
- expect(message.type).toBe('FILE_CHUNK');
214
- expect(message.fileId).toBe(fileId);
215
- expect(message.chunkIndex).toBe(0);
216
- expect(message.totalChunks).toBeDefined();
217
- });
218
- it('should handle file not found in subFile', async () => {
219
- const deviceId = 'error-device';
220
- const mockSocket = new MockWebSocket();
221
- realtime.register(deviceId, mockSocket);
222
- const result = await realtime.subFile(deviceId, 'non-existent-file', 0);
223
- expect(result).toBe(false);
224
- const message = JSON.parse(mockSocket.messages[0]);
225
- expect(message.type).toBe('FILE_ERROR');
226
- expect(message.error).toBe('File not found');
227
- });
228
- });
229
- // ============================================
230
- // v2 NEW: Resume Feature Tests
231
- // ============================================
232
- describe('Resume Feature', () => {
233
- it('should save and get file progress', () => {
234
- const deviceId = 'resume-device';
235
- const fileId = 'resume-file';
236
- // Save progress at chunk 5
237
- realtime.saveFileProgress(deviceId, fileId, 5);
238
- const progress = realtime.getFileProgress(deviceId, fileId);
239
- expect(progress).toBe(5);
240
- });
241
- it('should return -1 for no progress', () => {
242
- const progress = realtime.getFileProgress('unknown-device', 'unknown-file');
243
- expect(progress).toBe(-1);
244
- });
245
- it('should resume file from last chunk', async () => {
246
- const deviceId = 'resume-device-2';
247
- const mockSocket = new MockWebSocket();
248
- const fileId = 'test-resume-file';
249
- realtime.register(deviceId, mockSocket);
250
- realtime.pubFile(fileId, testFilePath);
251
- // Simulate partial download (chunk 5 completed)
252
- realtime.saveFileProgress(deviceId, fileId, 5);
253
- // Resume from chunk 6
254
- const result = await realtime.resumeFile(deviceId, fileId);
255
- expect(result).toBe(true);
256
- const message = JSON.parse(mockSocket.messages[0]);
257
- expect(message.type).toBe('FILE_CHUNK');
258
- expect(message.chunkIndex).toBe(6); // Should start from chunk 6
259
- });
260
- it('should handle complete file in resume', async () => {
261
- const deviceId = 'complete-device';
262
- const mockSocket = new MockWebSocket();
263
- const fileId = 'test-complete-file';
264
- realtime.register(deviceId, mockSocket);
265
- const metadata = realtime.pubFile(fileId, testFilePath);
266
- if (metadata) {
267
- // Save progress at last chunk
268
- realtime.saveFileProgress(deviceId, fileId, metadata.totalChunks);
269
- const result = await realtime.resumeFile(deviceId, fileId);
270
- expect(result).toBe(true);
271
- const message = JSON.parse(mockSocket.messages[0]);
272
- expect(message.type).toBe('FILE_COMPLETE');
273
- }
274
- });
275
- });
276
- // ============================================
277
- // v2 NEW: Private Messaging Tests
278
- // ============================================
279
- describe('Private Messaging', () => {
280
- it('should send private message to specific device', (done) => {
281
- const deviceId = 'private-device-001';
282
- realtime.privateSub(deviceId, (payload) => {
283
- expect(payload).toEqual({ secret: 'OTP: 123456' });
284
- done();
285
- });
286
- realtime.privatePub(deviceId, { secret: 'OTP: 123456' });
287
- });
288
- it('should not deliver private message to wrong device', () => {
289
- const fn1 = jest.fn();
290
- const fn2 = jest.fn();
291
- const deviceId1 = 'user-001';
292
- const deviceId2 = 'user-002';
293
- realtime.privateSub(deviceId1, fn1);
294
- realtime.privateSub(deviceId2, fn2);
295
- realtime.privatePub(deviceId1, { message: 'for user 1 only' });
296
- expect(fn1).toHaveBeenCalledTimes(1);
297
- expect(fn2).toHaveBeenCalledTimes(0);
298
- });
299
- });
300
- // ============================================
301
- // v2 NEW: Socket Helper Tests
302
- // ============================================
303
- describe('Socket Helpers', () => {
304
- it('should check if device is ready', () => {
305
- const deviceId = 'ready-device';
306
- const mockSocket = new MockWebSocket();
307
- expect(realtime.isReady(deviceId)).toBe(false);
308
- realtime.register(deviceId, mockSocket);
309
- expect(realtime.isReady(deviceId)).toBe(true);
310
- });
311
- it('should check if device is online', () => {
312
- const deviceId = 'online-device';
313
- expect(realtime.isOnline(deviceId)).toBe(false);
314
- realtime.register(deviceId);
315
- expect(realtime.isOnline(deviceId)).toBe(true);
316
- });
317
- it('should send direct message with sendTo', () => {
318
- const deviceId = 'direct-device';
319
- const mockSocket = new MockWebSocket();
320
- const testPayload = { direct: 'message' };
321
- realtime.register(deviceId, mockSocket);
322
- const result = realtime.sendTo(deviceId, testPayload);
323
- expect(result).toBe(true);
324
- expect(mockSocket.messages.length).toBeGreaterThan(0);
325
- });
326
- it('should return false for sendTo when device not ready', () => {
327
- const result = realtime.sendTo('non-existent-device', { data: 'test' });
328
- expect(result).toBe(false);
329
- });
330
- it('should kick device', () => {
331
- const deviceId = 'kick-device';
332
- const mockSocket = new MockWebSocket();
333
- realtime.register(deviceId, mockSocket);
334
- expect(realtime.isOnline(deviceId)).toBe(true);
335
- realtime.kick(deviceId, 'Test kick');
336
- expect(mockSocket.closed).toBe(true);
337
- expect(realtime.isOnline(deviceId)).toBe(false);
338
- });
339
- it('should broadcast to group', () => {
340
- const device1 = new MockWebSocket();
341
- const device2 = new MockWebSocket();
342
- const device3 = new MockWebSocket();
343
- realtime.register('admin-1', device1, { group: 'admin' });
344
- realtime.register('admin-2', device2, { group: 'admin' });
345
- realtime.register('user-1', device3, { group: 'user' });
346
- realtime.broadcastToGroup('admin', { alert: 'Server update' });
347
- expect(device1.messages.length).toBeGreaterThan(0);
348
- expect(device2.messages.length).toBeGreaterThan(0);
349
- expect(device3.messages.length).toBe(0);
350
- });
351
- it('should get online devices list', () => {
352
- realtime.register('device-a', new MockWebSocket(), { group: 'group1' });
353
- realtime.register('device-b', new MockWebSocket(), { group: 'group2' });
354
- const devices = realtime.getOnlineDevices();
355
- expect(devices.length).toBe(2);
356
- expect(devices[0]).toHaveProperty('id');
357
- expect(devices[0]).toHaveProperty('lastSeen');
358
- expect(devices[0]).toHaveProperty('group');
359
- });
360
- it('should ping device', () => {
361
- const deviceId = 'ping-device';
362
- const mockSocket = new MockWebSocket();
363
- realtime.register(deviceId, mockSocket);
364
- const result = realtime.ping(deviceId);
365
- expect(result).toBe(true);
366
- expect(mockSocket.pingCalled).toBe(true);
367
- });
368
- });
369
- // ============================================
370
- // v2 NEW: P2P Tests
371
- // ============================================
372
- describe('P2P Features', () => {
373
- it('should announce file to peers', () => {
374
- const fileId = 'p2p-file';
375
- const sourceDevice = 'source-device';
376
- const peerDevice = new MockWebSocket();
377
- realtime.register('peer-device', peerDevice);
378
- realtime.announceToPeers(fileId, sourceDevice);
379
- const peers = realtime.getPeersForFile(fileId);
380
- expect(peers).toContain(sourceDevice);
381
- });
382
- it('should request chunk from peer', () => {
383
- const peerDevice = new MockWebSocket();
384
- const requestingDevice = 'requesting-device';
385
- const fileId = 'p2p-file';
386
- realtime.register('peer-001', peerDevice);
387
- const result = realtime.requestFromPeer(requestingDevice, 'peer-001', fileId, 5);
388
- expect(result).toBe(true);
389
- const message = JSON.parse(peerDevice.messages[0]);
390
- expect(message.type).toBe('P2P_REQUEST');
391
- expect(message.fileId).toBe(fileId);
392
- expect(message.chunkIndex).toBe(5);
393
- });
394
- it('should send data to peer', () => {
395
- const targetDevice = new MockWebSocket();
396
- const fromDevice = 'sender-device';
397
- const testData = { chunk: 'data' };
398
- realtime.register('target-peer', targetDevice);
399
- const result = realtime.sendToPeer(fromDevice, 'target-peer', testData);
400
- expect(result).toBe(true);
401
- const message = JSON.parse(targetDevice.messages[0]);
402
- expect(message.type).toBe('P2P_DATA');
403
- expect(message.from).toBe(fromDevice);
404
- });
405
- });
406
- // ============================================
407
- // Device Management Tests (Enhanced)
408
- // ============================================
409
- describe('Device Management', () => {
410
- it('should handle reconnection (remove old ghost connection)', () => {
411
- const deviceId = 'reconnect-device';
412
- const oldSocket = new MockWebSocket();
413
- const newSocket = new MockWebSocket();
414
- realtime.register(deviceId, oldSocket);
415
- expect(realtime.getSocket(deviceId)).toBe(oldSocket);
416
- // Reconnect with new socket
417
- realtime.register(deviceId, newSocket);
418
- expect(oldSocket.closed).toBe(true); // Old socket should be closed
419
- expect(realtime.getSocket(deviceId)).toBe(newSocket);
420
- });
421
- it('should register and track devices', () => {
422
- const deviceId = 'device-001';
423
- const mockSocket = new MockWebSocket();
424
- realtime.register(deviceId, mockSocket, { type: 'sensor' });
425
- const socket = realtime.getSocket(deviceId);
426
- expect(socket).toBe(mockSocket);
427
- });
428
- it('should handle device heartbeat', () => {
429
- const deviceId = 'device-002';
430
- realtime.register(deviceId);
431
- realtime.touch(deviceId);
432
- const stats = realtime.getStats();
433
- expect(stats.devices).toBe(1);
434
- });
435
- it('should unregister devices', () => {
436
- const deviceId = 'device-003';
437
- const mockSocket = new MockWebSocket();
438
- realtime.register(deviceId, mockSocket);
439
- expect(realtime.getSocket(deviceId)).toBe(mockSocket);
440
- realtime.unregister(deviceId);
441
- expect(realtime.getSocket(deviceId)).toBeUndefined();
442
- expect(mockSocket.closed).toBe(true);
443
- });
444
- });
445
- // ============================================
446
- // Broadcast Tests (Existing + Enhanced)
447
- // ============================================
448
- describe('Broadcast', () => {
449
- it('should broadcast to all devices', () => {
450
- const device1 = new MockWebSocket();
451
- const device2 = new MockWebSocket();
452
- realtime.register('device-1', device1);
453
- realtime.register('device-2', device2);
454
- realtime.broadcast('test', { data: 'test' });
455
- expect(device1.messages.length).toBeGreaterThan(0);
456
- expect(device2.messages.length).toBeGreaterThan(0);
457
- });
458
- it('should exclude specific devices from broadcast', () => {
459
- const device1 = new MockWebSocket();
460
- const device2 = new MockWebSocket();
461
- realtime.register('device-1', device1);
462
- realtime.register('device-2', device2);
463
- realtime.broadcast('test', { data: 'test' }, { exclude: ['device-1'] });
464
- expect(device1.messages.length).toBe(0);
465
- expect(device2.messages.length).toBeGreaterThan(0);
466
- });
467
- });
468
- // ============================================
469
- // Performance Tests
470
- // ============================================
471
- describe('Performance', () => {
472
- it('should respect max message size', () => {
473
- const largePayload = { data: 'x'.repeat(1024 * 1024 + 1) };
474
- expect(() => {
475
- realtime.publish('test/large', largePayload);
476
- }).toThrow('Payload too large');
477
- });
478
- it('should cache JSON serialization results', () => {
479
- const payload = { repeated: 'data', value: 123 };
480
- const cachedRealtime = new RealtimeCore({ enableJSONCache: true, debug: false });
481
- createdInstances.push(cachedRealtime);
482
- cachedRealtime.publish('test', payload);
483
- const start1 = Date.now();
484
- cachedRealtime.publish('test', payload);
485
- const time1 = Date.now() - start1;
486
- const start2 = Date.now();
487
- cachedRealtime.publish('test', payload);
488
- const time2 = Date.now() - start2;
489
- expect(time2).toBeLessThanOrEqual(time1 + 10);
490
- const stats = cachedRealtime.getStats();
491
- expect(stats.cacheEnabled).toBe(true);
492
- });
493
- it('should provide accurate stats', () => {
494
- realtime.register('stat-device-1', new MockWebSocket());
495
- realtime.register('stat-device-2', new MockWebSocket());
496
- realtime.pubFile('stat-file', testFilePath);
497
- const stats = realtime.getStats();
498
- expect(stats.version).toBe('2.0');
499
- expect(stats.devices).toBe(2);
500
- expect(stats.files).toBeGreaterThan(0);
501
- expect(stats).toHaveProperty('highFreqBuffers');
502
- expect(stats).toHaveProperty('activeTransfers');
503
- expect(stats).toHaveProperty('peers');
504
- });
505
- });
506
- // ============================================
507
- // ACL Tests (Existing)
508
- // ============================================
509
- describe('ACL', () => {
510
- it('should enforce subscribe ACL', () => {
511
- const aclRealtime = new RealtimeCore({
512
- acl: {
513
- canSubscribe: (deviceId, topic) => deviceId === 'allowed-device' && topic === 'allowed/topic',
514
- canPublish: () => true
515
- },
516
- debug: false
517
- });
518
- createdInstances.push(aclRealtime);
519
- const fn = jest.fn();
520
- expect(() => {
521
- aclRealtime.subscribe('allowed/topic', fn, 'allowed-device');
522
- }).not.toThrow();
523
- expect(() => {
524
- aclRealtime.subscribe('forbidden/topic', fn, 'bad-device');
525
- }).toThrow('ACL deny');
526
- });
527
- it('should enforce publish ACL', () => {
528
- const aclRealtime = new RealtimeCore({
529
- acl: {
530
- canSubscribe: () => true,
531
- canPublish: (deviceId, topic) => deviceId === 'allowed-device' && topic === 'allowed/topic'
532
- },
533
- debug: false
534
- });
535
- createdInstances.push(aclRealtime);
536
- expect(() => {
537
- aclRealtime.publish('allowed/topic', { data: 'test' }, {}, 'allowed-device');
538
- }).not.toThrow();
539
- expect(() => {
540
- aclRealtime.publish('forbidden/topic', { data: 'test' }, {}, 'bad-device');
541
- }).toThrow('ACL deny');
542
- });
543
- });
544
- // ============================================
545
- // Error Handling Tests (Existing + Enhanced)
546
- // ============================================
547
- describe('Error Handling', () => {
548
- it('should handle socket send errors', () => {
549
- const faultySocket = new MockWebSocket();
550
- const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
551
- faultySocket.send = jest.fn(() => { throw new Error('Send failed'); });
552
- realtime.register('faulty-device', faultySocket);
553
- expect(() => {
554
- realtime.broadcast('test', { data: 'test' });
555
- }).not.toThrow();
556
- expect(faultySocket.send).toHaveBeenCalled();
557
- consoleSpy.mockRestore();
558
- });
559
- it('should handle destroy cleanup', async () => {
560
- const testRealtime = new RealtimeCore({ debug: false });
561
- createdInstances.push(testRealtime);
562
- testRealtime.register('cleanup-device', new MockWebSocket());
563
- await testRealtime.destroy();
564
- const stats = testRealtime.getStats();
565
- expect(stats.devices).toBe(0);
566
- });
567
- });
568
- // ============================================
569
- // Direct Publish via Handle Tests (Existing)
570
- // ============================================
571
- describe('Direct Publish via Handle', () => {
572
- it('should handle pub format JSON payload', async () => {
573
- const fn = jest.fn();
574
- const topic = 'direct/test';
575
- const testPayload = { sensor: 'temp', value: 25 };
576
- realtime.subscribe(topic, fn);
577
- const message = Buffer.from(JSON.stringify({
578
- type: 'pub',
579
- topic: topic,
580
- payload: testPayload
581
- }));
582
- await realtime.handle(message, null, 'device-001');
583
- await new Promise(resolve => setTimeout(resolve, 50));
584
- expect(fn).toHaveBeenCalledTimes(1);
585
- expect(fn).toHaveBeenCalledWith(testPayload);
586
- });
587
- it('should handle base64 encoded pub message', async () => {
588
- const fn = jest.fn();
589
- const topic = 'base64/test';
590
- const testPayload = { command: 'start', value: 100 };
591
- realtime.subscribe(topic, fn);
592
- const originalMessage = {
593
- type: 'pub',
594
- topic: topic,
595
- payload: testPayload
596
- };
597
- const jsonStr = JSON.stringify(originalMessage);
598
- const base64Message = Buffer.from(jsonStr).toString('base64');
599
- await realtime.handle(Buffer.from(base64Message), null, 'device-002');
600
- await new Promise(resolve => setTimeout(resolve, 50));
601
- expect(fn).toHaveBeenCalledTimes(1);
602
- expect(fn).toHaveBeenCalledWith(testPayload);
603
- });
604
- it('should handle hex encoded pub message', async () => {
605
- const fn = jest.fn();
606
- const topic = 'hex/test';
607
- const testPayload = { command: 'toggle', state: true };
608
- realtime.subscribe(topic, fn);
609
- const originalMessage = {
610
- type: 'pub',
611
- topic: topic,
612
- payload: testPayload
613
- };
614
- const jsonStr = JSON.stringify(originalMessage);
615
- const hexMessage = Buffer.from(jsonStr).toString('hex');
616
- await realtime.handle(Buffer.from(hexMessage), null, 'device-003');
617
- await new Promise(resolve => setTimeout(resolve, 50));
618
- expect(fn).toHaveBeenCalledTimes(1);
619
- expect(fn).toHaveBeenCalledWith(testPayload);
620
- });
621
- });
622
- });
623
- //# sourceMappingURL=realtime.test.js.map