@onebun/core 0.1.0 → 0.1.2
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 +277 -3
- package/package.json +13 -2
- package/src/application.test.ts +119 -0
- package/src/application.ts +112 -5
- package/src/docs-examples.test.ts +2919 -0
- package/src/index.ts +96 -0
- package/src/module.ts +10 -4
- package/src/redis-client.ts +502 -0
- package/src/shared-redis.ts +231 -0
- package/src/types.ts +50 -0
- package/src/ws-base-gateway.test.ts +479 -0
- package/src/ws-base-gateway.ts +514 -0
- package/src/ws-client.test.ts +511 -0
- package/src/ws-client.ts +628 -0
- package/src/ws-client.types.ts +129 -0
- package/src/ws-decorators.test.ts +331 -0
- package/src/ws-decorators.ts +417 -0
- package/src/ws-guards.test.ts +334 -0
- package/src/ws-guards.ts +298 -0
- package/src/ws-handler.ts +658 -0
- package/src/ws-integration.test.ts +517 -0
- package/src/ws-pattern-matcher.test.ts +152 -0
- package/src/ws-pattern-matcher.ts +240 -0
- package/src/ws-service-definition.ts +223 -0
- package/src/ws-socketio-protocol.test.ts +344 -0
- package/src/ws-socketio-protocol.ts +567 -0
- package/src/ws-storage-memory.test.ts +246 -0
- package/src/ws-storage-memory.ts +222 -0
- package/src/ws-storage-redis.ts +302 -0
- package/src/ws-storage.ts +210 -0
- package/src/ws.types.ts +342 -0
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for ws-base-gateway.ts
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
describe,
|
|
7
|
+
it,
|
|
8
|
+
expect,
|
|
9
|
+
beforeEach,
|
|
10
|
+
mock,
|
|
11
|
+
} from 'bun:test';
|
|
12
|
+
|
|
13
|
+
import type { WsClientData } from './ws.types';
|
|
14
|
+
|
|
15
|
+
import { BaseWebSocketGateway, _resetClientSocketsForTesting } from './ws-base-gateway';
|
|
16
|
+
import { InMemoryWsStorage } from './ws-storage-memory';
|
|
17
|
+
|
|
18
|
+
// Mock WebSocket
|
|
19
|
+
interface MockWebSocket {
|
|
20
|
+
data: WsClientData;
|
|
21
|
+
send: ReturnType<typeof mock>;
|
|
22
|
+
close: ReturnType<typeof mock>;
|
|
23
|
+
subscribe: ReturnType<typeof mock>;
|
|
24
|
+
unsubscribe: ReturnType<typeof mock>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function createMockSocket(clientData: WsClientData): MockWebSocket {
|
|
28
|
+
return {
|
|
29
|
+
data: clientData,
|
|
30
|
+
send: mock(() => undefined),
|
|
31
|
+
close: mock(() => undefined),
|
|
32
|
+
subscribe: mock(() => undefined),
|
|
33
|
+
unsubscribe: mock(() => undefined),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function createClientData(id: string, rooms: string[] = []): WsClientData {
|
|
38
|
+
return {
|
|
39
|
+
id,
|
|
40
|
+
rooms,
|
|
41
|
+
connectedAt: Date.now(),
|
|
42
|
+
auth: null,
|
|
43
|
+
metadata: {},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Concrete implementation for testing
|
|
48
|
+
class TestGateway extends BaseWebSocketGateway {
|
|
49
|
+
// Expose internal methods for testing
|
|
50
|
+
public exposeRegisterSocket(clientId: string, socket: MockWebSocket): void {
|
|
51
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
52
|
+
(this as any)._registerSocket(clientId, socket);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public exposeUnregisterSocket(clientId: string): void {
|
|
56
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
57
|
+
(this as any)._unregisterSocket(clientId);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
public exposeInitialize(storage: InMemoryWsStorage, server: unknown): void {
|
|
61
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
62
|
+
(this as any)._initialize(storage, server);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public exposeGetSocket(clientId: string): MockWebSocket | undefined {
|
|
66
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
67
|
+
return (this as any).getSocket(clientId);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
describe('BaseWebSocketGateway', () => {
|
|
72
|
+
let gateway: TestGateway;
|
|
73
|
+
let storage: InMemoryWsStorage;
|
|
74
|
+
|
|
75
|
+
beforeEach(() => {
|
|
76
|
+
_resetClientSocketsForTesting();
|
|
77
|
+
gateway = new TestGateway();
|
|
78
|
+
storage = new InMemoryWsStorage();
|
|
79
|
+
gateway.exposeInitialize(storage, {});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe('socket registration', () => {
|
|
83
|
+
it('should register a socket', () => {
|
|
84
|
+
const clientData = createClientData('client1');
|
|
85
|
+
const socket = createMockSocket(clientData);
|
|
86
|
+
|
|
87
|
+
gateway.exposeRegisterSocket('client1', socket);
|
|
88
|
+
|
|
89
|
+
expect(gateway.exposeGetSocket('client1')).toBe(socket);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should unregister a socket', () => {
|
|
93
|
+
const clientData = createClientData('client1');
|
|
94
|
+
const socket = createMockSocket(clientData);
|
|
95
|
+
|
|
96
|
+
gateway.exposeRegisterSocket('client1', socket);
|
|
97
|
+
gateway.exposeUnregisterSocket('client1');
|
|
98
|
+
|
|
99
|
+
expect(gateway.exposeGetSocket('client1')).toBeUndefined();
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('clients getter', () => {
|
|
104
|
+
it('should return empty map when no clients', () => {
|
|
105
|
+
expect(gateway.clients.size).toBe(0);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should return map of connected clients', () => {
|
|
109
|
+
const client1 = createClientData('client1');
|
|
110
|
+
const client2 = createClientData('client2');
|
|
111
|
+
|
|
112
|
+
gateway.exposeRegisterSocket('client1', createMockSocket(client1));
|
|
113
|
+
gateway.exposeRegisterSocket('client2', createMockSocket(client2));
|
|
114
|
+
|
|
115
|
+
expect(gateway.clients.size).toBe(2);
|
|
116
|
+
expect(gateway.clients.get('client1')).toEqual(client1);
|
|
117
|
+
expect(gateway.clients.get('client2')).toEqual(client2);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe('getClient', () => {
|
|
122
|
+
it('should return undefined when storage not initialized', async () => {
|
|
123
|
+
const uninitializedGateway = new TestGateway();
|
|
124
|
+
const result = await uninitializedGateway.getClient('client1');
|
|
125
|
+
expect(result).toBeUndefined();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should return client from storage', async () => {
|
|
129
|
+
const clientData = createClientData('client1');
|
|
130
|
+
await storage.addClient(clientData);
|
|
131
|
+
|
|
132
|
+
const result = await gateway.getClient('client1');
|
|
133
|
+
expect(result).toEqual(clientData);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should return undefined for non-existent client', async () => {
|
|
137
|
+
const result = await gateway.getClient('nonexistent');
|
|
138
|
+
expect(result).toBeUndefined();
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe('getRoom', () => {
|
|
143
|
+
it('should return undefined when storage not initialized', async () => {
|
|
144
|
+
const uninitializedGateway = new TestGateway();
|
|
145
|
+
const result = await uninitializedGateway.getRoom('room1');
|
|
146
|
+
expect(result).toBeUndefined();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should return room from storage', async () => {
|
|
150
|
+
// Rooms are created automatically when a client joins
|
|
151
|
+
const client = createClientData('client1');
|
|
152
|
+
await storage.addClient(client);
|
|
153
|
+
await storage.addClientToRoom('client1', 'room1');
|
|
154
|
+
|
|
155
|
+
const result = await gateway.getRoom('room1');
|
|
156
|
+
expect(result?.name).toBe('room1');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('should return undefined for non-existent room', async () => {
|
|
160
|
+
const result = await gateway.getRoom('nonexistent');
|
|
161
|
+
expect(result).toBeUndefined();
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe('getClientsByRoom', () => {
|
|
166
|
+
it('should return empty array when storage not initialized', async () => {
|
|
167
|
+
const uninitializedGateway = new TestGateway();
|
|
168
|
+
const result = await uninitializedGateway.getClientsByRoom('room1');
|
|
169
|
+
expect(result).toEqual([]);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('should return clients in room', async () => {
|
|
173
|
+
const client1 = createClientData('client1');
|
|
174
|
+
const client2 = createClientData('client2');
|
|
175
|
+
|
|
176
|
+
await storage.addClient(client1);
|
|
177
|
+
await storage.addClient(client2);
|
|
178
|
+
await storage.addClientToRoom('client1', 'room1');
|
|
179
|
+
await storage.addClientToRoom('client2', 'room1');
|
|
180
|
+
|
|
181
|
+
const result = await gateway.getClientsByRoom('room1');
|
|
182
|
+
expect(result.length).toBe(2);
|
|
183
|
+
expect(result.map(c => c.id).sort()).toEqual(['client1', 'client2']);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
describe('emit', () => {
|
|
188
|
+
it('should send message to specific client', () => {
|
|
189
|
+
const clientData = createClientData('client1');
|
|
190
|
+
const socket = createMockSocket(clientData);
|
|
191
|
+
|
|
192
|
+
gateway.exposeRegisterSocket('client1', socket);
|
|
193
|
+
gateway.emit('client1', 'test:event', { foo: 'bar' });
|
|
194
|
+
|
|
195
|
+
expect(socket.send).toHaveBeenCalledTimes(1);
|
|
196
|
+
const sentMessage = JSON.parse(socket.send.mock.calls[0][0]);
|
|
197
|
+
expect(sentMessage).toEqual({ event: 'test:event', data: { foo: 'bar' } });
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('should not throw for non-existent client', () => {
|
|
201
|
+
expect(() => gateway.emit('nonexistent', 'test:event', {})).not.toThrow();
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
describe('broadcast', () => {
|
|
206
|
+
it('should send message to all clients', () => {
|
|
207
|
+
const client1 = createClientData('client1');
|
|
208
|
+
const client2 = createClientData('client2');
|
|
209
|
+
const socket1 = createMockSocket(client1);
|
|
210
|
+
const socket2 = createMockSocket(client2);
|
|
211
|
+
|
|
212
|
+
gateway.exposeRegisterSocket('client1', socket1);
|
|
213
|
+
gateway.exposeRegisterSocket('client2', socket2);
|
|
214
|
+
gateway.broadcast('test:event', { message: 'hello' });
|
|
215
|
+
|
|
216
|
+
expect(socket1.send).toHaveBeenCalledTimes(1);
|
|
217
|
+
expect(socket2.send).toHaveBeenCalledTimes(1);
|
|
218
|
+
|
|
219
|
+
const sent1 = JSON.parse(socket1.send.mock.calls[0][0]);
|
|
220
|
+
const sent2 = JSON.parse(socket2.send.mock.calls[0][0]);
|
|
221
|
+
expect(sent1).toEqual({ event: 'test:event', data: { message: 'hello' } });
|
|
222
|
+
expect(sent2).toEqual({ event: 'test:event', data: { message: 'hello' } });
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('should exclude specified clients', () => {
|
|
226
|
+
const client1 = createClientData('client1');
|
|
227
|
+
const client2 = createClientData('client2');
|
|
228
|
+
const socket1 = createMockSocket(client1);
|
|
229
|
+
const socket2 = createMockSocket(client2);
|
|
230
|
+
|
|
231
|
+
gateway.exposeRegisterSocket('client1', socket1);
|
|
232
|
+
gateway.exposeRegisterSocket('client2', socket2);
|
|
233
|
+
gateway.broadcast('test:event', { message: 'hello' }, ['client1']);
|
|
234
|
+
|
|
235
|
+
expect(socket1.send).not.toHaveBeenCalled();
|
|
236
|
+
expect(socket2.send).toHaveBeenCalledTimes(1);
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
describe('emitToRoom', () => {
|
|
241
|
+
it('should send message to clients in room', async () => {
|
|
242
|
+
const client1 = createClientData('client1');
|
|
243
|
+
const client2 = createClientData('client2');
|
|
244
|
+
const client3 = createClientData('client3');
|
|
245
|
+
const socket1 = createMockSocket(client1);
|
|
246
|
+
const socket2 = createMockSocket(client2);
|
|
247
|
+
const socket3 = createMockSocket(client3);
|
|
248
|
+
|
|
249
|
+
gateway.exposeRegisterSocket('client1', socket1);
|
|
250
|
+
gateway.exposeRegisterSocket('client2', socket2);
|
|
251
|
+
gateway.exposeRegisterSocket('client3', socket3);
|
|
252
|
+
|
|
253
|
+
await storage.addClient(client1);
|
|
254
|
+
await storage.addClient(client2);
|
|
255
|
+
await storage.addClient(client3);
|
|
256
|
+
await storage.addClientToRoom('client1', 'room1');
|
|
257
|
+
await storage.addClientToRoom('client2', 'room1');
|
|
258
|
+
// client3 is not in room1
|
|
259
|
+
|
|
260
|
+
gateway.emitToRoom('room1', 'room:message', { text: 'hi' });
|
|
261
|
+
|
|
262
|
+
// Need a small delay for async operation
|
|
263
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
264
|
+
|
|
265
|
+
expect(socket1.send).toHaveBeenCalled();
|
|
266
|
+
expect(socket2.send).toHaveBeenCalled();
|
|
267
|
+
expect(socket3.send).not.toHaveBeenCalled();
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('should exclude specified clients in room', async () => {
|
|
271
|
+
const client1 = createClientData('client1');
|
|
272
|
+
const client2 = createClientData('client2');
|
|
273
|
+
const socket1 = createMockSocket(client1);
|
|
274
|
+
const socket2 = createMockSocket(client2);
|
|
275
|
+
|
|
276
|
+
gateway.exposeRegisterSocket('client1', socket1);
|
|
277
|
+
gateway.exposeRegisterSocket('client2', socket2);
|
|
278
|
+
|
|
279
|
+
await storage.addClient(client1);
|
|
280
|
+
await storage.addClient(client2);
|
|
281
|
+
await storage.addClientToRoom('client1', 'room1');
|
|
282
|
+
await storage.addClientToRoom('client2', 'room1');
|
|
283
|
+
|
|
284
|
+
gateway.emitToRoom('room1', 'room:message', { text: 'hi' }, ['client1']);
|
|
285
|
+
|
|
286
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
287
|
+
|
|
288
|
+
expect(socket1.send).not.toHaveBeenCalled();
|
|
289
|
+
expect(socket2.send).toHaveBeenCalled();
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
describe('emitToRooms', () => {
|
|
294
|
+
it('should send to clients in multiple rooms without duplicates', async () => {
|
|
295
|
+
const client1 = createClientData('client1');
|
|
296
|
+
const client2 = createClientData('client2');
|
|
297
|
+
const socket1 = createMockSocket(client1);
|
|
298
|
+
const socket2 = createMockSocket(client2);
|
|
299
|
+
|
|
300
|
+
gateway.exposeRegisterSocket('client1', socket1);
|
|
301
|
+
gateway.exposeRegisterSocket('client2', socket2);
|
|
302
|
+
|
|
303
|
+
await storage.addClient(client1);
|
|
304
|
+
await storage.addClient(client2);
|
|
305
|
+
await storage.addClientToRoom('client1', 'room1');
|
|
306
|
+
await storage.addClientToRoom('client1', 'room2'); // client1 in both rooms
|
|
307
|
+
await storage.addClientToRoom('client2', 'room2');
|
|
308
|
+
|
|
309
|
+
await gateway.emitToRooms(['room1', 'room2'], 'multi:message', { data: 'test' });
|
|
310
|
+
|
|
311
|
+
// client1 should receive only once even though in both rooms
|
|
312
|
+
expect(socket1.send).toHaveBeenCalledTimes(1);
|
|
313
|
+
expect(socket2.send).toHaveBeenCalledTimes(1);
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
describe('disconnectClient', () => {
|
|
318
|
+
it('should close client socket', () => {
|
|
319
|
+
const clientData = createClientData('client1');
|
|
320
|
+
const socket = createMockSocket(clientData);
|
|
321
|
+
|
|
322
|
+
gateway.exposeRegisterSocket('client1', socket);
|
|
323
|
+
gateway.disconnectClient('client1', 'test reason');
|
|
324
|
+
|
|
325
|
+
expect(socket.close).toHaveBeenCalledWith(1000, 'test reason');
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it('should use default reason if not provided', () => {
|
|
329
|
+
const clientData = createClientData('client1');
|
|
330
|
+
const socket = createMockSocket(clientData);
|
|
331
|
+
|
|
332
|
+
gateway.exposeRegisterSocket('client1', socket);
|
|
333
|
+
gateway.disconnectClient('client1');
|
|
334
|
+
|
|
335
|
+
expect(socket.close).toHaveBeenCalledWith(1000, 'Disconnected by server');
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it('should not throw for non-existent client', () => {
|
|
339
|
+
expect(() => gateway.disconnectClient('nonexistent')).not.toThrow();
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
describe('disconnectAll', () => {
|
|
344
|
+
it('should close all client sockets', () => {
|
|
345
|
+
const client1 = createClientData('client1');
|
|
346
|
+
const client2 = createClientData('client2');
|
|
347
|
+
const socket1 = createMockSocket(client1);
|
|
348
|
+
const socket2 = createMockSocket(client2);
|
|
349
|
+
|
|
350
|
+
gateway.exposeRegisterSocket('client1', socket1);
|
|
351
|
+
gateway.exposeRegisterSocket('client2', socket2);
|
|
352
|
+
gateway.disconnectAll('server shutdown');
|
|
353
|
+
|
|
354
|
+
expect(socket1.close).toHaveBeenCalledWith(1000, 'server shutdown');
|
|
355
|
+
expect(socket2.close).toHaveBeenCalledWith(1000, 'server shutdown');
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
describe('disconnectRoom', () => {
|
|
360
|
+
it('should disconnect all clients in room', async () => {
|
|
361
|
+
const client1 = createClientData('client1');
|
|
362
|
+
const client2 = createClientData('client2');
|
|
363
|
+
const socket1 = createMockSocket(client1);
|
|
364
|
+
const socket2 = createMockSocket(client2);
|
|
365
|
+
|
|
366
|
+
gateway.exposeRegisterSocket('client1', socket1);
|
|
367
|
+
gateway.exposeRegisterSocket('client2', socket2);
|
|
368
|
+
|
|
369
|
+
await storage.addClient(client1);
|
|
370
|
+
await storage.addClient(client2);
|
|
371
|
+
await storage.addClientToRoom('client1', 'room1');
|
|
372
|
+
await storage.addClientToRoom('client2', 'room1');
|
|
373
|
+
|
|
374
|
+
await gateway.disconnectRoom('room1', 'room closed');
|
|
375
|
+
|
|
376
|
+
expect(socket1.close).toHaveBeenCalledWith(1000, 'room closed');
|
|
377
|
+
expect(socket2.close).toHaveBeenCalledWith(1000, 'room closed');
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
describe('joinRoom', () => {
|
|
382
|
+
it('should add client to room in storage', async () => {
|
|
383
|
+
const clientData = createClientData('client1');
|
|
384
|
+
const socket = createMockSocket(clientData);
|
|
385
|
+
|
|
386
|
+
gateway.exposeRegisterSocket('client1', socket);
|
|
387
|
+
await storage.addClient(clientData);
|
|
388
|
+
|
|
389
|
+
await gateway.joinRoom('client1', 'room1');
|
|
390
|
+
|
|
391
|
+
const room = await storage.getRoom('room1');
|
|
392
|
+
expect(room?.clientIds).toContain('client1');
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it('should subscribe socket to room topic', async () => {
|
|
396
|
+
const clientData = createClientData('client1');
|
|
397
|
+
const socket = createMockSocket(clientData);
|
|
398
|
+
|
|
399
|
+
gateway.exposeRegisterSocket('client1', socket);
|
|
400
|
+
await storage.addClient(clientData);
|
|
401
|
+
|
|
402
|
+
await gateway.joinRoom('client1', 'room1');
|
|
403
|
+
|
|
404
|
+
expect(socket.subscribe).toHaveBeenCalledWith('room1');
|
|
405
|
+
});
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
describe('leaveRoom', () => {
|
|
409
|
+
it('should remove client from room in storage', async () => {
|
|
410
|
+
const clientData = createClientData('client1');
|
|
411
|
+
const socket = createMockSocket(clientData);
|
|
412
|
+
|
|
413
|
+
gateway.exposeRegisterSocket('client1', socket);
|
|
414
|
+
await storage.addClient(clientData);
|
|
415
|
+
await storage.addClientToRoom('client1', 'room1');
|
|
416
|
+
|
|
417
|
+
await gateway.leaveRoom('client1', 'room1');
|
|
418
|
+
|
|
419
|
+
const clientsInRoom = await storage.getClientsInRoom('room1');
|
|
420
|
+
expect(clientsInRoom).not.toContain('client1');
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
it('should unsubscribe socket from room topic', async () => {
|
|
424
|
+
const clientData = createClientData('client1');
|
|
425
|
+
const socket = createMockSocket(clientData);
|
|
426
|
+
|
|
427
|
+
gateway.exposeRegisterSocket('client1', socket);
|
|
428
|
+
await storage.addClient(clientData);
|
|
429
|
+
await storage.addClientToRoom('client1', 'room1');
|
|
430
|
+
|
|
431
|
+
await gateway.leaveRoom('client1', 'room1');
|
|
432
|
+
|
|
433
|
+
expect(socket.unsubscribe).toHaveBeenCalledWith('room1');
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
describe('getRoomsByPattern', () => {
|
|
438
|
+
it('should return empty array when storage not initialized', async () => {
|
|
439
|
+
const uninitializedGateway = new TestGateway();
|
|
440
|
+
const result = await uninitializedGateway.getRoomsByPattern('room:*');
|
|
441
|
+
expect(result).toEqual([]);
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
it('should return rooms matching pattern', async () => {
|
|
445
|
+
// Create rooms by adding clients to them
|
|
446
|
+
const client1 = createClientData('client1');
|
|
447
|
+
const client2 = createClientData('client2');
|
|
448
|
+
const client3 = createClientData('client3');
|
|
449
|
+
await storage.addClient(client1);
|
|
450
|
+
await storage.addClient(client2);
|
|
451
|
+
await storage.addClient(client3);
|
|
452
|
+
await storage.addClientToRoom('client1', 'room:general');
|
|
453
|
+
await storage.addClientToRoom('client2', 'room:private');
|
|
454
|
+
await storage.addClientToRoom('client3', 'other:room');
|
|
455
|
+
|
|
456
|
+
const result = await gateway.getRoomsByPattern('room:*');
|
|
457
|
+
expect(result.length).toBe(2);
|
|
458
|
+
expect(result.map(r => r.name).sort()).toEqual(['room:general', 'room:private']);
|
|
459
|
+
});
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
describe('getWsServer', () => {
|
|
463
|
+
it('should return null when server not initialized', () => {
|
|
464
|
+
const uninitializedGateway = new TestGateway();
|
|
465
|
+
expect(uninitializedGateway.getWsServer()).toBeNull();
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
it('should return server wrapper with publish method', () => {
|
|
469
|
+
const mockServer = {
|
|
470
|
+
publish: mock(() => undefined),
|
|
471
|
+
};
|
|
472
|
+
gateway.exposeInitialize(storage, mockServer);
|
|
473
|
+
|
|
474
|
+
const wsServer = gateway.getWsServer();
|
|
475
|
+
expect(wsServer).not.toBeNull();
|
|
476
|
+
expect(typeof wsServer?.publish).toBe('function');
|
|
477
|
+
});
|
|
478
|
+
});
|
|
479
|
+
});
|