UNPKG

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