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,526 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { HashRing, KetamaHash } from "../src/ketama.js";
3
+ import { MemcacheNode } from "../src/node.js";
4
+
5
+ describe("HashRing", () => {
6
+ describe("constructor", () => {
7
+ it("should create empty ring with no initial nodes", () => {
8
+ const ring = new HashRing<string>();
9
+ expect(ring.nodes.size).toBe(0);
10
+ expect(ring.clock.length).toBe(0);
11
+ });
12
+
13
+ it("should create ring with simple string nodes", () => {
14
+ const ring = new HashRing<string>(["node1", "node2", "node3"]);
15
+ expect(ring.nodes.size).toBe(3);
16
+ expect(ring.clock.length).toBeGreaterThan(0);
17
+ });
18
+
19
+ it("should create ring with weighted nodes", () => {
20
+ const ring = new HashRing<string>([
21
+ { node: "heavy", weight: 3 },
22
+ { node: "light", weight: 1 },
23
+ ]);
24
+ expect(ring.nodes.size).toBe(2);
25
+ // Heavy node should have more virtual nodes
26
+ const heavyVirtualNodes = ring.clock.filter(([, n]) => n === "heavy");
27
+ const lightVirtualNodes = ring.clock.filter(([, n]) => n === "light");
28
+ expect(heavyVirtualNodes.length).toBeGreaterThan(
29
+ lightVirtualNodes.length,
30
+ );
31
+ });
32
+
33
+ it("should create ring with mixed weighted and unweighted nodes", () => {
34
+ const ring = new HashRing<string>([
35
+ "simple",
36
+ { node: "weighted", weight: 2 },
37
+ ]);
38
+ expect(ring.nodes.size).toBe(2);
39
+ });
40
+
41
+ it("should create ring with custom hash function (md5)", () => {
42
+ const ring = new HashRing<string>(["node1"], "md5");
43
+ expect(ring.clock.length).toBeGreaterThan(0);
44
+ });
45
+
46
+ it("should create ring with custom hash function", () => {
47
+ const customHash = (buf: Buffer) => {
48
+ let hash = 0;
49
+ for (let i = 0; i < buf.length; i++) {
50
+ hash = (hash << 5) - hash + buf[i];
51
+ hash |= 0; // Convert to 32bit integer
52
+ }
53
+ return hash;
54
+ };
55
+ const ring = new HashRing<string>(["node1"], customHash);
56
+ expect(ring.clock.length).toBeGreaterThan(0);
57
+ });
58
+
59
+ it("should handle object nodes with key property", () => {
60
+ const ring = new HashRing<{ key: string; port: number }>([
61
+ { key: "server1", port: 11211 },
62
+ { key: "server2", port: 11212 },
63
+ ]);
64
+ expect(ring.nodes.size).toBe(2);
65
+ });
66
+ });
67
+
68
+ describe("getters", () => {
69
+ it("should return clock via getter", () => {
70
+ const ring = new HashRing<string>(["node1"]);
71
+ const clock = ring.clock;
72
+ expect(Array.isArray(clock)).toBe(true);
73
+ expect(clock.length).toBeGreaterThan(0);
74
+ expect(clock[0]).toHaveLength(2); // [hash, node]
75
+ });
76
+
77
+ it("should return nodes via getter", () => {
78
+ const ring = new HashRing<string>(["node1", "node2"]);
79
+ const nodes = ring.nodes;
80
+ expect(nodes instanceof Map).toBe(true);
81
+ expect(nodes.size).toBe(2);
82
+ expect(nodes.get("node1")).toBe("node1");
83
+ });
84
+
85
+ it("should return readonly nodes map", () => {
86
+ const ring = new HashRing<string>(["node1"]);
87
+ const nodes = ring.nodes;
88
+ expect(nodes).toBeInstanceOf(Map);
89
+ });
90
+ });
91
+
92
+ describe("addNode", () => {
93
+ it("should add a node with default weight", () => {
94
+ const ring = new HashRing<string>();
95
+ ring.addNode("node1");
96
+ expect(ring.nodes.size).toBe(1);
97
+ expect(ring.clock.length).toBe(HashRing.baseWeight);
98
+ });
99
+
100
+ it("should add a node with custom weight", () => {
101
+ const ring = new HashRing<string>();
102
+ ring.addNode("node1", 2);
103
+ expect(ring.nodes.size).toBe(1);
104
+ expect(ring.clock.length).toBe(HashRing.baseWeight * 2);
105
+ });
106
+
107
+ it("should update existing node weight", () => {
108
+ const ring = new HashRing<string>();
109
+ ring.addNode("node1", 1);
110
+ const initialClockLength = ring.clock.length;
111
+ ring.addNode("node1", 2);
112
+ expect(ring.nodes.size).toBe(1);
113
+ expect(ring.clock.length).toBe(initialClockLength * 2);
114
+ });
115
+
116
+ it("should remove node when weight is 0", () => {
117
+ const ring = new HashRing<string>(["node1"]);
118
+ expect(ring.nodes.size).toBe(1);
119
+ ring.addNode("node1", 0);
120
+ expect(ring.nodes.size).toBe(0);
121
+ expect(ring.clock.length).toBe(0);
122
+ });
123
+
124
+ it("should throw error for negative weight", () => {
125
+ const ring = new HashRing<string>();
126
+ expect(() => ring.addNode("node1", -1)).toThrow(RangeError);
127
+ expect(() => ring.addNode("node1", -1)).toThrow(
128
+ "Cannot add a node to the hashring with weight < 0",
129
+ );
130
+ });
131
+ });
132
+
133
+ describe("removeNode", () => {
134
+ it("should remove existing node", () => {
135
+ const ring = new HashRing<string>(["node1", "node2"]);
136
+ expect(ring.nodes.size).toBe(2);
137
+ ring.removeNode("node1");
138
+ expect(ring.nodes.size).toBe(1);
139
+ expect(ring.nodes.has("node1")).toBe(false);
140
+ });
141
+
142
+ it("should be no-op when removing non-existent node", () => {
143
+ const ring = new HashRing<string>(["node1"]);
144
+ ring.removeNode("nonexistent");
145
+ expect(ring.nodes.size).toBe(1);
146
+ });
147
+
148
+ it("should remove all virtual nodes from clock", () => {
149
+ const ring = new HashRing<string>(["node1"]);
150
+ const initialClockLength = ring.clock.length;
151
+ expect(initialClockLength).toBeGreaterThan(0);
152
+ ring.removeNode("node1");
153
+ expect(ring.clock.length).toBe(0);
154
+ });
155
+ });
156
+
157
+ describe("getNode", () => {
158
+ it("should return node for a given key", () => {
159
+ const ring = new HashRing<string>(["node1", "node2", "node3"]);
160
+ const node = ring.getNode("test-key");
161
+ expect(node).toBeDefined();
162
+ expect(["node1", "node2", "node3"]).toContain(node);
163
+ });
164
+
165
+ it("should return consistent node for same key", () => {
166
+ const ring = new HashRing<string>(["node1", "node2", "node3"]);
167
+ const node1 = ring.getNode("test-key");
168
+ const node2 = ring.getNode("test-key");
169
+ expect(node1).toBe(node2);
170
+ });
171
+
172
+ it("should return undefined for empty ring", () => {
173
+ const ring = new HashRing<string>();
174
+ const node = ring.getNode("test-key");
175
+ expect(node).toBeUndefined();
176
+ });
177
+
178
+ it("should accept Buffer input", () => {
179
+ const ring = new HashRing<string>(["node1", "node2"]);
180
+ const node = ring.getNode(Buffer.from("test-key"));
181
+ expect(node).toBeDefined();
182
+ });
183
+
184
+ it("should return same node for string and Buffer with same content", () => {
185
+ const ring = new HashRing<string>(["node1", "node2"]);
186
+ const nodeFromString = ring.getNode("test-key");
187
+ const nodeFromBuffer = ring.getNode(Buffer.from("test-key"));
188
+ expect(nodeFromString).toBe(nodeFromBuffer);
189
+ });
190
+
191
+ it("should distribute keys across nodes", () => {
192
+ const ring = new HashRing<string>(["node1", "node2", "node3"]);
193
+ const distribution = new Map<string, number>();
194
+
195
+ // Test with many keys to ensure distribution
196
+ for (let i = 0; i < 300; i++) {
197
+ const node = ring.getNode(`key-${i}`);
198
+ if (node) {
199
+ distribution.set(node, (distribution.get(node) || 0) + 1);
200
+ }
201
+ }
202
+
203
+ // All nodes should get some keys
204
+ expect(distribution.size).toBe(3);
205
+ // Each node should get roughly 1/3 of keys (with some variance)
206
+ for (const count of distribution.values()) {
207
+ expect(count).toBeGreaterThan(50); // At least some keys
208
+ expect(count).toBeLessThan(200); // Not too many keys
209
+ }
210
+ });
211
+ });
212
+
213
+ describe("getNodes (replicas)", () => {
214
+ it("should return empty array for empty ring", () => {
215
+ const ring = new HashRing<string>();
216
+ const nodes = ring.getNodes("test-key", 3);
217
+ expect(nodes).toEqual([]);
218
+ });
219
+
220
+ it("should return all nodes when replicas >= node count", () => {
221
+ const ring = new HashRing<string>(["node1", "node2", "node3"]);
222
+ const nodes = ring.getNodes("test-key", 5);
223
+ expect(nodes.length).toBe(3);
224
+ expect(nodes).toContain("node1");
225
+ expect(nodes).toContain("node2");
226
+ expect(nodes).toContain("node3");
227
+ });
228
+
229
+ it("should return all nodes when replicas equals node count", () => {
230
+ const ring = new HashRing<string>(["node1", "node2", "node3"]);
231
+ const nodes = ring.getNodes("test-key", 3);
232
+ expect(nodes.length).toBe(3);
233
+ });
234
+
235
+ it("should return requested number of unique nodes", () => {
236
+ const ring = new HashRing<string>(["node1", "node2", "node3", "node4"]);
237
+ const nodes = ring.getNodes("test-key", 2);
238
+ expect(nodes.length).toBe(2);
239
+ expect(new Set(nodes).size).toBe(2); // All unique
240
+ });
241
+
242
+ it("should return consistent replicas for same key", () => {
243
+ const ring = new HashRing<string>(["node1", "node2", "node3", "node4"]);
244
+ const nodes1 = ring.getNodes("test-key", 3);
245
+ const nodes2 = ring.getNodes("test-key", 3);
246
+ expect(nodes1).toEqual(nodes2);
247
+ });
248
+
249
+ it("should return nodes in ring order", () => {
250
+ const ring = new HashRing<string>(["node1", "node2", "node3", "node4"]);
251
+ const nodes = ring.getNodes("test-key", 3);
252
+ expect(nodes.length).toBe(3);
253
+ // All nodes should be unique
254
+ const uniqueNodes = new Set(nodes);
255
+ expect(uniqueNodes.size).toBe(3);
256
+ });
257
+
258
+ it("should handle single node ring", () => {
259
+ const ring = new HashRing<string>(["node1"]);
260
+ const nodes = ring.getNodes("test-key", 3);
261
+ expect(nodes).toEqual(["node1"]);
262
+ });
263
+ });
264
+ });
265
+
266
+ describe("KetamaHash", () => {
267
+ describe("constructor", () => {
268
+ it("should create instance with default hash function", () => {
269
+ const distribution = new KetamaHash();
270
+ expect(distribution.name).toBe("ketama");
271
+ expect(distribution.nodes).toEqual([]);
272
+ });
273
+
274
+ it("should create instance with custom hash algorithm", () => {
275
+ const distribution = new KetamaHash("md5");
276
+ expect(distribution.name).toBe("ketama");
277
+ });
278
+
279
+ it("should create instance with custom hash function", () => {
280
+ const customHash = (buf: Buffer) => buf.readInt32BE();
281
+ const distribution = new KetamaHash(customHash);
282
+ expect(distribution.name).toBe("ketama");
283
+ });
284
+ });
285
+
286
+ describe("nodes getter", () => {
287
+ it("should return empty array when no nodes added", () => {
288
+ const distribution = new KetamaHash();
289
+ expect(distribution.nodes).toEqual([]);
290
+ });
291
+
292
+ it("should return all added nodes", () => {
293
+ const distribution = new KetamaHash();
294
+ const node1 = new MemcacheNode("localhost", 11211);
295
+ const node2 = new MemcacheNode("localhost", 11212);
296
+
297
+ distribution.addNode(node1);
298
+ distribution.addNode(node2);
299
+
300
+ const nodes = distribution.nodes;
301
+ expect(nodes.length).toBe(2);
302
+ expect(nodes).toContain(node1);
303
+ expect(nodes).toContain(node2);
304
+ });
305
+ });
306
+
307
+ describe("addNode", () => {
308
+ it("should add node to distribution", () => {
309
+ const distribution = new KetamaHash();
310
+ const node = new MemcacheNode("localhost", 11211);
311
+
312
+ distribution.addNode(node);
313
+ expect(distribution.nodes.length).toBe(1);
314
+ expect(distribution.nodes[0]).toBe(node);
315
+ });
316
+
317
+ it("should add node with custom weight", () => {
318
+ const distribution = new KetamaHash();
319
+ const node = new MemcacheNode("localhost", 11211, { weight: 3 });
320
+
321
+ distribution.addNode(node);
322
+ expect(distribution.nodes.length).toBe(1);
323
+ });
324
+
325
+ it("should handle multiple nodes", () => {
326
+ const distribution = new KetamaHash();
327
+ const node1 = new MemcacheNode("server1", 11211);
328
+ const node2 = new MemcacheNode("server2", 11211);
329
+
330
+ distribution.addNode(node1);
331
+ distribution.addNode(node2);
332
+
333
+ expect(distribution.nodes.length).toBe(2);
334
+ });
335
+ });
336
+
337
+ describe("removeNode", () => {
338
+ it("should remove node by ID", () => {
339
+ const distribution = new KetamaHash();
340
+ const node = new MemcacheNode("localhost", 11211);
341
+
342
+ distribution.addNode(node);
343
+ expect(distribution.nodes.length).toBe(1);
344
+
345
+ distribution.removeNode(node.id);
346
+ expect(distribution.nodes.length).toBe(0);
347
+ });
348
+
349
+ it("should be no-op when removing non-existent node", () => {
350
+ const distribution = new KetamaHash();
351
+ const node = new MemcacheNode("localhost", 11211);
352
+
353
+ distribution.addNode(node);
354
+ distribution.removeNode("nonexistent:11211");
355
+ expect(distribution.nodes.length).toBe(1);
356
+ });
357
+ });
358
+
359
+ describe("getNode", () => {
360
+ it("should get node by ID", () => {
361
+ const distribution = new KetamaHash();
362
+ const node = new MemcacheNode("localhost", 11211);
363
+
364
+ distribution.addNode(node);
365
+ const retrieved = distribution.getNode("localhost:11211");
366
+
367
+ expect(retrieved).toBe(node);
368
+ });
369
+
370
+ it("should return undefined for non-existent node", () => {
371
+ const distribution = new KetamaHash();
372
+ const retrieved = distribution.getNode("nonexistent:11211");
373
+
374
+ expect(retrieved).toBeUndefined();
375
+ });
376
+
377
+ it("should distinguish between different node IDs", () => {
378
+ const distribution = new KetamaHash();
379
+ const node1 = new MemcacheNode("server1", 11211);
380
+ const node2 = new MemcacheNode("server2", 11211);
381
+
382
+ distribution.addNode(node1);
383
+ distribution.addNode(node2);
384
+
385
+ expect(distribution.getNode("server1:11211")).toBe(node1);
386
+ expect(distribution.getNode("server2:11211")).toBe(node2);
387
+ });
388
+ });
389
+
390
+ describe("getNodesByKey", () => {
391
+ it("should return node for given key", () => {
392
+ const distribution = new KetamaHash();
393
+ const node1 = new MemcacheNode("localhost", 11211);
394
+ const node2 = new MemcacheNode("localhost", 11212);
395
+
396
+ distribution.addNode(node1);
397
+ distribution.addNode(node2);
398
+
399
+ const nodes = distribution.getNodesByKey("test-key");
400
+ expect(nodes.length).toBe(1);
401
+ expect([node1, node2]).toContain(nodes[0]);
402
+ });
403
+
404
+ it("should return consistent node for same key", () => {
405
+ const distribution = new KetamaHash();
406
+ const node1 = new MemcacheNode("localhost", 11211);
407
+ const node2 = new MemcacheNode("localhost", 11212);
408
+
409
+ distribution.addNode(node1);
410
+ distribution.addNode(node2);
411
+
412
+ const nodes1 = distribution.getNodesByKey("test-key");
413
+ const nodes2 = distribution.getNodesByKey("test-key");
414
+ expect(nodes1[0]).toBe(nodes2[0]);
415
+ });
416
+
417
+ it("should return empty array when no nodes available", () => {
418
+ const distribution = new KetamaHash();
419
+ const nodes = distribution.getNodesByKey("test-key");
420
+ expect(nodes).toEqual([]);
421
+ });
422
+
423
+ it("should distribute keys across nodes", () => {
424
+ const distribution = new KetamaHash();
425
+ const node1 = new MemcacheNode("server1", 11211);
426
+ const node2 = new MemcacheNode("server2", 11211);
427
+ const node3 = new MemcacheNode("server3", 11211);
428
+
429
+ distribution.addNode(node1);
430
+ distribution.addNode(node2);
431
+ distribution.addNode(node3);
432
+
433
+ const distributionMap = new Map<string, number>();
434
+
435
+ // Test with many keys
436
+ for (let i = 0; i < 300; i++) {
437
+ const nodes = distribution.getNodesByKey(`key-${i}`);
438
+ if (nodes.length > 0) {
439
+ const nodeId = nodes[0].id;
440
+ distributionMap.set(nodeId, (distributionMap.get(nodeId) || 0) + 1);
441
+ }
442
+ }
443
+
444
+ // All nodes should receive some keys
445
+ expect(distributionMap.size).toBe(3);
446
+ });
447
+
448
+ it("should handle weighted nodes", () => {
449
+ const distribution = new KetamaHash();
450
+ const heavyNode = new MemcacheNode("heavy", 11211, { weight: 3 });
451
+ const lightNode = new MemcacheNode("light", 11211, { weight: 1 });
452
+
453
+ distribution.addNode(heavyNode);
454
+ distribution.addNode(lightNode);
455
+
456
+ const distributionMap = new Map<string, number>();
457
+
458
+ // Test with many keys
459
+ for (let i = 0; i < 400; i++) {
460
+ const nodes = distribution.getNodesByKey(`key-${i}`);
461
+ if (nodes.length > 0) {
462
+ const nodeId = nodes[0].id;
463
+ distributionMap.set(nodeId, (distributionMap.get(nodeId) || 0) + 1);
464
+ }
465
+ }
466
+
467
+ const heavyCount = distributionMap.get("heavy:11211") || 0;
468
+ const lightCount = distributionMap.get("light:11211") || 0;
469
+
470
+ // Heavy node should handle more keys than light node
471
+ expect(heavyCount).toBeGreaterThan(lightCount);
472
+ });
473
+ });
474
+
475
+ describe("integration", () => {
476
+ it("should handle add, get, and remove operations", () => {
477
+ const distribution = new KetamaHash();
478
+ const node1 = new MemcacheNode("server1", 11211);
479
+ const node2 = new MemcacheNode("server2", 11211);
480
+
481
+ // Add nodes
482
+ distribution.addNode(node1);
483
+ distribution.addNode(node2);
484
+ expect(distribution.nodes.length).toBe(2);
485
+
486
+ // Get by key
487
+ const nodeForKey = distribution.getNodesByKey("my-key");
488
+ expect(nodeForKey.length).toBe(1);
489
+
490
+ // Get by ID
491
+ const retrievedNode = distribution.getNode("server1:11211");
492
+ expect(retrievedNode).toBe(node1);
493
+
494
+ // Remove node
495
+ distribution.removeNode("server1:11211");
496
+ expect(distribution.nodes.length).toBe(1);
497
+ expect(distribution.getNode("server1:11211")).toBeUndefined();
498
+ });
499
+
500
+ it("should redistribute keys when nodes are removed", () => {
501
+ const distribution = new KetamaHash();
502
+ const node1 = new MemcacheNode("server1", 11211);
503
+ const node2 = new MemcacheNode("server2", 11211);
504
+
505
+ distribution.addNode(node1);
506
+ distribution.addNode(node2);
507
+
508
+ const keysOnNode1BeforeRemoval = [];
509
+ for (let i = 0; i < 100; i++) {
510
+ const nodes = distribution.getNodesByKey(`key-${i}`);
511
+ if (nodes[0]?.id === "server1:11211") {
512
+ keysOnNode1BeforeRemoval.push(`key-${i}`);
513
+ }
514
+ }
515
+
516
+ // Remove node1
517
+ distribution.removeNode("server1:11211");
518
+
519
+ // All keys previously on node1 should now be on node2
520
+ for (const key of keysOnNode1BeforeRemoval) {
521
+ const nodes = distribution.getNodesByKey(key);
522
+ expect(nodes[0]?.id).toBe("server2:11211");
523
+ }
524
+ });
525
+ });
526
+ });
@@ -0,0 +1,102 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import Memcache, { createNode } from "../src/index";
3
+
4
+ describe("MemcacheNode Instances Support", () => {
5
+ describe("addNode with MemcacheNode instances", () => {
6
+ it("should accept MemcacheNode instances via addNode", async () => {
7
+ const client = new Memcache({ timeout: 5000 });
8
+
9
+ // Create a MemcacheNode instance using createNode
10
+ const node1 = createNode("localhost", 11212, { weight: 2 });
11
+ const node2 = createNode("localhost", 11213, { weight: 3 });
12
+
13
+ // Add the node instances directly
14
+ await client.addNode(node1);
15
+ await client.addNode(node2);
16
+
17
+ // Verify nodes were added
18
+ expect(client.nodeIds).toHaveLength(3); // 2 + default
19
+ expect(client.nodeIds).toContain("localhost:11212");
20
+ expect(client.nodeIds).toContain("localhost:11213");
21
+
22
+ // Verify the nodes retain their properties
23
+ const addedNode1 = client.getNode("localhost:11212");
24
+ expect(addedNode1).toBeDefined();
25
+ expect(addedNode1?.weight).toBe(2);
26
+
27
+ const addedNode2 = client.getNode("localhost:11213");
28
+ expect(addedNode2).toBeDefined();
29
+ expect(addedNode2?.weight).toBe(3);
30
+ });
31
+
32
+ it("should throw error when adding duplicate MemcacheNode instance", async () => {
33
+ const client = new Memcache({ timeout: 5000 });
34
+
35
+ // Create a node instance
36
+ const node = createNode("localhost", 11212);
37
+
38
+ // Add it once
39
+ await client.addNode(node);
40
+ expect(client.nodeIds).toContain("localhost:11212");
41
+
42
+ // Try to add it again - should throw error
43
+ await expect(client.addNode(node)).rejects.toThrow(
44
+ "Node localhost:11212 already exists",
45
+ );
46
+ });
47
+ });
48
+
49
+ describe("Constructor with MemcacheNode instances", () => {
50
+ it("should initialize with MemcacheNode instances in options", () => {
51
+ const node1 = createNode("server1", 11211, { weight: 2 });
52
+ const node2 = createNode("server2", 11211, { weight: 3 });
53
+
54
+ const testClient = new Memcache({
55
+ nodes: [node1, node2],
56
+ });
57
+
58
+ expect(testClient.nodeIds).toHaveLength(2);
59
+ expect(testClient.nodeIds).toContain("server1:11211");
60
+ expect(testClient.nodeIds).toContain("server2:11211");
61
+
62
+ // Verify nodes retain their properties
63
+ const addedNode1 = testClient.getNode("server1:11211");
64
+ expect(addedNode1?.weight).toBe(2);
65
+
66
+ const addedNode2 = testClient.getNode("server2:11211");
67
+ expect(addedNode2?.weight).toBe(3);
68
+ });
69
+
70
+ it("should initialize with mixed string URIs and MemcacheNode instances", () => {
71
+ const node1 = createNode("server1", 11211, { weight: 5 });
72
+
73
+ const testClient = new Memcache({
74
+ nodes: ["localhost:11211", node1, "server2:11212"],
75
+ });
76
+
77
+ expect(testClient.nodeIds).toHaveLength(3);
78
+ expect(testClient.nodeIds).toContain("localhost:11211");
79
+ expect(testClient.nodeIds).toContain("server1:11211");
80
+ expect(testClient.nodeIds).toContain("server2:11212");
81
+
82
+ // Verify the MemcacheNode instance retained its weight
83
+ const addedNode = testClient.getNode("server1:11211");
84
+ expect(addedNode?.weight).toBe(5);
85
+ });
86
+
87
+ it("should work with only MemcacheNode instances (no string URIs)", () => {
88
+ const node1 = createNode("host1", 11211);
89
+ const node2 = createNode("host2", 11212);
90
+ const node3 = createNode("host3", 11213);
91
+
92
+ const testClient = new Memcache({
93
+ nodes: [node1, node2, node3],
94
+ });
95
+
96
+ expect(testClient.nodeIds).toHaveLength(3);
97
+ expect(testClient.nodeIds).toContain("host1:11211");
98
+ expect(testClient.nodeIds).toContain("host2:11212");
99
+ expect(testClient.nodeIds).toContain("host3:11213");
100
+ });
101
+ });
102
+ });