cojson 0.9.23 → 0.10.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.
@@ -1,10 +1,24 @@
1
- import { describe, expect, test, vi } from "vitest";
1
+ import { beforeEach, describe, expect, onTestFinished, test, vi } from "vitest";
2
2
  import { PeerState } from "../PeerState";
3
3
  import { CoValueCore } from "../coValueCore";
4
- import { CO_VALUE_LOADING_MAX_RETRIES, CoValueState } from "../coValueState";
4
+ import { CO_VALUE_LOADING_CONFIG, CoValueState } from "../coValueState";
5
5
  import { RawCoID } from "../ids";
6
6
  import { Peer } from "../sync";
7
7
 
8
+ const initialMaxRetries = CO_VALUE_LOADING_CONFIG.MAX_RETRIES;
9
+
10
+ function mockMaxRetries(maxRetries: number) {
11
+ CO_VALUE_LOADING_CONFIG.MAX_RETRIES = maxRetries;
12
+
13
+ onTestFinished(() => {
14
+ CO_VALUE_LOADING_CONFIG.MAX_RETRIES = initialMaxRetries;
15
+ });
16
+ }
17
+
18
+ beforeEach(() => {
19
+ mockMaxRetries(5);
20
+ });
21
+
8
22
  describe("CoValueState", () => {
9
23
  const mockCoValueId = "co_test123" as RawCoID;
10
24
 
@@ -92,18 +106,18 @@ describe("CoValueState", () => {
92
106
  const state = CoValueState.Unknown(mockCoValueId);
93
107
  const loadPromise = state.loadFromPeers(mockPeers);
94
108
 
95
- // Should attempt CO_VALUE_LOADING_MAX_RETRIES retries
96
- for (let i = 0; i < CO_VALUE_LOADING_MAX_RETRIES; i++) {
109
+ // Should attempt CO_VALUE_LOADING_CONFIG.MAX_RETRIES retries
110
+ for (let i = 0; i < CO_VALUE_LOADING_CONFIG.MAX_RETRIES; i++) {
97
111
  await vi.runAllTimersAsync();
98
112
  }
99
113
 
100
114
  await loadPromise;
101
115
 
102
116
  expect(peer1.pushOutgoingMessage).toHaveBeenCalledTimes(
103
- CO_VALUE_LOADING_MAX_RETRIES,
117
+ CO_VALUE_LOADING_CONFIG.MAX_RETRIES,
104
118
  );
105
119
  expect(peer2.pushOutgoingMessage).toHaveBeenCalledTimes(
106
- CO_VALUE_LOADING_MAX_RETRIES,
120
+ CO_VALUE_LOADING_CONFIG.MAX_RETRIES,
107
121
  );
108
122
  expect(state.state.type).toBe("unavailable");
109
123
  await expect(state.getCoValue()).resolves.toBe("unavailable");
@@ -145,8 +159,8 @@ describe("CoValueState", () => {
145
159
  const state = CoValueState.Unknown(mockCoValueId);
146
160
  const loadPromise = state.loadFromPeers(mockPeers);
147
161
 
148
- // Should attempt CO_VALUE_LOADING_MAX_RETRIES retries
149
- for (let i = 0; i < CO_VALUE_LOADING_MAX_RETRIES; i++) {
162
+ // Should attempt CO_VALUE_LOADING_CONFIG.MAX_RETRIES retries
163
+ for (let i = 0; i < CO_VALUE_LOADING_CONFIG.MAX_RETRIES; i++) {
150
164
  await vi.runAllTimersAsync();
151
165
  }
152
166
 
@@ -154,7 +168,7 @@ describe("CoValueState", () => {
154
168
 
155
169
  expect(peer1.pushOutgoingMessage).toHaveBeenCalledTimes(1);
156
170
  expect(peer2.pushOutgoingMessage).toHaveBeenCalledTimes(
157
- CO_VALUE_LOADING_MAX_RETRIES,
171
+ CO_VALUE_LOADING_CONFIG.MAX_RETRIES,
158
172
  );
159
173
  expect(state.state.type).toBe("unavailable");
160
174
  await expect(state.getCoValue()).resolves.toBe("unavailable");
@@ -194,8 +208,8 @@ describe("CoValueState", () => {
194
208
  const state = CoValueState.Unknown(mockCoValueId);
195
209
  const loadPromise = state.loadFromPeers(mockPeers);
196
210
 
197
- // Should attempt CO_VALUE_LOADING_MAX_RETRIES retries
198
- for (let i = 0; i < CO_VALUE_LOADING_MAX_RETRIES; i++) {
211
+ // Should attempt CO_VALUE_LOADING_CONFIG.MAX_RETRIES retries
212
+ for (let i = 0; i < CO_VALUE_LOADING_CONFIG.MAX_RETRIES; i++) {
199
213
  await vi.runAllTimersAsync();
200
214
  }
201
215
 
@@ -203,7 +217,7 @@ describe("CoValueState", () => {
203
217
 
204
218
  expect(peer1.pushOutgoingMessage).toHaveBeenCalledTimes(1);
205
219
  expect(peer2.pushOutgoingMessage).toHaveBeenCalledTimes(
206
- CO_VALUE_LOADING_MAX_RETRIES,
220
+ CO_VALUE_LOADING_CONFIG.MAX_RETRIES,
207
221
  );
208
222
  expect(state.state.type).toBe("unavailable");
209
223
  await expect(state.getCoValue()).resolves.toEqual("unavailable");
@@ -214,6 +228,8 @@ describe("CoValueState", () => {
214
228
  test("should handle the coValues that become available in between of the retries", async () => {
215
229
  vi.useFakeTimers();
216
230
 
231
+ mockMaxRetries(5);
232
+
217
233
  let retries = 0;
218
234
 
219
235
  const peer1 = createMockPeerState(
@@ -244,8 +260,8 @@ describe("CoValueState", () => {
244
260
  const state = CoValueState.Unknown(mockCoValueId);
245
261
  const loadPromise = state.loadFromPeers(mockPeers);
246
262
 
247
- // Should attempt CO_VALUE_LOADING_MAX_RETRIES retries
248
- for (let i = 0; i < CO_VALUE_LOADING_MAX_RETRIES + 1; i++) {
263
+ // Should attempt CO_VALUE_LOADING_CONFIG.MAX_RETRIES retries
264
+ for (let i = 0; i < CO_VALUE_LOADING_CONFIG.MAX_RETRIES + 1; i++) {
249
265
  await vi.runAllTimersAsync();
250
266
  }
251
267
 
@@ -278,8 +294,8 @@ describe("CoValueState", () => {
278
294
  const state = CoValueState.Unknown(mockCoValueId);
279
295
  const loadPromise = state.loadFromPeers(mockPeers);
280
296
 
281
- // Should attempt CO_VALUE_LOADING_MAX_RETRIES retries
282
- for (let i = 0; i < CO_VALUE_LOADING_MAX_RETRIES; i++) {
297
+ // Should attempt CO_VALUE_LOADING_CONFIG.MAX_RETRIES retries
298
+ for (let i = 0; i < CO_VALUE_LOADING_CONFIG.MAX_RETRIES; i++) {
283
299
  await vi.runAllTimersAsync();
284
300
  }
285
301
 
@@ -290,7 +306,9 @@ describe("CoValueState", () => {
290
306
 
291
307
  await loadPromise;
292
308
 
293
- expect(peer1.pushOutgoingMessage).toHaveBeenCalledTimes(5);
309
+ expect(peer1.pushOutgoingMessage).toHaveBeenCalledTimes(
310
+ CO_VALUE_LOADING_CONFIG.MAX_RETRIES,
311
+ );
294
312
  expect(state.state.type).toBe("available");
295
313
  await expect(state.getCoValue()).resolves.toEqual({ id: mockCoValueId });
296
314
 
@@ -300,6 +318,8 @@ describe("CoValueState", () => {
300
318
  test("should stop retrying when value becomes available", async () => {
301
319
  vi.useFakeTimers();
302
320
 
321
+ mockMaxRetries(5);
322
+
303
323
  let run = 1;
304
324
 
305
325
  const peer1 = createMockPeerState(
@@ -327,7 +347,7 @@ describe("CoValueState", () => {
327
347
  const state = CoValueState.Unknown(mockCoValueId);
328
348
  const loadPromise = state.loadFromPeers(mockPeers);
329
349
 
330
- for (let i = 0; i < CO_VALUE_LOADING_MAX_RETRIES; i++) {
350
+ for (let i = 0; i < CO_VALUE_LOADING_CONFIG.MAX_RETRIES; i++) {
331
351
  await vi.runAllTimersAsync();
332
352
  }
333
353
  await loadPromise;
@@ -372,7 +392,7 @@ describe("CoValueState", () => {
372
392
  const state = CoValueState.Unknown(mockCoValueId);
373
393
  const loadPromise = state.loadFromPeers([peer1, peer2]);
374
394
 
375
- for (let i = 0; i < CO_VALUE_LOADING_MAX_RETRIES; i++) {
395
+ for (let i = 0; i < CO_VALUE_LOADING_CONFIG.MAX_RETRIES; i++) {
376
396
  await vi.runAllTimersAsync();
377
397
  }
378
398
  await loadPromise;
@@ -421,7 +441,7 @@ describe("CoValueState", () => {
421
441
  const state = CoValueState.Unknown(mockCoValueId);
422
442
  const loadPromise = state.loadFromPeers([peer1, peer2]);
423
443
 
424
- for (let i = 0; i < CO_VALUE_LOADING_MAX_RETRIES; i++) {
444
+ for (let i = 0; i < CO_VALUE_LOADING_CONFIG.MAX_RETRIES; i++) {
425
445
  await vi.runAllTimersAsync();
426
446
  }
427
447
  await loadPromise;
@@ -449,12 +469,14 @@ describe("CoValueState", () => {
449
469
  const state = CoValueState.Unknown(mockCoValueId);
450
470
  const loadPromise = state.loadFromPeers([peer1]);
451
471
 
452
- for (let i = 0; i < CO_VALUE_LOADING_MAX_RETRIES * 2; i++) {
472
+ for (let i = 0; i < CO_VALUE_LOADING_CONFIG.MAX_RETRIES * 2; i++) {
453
473
  await vi.runAllTimersAsync();
454
474
  }
455
475
  await loadPromise;
456
476
 
457
- expect(peer1.pushOutgoingMessage).toHaveBeenCalledTimes(5);
477
+ expect(peer1.pushOutgoingMessage).toHaveBeenCalledTimes(
478
+ CO_VALUE_LOADING_CONFIG.MAX_RETRIES,
479
+ );
458
480
 
459
481
  expect(state.state.type).toBe("unavailable");
460
482
  await expect(state.getCoValue()).resolves.toEqual("unavailable");
@@ -467,33 +467,6 @@ describe("writeOnly", () => {
467
467
  expect(mapOnNode3.get("test")).toEqual("Written from a writeOnly member");
468
468
  });
469
469
 
470
- test("inherited writer roles should work correctly", async () => {
471
- const { node1, node2 } = await createTwoConnectedNodes("server", "server");
472
-
473
- const group = node1.node.createGroup();
474
- group.addMember(
475
- await loadCoValueOrFail(node1.node, node2.accountID),
476
- "writer",
477
- );
478
-
479
- const childGroup = node1.node.createGroup();
480
- childGroup.extend(group);
481
- childGroup.addMember(
482
- await loadCoValueOrFail(node1.node, node2.accountID),
483
- "writeOnly",
484
- );
485
-
486
- const map = childGroup.createMap();
487
- map.set("test", "Written from the admin");
488
-
489
- await map.core.waitForSync();
490
-
491
- const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
492
-
493
- // The writer role should be able to see the edits from the admin
494
- expect(mapOnNode2.get("test")).toEqual("Written from the admin");
495
- });
496
-
497
470
  test("upgrade to writer roles should work correctly", async () => {
498
471
  const { node1, node2 } = await createTwoConnectedNodes("server", "server");
499
472
 
@@ -528,6 +501,35 @@ describe("writeOnly", () => {
528
501
  // The writer role should be able to see the edits from the admin
529
502
  expect(mapOnNode2.get("test")).toEqual("Written from the writeOnly member");
530
503
  });
504
+ });
505
+
506
+ describe("extend", () => {
507
+ test("inherited writer roles should work correctly", async () => {
508
+ const { node1, node2 } = await createTwoConnectedNodes("server", "server");
509
+
510
+ const group = node1.node.createGroup();
511
+ group.addMember(
512
+ await loadCoValueOrFail(node1.node, node2.accountID),
513
+ "writer",
514
+ );
515
+
516
+ const childGroup = node1.node.createGroup();
517
+ childGroup.extend(group);
518
+ childGroup.addMember(
519
+ await loadCoValueOrFail(node1.node, node2.accountID),
520
+ "writeOnly",
521
+ );
522
+
523
+ const map = childGroup.createMap();
524
+ map.set("test", "Written from the admin");
525
+
526
+ await map.core.waitForSync();
527
+
528
+ const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
529
+
530
+ // The writer role should be able to see the edits from the admin
531
+ expect(mapOnNode2.get("test")).toEqual("Written from the admin");
532
+ });
531
533
 
532
534
  test("a user should be able to extend a group when his role on the parent group is writer", async () => {
533
535
  const { node1, node2 } = await createTwoConnectedNodes("server", "server");
@@ -638,4 +640,205 @@ describe("writeOnly", () => {
638
640
 
639
641
  expect(map.get("test")).toEqual("Hello!");
640
642
  });
643
+
644
+ test("a writerInvite role should not be inherited", async () => {
645
+ const { node1, node2 } = await createTwoConnectedNodes("server", "server");
646
+
647
+ const group = node1.node.createGroup();
648
+ group.addMember(
649
+ await loadCoValueOrFail(node1.node, node2.accountID),
650
+ "writerInvite",
651
+ );
652
+
653
+ const childGroup = node1.node.createGroup();
654
+ childGroup.extend(group);
655
+
656
+ expect(childGroup.roleOf(node2.accountID)).toEqual(undefined);
657
+ });
658
+ });
659
+
660
+ describe("extend with role mapping", () => {
661
+ test("mapping to writer should add the ability to write", async () => {
662
+ const { node1, node2 } = await createTwoConnectedNodes("server", "server");
663
+
664
+ const group = node1.node.createGroup();
665
+ group.addMember(
666
+ await loadCoValueOrFail(node1.node, node2.accountID),
667
+ "reader",
668
+ );
669
+
670
+ const childGroup = node1.node.createGroup();
671
+ childGroup.extend(group, "writer");
672
+
673
+ expect(childGroup.roleOf(node2.accountID)).toEqual("writer");
674
+
675
+ const map = childGroup.createMap();
676
+ map.set("test", "Written from the admin");
677
+
678
+ await map.core.waitForSync();
679
+
680
+ const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
681
+
682
+ expect(mapOnNode2.get("test")).toEqual("Written from the admin");
683
+
684
+ mapOnNode2.set("test", "Written from the inherited role");
685
+ expect(mapOnNode2.get("test")).toEqual("Written from the inherited role");
686
+
687
+ await mapOnNode2.core.waitForSync();
688
+
689
+ expect(map.get("test")).toEqual("Written from the inherited role");
690
+ });
691
+
692
+ test("mapping to reader should remove the ability to write", async () => {
693
+ const { node1, node2 } = await createTwoConnectedNodes("server", "server");
694
+
695
+ const group = node1.node.createGroup();
696
+ group.addMember(
697
+ await loadCoValueOrFail(node1.node, node2.accountID),
698
+ "writer",
699
+ );
700
+
701
+ const childGroup = node1.node.createGroup();
702
+ childGroup.extend(group, "reader");
703
+
704
+ expect(childGroup.roleOf(node2.accountID)).toEqual("reader");
705
+
706
+ const map = childGroup.createMap();
707
+ map.set("test", "Written from the admin");
708
+
709
+ await map.core.waitForSync();
710
+
711
+ const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
712
+
713
+ expect(mapOnNode2.get("test")).toEqual("Written from the admin");
714
+
715
+ mapOnNode2.set("test", "Should not be visible");
716
+
717
+ await mapOnNode2.core.waitForSync();
718
+
719
+ expect(map.get("test")).toEqual("Written from the admin");
720
+ expect(mapOnNode2.get("test")).toEqual("Written from the admin");
721
+ });
722
+
723
+ test("mapping to admin should add the ability to add members", async () => {
724
+ const { node1, node2, node3 } = await createThreeConnectedNodes(
725
+ "server",
726
+ "server",
727
+ "server",
728
+ );
729
+
730
+ const group = node1.node.createGroup();
731
+ group.addMember(
732
+ await loadCoValueOrFail(node1.node, node2.accountID),
733
+ "reader",
734
+ );
735
+
736
+ const childGroup = node1.node.createGroup();
737
+ childGroup.extend(group, "admin");
738
+
739
+ expect(childGroup.roleOf(node2.accountID)).toEqual("admin");
740
+
741
+ await childGroup.core.waitForSync();
742
+
743
+ const childGroupOnNode2 = await loadCoValueOrFail(
744
+ node2.node,
745
+ childGroup.id,
746
+ );
747
+
748
+ childGroupOnNode2.addMember(
749
+ await loadCoValueOrFail(node2.node, node3.accountID),
750
+ "reader",
751
+ );
752
+
753
+ expect(childGroupOnNode2.roleOf(node3.accountID)).toEqual("reader");
754
+ });
755
+
756
+ test("mapping to reader should remove the ability to add members", async () => {
757
+ const { node1, node2, node3 } = await createThreeConnectedNodes(
758
+ "server",
759
+ "server",
760
+ "server",
761
+ );
762
+
763
+ const group = node1.node.createGroup();
764
+ group.addMember(
765
+ await loadCoValueOrFail(node1.node, node2.accountID),
766
+ "admin",
767
+ );
768
+
769
+ const childGroup = node1.node.createGroup();
770
+ childGroup.extend(group, "reader");
771
+
772
+ expect(childGroup.roleOf(node2.accountID)).toEqual("reader");
773
+
774
+ await childGroup.core.waitForSync();
775
+
776
+ const childGroupOnNode2 = await loadCoValueOrFail(
777
+ node2.node,
778
+ childGroup.id,
779
+ );
780
+
781
+ const accountToAdd = await loadCoValueOrFail(node2.node, node3.accountID);
782
+
783
+ expect(() => {
784
+ childGroupOnNode2.addMember(accountToAdd, "reader");
785
+ }).toThrow();
786
+
787
+ expect(childGroupOnNode2.roleOf(node3.accountID)).toEqual(undefined);
788
+ });
789
+
790
+ test("non-inheritable roles should not give access to the child group when role mapping is used", async () => {
791
+ const { node1, node2 } = await createTwoConnectedNodes("server", "server");
792
+
793
+ const group = node1.node.createGroup();
794
+ group.addMember(
795
+ await loadCoValueOrFail(node1.node, node2.accountID),
796
+ "writeOnly",
797
+ );
798
+
799
+ const childGroup = node1.node.createGroup();
800
+ childGroup.extend(group, "reader");
801
+
802
+ expect(childGroup.roleOf(node2.accountID)).toEqual(undefined);
803
+
804
+ const map = childGroup.createMap();
805
+ map.set("test", "Written from the admin");
806
+
807
+ await map.core.waitForSync();
808
+
809
+ const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
810
+
811
+ expect(mapOnNode2.get("test")).toEqual(undefined);
812
+ });
813
+
814
+ test("invite roles should not give write access to the child group when role mapping is used", async () => {
815
+ const { node1, node2 } = await createTwoConnectedNodes("server", "server");
816
+
817
+ const group = node1.node.createGroup();
818
+ group.addMember(
819
+ await loadCoValueOrFail(node1.node, node2.accountID),
820
+ "writerInvite",
821
+ );
822
+
823
+ const childGroup = node1.node.createGroup();
824
+ childGroup.extend(group, "writer");
825
+
826
+ expect(childGroup.roleOf(node2.accountID)).toEqual(undefined);
827
+
828
+ const map = childGroup.createMap();
829
+ map.set("test", "Written from the admin");
830
+
831
+ await map.core.waitForSync();
832
+
833
+ const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
834
+
835
+ expect(mapOnNode2.get("test")).toEqual("Written from the admin"); // The invite roles have access to the readKey hence can read the values on inherited groups
836
+
837
+ mapOnNode2.set("test", "Should not be visible");
838
+
839
+ await mapOnNode2.core.waitForSync();
840
+
841
+ expect(map.get("test")).toEqual("Written from the admin");
842
+ expect(mapOnNode2.get("test")).toEqual("Written from the admin");
843
+ });
641
844
  });