@hazeljs/websocket 0.2.0-beta.54 → 0.2.0-beta.55
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/dist/websocket.test.d.ts +2 -0
- package/dist/websocket.test.d.ts.map +1 -0
- package/dist/websocket.test.js +972 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -528,4 +528,4 @@ Apache 2.0 © [HazelJS](https://hazeljs.com)
|
|
|
528
528
|
- [WebSocket API](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
|
|
529
529
|
- [GitHub](https://github.com/hazel-js/hazeljs)
|
|
530
530
|
- [Issues](https://github.com/hazel-js/hazeljs/issues)
|
|
531
|
-
- [Discord](https://discord.
|
|
531
|
+
- [Discord](https://discord.com/channels/1448263814238965833/1448263814859456575)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"websocket.test.d.ts","sourceRoot":"","sources":["../src/websocket.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,972 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
const room_manager_1 = require("./room/room.manager");
|
|
16
|
+
const sse_handler_1 = require("./sse/sse.handler");
|
|
17
|
+
const websocket_gateway_1 = require("./websocket.gateway");
|
|
18
|
+
const realtime_decorator_1 = require("./decorators/realtime.decorator");
|
|
19
|
+
// Mock logger to suppress error logs during tests
|
|
20
|
+
jest.mock('@hazeljs/core', () => ({
|
|
21
|
+
__esModule: true,
|
|
22
|
+
default: {
|
|
23
|
+
info: jest.fn(),
|
|
24
|
+
debug: jest.fn(),
|
|
25
|
+
error: jest.fn(),
|
|
26
|
+
warn: jest.fn(),
|
|
27
|
+
},
|
|
28
|
+
}));
|
|
29
|
+
// Test index.ts exports
|
|
30
|
+
describe('Index exports', () => {
|
|
31
|
+
it('should export all expected modules', () => {
|
|
32
|
+
// This test ensures all exports are available
|
|
33
|
+
// If imports fail, TypeScript will catch it
|
|
34
|
+
expect(room_manager_1.RoomManager).toBeDefined();
|
|
35
|
+
expect(sse_handler_1.SSEHandler).toBeDefined();
|
|
36
|
+
expect(websocket_gateway_1.WebSocketGateway).toBeDefined();
|
|
37
|
+
expect(websocket_gateway_1.createWebSocketClient).toBeDefined();
|
|
38
|
+
expect(sse_handler_1.createSSEResponse).toBeDefined();
|
|
39
|
+
expect(sse_handler_1.sendSSEMessage).toBeDefined();
|
|
40
|
+
expect(realtime_decorator_1.Realtime).toBeDefined();
|
|
41
|
+
expect(realtime_decorator_1.Subscribe).toBeDefined();
|
|
42
|
+
expect(realtime_decorator_1.OnConnect).toBeDefined();
|
|
43
|
+
expect(realtime_decorator_1.OnDisconnect).toBeDefined();
|
|
44
|
+
expect(realtime_decorator_1.OnMessage).toBeDefined();
|
|
45
|
+
expect(realtime_decorator_1.Client).toBeDefined();
|
|
46
|
+
expect(realtime_decorator_1.Data).toBeDefined();
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
describe('RoomManager', () => {
|
|
50
|
+
let roomManager;
|
|
51
|
+
beforeEach(() => {
|
|
52
|
+
roomManager = new room_manager_1.RoomManager();
|
|
53
|
+
});
|
|
54
|
+
describe('room operations', () => {
|
|
55
|
+
it('should create a room', () => {
|
|
56
|
+
const room = roomManager.createRoom('test-room');
|
|
57
|
+
expect(room).toBeDefined();
|
|
58
|
+
expect(room.name).toBe('test-room');
|
|
59
|
+
expect(room.clients.size).toBe(0);
|
|
60
|
+
});
|
|
61
|
+
it('should return existing room if already exists', () => {
|
|
62
|
+
const room1 = roomManager.createRoom('test-room');
|
|
63
|
+
const room2 = roomManager.createRoom('test-room');
|
|
64
|
+
expect(room1).toBe(room2);
|
|
65
|
+
});
|
|
66
|
+
it('should get a room', () => {
|
|
67
|
+
roomManager.createRoom('test-room');
|
|
68
|
+
const room = roomManager.getRoom('test-room');
|
|
69
|
+
expect(room).toBeDefined();
|
|
70
|
+
expect(room?.name).toBe('test-room');
|
|
71
|
+
});
|
|
72
|
+
it('should return undefined for non-existent room', () => {
|
|
73
|
+
const room = roomManager.getRoom('non-existent');
|
|
74
|
+
expect(room).toBeUndefined();
|
|
75
|
+
});
|
|
76
|
+
it('should delete a room', () => {
|
|
77
|
+
roomManager.createRoom('test-room');
|
|
78
|
+
const deleted = roomManager.deleteRoom('test-room');
|
|
79
|
+
expect(deleted).toBe(true);
|
|
80
|
+
expect(roomManager.getRoom('test-room')).toBeUndefined();
|
|
81
|
+
});
|
|
82
|
+
it('should return false when deleting non-existent room', () => {
|
|
83
|
+
const deleted = roomManager.deleteRoom('non-existent');
|
|
84
|
+
expect(deleted).toBe(false);
|
|
85
|
+
});
|
|
86
|
+
it('should get all rooms', () => {
|
|
87
|
+
roomManager.createRoom('room1');
|
|
88
|
+
roomManager.createRoom('room2');
|
|
89
|
+
roomManager.createRoom('room3');
|
|
90
|
+
const rooms = roomManager.getAllRooms();
|
|
91
|
+
expect(rooms).toHaveLength(3);
|
|
92
|
+
expect(rooms.map((r) => r.name)).toContain('room1');
|
|
93
|
+
expect(rooms.map((r) => r.name)).toContain('room2');
|
|
94
|
+
expect(rooms.map((r) => r.name)).toContain('room3');
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
describe('client operations', () => {
|
|
98
|
+
it('should add client to room', () => {
|
|
99
|
+
roomManager.addClientToRoom('client1', 'room1');
|
|
100
|
+
const room = roomManager.getRoom('room1');
|
|
101
|
+
expect(room?.clients.has('client1')).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
it('should remove client from room', () => {
|
|
104
|
+
roomManager.addClientToRoom('client1', 'room1');
|
|
105
|
+
roomManager.removeClientFromRoom('client1', 'room1');
|
|
106
|
+
const room = roomManager.getRoom('room1');
|
|
107
|
+
expect(room).toBeUndefined(); // Room deleted when empty
|
|
108
|
+
});
|
|
109
|
+
it('should get client rooms', () => {
|
|
110
|
+
roomManager.addClientToRoom('client1', 'room1');
|
|
111
|
+
roomManager.addClientToRoom('client1', 'room2');
|
|
112
|
+
const rooms = roomManager.getClientRooms('client1');
|
|
113
|
+
expect(rooms).toHaveLength(2);
|
|
114
|
+
expect(rooms).toContain('room1');
|
|
115
|
+
expect(rooms).toContain('room2');
|
|
116
|
+
});
|
|
117
|
+
it('should get room clients', () => {
|
|
118
|
+
roomManager.addClientToRoom('client1', 'room1');
|
|
119
|
+
roomManager.addClientToRoom('client2', 'room1');
|
|
120
|
+
const clients = roomManager.getRoomClients('room1');
|
|
121
|
+
expect(clients).toHaveLength(2);
|
|
122
|
+
expect(clients).toContain('client1');
|
|
123
|
+
expect(clients).toContain('client2');
|
|
124
|
+
});
|
|
125
|
+
it('should check if client is in room', () => {
|
|
126
|
+
roomManager.addClientToRoom('client1', 'room1');
|
|
127
|
+
expect(roomManager.isClientInRoom('client1', 'room1')).toBe(true);
|
|
128
|
+
expect(roomManager.isClientInRoom('client1', 'room2')).toBe(false);
|
|
129
|
+
});
|
|
130
|
+
it('should remove client from all rooms', () => {
|
|
131
|
+
roomManager.addClientToRoom('client1', 'room1');
|
|
132
|
+
roomManager.addClientToRoom('client1', 'room2');
|
|
133
|
+
roomManager.removeClientFromAllRooms('client1');
|
|
134
|
+
expect(roomManager.getClientRooms('client1')).toHaveLength(0);
|
|
135
|
+
});
|
|
136
|
+
it('should handle removing client from all rooms when client has no rooms', () => {
|
|
137
|
+
roomManager.removeClientFromAllRooms('non-existent');
|
|
138
|
+
// Should not throw
|
|
139
|
+
});
|
|
140
|
+
it('should handle removing client from non-existent room', () => {
|
|
141
|
+
roomManager.removeClientFromRoom('client1', 'non-existent');
|
|
142
|
+
// Should not throw
|
|
143
|
+
});
|
|
144
|
+
it('should get total client count across all rooms', () => {
|
|
145
|
+
roomManager.addClientToRoom('client1', 'room1');
|
|
146
|
+
roomManager.addClientToRoom('client2', 'room1');
|
|
147
|
+
roomManager.addClientToRoom('client3', 'room2');
|
|
148
|
+
const count = roomManager.getTotalClientCount();
|
|
149
|
+
expect(count).toBe(3);
|
|
150
|
+
});
|
|
151
|
+
it('should broadcast to room', () => {
|
|
152
|
+
const mockClient1 = {
|
|
153
|
+
id: 'client1',
|
|
154
|
+
send: jest.fn(),
|
|
155
|
+
disconnect: jest.fn(),
|
|
156
|
+
join: jest.fn(),
|
|
157
|
+
leave: jest.fn(),
|
|
158
|
+
inRoom: jest.fn(),
|
|
159
|
+
socket: {},
|
|
160
|
+
metadata: new Map(),
|
|
161
|
+
rooms: new Set(),
|
|
162
|
+
};
|
|
163
|
+
const mockClient2 = {
|
|
164
|
+
id: 'client2',
|
|
165
|
+
send: jest.fn(),
|
|
166
|
+
disconnect: jest.fn(),
|
|
167
|
+
join: jest.fn(),
|
|
168
|
+
leave: jest.fn(),
|
|
169
|
+
inRoom: jest.fn(),
|
|
170
|
+
socket: {},
|
|
171
|
+
metadata: new Map(),
|
|
172
|
+
rooms: new Set(),
|
|
173
|
+
};
|
|
174
|
+
const clients = new Map([
|
|
175
|
+
['client1', mockClient1],
|
|
176
|
+
['client2', mockClient2],
|
|
177
|
+
]);
|
|
178
|
+
roomManager.addClientToRoom('client1', 'room1');
|
|
179
|
+
roomManager.addClientToRoom('client2', 'room1');
|
|
180
|
+
roomManager.broadcastToRoom('room1', 'test-event', { data: 'test' }, clients);
|
|
181
|
+
expect(mockClient1.send).toHaveBeenCalledWith('test-event', { data: 'test' });
|
|
182
|
+
expect(mockClient2.send).toHaveBeenCalledWith('test-event', { data: 'test' });
|
|
183
|
+
});
|
|
184
|
+
it('should broadcast to room excluding client', () => {
|
|
185
|
+
const mockClient1 = {
|
|
186
|
+
id: 'client1',
|
|
187
|
+
send: jest.fn(),
|
|
188
|
+
disconnect: jest.fn(),
|
|
189
|
+
join: jest.fn(),
|
|
190
|
+
leave: jest.fn(),
|
|
191
|
+
inRoom: jest.fn(),
|
|
192
|
+
socket: {},
|
|
193
|
+
metadata: new Map(),
|
|
194
|
+
rooms: new Set(),
|
|
195
|
+
};
|
|
196
|
+
const mockClient2 = {
|
|
197
|
+
id: 'client2',
|
|
198
|
+
send: jest.fn(),
|
|
199
|
+
disconnect: jest.fn(),
|
|
200
|
+
join: jest.fn(),
|
|
201
|
+
leave: jest.fn(),
|
|
202
|
+
inRoom: jest.fn(),
|
|
203
|
+
socket: {},
|
|
204
|
+
metadata: new Map(),
|
|
205
|
+
rooms: new Set(),
|
|
206
|
+
};
|
|
207
|
+
const clients = new Map([
|
|
208
|
+
['client1', mockClient1],
|
|
209
|
+
['client2', mockClient2],
|
|
210
|
+
]);
|
|
211
|
+
roomManager.addClientToRoom('client1', 'room1');
|
|
212
|
+
roomManager.addClientToRoom('client2', 'room1');
|
|
213
|
+
roomManager.broadcastToRoom('room1', 'test-event', { data: 'test' }, clients, 'client1');
|
|
214
|
+
expect(mockClient1.send).not.toHaveBeenCalled();
|
|
215
|
+
expect(mockClient2.send).toHaveBeenCalledWith('test-event', { data: 'test' });
|
|
216
|
+
});
|
|
217
|
+
it('should not broadcast to non-existent room', () => {
|
|
218
|
+
const clients = new Map();
|
|
219
|
+
roomManager.broadcastToRoom('non-existent', 'test-event', { data: 'test' }, clients);
|
|
220
|
+
// Should not throw
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
describe('statistics', () => {
|
|
224
|
+
it('should get room count', () => {
|
|
225
|
+
roomManager.createRoom('room1');
|
|
226
|
+
roomManager.createRoom('room2');
|
|
227
|
+
expect(roomManager.getRoomCount()).toBe(2);
|
|
228
|
+
});
|
|
229
|
+
it('should get stats', () => {
|
|
230
|
+
roomManager.addClientToRoom('client1', 'room1');
|
|
231
|
+
roomManager.addClientToRoom('client2', 'room1');
|
|
232
|
+
const stats = roomManager.getStats();
|
|
233
|
+
expect(stats.totalRooms).toBe(1);
|
|
234
|
+
expect(stats.totalClients).toBe(2);
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
describe('metadata', () => {
|
|
238
|
+
it('should set and get room metadata', () => {
|
|
239
|
+
roomManager.createRoom('room1');
|
|
240
|
+
roomManager.setRoomMetadata('room1', 'key', 'value');
|
|
241
|
+
expect(roomManager.getRoomMetadata('room1', 'key')).toBe('value');
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
describe('clear', () => {
|
|
245
|
+
it('should clear all rooms', () => {
|
|
246
|
+
roomManager.createRoom('room1');
|
|
247
|
+
roomManager.createRoom('room2');
|
|
248
|
+
roomManager.clear();
|
|
249
|
+
expect(roomManager.getRoomCount()).toBe(0);
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
describe('SSEHandler', () => {
|
|
254
|
+
let sseHandler;
|
|
255
|
+
let mockReq;
|
|
256
|
+
let mockRes;
|
|
257
|
+
beforeEach(() => {
|
|
258
|
+
sseHandler = new sse_handler_1.SSEHandler();
|
|
259
|
+
mockReq = {
|
|
260
|
+
on: jest.fn(),
|
|
261
|
+
};
|
|
262
|
+
mockRes = {
|
|
263
|
+
writeHead: jest.fn(),
|
|
264
|
+
write: jest.fn(),
|
|
265
|
+
end: jest.fn(),
|
|
266
|
+
};
|
|
267
|
+
});
|
|
268
|
+
afterEach(() => {
|
|
269
|
+
sseHandler.closeAll();
|
|
270
|
+
jest.clearAllMocks();
|
|
271
|
+
});
|
|
272
|
+
it('should get connection count', () => {
|
|
273
|
+
expect(sseHandler.getConnectionCount()).toBe(0);
|
|
274
|
+
});
|
|
275
|
+
it('should check if connection exists', () => {
|
|
276
|
+
expect(sseHandler.hasConnection('test-id')).toBe(false);
|
|
277
|
+
});
|
|
278
|
+
it('should get connection IDs', () => {
|
|
279
|
+
const ids = sseHandler.getConnectionIds();
|
|
280
|
+
expect(Array.isArray(ids)).toBe(true);
|
|
281
|
+
expect(ids).toHaveLength(0);
|
|
282
|
+
});
|
|
283
|
+
it('should initialize connection', () => {
|
|
284
|
+
const connectionId = sseHandler.initConnection(mockReq, mockRes);
|
|
285
|
+
expect(connectionId).toBeDefined();
|
|
286
|
+
expect(mockRes.writeHead).toHaveBeenCalledWith(200, expect.objectContaining({
|
|
287
|
+
'Content-Type': 'text/event-stream',
|
|
288
|
+
'Cache-Control': 'no-cache',
|
|
289
|
+
}));
|
|
290
|
+
expect(sseHandler.getConnectionCount()).toBe(1);
|
|
291
|
+
expect(sseHandler.hasConnection(connectionId)).toBe(true);
|
|
292
|
+
});
|
|
293
|
+
it('should initialize connection with retry option', () => {
|
|
294
|
+
const connectionId = sseHandler.initConnection(mockReq, mockRes, { retry: 5000 });
|
|
295
|
+
expect(connectionId).toBeDefined();
|
|
296
|
+
expect(mockRes.write).toHaveBeenCalledWith('retry: 5000\n\n');
|
|
297
|
+
});
|
|
298
|
+
it('should handle connection close', () => {
|
|
299
|
+
const connectionId = sseHandler.initConnection(mockReq, mockRes);
|
|
300
|
+
// Simulate connection close
|
|
301
|
+
const closeCallback = mockReq.on.mock.calls.find((call) => call[0] === 'close')?.[1];
|
|
302
|
+
if (closeCallback) {
|
|
303
|
+
closeCallback();
|
|
304
|
+
}
|
|
305
|
+
expect(sseHandler.hasConnection(connectionId)).toBe(false);
|
|
306
|
+
});
|
|
307
|
+
it('should send message to connection', () => {
|
|
308
|
+
const connectionId = sseHandler.initConnection(mockReq, mockRes);
|
|
309
|
+
const result = sseHandler.send(connectionId, {
|
|
310
|
+
event: 'test-event',
|
|
311
|
+
data: { foo: 'bar' },
|
|
312
|
+
});
|
|
313
|
+
expect(result).toBe(true);
|
|
314
|
+
expect(mockRes.write).toHaveBeenCalled();
|
|
315
|
+
});
|
|
316
|
+
it('should return false when sending to non-existent connection', () => {
|
|
317
|
+
const result = sseHandler.send('non-existent', {
|
|
318
|
+
event: 'test-event',
|
|
319
|
+
data: { foo: 'bar' },
|
|
320
|
+
});
|
|
321
|
+
expect(result).toBe(false);
|
|
322
|
+
});
|
|
323
|
+
it('should send message with all fields', () => {
|
|
324
|
+
const connectionId = sseHandler.initConnection(mockReq, mockRes);
|
|
325
|
+
sseHandler.send(connectionId, {
|
|
326
|
+
event: 'test-event',
|
|
327
|
+
id: '123',
|
|
328
|
+
retry: 5000,
|
|
329
|
+
data: { foo: 'bar' },
|
|
330
|
+
});
|
|
331
|
+
expect(mockRes.write).toHaveBeenCalled();
|
|
332
|
+
const written = mockRes.write.mock.calls[0][0];
|
|
333
|
+
expect(written).toContain('event: test-event');
|
|
334
|
+
expect(written).toContain('id: 123');
|
|
335
|
+
expect(written).toContain('retry: 5000');
|
|
336
|
+
expect(written).toContain('data: {"foo":"bar"}');
|
|
337
|
+
});
|
|
338
|
+
it('should handle multi-line data', () => {
|
|
339
|
+
const connectionId = sseHandler.initConnection(mockReq, mockRes);
|
|
340
|
+
sseHandler.send(connectionId, {
|
|
341
|
+
event: 'test-event',
|
|
342
|
+
data: 'line1\nline2\nline3',
|
|
343
|
+
});
|
|
344
|
+
expect(mockRes.write).toHaveBeenCalled();
|
|
345
|
+
const written = mockRes.write.mock.calls[0][0];
|
|
346
|
+
expect(written).toContain('data: line1');
|
|
347
|
+
expect(written).toContain('data: line2');
|
|
348
|
+
expect(written).toContain('data: line3');
|
|
349
|
+
});
|
|
350
|
+
it('should broadcast to all connections', () => {
|
|
351
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
352
|
+
const _connectionId1 = sseHandler.initConnection(mockReq, mockRes);
|
|
353
|
+
const mockReq2 = { on: jest.fn() };
|
|
354
|
+
const mockRes2 = { writeHead: jest.fn(), write: jest.fn(), end: jest.fn() };
|
|
355
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
356
|
+
const _connectionId2 = sseHandler.initConnection(mockReq2, mockRes2);
|
|
357
|
+
sseHandler.broadcast({
|
|
358
|
+
event: 'broadcast-event',
|
|
359
|
+
data: { message: 'broadcast' },
|
|
360
|
+
});
|
|
361
|
+
expect(mockRes.write).toHaveBeenCalled();
|
|
362
|
+
expect(mockRes2.write).toHaveBeenCalled();
|
|
363
|
+
});
|
|
364
|
+
it('should close specific connection', () => {
|
|
365
|
+
const connectionId = sseHandler.initConnection(mockReq, mockRes);
|
|
366
|
+
expect(sseHandler.hasConnection(connectionId)).toBe(true);
|
|
367
|
+
sseHandler.closeConnection(connectionId);
|
|
368
|
+
expect(sseHandler.hasConnection(connectionId)).toBe(false);
|
|
369
|
+
expect(mockRes.end).toHaveBeenCalled();
|
|
370
|
+
});
|
|
371
|
+
it('should close all connections', () => {
|
|
372
|
+
sseHandler.initConnection(mockReq, mockRes);
|
|
373
|
+
const mockReq2 = { on: jest.fn() };
|
|
374
|
+
const mockRes2 = { writeHead: jest.fn(), write: jest.fn(), end: jest.fn() };
|
|
375
|
+
sseHandler.initConnection(mockReq2, mockRes2);
|
|
376
|
+
expect(sseHandler.getConnectionCount()).toBe(2);
|
|
377
|
+
sseHandler.closeAll();
|
|
378
|
+
expect(sseHandler.getConnectionCount()).toBe(0);
|
|
379
|
+
expect(mockRes.end).toHaveBeenCalled();
|
|
380
|
+
expect(mockRes2.end).toHaveBeenCalled();
|
|
381
|
+
});
|
|
382
|
+
describe('createSSEResponse', () => {
|
|
383
|
+
it('should create SSE response with headers', () => {
|
|
384
|
+
const mockRes = {
|
|
385
|
+
writeHead: jest.fn(),
|
|
386
|
+
write: jest.fn(),
|
|
387
|
+
};
|
|
388
|
+
(0, sse_handler_1.createSSEResponse)(mockRes);
|
|
389
|
+
expect(mockRes.writeHead).toHaveBeenCalledWith(200, expect.objectContaining({
|
|
390
|
+
'Content-Type': 'text/event-stream',
|
|
391
|
+
'Cache-Control': 'no-cache',
|
|
392
|
+
}));
|
|
393
|
+
});
|
|
394
|
+
it('should include retry option', () => {
|
|
395
|
+
const mockRes = {
|
|
396
|
+
writeHead: jest.fn(),
|
|
397
|
+
write: jest.fn(),
|
|
398
|
+
};
|
|
399
|
+
(0, sse_handler_1.createSSEResponse)(mockRes, { retry: 5000 });
|
|
400
|
+
expect(mockRes.write).toHaveBeenCalledWith('retry: 5000\n\n');
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
describe('sendSSEMessage', () => {
|
|
404
|
+
it('should send SSE message', () => {
|
|
405
|
+
const mockRes = {
|
|
406
|
+
write: jest.fn(),
|
|
407
|
+
};
|
|
408
|
+
(0, sse_handler_1.sendSSEMessage)(mockRes, {
|
|
409
|
+
event: 'test-event',
|
|
410
|
+
data: { foo: 'bar' },
|
|
411
|
+
});
|
|
412
|
+
expect(mockRes.write).toHaveBeenCalled();
|
|
413
|
+
const written = mockRes.write.mock.calls[0][0];
|
|
414
|
+
expect(written).toContain('event: test-event');
|
|
415
|
+
expect(written).toContain('data: {"foo":"bar"}');
|
|
416
|
+
});
|
|
417
|
+
it('should send message with all fields', () => {
|
|
418
|
+
const mockRes = {
|
|
419
|
+
write: jest.fn(),
|
|
420
|
+
};
|
|
421
|
+
(0, sse_handler_1.sendSSEMessage)(mockRes, {
|
|
422
|
+
event: 'test-event',
|
|
423
|
+
id: '123',
|
|
424
|
+
retry: 5000,
|
|
425
|
+
data: 'test data',
|
|
426
|
+
});
|
|
427
|
+
const written = mockRes.write.mock.calls[0][0];
|
|
428
|
+
expect(written).toContain('event: test-event');
|
|
429
|
+
expect(written).toContain('id: 123');
|
|
430
|
+
expect(written).toContain('retry: 5000');
|
|
431
|
+
expect(written).toContain('data: test data');
|
|
432
|
+
});
|
|
433
|
+
it('should handle multi-line data', () => {
|
|
434
|
+
const mockRes = {
|
|
435
|
+
write: jest.fn(),
|
|
436
|
+
};
|
|
437
|
+
(0, sse_handler_1.sendSSEMessage)(mockRes, {
|
|
438
|
+
event: 'test-event',
|
|
439
|
+
data: 'line1\nline2',
|
|
440
|
+
});
|
|
441
|
+
const written = mockRes.write.mock.calls[0][0];
|
|
442
|
+
expect(written).toContain('data: line1');
|
|
443
|
+
expect(written).toContain('data: line2');
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
describe('createStream', () => {
|
|
447
|
+
it('should create async stream', async () => {
|
|
448
|
+
const connectionId = sseHandler.initConnection(mockReq, mockRes);
|
|
449
|
+
const dataSource = async function* () {
|
|
450
|
+
yield { id: 1, value: 'first' };
|
|
451
|
+
yield { id: 2, value: 'second' };
|
|
452
|
+
yield { id: 3, value: 'third' };
|
|
453
|
+
};
|
|
454
|
+
const results = [];
|
|
455
|
+
for await (const result of sseHandler.createStream(connectionId, dataSource(), {
|
|
456
|
+
event: 'stream-event',
|
|
457
|
+
})) {
|
|
458
|
+
results.push(result);
|
|
459
|
+
}
|
|
460
|
+
expect(results.length).toBeGreaterThan(0);
|
|
461
|
+
expect(mockRes.write).toHaveBeenCalled();
|
|
462
|
+
});
|
|
463
|
+
it('should handle stream with transform', async () => {
|
|
464
|
+
const connectionId = sseHandler.initConnection(mockReq, mockRes);
|
|
465
|
+
const dataSource = [1, 2, 3];
|
|
466
|
+
const results = [];
|
|
467
|
+
for await (const result of sseHandler.createStream(connectionId, dataSource, {
|
|
468
|
+
event: 'stream-event',
|
|
469
|
+
transform: (n) => ({ value: n * 2 }),
|
|
470
|
+
})) {
|
|
471
|
+
results.push(result);
|
|
472
|
+
}
|
|
473
|
+
expect(results.length).toBe(3);
|
|
474
|
+
});
|
|
475
|
+
it('should handle stream errors gracefully', async () => {
|
|
476
|
+
const connectionId = sseHandler.initConnection(mockReq, mockRes);
|
|
477
|
+
const dataSource = async function* () {
|
|
478
|
+
yield { id: 1 };
|
|
479
|
+
throw new Error('Stream error');
|
|
480
|
+
};
|
|
481
|
+
const results = [];
|
|
482
|
+
for await (const result of sseHandler.createStream(connectionId, dataSource())) {
|
|
483
|
+
results.push(result);
|
|
484
|
+
}
|
|
485
|
+
// Should handle error without throwing
|
|
486
|
+
expect(results.length).toBeGreaterThanOrEqual(0);
|
|
487
|
+
});
|
|
488
|
+
});
|
|
489
|
+
});
|
|
490
|
+
describe('WebSocketGateway', () => {
|
|
491
|
+
let gateway;
|
|
492
|
+
beforeEach(() => {
|
|
493
|
+
gateway = new websocket_gateway_1.WebSocketGateway();
|
|
494
|
+
});
|
|
495
|
+
afterEach(() => {
|
|
496
|
+
// Clean up all clients to prevent open handles
|
|
497
|
+
gateway.disconnectAll();
|
|
498
|
+
jest.clearAllMocks();
|
|
499
|
+
});
|
|
500
|
+
describe('client management', () => {
|
|
501
|
+
it('should get client count', () => {
|
|
502
|
+
expect(gateway.getClientCount()).toBe(0);
|
|
503
|
+
});
|
|
504
|
+
it('should get clients', () => {
|
|
505
|
+
const clients = gateway.getClients();
|
|
506
|
+
expect(Array.isArray(clients)).toBe(true);
|
|
507
|
+
expect(clients).toHaveLength(0);
|
|
508
|
+
});
|
|
509
|
+
it('should handle client connection', () => {
|
|
510
|
+
const mockSocket = { send: jest.fn(), close: jest.fn() };
|
|
511
|
+
const client = (0, websocket_gateway_1.createWebSocketClient)(mockSocket, 'client1');
|
|
512
|
+
// Access protected method via type assertion
|
|
513
|
+
gateway.handleConnection(client);
|
|
514
|
+
expect(gateway.getClientCount()).toBe(1);
|
|
515
|
+
expect(gateway.getClient('client1')).toBeDefined();
|
|
516
|
+
});
|
|
517
|
+
it('should handle client disconnection', () => {
|
|
518
|
+
const mockSocket = { send: jest.fn(), close: jest.fn() };
|
|
519
|
+
const client = (0, websocket_gateway_1.createWebSocketClient)(mockSocket, 'client1');
|
|
520
|
+
gateway.handleConnection(client);
|
|
521
|
+
expect(gateway.getClientCount()).toBe(1);
|
|
522
|
+
gateway.handleDisconnection('client1');
|
|
523
|
+
expect(gateway.getClientCount()).toBe(0);
|
|
524
|
+
expect(gateway.getClient('client1')).toBeUndefined();
|
|
525
|
+
});
|
|
526
|
+
it('should handle disconnection of non-existent client', () => {
|
|
527
|
+
gateway.handleDisconnection('non-existent');
|
|
528
|
+
expect(gateway.getClientCount()).toBe(0);
|
|
529
|
+
});
|
|
530
|
+
it('should handle incoming message', () => {
|
|
531
|
+
const mockSocket = { send: jest.fn(), close: jest.fn() };
|
|
532
|
+
const client = (0, websocket_gateway_1.createWebSocketClient)(mockSocket, 'client1');
|
|
533
|
+
gateway.handleConnection(client);
|
|
534
|
+
const message = {
|
|
535
|
+
event: 'test',
|
|
536
|
+
data: { foo: 'bar' },
|
|
537
|
+
timestamp: Date.now(),
|
|
538
|
+
};
|
|
539
|
+
gateway.handleMessage('client1', message);
|
|
540
|
+
const stats = gateway.getStats();
|
|
541
|
+
expect(stats.messagesReceived).toBe(1);
|
|
542
|
+
expect(stats.bytesReceived).toBeGreaterThan(0);
|
|
543
|
+
});
|
|
544
|
+
it('should get client by ID', () => {
|
|
545
|
+
const mockSocket = { send: jest.fn(), close: jest.fn() };
|
|
546
|
+
const client = (0, websocket_gateway_1.createWebSocketClient)(mockSocket, 'client1');
|
|
547
|
+
gateway.handleConnection(client);
|
|
548
|
+
const retrieved = gateway.getClient('client1');
|
|
549
|
+
expect(retrieved).toBeDefined();
|
|
550
|
+
expect(retrieved?.id).toBe('client1');
|
|
551
|
+
const notFound = gateway.getClient('non-existent');
|
|
552
|
+
expect(notFound).toBeUndefined();
|
|
553
|
+
});
|
|
554
|
+
});
|
|
555
|
+
describe('messaging', () => {
|
|
556
|
+
it('should send message to specific client', () => {
|
|
557
|
+
const mockSocket = { send: jest.fn(), close: jest.fn() };
|
|
558
|
+
const client = (0, websocket_gateway_1.createWebSocketClient)(mockSocket, 'client1');
|
|
559
|
+
gateway.handleConnection(client);
|
|
560
|
+
const result = gateway.sendToClient('client1', 'test-event', { data: 'test' });
|
|
561
|
+
expect(result).toBe(true);
|
|
562
|
+
expect(mockSocket.send).toHaveBeenCalled();
|
|
563
|
+
const stats = gateway.getStats();
|
|
564
|
+
expect(stats.messagesSent).toBe(1);
|
|
565
|
+
});
|
|
566
|
+
it('should return false when sending to non-existent client', () => {
|
|
567
|
+
const result = gateway.sendToClient('non-existent', 'test-event', { data: 'test' });
|
|
568
|
+
expect(result).toBe(false);
|
|
569
|
+
});
|
|
570
|
+
it('should broadcast to all clients', () => {
|
|
571
|
+
const mockSocket1 = { send: jest.fn(), close: jest.fn() };
|
|
572
|
+
const mockSocket2 = { send: jest.fn(), close: jest.fn() };
|
|
573
|
+
const client1 = (0, websocket_gateway_1.createWebSocketClient)(mockSocket1, 'client1');
|
|
574
|
+
const client2 = (0, websocket_gateway_1.createWebSocketClient)(mockSocket2, 'client2');
|
|
575
|
+
gateway.handleConnection(client1);
|
|
576
|
+
gateway.handleConnection(client2);
|
|
577
|
+
gateway.broadcast('test-event', { data: 'broadcast' });
|
|
578
|
+
expect(mockSocket1.send).toHaveBeenCalled();
|
|
579
|
+
expect(mockSocket2.send).toHaveBeenCalled();
|
|
580
|
+
const stats = gateway.getStats();
|
|
581
|
+
expect(stats.messagesSent).toBe(2);
|
|
582
|
+
});
|
|
583
|
+
it('should broadcast excluding specific client', () => {
|
|
584
|
+
const mockSocket1 = { send: jest.fn(), close: jest.fn() };
|
|
585
|
+
const mockSocket2 = { send: jest.fn(), close: jest.fn() };
|
|
586
|
+
const client1 = (0, websocket_gateway_1.createWebSocketClient)(mockSocket1, 'client1');
|
|
587
|
+
const client2 = (0, websocket_gateway_1.createWebSocketClient)(mockSocket2, 'client2');
|
|
588
|
+
gateway.handleConnection(client1);
|
|
589
|
+
gateway.handleConnection(client2);
|
|
590
|
+
gateway.broadcast('test-event', { data: 'broadcast' }, 'client1');
|
|
591
|
+
expect(mockSocket1.send).not.toHaveBeenCalled();
|
|
592
|
+
expect(mockSocket2.send).toHaveBeenCalled();
|
|
593
|
+
});
|
|
594
|
+
it('should broadcast to room', () => {
|
|
595
|
+
const mockSocket1 = { send: jest.fn(), close: jest.fn() };
|
|
596
|
+
const mockSocket2 = { send: jest.fn(), close: jest.fn() };
|
|
597
|
+
const client1 = (0, websocket_gateway_1.createWebSocketClient)(mockSocket1, 'client1');
|
|
598
|
+
const client2 = (0, websocket_gateway_1.createWebSocketClient)(mockSocket2, 'client2');
|
|
599
|
+
gateway.handleConnection(client1);
|
|
600
|
+
gateway.handleConnection(client2);
|
|
601
|
+
gateway.joinRoom('client1', 'room1');
|
|
602
|
+
gateway.joinRoom('client2', 'room1');
|
|
603
|
+
gateway.broadcastToRoom('room1', 'test-event', { data: 'room-broadcast' });
|
|
604
|
+
expect(mockSocket1.send).toHaveBeenCalled();
|
|
605
|
+
expect(mockSocket2.send).toHaveBeenCalled();
|
|
606
|
+
const stats = gateway.getStats();
|
|
607
|
+
expect(stats.messagesSent).toBe(1);
|
|
608
|
+
});
|
|
609
|
+
it('should broadcast to room excluding client', () => {
|
|
610
|
+
const mockSocket1 = { send: jest.fn(), close: jest.fn() };
|
|
611
|
+
const mockSocket2 = { send: jest.fn(), close: jest.fn() };
|
|
612
|
+
const client1 = (0, websocket_gateway_1.createWebSocketClient)(mockSocket1, 'client1');
|
|
613
|
+
const client2 = (0, websocket_gateway_1.createWebSocketClient)(mockSocket2, 'client2');
|
|
614
|
+
gateway.handleConnection(client1);
|
|
615
|
+
gateway.handleConnection(client2);
|
|
616
|
+
gateway.joinRoom('client1', 'room1');
|
|
617
|
+
gateway.joinRoom('client2', 'room1');
|
|
618
|
+
gateway.broadcastToRoom('room1', 'test-event', { data: 'room-broadcast' }, 'client1');
|
|
619
|
+
expect(mockSocket1.send).not.toHaveBeenCalled();
|
|
620
|
+
expect(mockSocket2.send).toHaveBeenCalled();
|
|
621
|
+
});
|
|
622
|
+
});
|
|
623
|
+
describe('room management', () => {
|
|
624
|
+
it('should join room', () => {
|
|
625
|
+
const mockSocket = { send: jest.fn(), close: jest.fn() };
|
|
626
|
+
const client = (0, websocket_gateway_1.createWebSocketClient)(mockSocket, 'client1');
|
|
627
|
+
gateway.handleConnection(client);
|
|
628
|
+
gateway.joinRoom('client1', 'room1');
|
|
629
|
+
expect(gateway.getClientRooms('client1')).toContain('room1');
|
|
630
|
+
expect(gateway.getRoomClients('room1')).toContain('client1');
|
|
631
|
+
const stats = gateway.getStats();
|
|
632
|
+
expect(stats.totalRooms).toBe(1);
|
|
633
|
+
});
|
|
634
|
+
it('should not join room for non-existent client', () => {
|
|
635
|
+
gateway.joinRoom('non-existent', 'room1');
|
|
636
|
+
expect(gateway.getRoomClients('room1')).toHaveLength(0);
|
|
637
|
+
});
|
|
638
|
+
it('should leave room', () => {
|
|
639
|
+
const mockSocket = { send: jest.fn(), close: jest.fn() };
|
|
640
|
+
const client = (0, websocket_gateway_1.createWebSocketClient)(mockSocket, 'client1');
|
|
641
|
+
gateway.handleConnection(client);
|
|
642
|
+
gateway.joinRoom('client1', 'room1');
|
|
643
|
+
expect(gateway.getClientRooms('client1')).toContain('room1');
|
|
644
|
+
gateway.leaveRoom('client1', 'room1');
|
|
645
|
+
expect(gateway.getClientRooms('client1')).not.toContain('room1');
|
|
646
|
+
});
|
|
647
|
+
it('should not leave room for non-existent client', () => {
|
|
648
|
+
gateway.leaveRoom('non-existent', 'room1');
|
|
649
|
+
// Should not throw
|
|
650
|
+
});
|
|
651
|
+
it('should get room clients', () => {
|
|
652
|
+
const mockSocket1 = { send: jest.fn(), close: jest.fn() };
|
|
653
|
+
const mockSocket2 = { send: jest.fn(), close: jest.fn() };
|
|
654
|
+
const client1 = (0, websocket_gateway_1.createWebSocketClient)(mockSocket1, 'client1');
|
|
655
|
+
const client2 = (0, websocket_gateway_1.createWebSocketClient)(mockSocket2, 'client2');
|
|
656
|
+
gateway.handleConnection(client1);
|
|
657
|
+
gateway.handleConnection(client2);
|
|
658
|
+
gateway.joinRoom('client1', 'room1');
|
|
659
|
+
gateway.joinRoom('client2', 'room1');
|
|
660
|
+
const clients = gateway.getRoomClients('room1');
|
|
661
|
+
expect(clients).toHaveLength(2);
|
|
662
|
+
expect(clients).toContain('client1');
|
|
663
|
+
expect(clients).toContain('client2');
|
|
664
|
+
});
|
|
665
|
+
it('should get client rooms', () => {
|
|
666
|
+
const mockSocket = { send: jest.fn(), close: jest.fn() };
|
|
667
|
+
const client = (0, websocket_gateway_1.createWebSocketClient)(mockSocket, 'client1');
|
|
668
|
+
gateway.handleConnection(client);
|
|
669
|
+
gateway.joinRoom('client1', 'room1');
|
|
670
|
+
gateway.joinRoom('client1', 'room2');
|
|
671
|
+
const rooms = gateway.getClientRooms('client1');
|
|
672
|
+
expect(rooms).toHaveLength(2);
|
|
673
|
+
expect(rooms).toContain('room1');
|
|
674
|
+
expect(rooms).toContain('room2');
|
|
675
|
+
});
|
|
676
|
+
});
|
|
677
|
+
describe('disconnection', () => {
|
|
678
|
+
it('should disconnect specific client', () => {
|
|
679
|
+
const mockSocket = { send: jest.fn(), close: jest.fn() };
|
|
680
|
+
const client = (0, websocket_gateway_1.createWebSocketClient)(mockSocket, 'client1');
|
|
681
|
+
gateway.handleConnection(client);
|
|
682
|
+
gateway.joinRoom('client1', 'room1');
|
|
683
|
+
expect(gateway.getClientCount()).toBe(1);
|
|
684
|
+
gateway.disconnectClient('client1');
|
|
685
|
+
expect(gateway.getClientCount()).toBe(0);
|
|
686
|
+
expect(mockSocket.close).toHaveBeenCalled();
|
|
687
|
+
});
|
|
688
|
+
it('should not disconnect non-existent client', () => {
|
|
689
|
+
gateway.disconnectClient('non-existent');
|
|
690
|
+
// Should not throw
|
|
691
|
+
});
|
|
692
|
+
it('should disconnect all clients', () => {
|
|
693
|
+
const mockSocket1 = { send: jest.fn(), close: jest.fn() };
|
|
694
|
+
const mockSocket2 = { send: jest.fn(), close: jest.fn() };
|
|
695
|
+
const client1 = (0, websocket_gateway_1.createWebSocketClient)(mockSocket1, 'client1');
|
|
696
|
+
const client2 = (0, websocket_gateway_1.createWebSocketClient)(mockSocket2, 'client2');
|
|
697
|
+
gateway.handleConnection(client1);
|
|
698
|
+
gateway.handleConnection(client2);
|
|
699
|
+
gateway.disconnectAll();
|
|
700
|
+
expect(gateway.getClientCount()).toBe(0);
|
|
701
|
+
expect(mockSocket1.close).toHaveBeenCalled();
|
|
702
|
+
expect(mockSocket2.close).toHaveBeenCalled();
|
|
703
|
+
});
|
|
704
|
+
});
|
|
705
|
+
describe('statistics', () => {
|
|
706
|
+
it('should get stats', () => {
|
|
707
|
+
const stats = gateway.getStats();
|
|
708
|
+
expect(stats).toBeDefined();
|
|
709
|
+
expect(stats.connectedClients).toBe(0);
|
|
710
|
+
expect(stats.totalRooms).toBe(0);
|
|
711
|
+
expect(stats.messagesSent).toBe(0);
|
|
712
|
+
expect(stats.messagesReceived).toBe(0);
|
|
713
|
+
expect(stats.uptime).toBeGreaterThanOrEqual(0);
|
|
714
|
+
});
|
|
715
|
+
it('should update stats correctly', () => {
|
|
716
|
+
const mockSocket = { send: jest.fn(), close: jest.fn() };
|
|
717
|
+
const client = (0, websocket_gateway_1.createWebSocketClient)(mockSocket, 'client1');
|
|
718
|
+
gateway.handleConnection(client);
|
|
719
|
+
gateway.sendToClient('client1', 'test', {});
|
|
720
|
+
gateway.joinRoom('client1', 'room1');
|
|
721
|
+
const stats = gateway.getStats();
|
|
722
|
+
expect(stats.connectedClients).toBe(1);
|
|
723
|
+
expect(stats.totalRooms).toBe(1);
|
|
724
|
+
expect(stats.messagesSent).toBe(1);
|
|
725
|
+
});
|
|
726
|
+
});
|
|
727
|
+
});
|
|
728
|
+
describe('WebSocket Decorators', () => {
|
|
729
|
+
describe('@Realtime', () => {
|
|
730
|
+
it('should mark class as realtime gateway', () => {
|
|
731
|
+
let TestGateway = class TestGateway {
|
|
732
|
+
};
|
|
733
|
+
TestGateway = __decorate([
|
|
734
|
+
(0, realtime_decorator_1.Realtime)('/test')
|
|
735
|
+
], TestGateway);
|
|
736
|
+
const metadata = (0, realtime_decorator_1.getRealtimeMetadata)(TestGateway);
|
|
737
|
+
expect(metadata).toBeDefined();
|
|
738
|
+
expect(metadata?.path).toBe('/test');
|
|
739
|
+
expect((0, realtime_decorator_1.isRealtimeGateway)(TestGateway)).toBe(true);
|
|
740
|
+
});
|
|
741
|
+
it('should accept options object', () => {
|
|
742
|
+
let TestGateway = class TestGateway {
|
|
743
|
+
};
|
|
744
|
+
TestGateway = __decorate([
|
|
745
|
+
(0, realtime_decorator_1.Realtime)({ path: '/test', auth: true })
|
|
746
|
+
], TestGateway);
|
|
747
|
+
const metadata = (0, realtime_decorator_1.getRealtimeMetadata)(TestGateway);
|
|
748
|
+
expect(metadata?.path).toBe('/test');
|
|
749
|
+
expect(metadata?.auth).toBe(true);
|
|
750
|
+
});
|
|
751
|
+
it('should use default options when no argument provided', () => {
|
|
752
|
+
let TestGateway = class TestGateway {
|
|
753
|
+
};
|
|
754
|
+
TestGateway = __decorate([
|
|
755
|
+
(0, realtime_decorator_1.Realtime)()
|
|
756
|
+
], TestGateway);
|
|
757
|
+
const metadata = (0, realtime_decorator_1.getRealtimeMetadata)(TestGateway);
|
|
758
|
+
expect(metadata).toBeDefined();
|
|
759
|
+
expect(metadata?.path).toBe('/');
|
|
760
|
+
});
|
|
761
|
+
it('should return false for non-realtime gateway', () => {
|
|
762
|
+
class RegularClass {
|
|
763
|
+
}
|
|
764
|
+
expect((0, realtime_decorator_1.isRealtimeGateway)(RegularClass)).toBe(false);
|
|
765
|
+
});
|
|
766
|
+
});
|
|
767
|
+
describe('@Subscribe', () => {
|
|
768
|
+
it('should store subscribe metadata', () => {
|
|
769
|
+
class TestGateway {
|
|
770
|
+
handleEvent() { }
|
|
771
|
+
}
|
|
772
|
+
__decorate([
|
|
773
|
+
(0, realtime_decorator_1.Subscribe)('test-event'),
|
|
774
|
+
__metadata("design:type", Function),
|
|
775
|
+
__metadata("design:paramtypes", []),
|
|
776
|
+
__metadata("design:returntype", void 0)
|
|
777
|
+
], TestGateway.prototype, "handleEvent", null);
|
|
778
|
+
const instance = new TestGateway();
|
|
779
|
+
const metadata = (0, realtime_decorator_1.getSubscribeMetadata)(instance, 'handleEvent');
|
|
780
|
+
expect(metadata).toBe('test-event');
|
|
781
|
+
});
|
|
782
|
+
});
|
|
783
|
+
describe('@OnConnect', () => {
|
|
784
|
+
it('should store onconnect metadata', () => {
|
|
785
|
+
class TestGateway {
|
|
786
|
+
handleConnect() { }
|
|
787
|
+
}
|
|
788
|
+
__decorate([
|
|
789
|
+
(0, realtime_decorator_1.OnConnect)(),
|
|
790
|
+
__metadata("design:type", Function),
|
|
791
|
+
__metadata("design:paramtypes", []),
|
|
792
|
+
__metadata("design:returntype", void 0)
|
|
793
|
+
], TestGateway.prototype, "handleConnect", null);
|
|
794
|
+
const instance = new TestGateway();
|
|
795
|
+
const metadata = (0, realtime_decorator_1.getOnConnectMetadata)(instance, 'handleConnect');
|
|
796
|
+
expect(metadata).toBe(true);
|
|
797
|
+
});
|
|
798
|
+
it('should return false when metadata not found', () => {
|
|
799
|
+
class TestGateway {
|
|
800
|
+
handleConnect() { }
|
|
801
|
+
}
|
|
802
|
+
const instance = new TestGateway();
|
|
803
|
+
const metadata = (0, realtime_decorator_1.getOnConnectMetadata)(instance, 'handleConnect');
|
|
804
|
+
expect(metadata).toBe(false);
|
|
805
|
+
});
|
|
806
|
+
});
|
|
807
|
+
describe('@OnDisconnect', () => {
|
|
808
|
+
it('should store ondisconnect metadata', () => {
|
|
809
|
+
class TestGateway {
|
|
810
|
+
handleDisconnect() { }
|
|
811
|
+
}
|
|
812
|
+
__decorate([
|
|
813
|
+
(0, realtime_decorator_1.OnDisconnect)(),
|
|
814
|
+
__metadata("design:type", Function),
|
|
815
|
+
__metadata("design:paramtypes", []),
|
|
816
|
+
__metadata("design:returntype", void 0)
|
|
817
|
+
], TestGateway.prototype, "handleDisconnect", null);
|
|
818
|
+
const instance = new TestGateway();
|
|
819
|
+
const metadata = (0, realtime_decorator_1.getOnDisconnectMetadata)(instance, 'handleDisconnect');
|
|
820
|
+
expect(metadata).toBe(true);
|
|
821
|
+
});
|
|
822
|
+
});
|
|
823
|
+
describe('@OnMessage', () => {
|
|
824
|
+
it('should store onmessage metadata', () => {
|
|
825
|
+
class TestGateway {
|
|
826
|
+
handleMessage() { }
|
|
827
|
+
}
|
|
828
|
+
__decorate([
|
|
829
|
+
(0, realtime_decorator_1.OnMessage)('chat'),
|
|
830
|
+
__metadata("design:type", Function),
|
|
831
|
+
__metadata("design:paramtypes", []),
|
|
832
|
+
__metadata("design:returntype", void 0)
|
|
833
|
+
], TestGateway.prototype, "handleMessage", null);
|
|
834
|
+
const instance = new TestGateway();
|
|
835
|
+
const metadata = (0, realtime_decorator_1.getOnMessageMetadata)(instance, 'handleMessage');
|
|
836
|
+
expect(metadata).toBe('chat');
|
|
837
|
+
});
|
|
838
|
+
it('should return undefined when metadata not found', () => {
|
|
839
|
+
class TestGateway {
|
|
840
|
+
handleMessage() { }
|
|
841
|
+
}
|
|
842
|
+
const instance = new TestGateway();
|
|
843
|
+
const metadata = (0, realtime_decorator_1.getOnMessageMetadata)(instance, 'handleMessage');
|
|
844
|
+
expect(metadata).toBeUndefined();
|
|
845
|
+
});
|
|
846
|
+
});
|
|
847
|
+
describe('@Client', () => {
|
|
848
|
+
it('should store client parameter metadata', () => {
|
|
849
|
+
class TestGateway {
|
|
850
|
+
handleConnection(_client) { }
|
|
851
|
+
}
|
|
852
|
+
__decorate([
|
|
853
|
+
__param(0, (0, realtime_decorator_1.Client)()),
|
|
854
|
+
__metadata("design:type", Function),
|
|
855
|
+
__metadata("design:paramtypes", [Object]),
|
|
856
|
+
__metadata("design:returntype", void 0)
|
|
857
|
+
], TestGateway.prototype, "handleConnection", null);
|
|
858
|
+
const instance = new TestGateway();
|
|
859
|
+
const metadata = (0, realtime_decorator_1.getParameterMetadata)(instance, 'handleConnection');
|
|
860
|
+
expect(metadata).toContain('client');
|
|
861
|
+
});
|
|
862
|
+
});
|
|
863
|
+
describe('@Data', () => {
|
|
864
|
+
it('should store data parameter metadata', () => {
|
|
865
|
+
class TestGateway {
|
|
866
|
+
handleMessage(_data) { }
|
|
867
|
+
}
|
|
868
|
+
__decorate([
|
|
869
|
+
__param(0, (0, realtime_decorator_1.Data)()),
|
|
870
|
+
__metadata("design:type", Function),
|
|
871
|
+
__metadata("design:paramtypes", [Object]),
|
|
872
|
+
__metadata("design:returntype", void 0)
|
|
873
|
+
], TestGateway.prototype, "handleMessage", null);
|
|
874
|
+
const instance = new TestGateway();
|
|
875
|
+
const metadata = (0, realtime_decorator_1.getParameterMetadata)(instance, 'handleMessage');
|
|
876
|
+
expect(metadata).toContain('data');
|
|
877
|
+
});
|
|
878
|
+
});
|
|
879
|
+
describe('parameter decorators combination', () => {
|
|
880
|
+
it('should handle multiple parameter decorators', () => {
|
|
881
|
+
class TestGateway {
|
|
882
|
+
handleMessage(_client, _data) { }
|
|
883
|
+
}
|
|
884
|
+
__decorate([
|
|
885
|
+
__param(0, (0, realtime_decorator_1.Client)()),
|
|
886
|
+
__param(1, (0, realtime_decorator_1.Data)()),
|
|
887
|
+
__metadata("design:type", Function),
|
|
888
|
+
__metadata("design:paramtypes", [Object, Object]),
|
|
889
|
+
__metadata("design:returntype", void 0)
|
|
890
|
+
], TestGateway.prototype, "handleMessage", null);
|
|
891
|
+
const instance = new TestGateway();
|
|
892
|
+
const metadata = (0, realtime_decorator_1.getParameterMetadata)(instance, 'handleMessage');
|
|
893
|
+
expect(metadata[0]).toBe('client');
|
|
894
|
+
expect(metadata[1]).toBe('data');
|
|
895
|
+
});
|
|
896
|
+
});
|
|
897
|
+
});
|
|
898
|
+
describe('createWebSocketClient', () => {
|
|
899
|
+
it('should create a WebSocket client', () => {
|
|
900
|
+
const mockSocket = {
|
|
901
|
+
send: jest.fn(),
|
|
902
|
+
close: jest.fn(),
|
|
903
|
+
};
|
|
904
|
+
const client = (0, websocket_gateway_1.createWebSocketClient)(mockSocket, 'test-id');
|
|
905
|
+
expect(client.id).toBe('test-id');
|
|
906
|
+
expect(client.socket).toBe(mockSocket);
|
|
907
|
+
expect(client.metadata).toBeInstanceOf(Map);
|
|
908
|
+
expect(client.rooms).toBeInstanceOf(Set);
|
|
909
|
+
});
|
|
910
|
+
it('should handle join and leave room', () => {
|
|
911
|
+
const mockSocket = { send: jest.fn(), close: jest.fn() };
|
|
912
|
+
const client = (0, websocket_gateway_1.createWebSocketClient)(mockSocket, 'test-id');
|
|
913
|
+
client.join('room1');
|
|
914
|
+
expect(client.inRoom('room1')).toBe(true);
|
|
915
|
+
client.leave('room1');
|
|
916
|
+
expect(client.inRoom('room1')).toBe(false);
|
|
917
|
+
});
|
|
918
|
+
it('should send message', () => {
|
|
919
|
+
const mockSocket = {
|
|
920
|
+
send: jest.fn(),
|
|
921
|
+
close: jest.fn(),
|
|
922
|
+
};
|
|
923
|
+
const client = (0, websocket_gateway_1.createWebSocketClient)(mockSocket, 'test-id');
|
|
924
|
+
client.send('test-event', { data: 'test' });
|
|
925
|
+
expect(mockSocket.send).toHaveBeenCalled();
|
|
926
|
+
const sentMessage = JSON.parse(mockSocket.send.mock.calls[0][0]);
|
|
927
|
+
expect(sentMessage.event).toBe('test-event');
|
|
928
|
+
expect(sentMessage.data).toEqual({ data: 'test' });
|
|
929
|
+
expect(sentMessage.timestamp).toBeDefined();
|
|
930
|
+
});
|
|
931
|
+
it('should handle send error gracefully', () => {
|
|
932
|
+
const mockSocket = {
|
|
933
|
+
send: jest.fn().mockImplementation(() => {
|
|
934
|
+
throw new Error('Send failed');
|
|
935
|
+
}),
|
|
936
|
+
close: jest.fn(),
|
|
937
|
+
};
|
|
938
|
+
const client = (0, websocket_gateway_1.createWebSocketClient)(mockSocket, 'test-id');
|
|
939
|
+
// Should not throw
|
|
940
|
+
expect(() => {
|
|
941
|
+
client.send('test-event', { data: 'test' });
|
|
942
|
+
}).not.toThrow();
|
|
943
|
+
});
|
|
944
|
+
it('should disconnect client', () => {
|
|
945
|
+
const mockSocket = {
|
|
946
|
+
send: jest.fn(),
|
|
947
|
+
close: jest.fn(),
|
|
948
|
+
};
|
|
949
|
+
const client = (0, websocket_gateway_1.createWebSocketClient)(mockSocket, 'test-id');
|
|
950
|
+
client.disconnect();
|
|
951
|
+
expect(mockSocket.close).toHaveBeenCalled();
|
|
952
|
+
});
|
|
953
|
+
it('should handle disconnect error gracefully', () => {
|
|
954
|
+
const mockSocket = {
|
|
955
|
+
send: jest.fn(),
|
|
956
|
+
close: jest.fn().mockImplementation(() => {
|
|
957
|
+
throw new Error('Close failed');
|
|
958
|
+
}),
|
|
959
|
+
};
|
|
960
|
+
const client = (0, websocket_gateway_1.createWebSocketClient)(mockSocket, 'test-id');
|
|
961
|
+
// Should not throw
|
|
962
|
+
expect(() => {
|
|
963
|
+
client.disconnect();
|
|
964
|
+
}).not.toThrow();
|
|
965
|
+
});
|
|
966
|
+
it('should handle metadata operations', () => {
|
|
967
|
+
const mockSocket = { send: jest.fn(), close: jest.fn() };
|
|
968
|
+
const client = (0, websocket_gateway_1.createWebSocketClient)(mockSocket, 'test-id');
|
|
969
|
+
client.metadata.set('key', 'value');
|
|
970
|
+
expect(client.metadata.get('key')).toBe('value');
|
|
971
|
+
});
|
|
972
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hazeljs/websocket",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.55",
|
|
4
4
|
"description": "WebSocket and Server-Sent Events module for HazelJS framework",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -51,5 +51,5 @@
|
|
|
51
51
|
"peerDependencies": {
|
|
52
52
|
"@hazeljs/core": ">=0.2.0-beta.0"
|
|
53
53
|
},
|
|
54
|
-
"gitHead": "
|
|
54
|
+
"gitHead": "f2e54f346eea552595a44607999454a9e388cb9e"
|
|
55
55
|
}
|