@onebun/core 0.1.1 → 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.
@@ -0,0 +1,344 @@
1
+ /**
2
+ * Socket.IO Protocol Tests
3
+ */
4
+
5
+ import {
6
+ describe,
7
+ it,
8
+ expect,
9
+ } from 'bun:test';
10
+
11
+ import {
12
+ EngineIOPacketType,
13
+ encodeEngineIOPacket,
14
+ decodeEngineIOPacket,
15
+ createHandshake,
16
+ createOpenPacket,
17
+ createPingPacket,
18
+ createPongPacket,
19
+ SocketIOPacketType,
20
+ encodeSocketIOPacket,
21
+ decodeSocketIOPacket,
22
+ createConnectPacket,
23
+ createDisconnectPacket,
24
+ createEventPacket,
25
+ createAckPacket,
26
+ createConnectErrorPacket,
27
+ wrapInEngineIO,
28
+ unwrapFromEngineIO,
29
+ parseMessage,
30
+ createFullEventMessage,
31
+ createFullAckMessage,
32
+ isNativeMessage,
33
+ parseNativeMessage,
34
+ createNativeMessage,
35
+ detectProtocol,
36
+ } from './ws-socketio-protocol';
37
+
38
+ describe('ws-socketio-protocol', () => {
39
+ describe('Engine.IO', () => {
40
+ describe('encodeEngineIOPacket', () => {
41
+ it('should encode OPEN packet', () => {
42
+ const packet = { type: EngineIOPacketType.OPEN, data: '{"sid":"abc"}' };
43
+ expect(encodeEngineIOPacket(packet)).toBe('0{"sid":"abc"}');
44
+ });
45
+
46
+ it('should encode PING packet without data', () => {
47
+ const packet = { type: EngineIOPacketType.PING };
48
+ expect(encodeEngineIOPacket(packet)).toBe('2');
49
+ });
50
+
51
+ it('should encode PONG packet with data', () => {
52
+ const packet = { type: EngineIOPacketType.PONG, data: 'probe' };
53
+ expect(encodeEngineIOPacket(packet)).toBe('3probe');
54
+ });
55
+
56
+ it('should encode MESSAGE packet', () => {
57
+ const packet = { type: EngineIOPacketType.MESSAGE, data: 'hello' };
58
+ expect(encodeEngineIOPacket(packet)).toBe('4hello');
59
+ });
60
+ });
61
+
62
+ describe('decodeEngineIOPacket', () => {
63
+ it('should decode OPEN packet', () => {
64
+ const packet = decodeEngineIOPacket('0{"sid":"abc"}');
65
+ expect(packet.type).toBe(EngineIOPacketType.OPEN);
66
+ expect(packet.data).toBe('{"sid":"abc"}');
67
+ });
68
+
69
+ it('should decode PING packet', () => {
70
+ const packet = decodeEngineIOPacket('2');
71
+ expect(packet.type).toBe(EngineIOPacketType.PING);
72
+ });
73
+
74
+ it('should decode MESSAGE packet', () => {
75
+ const packet = decodeEngineIOPacket('4hello');
76
+ expect(packet.type).toBe(EngineIOPacketType.MESSAGE);
77
+ expect(packet.data).toBe('hello');
78
+ });
79
+
80
+ it('should handle empty string', () => {
81
+ const packet = decodeEngineIOPacket('');
82
+ expect(packet.type).toBe(EngineIOPacketType.NOOP);
83
+ });
84
+ });
85
+
86
+ describe('createHandshake', () => {
87
+ it('should create handshake with defaults', () => {
88
+ const handshake = createHandshake('test-sid');
89
+ expect(handshake.sid).toBe('test-sid');
90
+ expect(handshake.upgrades).toEqual(['websocket']);
91
+ expect(handshake.pingInterval).toBeGreaterThan(0);
92
+ expect(handshake.pingTimeout).toBeGreaterThan(0);
93
+ expect(handshake.maxPayload).toBeGreaterThan(0);
94
+ });
95
+
96
+ it('should allow custom options', () => {
97
+ const handshake = createHandshake('test-sid', {
98
+ pingInterval: 5000,
99
+ pingTimeout: 3000,
100
+ });
101
+ expect(handshake.pingInterval).toBe(5000);
102
+ expect(handshake.pingTimeout).toBe(3000);
103
+ });
104
+ });
105
+
106
+ describe('createOpenPacket', () => {
107
+ it('should create OPEN packet with handshake', () => {
108
+ const handshake = createHandshake('test-sid');
109
+ const packet = createOpenPacket(handshake);
110
+ expect(packet.startsWith('0')).toBe(true);
111
+ expect(packet).toContain('test-sid');
112
+ });
113
+ });
114
+
115
+ describe('createPingPacket and createPongPacket', () => {
116
+ it('should create PING packet', () => {
117
+ expect(createPingPacket()).toBe('2');
118
+ expect(createPingPacket('probe')).toBe('2probe');
119
+ });
120
+
121
+ it('should create PONG packet', () => {
122
+ expect(createPongPacket()).toBe('3');
123
+ expect(createPongPacket('probe')).toBe('3probe');
124
+ });
125
+ });
126
+ });
127
+
128
+ describe('Socket.IO', () => {
129
+ describe('encodeSocketIOPacket', () => {
130
+ it('should encode CONNECT packet', () => {
131
+ const packet = { type: SocketIOPacketType.CONNECT, nsp: '/' };
132
+ expect(encodeSocketIOPacket(packet)).toBe('0');
133
+ });
134
+
135
+ it('should encode CONNECT packet with namespace', () => {
136
+ const packet = { type: SocketIOPacketType.CONNECT, nsp: '/chat' };
137
+ expect(encodeSocketIOPacket(packet)).toBe('0/chat,');
138
+ });
139
+
140
+ it('should encode EVENT packet', () => {
141
+ const packet = {
142
+ type: SocketIOPacketType.EVENT,
143
+ nsp: '/',
144
+ data: ['message', { text: 'hello' }],
145
+ };
146
+ expect(encodeSocketIOPacket(packet)).toBe('2["message",{"text":"hello"}]');
147
+ });
148
+
149
+ it('should encode ACK packet', () => {
150
+ const packet = {
151
+ type: SocketIOPacketType.ACK,
152
+ nsp: '/',
153
+ id: 1,
154
+ data: [{ ok: true }],
155
+ };
156
+ expect(encodeSocketIOPacket(packet)).toBe('31[{"ok":true}]');
157
+ });
158
+ });
159
+
160
+ describe('decodeSocketIOPacket', () => {
161
+ it('should decode CONNECT packet', () => {
162
+ const packet = decodeSocketIOPacket('0');
163
+ expect(packet.type).toBe(SocketIOPacketType.CONNECT);
164
+ expect(packet.nsp).toBe('/');
165
+ });
166
+
167
+ it('should decode EVENT packet', () => {
168
+ const packet = decodeSocketIOPacket('2["message",{"text":"hello"}]');
169
+ expect(packet.type).toBe(SocketIOPacketType.EVENT);
170
+ expect(packet.data).toEqual(['message', { text: 'hello' }]);
171
+ });
172
+
173
+ it('should decode ACK packet with id', () => {
174
+ const packet = decodeSocketIOPacket('31[{"ok":true}]');
175
+ expect(packet.type).toBe(SocketIOPacketType.ACK);
176
+ expect(packet.id).toBe(1);
177
+ expect(packet.data).toEqual([{ ok: true }]);
178
+ });
179
+
180
+ it('should decode packet with namespace', () => {
181
+ const packet = decodeSocketIOPacket('2/chat,["join",{}]');
182
+ expect(packet.nsp).toBe('/chat');
183
+ expect(packet.data).toEqual(['join', {}]);
184
+ });
185
+ });
186
+
187
+ describe('createConnectPacket', () => {
188
+ it('should create connect packet for default namespace', () => {
189
+ const packet = createConnectPacket();
190
+ expect(packet).toBe('0');
191
+ });
192
+
193
+ it('should create connect packet with auth data', () => {
194
+ const packet = createConnectPacket('/', { token: 'abc' });
195
+ expect(packet).toContain('token');
196
+ });
197
+ });
198
+
199
+ describe('createDisconnectPacket', () => {
200
+ it('should create disconnect packet', () => {
201
+ expect(createDisconnectPacket()).toBe('1');
202
+ expect(createDisconnectPacket('/chat')).toBe('1/chat,');
203
+ });
204
+ });
205
+
206
+ describe('createEventPacket', () => {
207
+ it('should create event packet', () => {
208
+ const packet = createEventPacket('message', { text: 'hello' });
209
+ expect(packet).toContain('message');
210
+ expect(packet).toContain('hello');
211
+ });
212
+
213
+ it('should include ack id', () => {
214
+ const packet = createEventPacket('message', {}, '/', 123);
215
+ expect(packet).toContain('123');
216
+ });
217
+ });
218
+
219
+ describe('createAckPacket', () => {
220
+ it('should create ack packet', () => {
221
+ const packet = createAckPacket(1, { ok: true });
222
+ expect(packet).toBe('31[{"ok":true}]');
223
+ });
224
+ });
225
+
226
+ describe('createConnectErrorPacket', () => {
227
+ it('should create error packet', () => {
228
+ const packet = createConnectErrorPacket({ message: 'Unauthorized' });
229
+ expect(packet).toContain('Unauthorized');
230
+ });
231
+ });
232
+ });
233
+
234
+ describe('Combined messages', () => {
235
+ describe('wrapInEngineIO', () => {
236
+ it('should wrap Socket.IO packet in Engine.IO MESSAGE', () => {
237
+ const socketIO = createEventPacket('test', {});
238
+ const wrapped = wrapInEngineIO(socketIO);
239
+ expect(wrapped.startsWith('4')).toBe(true);
240
+ });
241
+ });
242
+
243
+ describe('unwrapFromEngineIO', () => {
244
+ it('should extract Socket.IO packet from Engine.IO MESSAGE', () => {
245
+ const message = '42["test",{}]';
246
+ const socketIO = unwrapFromEngineIO(message);
247
+ expect(socketIO).toBe('2["test",{}]');
248
+ });
249
+
250
+ it('should return null for non-MESSAGE packets', () => {
251
+ expect(unwrapFromEngineIO('2')).toBeNull();
252
+ expect(unwrapFromEngineIO('3')).toBeNull();
253
+ });
254
+ });
255
+
256
+ describe('parseMessage', () => {
257
+ it('should parse full message', () => {
258
+ const message = '42["test",{"data":"value"}]';
259
+ const { engineIO, socketIO } = parseMessage(message);
260
+
261
+ expect(engineIO.type).toBe(EngineIOPacketType.MESSAGE);
262
+ expect(socketIO?.type).toBe(SocketIOPacketType.EVENT);
263
+ expect(socketIO?.data).toEqual(['test', { data: 'value' }]);
264
+ });
265
+
266
+ it('should handle ping without socketIO', () => {
267
+ const { engineIO, socketIO } = parseMessage('2');
268
+ expect(engineIO.type).toBe(EngineIOPacketType.PING);
269
+ expect(socketIO).toBeUndefined();
270
+ });
271
+ });
272
+
273
+ describe('createFullEventMessage', () => {
274
+ it('should create full event message', () => {
275
+ const message = createFullEventMessage('chat:message', { text: 'hello' });
276
+ const { socketIO } = parseMessage(message);
277
+
278
+ expect(socketIO?.data?.[0]).toBe('chat:message');
279
+ expect(socketIO?.data?.[1]).toEqual({ text: 'hello' });
280
+ });
281
+ });
282
+
283
+ describe('createFullAckMessage', () => {
284
+ it('should create full ack message', () => {
285
+ const message = createFullAckMessage(123, { ok: true });
286
+ const { socketIO } = parseMessage(message);
287
+
288
+ expect(socketIO?.type).toBe(SocketIOPacketType.ACK);
289
+ expect(socketIO?.id).toBe(123);
290
+ });
291
+ });
292
+ });
293
+
294
+ describe('Native WebSocket format', () => {
295
+ describe('isNativeMessage', () => {
296
+ it('should detect native format', () => {
297
+ expect(isNativeMessage('{"event":"test","data":{}}')).toBe(true);
298
+ expect(isNativeMessage('{"event":"test"}')).toBe(true);
299
+ });
300
+
301
+ it('should reject non-native format', () => {
302
+ expect(isNativeMessage('42["test",{}]')).toBe(false);
303
+ expect(isNativeMessage('not json')).toBe(false);
304
+ expect(isNativeMessage('{}')).toBe(false);
305
+ });
306
+ });
307
+
308
+ describe('parseNativeMessage', () => {
309
+ it('should parse native message', () => {
310
+ const message = parseNativeMessage('{"event":"test","data":{"value":1}}');
311
+ expect(message?.event).toBe('test');
312
+ expect(message?.data).toEqual({ value: 1 });
313
+ });
314
+
315
+ it('should parse message with ack', () => {
316
+ const message = parseNativeMessage('{"event":"test","data":{},"ack":123}');
317
+ expect(message?.ack).toBe(123);
318
+ });
319
+ });
320
+
321
+ describe('createNativeMessage', () => {
322
+ it('should create native message', () => {
323
+ const message = createNativeMessage('test', { value: 1 });
324
+ expect(message).toBe('{"event":"test","data":{"value":1}}');
325
+ });
326
+
327
+ it('should include ack id', () => {
328
+ const message = createNativeMessage('test', {}, 123);
329
+ expect(message).toContain('"ack":123');
330
+ });
331
+ });
332
+ });
333
+
334
+ describe('Protocol detection', () => {
335
+ it('should detect socket.io protocol', () => {
336
+ expect(detectProtocol('42["test",{}]')).toBe('socket.io');
337
+ expect(detectProtocol('2')).toBe('socket.io');
338
+ });
339
+
340
+ it('should detect native protocol', () => {
341
+ expect(detectProtocol('{"event":"test","data":{}}')).toBe('native');
342
+ });
343
+ });
344
+ });