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