memcache 0.2.0 → 1.0.0

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,809 @@
1
+ // biome-ignore-all lint/suspicious/noExplicitAny: test file
2
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
3
+ import { createNode, MemcacheNode } from "../src/node";
4
+
5
+ describe("MemcacheNode", () => {
6
+ let node: MemcacheNode;
7
+
8
+ beforeEach(() => {
9
+ node = new MemcacheNode("localhost", 11211, {
10
+ timeout: 5000,
11
+ });
12
+ });
13
+
14
+ afterEach(async () => {
15
+ if (node.isConnected()) {
16
+ await node.disconnect();
17
+ }
18
+ });
19
+
20
+ describe("Constructor and Properties", () => {
21
+ it("should create instance with host and port", () => {
22
+ expect(node).toBeInstanceOf(MemcacheNode);
23
+ expect(node.host).toBe("localhost");
24
+ expect(node.port).toBe(11211);
25
+ });
26
+
27
+ it("should generate correct id for standard port", () => {
28
+ const testNode = new MemcacheNode("localhost", 11211);
29
+ expect(testNode.id).toBe("localhost:11211");
30
+ });
31
+
32
+ it("should generate correct id for Unix socket (port 0)", () => {
33
+ const testNode = new MemcacheNode("/var/run/memcached.sock", 0);
34
+ expect(testNode.id).toBe("/var/run/memcached.sock");
35
+ });
36
+
37
+ it("should generate correct uri for standard port", () => {
38
+ const testNode = new MemcacheNode("localhost", 11211);
39
+ expect(testNode.uri).toBe("memcache://localhost:11211");
40
+ });
41
+
42
+ it("should generate correct uri for Unix socket (port 0)", () => {
43
+ const testNode = new MemcacheNode("/var/run/memcached.sock", 0);
44
+ expect(testNode.uri).toBe("memcache:///var/run/memcached.sock");
45
+ });
46
+
47
+ it("should use default options if not provided", () => {
48
+ const testNode = new MemcacheNode("localhost", 11211);
49
+ expect(testNode).toBeInstanceOf(MemcacheNode);
50
+ });
51
+
52
+ it("should have default weight of 1", () => {
53
+ const testNode = new MemcacheNode("localhost", 11211);
54
+ expect(testNode.weight).toBe(1);
55
+ });
56
+
57
+ it("should accept weight in options", () => {
58
+ const testNode = new MemcacheNode("localhost", 11211, { weight: 5 });
59
+ expect(testNode.weight).toBe(5);
60
+ });
61
+
62
+ it("should allow getting and setting weight", () => {
63
+ const testNode = new MemcacheNode("localhost", 11211, { weight: 2 });
64
+ expect(testNode.weight).toBe(2);
65
+
66
+ testNode.weight = 10;
67
+ expect(testNode.weight).toBe(10);
68
+ });
69
+ });
70
+
71
+ describe("createNode factory function", () => {
72
+ it("should create a new MemcacheNode instance", () => {
73
+ const node = createNode("localhost", 11211);
74
+ expect(node).toBeInstanceOf(MemcacheNode);
75
+ expect(node.host).toBe("localhost");
76
+ expect(node.port).toBe(11211);
77
+ });
78
+
79
+ it("should create node with options", () => {
80
+ const node = createNode("localhost", 11211, {
81
+ timeout: 5000,
82
+ keepAlive: true,
83
+ keepAliveDelay: 1000,
84
+ weight: 3,
85
+ });
86
+ expect(node).toBeInstanceOf(MemcacheNode);
87
+ expect(node.weight).toBe(3);
88
+ });
89
+
90
+ it("should create node without options", () => {
91
+ const node = createNode("192.168.1.1", 11212);
92
+ expect(node).toBeInstanceOf(MemcacheNode);
93
+ expect(node.host).toBe("192.168.1.1");
94
+ expect(node.port).toBe(11212);
95
+ expect(node.weight).toBe(1); // default weight
96
+ });
97
+ });
98
+
99
+ describe("Constructor and Properties", () => {
100
+ it("should have default keepAlive of true", () => {
101
+ const testNode = new MemcacheNode("localhost", 11211);
102
+ expect(testNode.keepAlive).toBe(true);
103
+ });
104
+
105
+ it("should accept keepAlive in options", () => {
106
+ const testNode = new MemcacheNode("localhost", 11211, {
107
+ keepAlive: false,
108
+ });
109
+ expect(testNode.keepAlive).toBe(false);
110
+ });
111
+
112
+ it("should allow getting and setting keepAlive", () => {
113
+ const testNode = new MemcacheNode("localhost", 11211, {
114
+ keepAlive: true,
115
+ });
116
+ expect(testNode.keepAlive).toBe(true);
117
+
118
+ testNode.keepAlive = false;
119
+ expect(testNode.keepAlive).toBe(false);
120
+
121
+ testNode.keepAlive = true;
122
+ expect(testNode.keepAlive).toBe(true);
123
+ });
124
+
125
+ it("should have default keepAliveDelay of 1000", () => {
126
+ const testNode = new MemcacheNode("localhost", 11211);
127
+ expect(testNode.keepAliveDelay).toBe(1000);
128
+ });
129
+
130
+ it("should accept keepAliveDelay in options", () => {
131
+ const testNode = new MemcacheNode("localhost", 11211, {
132
+ keepAliveDelay: 5000,
133
+ });
134
+ expect(testNode.keepAliveDelay).toBe(5000);
135
+ });
136
+
137
+ it("should allow getting and setting keepAliveDelay", () => {
138
+ const testNode = new MemcacheNode("localhost", 11211, {
139
+ keepAliveDelay: 2000,
140
+ });
141
+ expect(testNode.keepAliveDelay).toBe(2000);
142
+
143
+ testNode.keepAliveDelay = 3000;
144
+ expect(testNode.keepAliveDelay).toBe(3000);
145
+
146
+ testNode.keepAliveDelay = 500;
147
+ expect(testNode.keepAliveDelay).toBe(500);
148
+ });
149
+ });
150
+
151
+ describe("Connection Lifecycle", () => {
152
+ it("should connect to memcached server", async () => {
153
+ await node.connect();
154
+ expect(node.isConnected()).toBe(true);
155
+ });
156
+
157
+ it("should handle connecting when already connected", async () => {
158
+ await node.connect();
159
+ expect(node.isConnected()).toBe(true);
160
+
161
+ // Try to connect again - should resolve immediately
162
+ await node.connect();
163
+ expect(node.isConnected()).toBe(true);
164
+ });
165
+
166
+ it("should disconnect from memcached server", async () => {
167
+ await node.connect();
168
+ expect(node.isConnected()).toBe(true);
169
+
170
+ await node.disconnect();
171
+ expect(node.isConnected()).toBe(false);
172
+ });
173
+
174
+ it("should emit connect event", async () => {
175
+ let connected = false;
176
+ node.on("connect", () => {
177
+ connected = true;
178
+ });
179
+
180
+ await node.connect();
181
+ expect(connected).toBe(true);
182
+ });
183
+
184
+ it("should emit close event on disconnect", async () => {
185
+ await node.connect();
186
+
187
+ const closePromise = new Promise<void>((resolve) => {
188
+ node.on("close", () => {
189
+ resolve();
190
+ });
191
+ });
192
+
193
+ await node.disconnect();
194
+
195
+ // Wait for close event with timeout
196
+ await Promise.race([
197
+ closePromise,
198
+ new Promise((_, reject) =>
199
+ setTimeout(() => reject(new Error("Close event not emitted")), 100),
200
+ ),
201
+ ]);
202
+ });
203
+
204
+ it("should handle quit command", async () => {
205
+ await node.connect();
206
+ await node.quit();
207
+ expect(node.isConnected()).toBe(false);
208
+ });
209
+
210
+ it("should reconnect successfully", async () => {
211
+ // First connection
212
+ await node.connect();
213
+ expect(node.isConnected()).toBe(true);
214
+
215
+ // Set a value to ensure connection is working
216
+ const key = "node-test-reconnect";
217
+ const value = "initial-value";
218
+ const bytes = Buffer.byteLength(value);
219
+ await node.command(`set ${key} 0 0 ${bytes}\r\n${value}`);
220
+
221
+ // Reconnect
222
+ await node.reconnect();
223
+ expect(node.isConnected()).toBe(true);
224
+
225
+ // Verify we can still execute commands after reconnect
226
+ const result = await node.command("version");
227
+ expect(result).toBeDefined();
228
+ expect(typeof result).toBe("string");
229
+ expect(result).toContain("VERSION");
230
+ });
231
+
232
+ it("should clear pending commands on reconnect", async () => {
233
+ await node.connect();
234
+
235
+ // Queue a command that won't complete
236
+ const promise = node.command("get slow-key", { isMultiline: true });
237
+
238
+ // Reconnect immediately (this will disconnect and clear pending commands)
239
+ setImmediate(async () => {
240
+ await node.reconnect();
241
+ });
242
+
243
+ // The pending command should be rejected
244
+ await expect(promise).rejects.toThrow(
245
+ "Connection reset for reconnection",
246
+ );
247
+ });
248
+
249
+ it("should reconnect when not initially connected", async () => {
250
+ // Don't connect first
251
+ expect(node.isConnected()).toBe(false);
252
+
253
+ // Reconnect should establish a connection
254
+ await node.reconnect();
255
+ expect(node.isConnected()).toBe(true);
256
+
257
+ // Verify connection works
258
+ const result = await node.command("version");
259
+ expect(result).toContain("VERSION");
260
+ });
261
+
262
+ it("should emit connect event on reconnect", async () => {
263
+ await node.connect();
264
+
265
+ let connectCount = 0;
266
+ node.on("connect", () => {
267
+ connectCount++;
268
+ });
269
+
270
+ // Reconnect
271
+ await node.reconnect();
272
+
273
+ // Should emit connect event for the new connection
274
+ expect(connectCount).toBe(1);
275
+ expect(node.isConnected()).toBe(true);
276
+ });
277
+
278
+ it("should reject connection to invalid host", async () => {
279
+ const badNode = new MemcacheNode("0.0.0.0", 99999, { timeout: 1000 });
280
+ await expect(badNode.connect()).rejects.toThrow();
281
+ });
282
+
283
+ it("should handle connection timeout", async () => {
284
+ // Use a valid IP that won't respond (TEST-NET-1)
285
+ const timeoutNode = new MemcacheNode("192.0.2.0", 11211, {
286
+ timeout: 1000,
287
+ });
288
+ await expect(timeoutNode.connect()).rejects.toThrow("Connection timeout");
289
+ });
290
+ });
291
+
292
+ describe("Generic Command Execution", () => {
293
+ beforeEach(async () => {
294
+ await node.connect();
295
+ });
296
+
297
+ it("should execute version command", async () => {
298
+ const result = await node.command("version");
299
+ expect(result).toBeDefined();
300
+ expect(typeof result).toBe("string");
301
+ expect(result).toContain("VERSION");
302
+ });
303
+
304
+ it("should execute set command", async () => {
305
+ const key = "node-test-set";
306
+ const value = "test-value";
307
+ const bytes = Buffer.byteLength(value);
308
+ const cmd = `set ${key} 0 0 ${bytes}\r\n${value}`;
309
+ const result = await node.command(cmd);
310
+ expect(result).toBe("STORED");
311
+ });
312
+
313
+ it("should execute get command with multiline option", async () => {
314
+ const key = "node-test-get";
315
+ const value = "test-value";
316
+
317
+ // First set the value
318
+ const bytes = Buffer.byteLength(value);
319
+ await node.command(`set ${key} 0 0 ${bytes}\r\n${value}`);
320
+
321
+ // Then get it
322
+ const result = await node.command(`get ${key}`, { isMultiline: true });
323
+ expect(result).toBeInstanceOf(Array);
324
+ expect(result[0]).toBe(value);
325
+ });
326
+
327
+ it("should execute get command for non-existent key", async () => {
328
+ const key = "node-test-nonexistent";
329
+ const result = await node.command(`get ${key}`, { isMultiline: true });
330
+ expect(result).toBeUndefined();
331
+ });
332
+
333
+ it("should execute delete command", async () => {
334
+ const key = "node-test-delete";
335
+ const value = "test-value";
336
+ const bytes = Buffer.byteLength(value);
337
+
338
+ // Set first
339
+ await node.command(`set ${key} 0 0 ${bytes}\r\n${value}`);
340
+
341
+ // Delete
342
+ const result = await node.command(`delete ${key}`);
343
+ expect(result).toBe("DELETED");
344
+ });
345
+
346
+ it("should execute incr command", async () => {
347
+ const key = "node-test-incr";
348
+
349
+ // Set initial value
350
+ await node.command(`set ${key} 0 0 1\r\n0`);
351
+
352
+ // Increment
353
+ const result = await node.command(`incr ${key} 1`);
354
+ expect(result).toBe(1);
355
+ });
356
+
357
+ it("should execute decr command", async () => {
358
+ const key = "node-test-decr";
359
+
360
+ // Set initial value
361
+ await node.command(`set ${key} 0 0 2\r\n10`);
362
+
363
+ // Decrement
364
+ const result = await node.command(`decr ${key} 1`);
365
+ expect(result).toBe(9);
366
+ });
367
+
368
+ it("should execute stats command", async () => {
369
+ const result = await node.command("stats", { isStats: true });
370
+ expect(result).toBeDefined();
371
+ expect(typeof result).toBe("object");
372
+ expect(result.version).toBeDefined();
373
+ });
374
+
375
+ it("should execute touch command", async () => {
376
+ const key = "node-test-touch";
377
+ const value = "test-value";
378
+ const bytes = Buffer.byteLength(value);
379
+
380
+ // Set first
381
+ await node.command(`set ${key} 0 0 ${bytes}\r\n${value}`);
382
+
383
+ // Touch
384
+ const result = await node.command(`touch ${key} 100`);
385
+ expect(result).toBe("TOUCHED");
386
+ });
387
+
388
+ it("should execute flush_all command", async () => {
389
+ const result = await node.command("flush_all");
390
+ expect(result).toBe("OK");
391
+ });
392
+
393
+ it("should handle NOT_STORED response for add command", async () => {
394
+ const key = "node-test-add-duplicate";
395
+ const value = "test-value";
396
+ const bytes = Buffer.byteLength(value);
397
+
398
+ // First set the key
399
+ await node.command(`set ${key} 0 0 ${bytes}\r\n${value}`);
400
+
401
+ // Try to add the same key (should fail since it exists)
402
+ const result = await node.command(`add ${key} 0 0 ${bytes}\r\n${value}`);
403
+ expect(result).toBe(false);
404
+ });
405
+
406
+ it("should handle EXISTS response for cas command", async () => {
407
+ const key = "node-test-cas-exists";
408
+ const value = "test-value";
409
+ const bytes = Buffer.byteLength(value);
410
+
411
+ // Set initial value
412
+ await node.command(`set ${key} 0 0 ${bytes}\r\n${value}`);
413
+
414
+ // Get with cas to get the cas token
415
+ const getResult = await node.command(`gets ${key}`, {
416
+ isMultiline: true,
417
+ });
418
+ expect(getResult).toBeDefined();
419
+
420
+ // Modify the value to change cas
421
+ await node.command(`set ${key} 0 0 ${bytes}\r\n${value}`);
422
+
423
+ // Try cas with old token (should get EXISTS)
424
+ const mockSocket = (node as any)._socket;
425
+ const commandPromise = node.command(
426
+ `cas ${key} 0 0 ${bytes} 12345\r\n${value}`,
427
+ );
428
+
429
+ // Simulate server EXISTS response
430
+ mockSocket.emit("data", "EXISTS\r\n");
431
+
432
+ const result = await commandPromise;
433
+ expect(result).toBe("EXISTS");
434
+ });
435
+
436
+ it("should handle NOT_FOUND response for delete command", async () => {
437
+ const key = "node-test-delete-nonexistent";
438
+
439
+ // Try to delete a key that doesn't exist
440
+ const mockSocket = (node as any)._socket;
441
+ const commandPromise = node.command(`delete ${key}`);
442
+
443
+ // Simulate server NOT_FOUND response
444
+ mockSocket.emit("data", "NOT_FOUND\r\n");
445
+
446
+ const result = await commandPromise;
447
+ expect(result).toBe("NOT_FOUND");
448
+ });
449
+
450
+ it("should handle multiple sequential commands", async () => {
451
+ const key1 = "node-test-seq1";
452
+ const key2 = "node-test-seq2";
453
+ const value = "test";
454
+ const bytes = Buffer.byteLength(value);
455
+
456
+ const result1 = await node.command(
457
+ `set ${key1} 0 0 ${bytes}\r\n${value}`,
458
+ );
459
+ expect(result1).toBe("STORED");
460
+
461
+ const result2 = await node.command(
462
+ `set ${key2} 0 0 ${bytes}\r\n${value}`,
463
+ );
464
+ expect(result2).toBe("STORED");
465
+
466
+ const result3 = await node.command(`get ${key1}`, { isMultiline: true });
467
+ expect(result3[0]).toBe(value);
468
+ });
469
+
470
+ it("should emit hit event for successful get", async () => {
471
+ const key = "node-test-hit";
472
+ const value = "test-value";
473
+ const bytes = Buffer.byteLength(value);
474
+
475
+ // Set first
476
+ await node.command(`set ${key} 0 0 ${bytes}\r\n${value}`);
477
+
478
+ let hitEmitted = false;
479
+ let hitKey = "";
480
+ let hitValue = "";
481
+
482
+ node.on("hit", (k: string, v: string) => {
483
+ hitEmitted = true;
484
+ hitKey = k;
485
+ hitValue = v;
486
+ });
487
+
488
+ // Get with requestedKeys to trigger event
489
+ await node.command(`get ${key}`, {
490
+ isMultiline: true,
491
+ requestedKeys: [key],
492
+ });
493
+
494
+ expect(hitEmitted).toBe(true);
495
+ expect(hitKey).toBe(key);
496
+ expect(hitValue).toBe(value);
497
+ });
498
+
499
+ it("should emit miss event for non-existent key", async () => {
500
+ const key = "node-test-miss";
501
+
502
+ let missEmitted = false;
503
+ let missKey = "";
504
+
505
+ node.on("miss", (k: string) => {
506
+ missEmitted = true;
507
+ missKey = k;
508
+ });
509
+
510
+ // Get non-existent key with requestedKeys to trigger event
511
+ await node.command(`get ${key}`, {
512
+ isMultiline: true,
513
+ requestedKeys: [key],
514
+ });
515
+
516
+ expect(missEmitted).toBe(true);
517
+ expect(missKey).toBe(key);
518
+ });
519
+ });
520
+
521
+ describe("Error Handling", () => {
522
+ it("should throw error when not connected", async () => {
523
+ const disconnectedNode = new MemcacheNode("localhost", 11211);
524
+ await expect(disconnectedNode.command("version")).rejects.toThrow(
525
+ "Not connected to memcache server",
526
+ );
527
+ });
528
+
529
+ it("should handle protocol errors", async () => {
530
+ await node.connect();
531
+
532
+ // Try to set with invalid key (contains space)
533
+ await expect(
534
+ node.command("set invalid key 0 0 5\r\nvalue"),
535
+ ).rejects.toThrow();
536
+ });
537
+
538
+ it("should reject pending commands on disconnect", async () => {
539
+ await node.connect();
540
+
541
+ // Queue a command
542
+ const promise = node.command("get slow-key", { isMultiline: true });
543
+
544
+ // Immediately disconnect
545
+ setImmediate(() => {
546
+ node.disconnect();
547
+ });
548
+
549
+ await expect(promise).rejects.toThrow("Connection closed");
550
+ });
551
+
552
+ it("should emit error event", async () => {
553
+ await node.connect();
554
+
555
+ let errorEmitted = false;
556
+ node.on("error", () => {
557
+ errorEmitted = true;
558
+ });
559
+
560
+ // Force an error by destroying the socket
561
+ node.socket?.emit("error", new Error("Test error"));
562
+
563
+ expect(errorEmitted).toBe(true);
564
+ });
565
+ });
566
+
567
+ describe("Command Queue", () => {
568
+ beforeEach(async () => {
569
+ await node.connect();
570
+ });
571
+
572
+ it("should maintain FIFO order for commands", async () => {
573
+ const key1 = "node-fifo-1";
574
+ const key2 = "node-fifo-2";
575
+ const key3 = "node-fifo-3";
576
+ const value = "test";
577
+ const bytes = Buffer.byteLength(value);
578
+
579
+ // Queue multiple commands
580
+ const promises = [
581
+ node.command(`set ${key1} 0 0 ${bytes}\r\n${value}`),
582
+ node.command(`set ${key2} 0 0 ${bytes}\r\n${value}`),
583
+ node.command(`set ${key3} 0 0 ${bytes}\r\n${value}`),
584
+ ];
585
+
586
+ const results = await Promise.all(promises);
587
+
588
+ expect(results[0]).toBe("STORED");
589
+ expect(results[1]).toBe("STORED");
590
+ expect(results[2]).toBe("STORED");
591
+ });
592
+
593
+ it("should expose command queue", () => {
594
+ expect(node.commandQueue).toBeDefined();
595
+ expect(Array.isArray(node.commandQueue)).toBe(true);
596
+ });
597
+ });
598
+
599
+ describe("Multiline Response Handling", () => {
600
+ beforeEach(async () => {
601
+ await node.connect();
602
+ });
603
+
604
+ it("should handle multiple keys in get command", async () => {
605
+ const key1 = "node-multi-1";
606
+ const key2 = "node-multi-2";
607
+ const value1 = "value1";
608
+ const value2 = "value2";
609
+
610
+ // Set both keys
611
+ await node.command(
612
+ `set ${key1} 0 0 ${Buffer.byteLength(value1)}\r\n${value1}`,
613
+ );
614
+ await node.command(
615
+ `set ${key2} 0 0 ${Buffer.byteLength(value2)}\r\n${value2}`,
616
+ );
617
+
618
+ // Get both
619
+ const result = await node.command(`get ${key1} ${key2}`, {
620
+ isMultiline: true,
621
+ });
622
+
623
+ expect(result).toBeInstanceOf(Array);
624
+ expect(result.length).toBe(2);
625
+ expect(result[0]).toBe(value1);
626
+ expect(result[1]).toBe(value2);
627
+ });
628
+
629
+ it("should handle large values", async () => {
630
+ const key = "node-large";
631
+ const value = "x".repeat(10000); // 10KB value
632
+ const bytes = Buffer.byteLength(value);
633
+
634
+ const setResult = await node.command(
635
+ `set ${key} 0 0 ${bytes}\r\n${value}`,
636
+ );
637
+ expect(setResult).toBe("STORED");
638
+
639
+ const result = await node.command(`get ${key}`, { isMultiline: true });
640
+ expect(result).toBeDefined();
641
+ expect(result[0]).toBe(value);
642
+ expect(result[0].length).toBe(10000);
643
+ });
644
+
645
+ it("should handle partial data delivery for value bytes", async () => {
646
+ const key = "node-partial";
647
+ const value = "test-value-12345";
648
+ const bytes = Buffer.byteLength(value);
649
+
650
+ // Set the value first
651
+ await node.command(`set ${key} 0 0 ${bytes}\r\n${value}`);
652
+
653
+ const mockSocket = (node as any)._socket;
654
+ const commandPromise = node.command(`get ${key}`, {
655
+ isMultiline: true,
656
+ });
657
+
658
+ // Simulate partial data delivery - send VALUE line first
659
+ mockSocket.emit("data", `VALUE ${key} 0 ${bytes}\r\n`);
660
+
661
+ // Send only part of the value bytes (not enough)
662
+ mockSocket.emit("data", value.substring(0, 5));
663
+
664
+ // Send rest of value and END
665
+ mockSocket.emit("data", `${value.substring(5)}\r\nEND\r\n`);
666
+
667
+ const result = await commandPromise;
668
+ expect(result).toBeDefined();
669
+ expect(result[0]).toBe(value);
670
+ });
671
+ });
672
+
673
+ describe("Error Handling", () => {
674
+ it("should handle ERROR response for stats command", async () => {
675
+ await node.connect();
676
+
677
+ const mockSocket = (node as any)._socket;
678
+ const commandPromise = node.command("stats invalid_type", {
679
+ isStats: true,
680
+ });
681
+
682
+ // Simulate server ERROR response
683
+ mockSocket.emit("data", "ERROR\r\n");
684
+
685
+ await expect(commandPromise).rejects.toThrow("ERROR");
686
+ });
687
+
688
+ it("should handle CLIENT_ERROR response for stats command", async () => {
689
+ await node.connect();
690
+
691
+ const mockSocket = (node as any)._socket;
692
+ const commandPromise = node.command("stats", { isStats: true });
693
+
694
+ // Simulate server CLIENT_ERROR response
695
+ mockSocket.emit("data", "CLIENT_ERROR bad command\r\n");
696
+
697
+ await expect(commandPromise).rejects.toThrow("CLIENT_ERROR bad command");
698
+ });
699
+
700
+ it("should handle SERVER_ERROR response for stats command", async () => {
701
+ await node.connect();
702
+
703
+ const mockSocket = (node as any)._socket;
704
+ const commandPromise = node.command("stats", { isStats: true });
705
+
706
+ // Simulate server SERVER_ERROR response
707
+ mockSocket.emit("data", "SERVER_ERROR out of memory\r\n");
708
+
709
+ await expect(commandPromise).rejects.toThrow(
710
+ "SERVER_ERROR out of memory",
711
+ );
712
+ });
713
+
714
+ it("should handle unexpected line in stats command response", async () => {
715
+ await node.connect();
716
+
717
+ const mockSocket = (node as any)._socket;
718
+ const commandPromise = node.command("stats", { isStats: true });
719
+
720
+ // Simulate unexpected response line (not STAT, not END, not ERROR)
721
+ mockSocket.emit("data", "UNEXPECTED_LINE\r\n");
722
+ // Then send END to complete the command
723
+ mockSocket.emit("data", "END\r\n");
724
+
725
+ // Should still resolve successfully, ignoring the unexpected line
726
+ const result = await commandPromise;
727
+ expect(result).toBeDefined();
728
+ });
729
+
730
+ it("should handle ERROR response for multiline get command", async () => {
731
+ await node.connect();
732
+
733
+ const mockSocket = (node as any)._socket;
734
+ const commandPromise = node.command("get test_key", {
735
+ isMultiline: true,
736
+ requestedKeys: ["test_key"],
737
+ });
738
+
739
+ // Simulate server ERROR response
740
+ mockSocket.emit("data", "ERROR\r\n");
741
+
742
+ await expect(commandPromise).rejects.toThrow("ERROR");
743
+ });
744
+
745
+ it("should handle CLIENT_ERROR response for multiline get command", async () => {
746
+ await node.connect();
747
+
748
+ const mockSocket = (node as any)._socket;
749
+ const commandPromise = node.command("get test_key", {
750
+ isMultiline: true,
751
+ requestedKeys: ["test_key"],
752
+ });
753
+
754
+ // Simulate server CLIENT_ERROR response
755
+ mockSocket.emit("data", "CLIENT_ERROR invalid key\r\n");
756
+
757
+ await expect(commandPromise).rejects.toThrow("CLIENT_ERROR invalid key");
758
+ });
759
+
760
+ it("should handle SERVER_ERROR response for multiline get command", async () => {
761
+ await node.connect();
762
+
763
+ const mockSocket = (node as any)._socket;
764
+ const commandPromise = node.command("get test_key", {
765
+ isMultiline: true,
766
+ requestedKeys: ["test_key"],
767
+ });
768
+
769
+ // Simulate server SERVER_ERROR response
770
+ mockSocket.emit("data", "SERVER_ERROR temporary failure\r\n");
771
+
772
+ await expect(commandPromise).rejects.toThrow(
773
+ "SERVER_ERROR temporary failure",
774
+ );
775
+ });
776
+
777
+ it("should reject current command on disconnect", async () => {
778
+ await node.connect();
779
+
780
+ // Start a command but don't let it complete
781
+ const commandPromise = node.command("get pending_key", {
782
+ isMultiline: true,
783
+ });
784
+
785
+ // Disconnect immediately
786
+ await node.disconnect();
787
+
788
+ // The command should be rejected
789
+ await expect(commandPromise).rejects.toThrow();
790
+ });
791
+
792
+ it("should reject queued commands on disconnect", async () => {
793
+ await node.connect();
794
+
795
+ // Queue multiple commands without responses
796
+ const promise1 = node.command("get key1", { isMultiline: true });
797
+ const promise2 = node.command("get key2", { isMultiline: true });
798
+ const promise3 = node.command("get key3", { isMultiline: true });
799
+
800
+ // Disconnect immediately
801
+ await node.disconnect();
802
+
803
+ // All commands should be rejected
804
+ await expect(promise1).rejects.toThrow();
805
+ await expect(promise2).rejects.toThrow();
806
+ await expect(promise3).rejects.toThrow();
807
+ });
808
+ });
809
+ });