1 |
|
2 |
|
3 | const expect = require('expect');
|
4 | const uuid = require('uuid');
|
5 | const { ObservedRemoveMap } = require('../src');
|
6 | const { generateValue } = require('./lib/values');
|
7 |
|
8 | describe('Map', () => {
|
9 | test('Set and delete values', () => {
|
10 | const keyA = uuid.v4();
|
11 | const keyB = uuid.v4();
|
12 | const valueA = generateValue();
|
13 | const valueB = generateValue();
|
14 | const map = new ObservedRemoveMap();
|
15 | expect(map.size).toEqual(0);
|
16 | map.set(keyA, valueA);
|
17 | expect(map.has(keyA)).toEqual(true);
|
18 | expect(map.has(keyB)).toEqual(false);
|
19 | expect(map.size).toEqual(1);
|
20 | map.set(keyB, valueB);
|
21 | expect(map.has(keyA)).toEqual(true);
|
22 | expect(map.has(keyB)).toEqual(true);
|
23 | expect(map.size).toEqual(2);
|
24 | map.delete(keyB);
|
25 | expect(map.has(keyA)).toEqual(true);
|
26 | expect(map.has(keyB)).toEqual(false);
|
27 | expect(map.size).toEqual(1);
|
28 | map.delete(keyA);
|
29 | expect(map.has(keyA)).toEqual(false);
|
30 | expect(map.has(keyB)).toEqual(false);
|
31 | expect(map.size).toEqual(0);
|
32 | map.set(keyA, valueA);
|
33 | expect(map.has(keyA)).toEqual(true);
|
34 | expect(map.has(keyB)).toEqual(false);
|
35 | expect(map.size).toEqual(1);
|
36 | map.set(keyB, valueB);
|
37 | expect(map.has(keyA)).toEqual(true);
|
38 | expect(map.has(keyB)).toEqual(true);
|
39 | expect(map.size).toEqual(2);
|
40 | expect([...map.values()]).toEqual([valueA, valueB]);
|
41 | expect([...map.keys()]).toEqual([keyA, keyB]);
|
42 | expect([...map]).toEqual([[keyA, valueA], [keyB, valueB]]);
|
43 | expect([...map.entries()]).toEqual([[keyA, valueA], [keyB, valueB]]);
|
44 | });
|
45 |
|
46 | test('Emit set and delete events', async () => {
|
47 | const keyA = uuid.v4();
|
48 | const keyB = uuid.v4();
|
49 | const valueA = generateValue();
|
50 | const valueB = generateValue();
|
51 | const map = new ObservedRemoveMap();
|
52 | const setAPromise = new Promise((resolve) => {
|
53 | map.once('set', (k, v) => {
|
54 | expect(k).toEqual(keyA);
|
55 | expect(v).toEqual(valueA);
|
56 | resolve();
|
57 | });
|
58 | map.set(keyA, valueA);
|
59 | });
|
60 | const setBPromise = new Promise((resolve) => {
|
61 | map.once('set', (k, v) => {
|
62 | expect(k).toEqual(keyB);
|
63 | expect(v).toEqual(valueB);
|
64 | resolve();
|
65 | });
|
66 | map.set(keyB, valueB);
|
67 | });
|
68 | await setAPromise;
|
69 | await setBPromise;
|
70 | const deleteAPromise = new Promise((resolve) => {
|
71 | map.once('delete', (k, v) => {
|
72 | expect(k).toEqual(keyA);
|
73 | expect(v).toEqual(valueA);
|
74 | resolve();
|
75 | });
|
76 | map.delete(keyA);
|
77 | });
|
78 | const deleteBPromise = new Promise((resolve) => {
|
79 | map.once('delete', (k, v) => {
|
80 | expect(k).toEqual(keyB);
|
81 | expect(v).toEqual(valueB);
|
82 | resolve();
|
83 | });
|
84 | map.delete(keyB);
|
85 | });
|
86 | await deleteAPromise;
|
87 | await deleteBPromise;
|
88 | });
|
89 |
|
90 |
|
91 | test('Iterate through values', () => {
|
92 | const keyA = uuid.v4();
|
93 | const keyB = uuid.v4();
|
94 | const keyC = uuid.v4();
|
95 | const valueA = generateValue();
|
96 | const valueB = generateValue();
|
97 | const valueC = generateValue();
|
98 | const map = new ObservedRemoveMap([[keyA, valueA], [keyB, valueB], [keyC, valueC]]);
|
99 | let i = 0;
|
100 | for (const [k, v] of map) {
|
101 | if (i === 0) {
|
102 | expect(k).toEqual(keyA);
|
103 | expect(v).toEqual(valueA);
|
104 | } else if (i === 1) {
|
105 | expect(k).toEqual(keyB);
|
106 | expect(v).toEqual(valueB);
|
107 | } else if (i === 2) {
|
108 | expect(k).toEqual(keyC);
|
109 | expect(v).toEqual(valueC);
|
110 | }
|
111 | i += 1;
|
112 | }
|
113 | map.forEach((v, k) => {
|
114 | if (k === keyA) {
|
115 | expect(v).toEqual(valueA);
|
116 | } else if (k === keyB) {
|
117 | expect(v).toEqual(valueB);
|
118 | } else if (k === keyC) {
|
119 | expect(v).toEqual(valueC);
|
120 | }
|
121 | });
|
122 | });
|
123 |
|
124 | test('Clear values', () => {
|
125 | const keyA = uuid.v4();
|
126 | const keyB = uuid.v4();
|
127 | const keyC = uuid.v4();
|
128 | const valueA = generateValue();
|
129 | const valueB = generateValue();
|
130 | const valueC = generateValue();
|
131 | const map = new ObservedRemoveMap([[keyA, valueA], [keyB, valueB], [keyC, valueC]], { maxAge: -1, bufferPublishing: 0 });
|
132 | expect(map.size).toEqual(3);
|
133 | map.clear();
|
134 | expect(map.size).toEqual(0);
|
135 | expect(map.insertQueue.length).toEqual(0);
|
136 | expect(map.deleteQueue.length).toEqual(0);
|
137 | expect(map.deletions.size).toEqual(3);
|
138 | map.flush();
|
139 | expect(map.size).toEqual(0);
|
140 | expect(map.insertQueue.length).toEqual(0);
|
141 | expect(map.deleteQueue.length).toEqual(0);
|
142 | expect(map.deletions.size).toEqual(0);
|
143 | });
|
144 |
|
145 | test('Synchronize maps', async () => {
|
146 | const keyX = uuid.v4();
|
147 | const keyY = uuid.v4();
|
148 | const keyZ = uuid.v4();
|
149 | const valueX = generateValue();
|
150 | const valueY = generateValue();
|
151 | const valueZ = generateValue();
|
152 | const alice = new ObservedRemoveMap();
|
153 | const bob = new ObservedRemoveMap();
|
154 | let aliceAddCount = 0;
|
155 | let bobAddCount = 0;
|
156 | let aliceDeleteCount = 0;
|
157 | let bobDeleteCount = 0;
|
158 | alice.on('set', () => (aliceAddCount += 1));
|
159 | bob.on('set', () => (bobAddCount += 1));
|
160 | alice.on('delete', () => (aliceDeleteCount += 1));
|
161 | bob.on('delete', () => (bobDeleteCount += 1));
|
162 | alice.on('publish', (message) => {
|
163 | bob.process(message);
|
164 | });
|
165 | bob.on('publish', (message) => {
|
166 | alice.process(message);
|
167 | });
|
168 | alice.set(keyX, valueX);
|
169 | alice.set(keyY, valueY);
|
170 | alice.set(keyZ, valueZ);
|
171 | while (aliceAddCount !== 3 || bobAddCount !== 3) {
|
172 | await new Promise((resolve) => setTimeout(resolve, 100));
|
173 | }
|
174 | expect(alice.get(keyX)).toEqual(valueX);
|
175 | expect(alice.get(keyY)).toEqual(valueY);
|
176 | expect(alice.get(keyZ)).toEqual(valueZ);
|
177 | expect(bob.get(keyX)).toEqual(valueX);
|
178 | expect(bob.get(keyY)).toEqual(valueY);
|
179 | expect(bob.get(keyZ)).toEqual(valueZ);
|
180 | expect([...alice]).toEqual([[keyX, valueX], [keyY, valueY], [keyZ, valueZ]]);
|
181 | expect([...bob]).toEqual([[keyX, valueX], [keyY, valueY], [keyZ, valueZ]]);
|
182 | bob.delete(keyX);
|
183 | bob.delete(keyY);
|
184 | bob.delete(keyZ);
|
185 | while (aliceDeleteCount !== 3 || bobDeleteCount !== 3) {
|
186 | await new Promise((resolve) => setTimeout(resolve, 100));
|
187 | }
|
188 | expect(alice.get(keyX)).toBeUndefined();
|
189 | expect(alice.get(keyY)).toBeUndefined();
|
190 | expect(alice.get(keyZ)).toBeUndefined();
|
191 | expect(bob.get(keyX)).toBeUndefined();
|
192 | expect(bob.get(keyY)).toBeUndefined();
|
193 | expect(bob.get(keyZ)).toBeUndefined();
|
194 | expect([...alice]).toEqual([]);
|
195 | expect([...bob]).toEqual([]);
|
196 | });
|
197 |
|
198 | test('Flush deletions', async () => {
|
199 | const keyX = uuid.v4();
|
200 | const keyY = uuid.v4();
|
201 | const keyZ = uuid.v4();
|
202 | const valueX = generateValue();
|
203 | const valueY = generateValue();
|
204 | const valueZ = generateValue();
|
205 | const map = new ObservedRemoveMap([[keyX, valueX], [keyY, valueY], [keyZ, valueZ]], { maxAge: 100 });
|
206 | map.delete(keyX);
|
207 | map.delete(keyY);
|
208 | map.delete(keyZ);
|
209 | expect(map.deletions.size).toEqual(3);
|
210 | map.flush();
|
211 | expect(map.deletions.size).toEqual(3);
|
212 | await new Promise((resolve) => setTimeout(resolve, 200));
|
213 | map.flush();
|
214 | expect(map.deletions.size).toEqual(0);
|
215 | });
|
216 |
|
217 | test('Synchronize set and delete events', async () => {
|
218 | const keyX = uuid.v4();
|
219 | const keyY = uuid.v4();
|
220 | const valueX = generateValue();
|
221 | const valueY = generateValue();
|
222 | const alice = new ObservedRemoveMap();
|
223 | const bob = new ObservedRemoveMap();
|
224 | alice.on('publish', (message) => {
|
225 | bob.process(message);
|
226 | });
|
227 | bob.on('publish', (message) => {
|
228 | alice.process(message);
|
229 | });
|
230 | const aliceSetXPromise = new Promise((resolve) => {
|
231 | alice.once('set', (key, value) => {
|
232 | expect(key).toEqual(keyX);
|
233 | expect(value).toEqual(valueX);
|
234 | resolve();
|
235 | });
|
236 | });
|
237 | const aliceDeleteXPromise = new Promise((resolve) => {
|
238 | alice.once('delete', (key, value) => {
|
239 | expect(key).toEqual(keyX);
|
240 | expect(value).toEqual(valueX);
|
241 | resolve();
|
242 | });
|
243 | });
|
244 | bob.set(keyX, valueX);
|
245 | await aliceSetXPromise;
|
246 | bob.delete(keyX);
|
247 | await aliceDeleteXPromise;
|
248 | const bobSetYPromise = new Promise((resolve) => {
|
249 | bob.once('set', (key, value) => {
|
250 | expect(key).toEqual(keyY);
|
251 | expect(value).toEqual(valueY);
|
252 | resolve();
|
253 | });
|
254 | });
|
255 | const bobDeleteYPromise = new Promise((resolve) => {
|
256 | bob.once('delete', (key, value) => {
|
257 | expect(key).toEqual(keyY);
|
258 | expect(value).toEqual(valueY);
|
259 | resolve();
|
260 | });
|
261 | });
|
262 | alice.set(keyY, valueY);
|
263 | await bobSetYPromise;
|
264 | alice.delete(keyY);
|
265 | await bobDeleteYPromise;
|
266 | });
|
267 |
|
268 | test('Should not emit events for remote set/delete combos on sync', async () => {
|
269 | const keyX = uuid.v4();
|
270 | const keyY = uuid.v4();
|
271 | const valueX = generateValue();
|
272 | const valueY = generateValue();
|
273 | const alice = new ObservedRemoveMap();
|
274 | const bob = new ObservedRemoveMap();
|
275 | alice.set(keyX, valueX);
|
276 | alice.delete(keyX);
|
277 | bob.set(keyY, valueY);
|
278 | bob.delete(keyY);
|
279 | await new Promise((resolve) => setTimeout(resolve, 250));
|
280 | const bobPromise = new Promise((resolve, reject) => {
|
281 | bob.once('set', () => {
|
282 | reject(new Error('Bob should not receive set event'));
|
283 | });
|
284 | bob.once('delete', () => {
|
285 | reject(new Error('Bob should not receive delete event'));
|
286 | });
|
287 | setTimeout(resolve, 500);
|
288 | });
|
289 | const alicePromise = new Promise((resolve, reject) => {
|
290 | alice.once('set', () => {
|
291 | reject(new Error('Alice should not receive set event'));
|
292 | });
|
293 | alice.once('delete', () => {
|
294 | reject(new Error('Alice should not receive delete event'));
|
295 | });
|
296 | setTimeout(resolve, 500);
|
297 | });
|
298 | alice.on('publish', (message) => {
|
299 | bob.process(message);
|
300 | });
|
301 | bob.on('publish', (message) => {
|
302 | alice.process(message);
|
303 | });
|
304 | alice.sync();
|
305 | bob.sync();
|
306 | await bobPromise;
|
307 | await alicePromise;
|
308 | expect(alice.get(keyX)).toBeUndefined();
|
309 | expect(alice.get(keyY)).toBeUndefined();
|
310 | expect(bob.get(keyX)).toBeUndefined();
|
311 | expect(bob.get(keyY)).toBeUndefined();
|
312 | });
|
313 |
|
314 | test('Synchronize mixed maps using sync', async () => {
|
315 | const keyA = uuid.v4();
|
316 | const keyB = uuid.v4();
|
317 | const keyC = uuid.v4();
|
318 | const keyX = uuid.v4();
|
319 | const keyY = uuid.v4();
|
320 | const keyZ = uuid.v4();
|
321 | const valueA = generateValue();
|
322 | const valueB = generateValue();
|
323 | const valueC = generateValue();
|
324 | const valueX = generateValue();
|
325 | const valueY = generateValue();
|
326 | const valueZ = generateValue();
|
327 | const alice = new ObservedRemoveMap();
|
328 | const bob = new ObservedRemoveMap();
|
329 | alice.set(keyA, valueA);
|
330 | bob.set(keyX, valueX);
|
331 | alice.set(keyB, valueB);
|
332 | bob.set(keyY, valueY);
|
333 | alice.set(keyC, valueC);
|
334 | bob.set(keyZ, valueZ);
|
335 | let aliceAddCount = 0;
|
336 | let bobAddCount = 0;
|
337 | let aliceDeleteCount = 0;
|
338 | let bobDeleteCount = 0;
|
339 | await new Promise((resolve) => setTimeout(resolve, 100));
|
340 | expect([...alice]).toEqual([[keyA, valueA], [keyB, valueB], [keyC, valueC]]);
|
341 | expect([...bob]).toEqual([[keyX, valueX], [keyY, valueY], [keyZ, valueZ]]);
|
342 | alice.on('set', () => (aliceAddCount += 1));
|
343 | bob.on('set', () => (bobAddCount += 1));
|
344 | alice.on('delete', () => (aliceDeleteCount += 1));
|
345 | bob.on('delete', () => (bobDeleteCount += 1));
|
346 | alice.on('publish', (message) => {
|
347 | bob.process(message);
|
348 | });
|
349 | bob.on('publish', (message) => {
|
350 | alice.process(message);
|
351 | });
|
352 | alice.sync();
|
353 | bob.sync();
|
354 | while (aliceAddCount !== 3 || bobAddCount !== 3) {
|
355 | await new Promise((resolve) => setTimeout(resolve, 20));
|
356 | }
|
357 | expect([...alice]).toEqual(expect.arrayContaining([[keyA, valueA], [keyX, valueX], [keyB, valueB], [keyY, valueY], [keyC, valueC], [keyZ, valueZ]]));
|
358 | expect([...bob]).toEqual(expect.arrayContaining([[keyA, valueA], [keyX, valueX], [keyB, valueB], [keyY, valueY], [keyC, valueC], [keyZ, valueZ]]));
|
359 | });
|
360 |
|
361 | test('Key-value pairs should not repeat', async () => {
|
362 | const key = uuid.v4();
|
363 | const value1 = generateValue();
|
364 | const value2 = generateValue();
|
365 | const alice = new ObservedRemoveMap();
|
366 | alice.set(key, value1);
|
367 | alice.set(key, value2);
|
368 | expect([...alice].length).toEqual(1);
|
369 | expect([...alice.entries()].length).toEqual(1);
|
370 | expect([...alice.keys()].length).toEqual(1);
|
371 | expect([...alice.values()].length).toEqual(1);
|
372 | expect([...alice]).toEqual([[key, value2]]);
|
373 | expect([...alice.entries()]).toEqual([[key, value2]]);
|
374 | expect([...alice.keys()]).toEqual([key]);
|
375 | expect([...alice.values()]).toEqual([value2]);
|
376 | expect(alice.get(key)).toEqual(value2);
|
377 | });
|
378 |
|
379 | test('Synchronizes 100 asynchrous maps', async () => {
|
380 | const keyA = uuid.v4();
|
381 | const keyB = uuid.v4();
|
382 | const keyC = uuid.v4();
|
383 | const valueA = generateValue();
|
384 | const valueB = generateValue();
|
385 | const valueC = generateValue();
|
386 | const maps = [];
|
387 | const callbacks = [];
|
388 | const publish = (sourceId:number, message:Buffer) => {
|
389 | for (let i = 0; i < callbacks.length; i += 1) {
|
390 | const [targetId, callback] = callbacks[i];
|
391 | if (targetId === sourceId) {
|
392 | continue;
|
393 | }
|
394 | setTimeout(() => callback(message), Math.round(1000 * Math.random()));
|
395 | }
|
396 | };
|
397 | const subscribe = (targetId: number, callback:Function) => {
|
398 | callbacks.push([targetId, callback]);
|
399 | };
|
400 | const getPair = () => {
|
401 | const mapA = maps[Math.floor(Math.random() * maps.length)];
|
402 | let mapB = mapA;
|
403 | while (mapB === mapA) {
|
404 | mapB = maps[Math.floor(Math.random() * maps.length)];
|
405 | }
|
406 | return [mapA, mapB];
|
407 | };
|
408 | for (let i = 0; i < 100; i += 1) {
|
409 | const map = new ObservedRemoveMap();
|
410 | map.on('publish', (message) => publish(i, message));
|
411 | subscribe(i, (message) => map.process(message));
|
412 | maps.push(map);
|
413 | }
|
414 | const [alice, bob] = getPair();
|
415 | let aliceAddCount = 0;
|
416 | let bobAddCount = 0;
|
417 | let aliceDeleteCount = 0;
|
418 | let bobDeleteCount = 0;
|
419 | alice.on('set', () => (aliceAddCount += 1));
|
420 | bob.on('set', () => (bobAddCount += 1));
|
421 | alice.on('delete', () => (aliceDeleteCount += 1));
|
422 | bob.on('delete', () => (bobDeleteCount += 1));
|
423 | alice.set(keyA, valueA);
|
424 | bob.set(keyB, valueB);
|
425 | alice.set(keyC, valueC);
|
426 | while (aliceAddCount !== 3 || bobAddCount !== 3) {
|
427 | await new Promise((resolve) => setTimeout(resolve, 20));
|
428 | }
|
429 | bob.delete(keyC);
|
430 | alice.delete(keyB);
|
431 | bob.delete(keyA);
|
432 | while (aliceDeleteCount !== 3 || bobDeleteCount !== 3) {
|
433 | await new Promise((resolve) => setTimeout(resolve, 20));
|
434 | }
|
435 | expect([...alice]).toEqual([]);
|
436 | expect([...bob]).toEqual([]);
|
437 | });
|
438 |
|
439 | test('Synchronize out of order sets', async () => {
|
440 | const alice = new ObservedRemoveMap([]);
|
441 | const bob = new ObservedRemoveMap([]);
|
442 | const key = uuid.v4();
|
443 | const value1 = generateValue();
|
444 | const value2 = generateValue();
|
445 | alice.set(key, value1);
|
446 | const aliceDump1 = alice.dump();
|
447 | alice.set(key, value2);
|
448 | const aliceDump2 = alice.dump();
|
449 | bob.process(aliceDump2);
|
450 | expect(bob.get(key)).toEqual(value2);
|
451 | bob.delete(key);
|
452 | expect(bob.get(key)).toBeUndefined();
|
453 | const bobDump1 = bob.dump();
|
454 | alice.process(bobDump1);
|
455 | expect(alice.get(key)).toBeUndefined();
|
456 | bob.process(aliceDump1);
|
457 | expect(alice.get(key)).toBeUndefined();
|
458 | expect(bob.get(key)).toBeUndefined();
|
459 | const bobDump2 = bob.dump();
|
460 | alice.process(bobDump2);
|
461 | expect(alice.get(key)).toBeUndefined();
|
462 | expect(bob.get(key)).toBeUndefined();
|
463 | });
|
464 |
|
465 | test('Affirm values if synchronization happens twice', async () => {
|
466 | const keyA = uuid.v4();
|
467 | const keyB = uuid.v4();
|
468 | const valueA = generateValue();
|
469 | const valueB = generateValue();
|
470 | const alice = new ObservedRemoveMap([[keyA, valueA]]);
|
471 | const bob = new ObservedRemoveMap([[keyB, valueB]]);
|
472 | const setAPromise = new Promise((resolve) => {
|
473 | bob.once('set', (k, v) => {
|
474 | expect(k).toEqual(keyA);
|
475 | expect(v).toEqual(valueA);
|
476 | resolve();
|
477 | });
|
478 | });
|
479 | const setBPromise = new Promise((resolve) => {
|
480 | alice.once('set', (k, v) => {
|
481 | expect(k).toEqual(keyB);
|
482 | expect(v).toEqual(valueB);
|
483 | resolve();
|
484 | });
|
485 | });
|
486 | const aliceDump = alice.dump();
|
487 | const bobDump = bob.dump();
|
488 | alice.process(bobDump);
|
489 | bob.process(aliceDump);
|
490 | await setAPromise;
|
491 | await setBPromise;
|
492 | const affirmAPromise = new Promise((resolve) => {
|
493 | bob.once('affirm', (k, v) => {
|
494 | expect(k).toEqual(keyA);
|
495 | expect(v).toEqual(valueA);
|
496 | resolve();
|
497 | });
|
498 | });
|
499 | const affirmBPromise = new Promise((resolve) => {
|
500 | alice.once('affirm', (k, v) => {
|
501 | expect(k).toEqual(keyB);
|
502 | expect(v).toEqual(valueB);
|
503 | resolve();
|
504 | });
|
505 | });
|
506 | alice.process(bobDump);
|
507 | bob.process(aliceDump);
|
508 | await affirmAPromise;
|
509 | await affirmBPromise;
|
510 | });
|
511 | });
|
512 |
|