cojson 0.8.16 → 0.8.17
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.
- package/CHANGELOG.md +6 -0
- package/dist/native/PeerKnownStates.js +5 -0
- package/dist/native/PeerKnownStates.js.map +1 -1
- package/dist/native/PeerState.js +3 -2
- package/dist/native/PeerState.js.map +1 -1
- package/dist/native/SyncStateSubscriptionManager.js +70 -0
- package/dist/native/SyncStateSubscriptionManager.js.map +1 -0
- package/dist/native/coValues/account.js.map +1 -1
- package/dist/native/sync.js +55 -3
- package/dist/native/sync.js.map +1 -1
- package/dist/web/PeerKnownStates.js +5 -0
- package/dist/web/PeerKnownStates.js.map +1 -1
- package/dist/web/PeerState.js +3 -2
- package/dist/web/PeerState.js.map +1 -1
- package/dist/web/SyncStateSubscriptionManager.js +70 -0
- package/dist/web/SyncStateSubscriptionManager.js.map +1 -0
- package/dist/web/coValues/account.js.map +1 -1
- package/dist/web/sync.js +55 -3
- package/dist/web/sync.js.map +1 -1
- package/package.json +1 -1
- package/src/PeerKnownStates.ts +6 -0
- package/src/PeerState.ts +22 -2
- package/src/SyncStateSubscriptionManager.ts +124 -0
- package/src/coValues/account.ts +1 -1
- package/src/sync.ts +73 -3
- package/src/tests/SyncStateSubscriptionManager.test.ts +232 -0
- package/src/tests/sync.test.ts +303 -2
- package/src/tests/testUtils.ts +33 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { describe, expect, onTestFinished, test, vi } from "vitest";
|
|
2
|
+
import { connectedPeers } from "../streamUtils.js";
|
|
3
|
+
import { emptyKnownState } from "../sync.js";
|
|
4
|
+
import { createTestNode, waitFor } from "./testUtils.js";
|
|
5
|
+
|
|
6
|
+
describe("SyncStateSubscriptionManager", () => {
|
|
7
|
+
test("subscribeToUpdates receives updates when peer state changes", async () => {
|
|
8
|
+
// Setup nodes
|
|
9
|
+
const client = createTestNode();
|
|
10
|
+
const jazzCloud = createTestNode();
|
|
11
|
+
|
|
12
|
+
// Create test data
|
|
13
|
+
const group = client.createGroup();
|
|
14
|
+
const map = group.createMap();
|
|
15
|
+
map.set("key1", "value1", "trusting");
|
|
16
|
+
|
|
17
|
+
// Connect nodes
|
|
18
|
+
const [clientAsPeer, jazzCloudAsPeer] = connectedPeers(
|
|
19
|
+
"clientConnection",
|
|
20
|
+
"jazzCloudConnection",
|
|
21
|
+
{
|
|
22
|
+
peer1role: "client",
|
|
23
|
+
peer2role: "server",
|
|
24
|
+
},
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
client.syncManager.addPeer(jazzCloudAsPeer);
|
|
28
|
+
jazzCloud.syncManager.addPeer(clientAsPeer);
|
|
29
|
+
|
|
30
|
+
const subscriptionManager = client.syncManager.syncStateSubscriptionManager;
|
|
31
|
+
|
|
32
|
+
const updateSpy = vi.fn();
|
|
33
|
+
const unsubscribe = subscriptionManager.subscribeToUpdates(updateSpy);
|
|
34
|
+
|
|
35
|
+
await client.syncManager.actuallySyncCoValue(map.core);
|
|
36
|
+
|
|
37
|
+
expect(updateSpy).toHaveBeenCalledWith(
|
|
38
|
+
"jazzCloudConnection",
|
|
39
|
+
emptyKnownState(map.core.id),
|
|
40
|
+
false,
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
await waitFor(() => {
|
|
44
|
+
return subscriptionManager.getIsCoValueFullyUploadedIntoPeer(
|
|
45
|
+
"jazzCloudConnection",
|
|
46
|
+
map.core.id,
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
expect(updateSpy).toHaveBeenCalledWith(
|
|
51
|
+
"jazzCloudConnection",
|
|
52
|
+
client.syncManager.peers["jazzCloudConnection"]!.knownStates.get(
|
|
53
|
+
map.core.id,
|
|
54
|
+
)!,
|
|
55
|
+
true,
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
// Cleanup
|
|
59
|
+
unsubscribe();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("subscribeToPeerUpdates receives updates only for specific peer", async () => {
|
|
63
|
+
// Setup nodes
|
|
64
|
+
const client = createTestNode();
|
|
65
|
+
const jazzCloud = createTestNode();
|
|
66
|
+
|
|
67
|
+
// Create test data
|
|
68
|
+
const group = client.createGroup();
|
|
69
|
+
const map = group.createMap();
|
|
70
|
+
map.set("key1", "value1", "trusting");
|
|
71
|
+
|
|
72
|
+
// Connect nodes
|
|
73
|
+
const [clientAsPeer, jazzCloudAsPeer] = connectedPeers(
|
|
74
|
+
"clientConnection",
|
|
75
|
+
"jazzCloudConnection",
|
|
76
|
+
{
|
|
77
|
+
peer1role: "client",
|
|
78
|
+
peer2role: "server",
|
|
79
|
+
},
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const [clientStoragePeer] = connectedPeers("clientStorage", "unusedPeer", {
|
|
83
|
+
peer1role: "client",
|
|
84
|
+
peer2role: "server",
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
client.syncManager.addPeer(jazzCloudAsPeer);
|
|
88
|
+
client.syncManager.addPeer(clientStoragePeer);
|
|
89
|
+
jazzCloud.syncManager.addPeer(clientAsPeer);
|
|
90
|
+
|
|
91
|
+
const subscriptionManager = client.syncManager.syncStateSubscriptionManager;
|
|
92
|
+
|
|
93
|
+
const updateToJazzCloudSpy = vi.fn();
|
|
94
|
+
const updateToStorageSpy = vi.fn();
|
|
95
|
+
const unsubscribe1 = subscriptionManager.subscribeToPeerUpdates(
|
|
96
|
+
"jazzCloudConnection",
|
|
97
|
+
updateToJazzCloudSpy,
|
|
98
|
+
);
|
|
99
|
+
const unsubscribe2 = subscriptionManager.subscribeToPeerUpdates(
|
|
100
|
+
"clientStorage",
|
|
101
|
+
updateToStorageSpy,
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
onTestFinished(() => {
|
|
105
|
+
unsubscribe1();
|
|
106
|
+
unsubscribe2();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
await client.syncManager.actuallySyncCoValue(map.core);
|
|
110
|
+
|
|
111
|
+
expect(updateToJazzCloudSpy).toHaveBeenCalledWith(
|
|
112
|
+
emptyKnownState(map.core.id),
|
|
113
|
+
false,
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
await waitFor(() => {
|
|
117
|
+
return subscriptionManager.getIsCoValueFullyUploadedIntoPeer(
|
|
118
|
+
"jazzCloudConnection",
|
|
119
|
+
map.core.id,
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
expect(updateToJazzCloudSpy).toHaveBeenLastCalledWith(
|
|
124
|
+
client.syncManager.peers["jazzCloudConnection"]!.knownStates.get(
|
|
125
|
+
map.core.id,
|
|
126
|
+
)!,
|
|
127
|
+
true,
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
expect(updateToStorageSpy).toHaveBeenLastCalledWith(
|
|
131
|
+
emptyKnownState(map.core.id),
|
|
132
|
+
false,
|
|
133
|
+
);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("getIsCoValueFullyUploadedIntoPeer returns correct status", async () => {
|
|
137
|
+
// Setup nodes
|
|
138
|
+
const client = createTestNode();
|
|
139
|
+
const jazzCloud = createTestNode();
|
|
140
|
+
|
|
141
|
+
// Create test data
|
|
142
|
+
const group = client.createGroup();
|
|
143
|
+
const map = group.createMap();
|
|
144
|
+
map.set("key1", "value1", "trusting");
|
|
145
|
+
|
|
146
|
+
// Connect nodes
|
|
147
|
+
const [clientAsPeer, jazzCloudAsPeer] = connectedPeers(
|
|
148
|
+
"clientConnection",
|
|
149
|
+
"jazzCloudConnection",
|
|
150
|
+
{
|
|
151
|
+
peer1role: "client",
|
|
152
|
+
peer2role: "server",
|
|
153
|
+
},
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
client.syncManager.addPeer(jazzCloudAsPeer);
|
|
157
|
+
jazzCloud.syncManager.addPeer(clientAsPeer);
|
|
158
|
+
|
|
159
|
+
await client.syncManager.actuallySyncCoValue(map.core);
|
|
160
|
+
|
|
161
|
+
const subscriptionManager = client.syncManager.syncStateSubscriptionManager;
|
|
162
|
+
|
|
163
|
+
expect(
|
|
164
|
+
subscriptionManager.getIsCoValueFullyUploadedIntoPeer(
|
|
165
|
+
"jazzCloudConnection",
|
|
166
|
+
map.core.id,
|
|
167
|
+
),
|
|
168
|
+
).toBe(false);
|
|
169
|
+
|
|
170
|
+
await waitFor(() => {
|
|
171
|
+
return subscriptionManager.getIsCoValueFullyUploadedIntoPeer(
|
|
172
|
+
"jazzCloudConnection",
|
|
173
|
+
map.core.id,
|
|
174
|
+
);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
expect(
|
|
178
|
+
subscriptionManager.getIsCoValueFullyUploadedIntoPeer(
|
|
179
|
+
"jazzCloudConnection",
|
|
180
|
+
map.core.id,
|
|
181
|
+
),
|
|
182
|
+
).toBe(true);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test("unsubscribe stops receiving updates", async () => {
|
|
186
|
+
// Setup nodes
|
|
187
|
+
const client = createTestNode();
|
|
188
|
+
const jazzCloud = createTestNode();
|
|
189
|
+
|
|
190
|
+
// Create test data
|
|
191
|
+
const group = client.createGroup();
|
|
192
|
+
const map = group.createMap();
|
|
193
|
+
map.set("key1", "value1", "trusting");
|
|
194
|
+
|
|
195
|
+
// Connect nodes
|
|
196
|
+
const [clientAsPeer, jazzCloudAsPeer] = connectedPeers(
|
|
197
|
+
"clientConnection",
|
|
198
|
+
"jazzCloudConnection",
|
|
199
|
+
{
|
|
200
|
+
peer1role: "client",
|
|
201
|
+
peer2role: "server",
|
|
202
|
+
},
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
client.syncManager.addPeer(jazzCloudAsPeer);
|
|
206
|
+
jazzCloud.syncManager.addPeer(clientAsPeer);
|
|
207
|
+
|
|
208
|
+
const subscriptionManager = client.syncManager.syncStateSubscriptionManager;
|
|
209
|
+
const anyUpdateSpy = vi.fn();
|
|
210
|
+
const unsubscribe1 = subscriptionManager.subscribeToUpdates(anyUpdateSpy);
|
|
211
|
+
const unsubscribe2 = subscriptionManager.subscribeToPeerUpdates(
|
|
212
|
+
"jazzCloudConnection",
|
|
213
|
+
anyUpdateSpy,
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
unsubscribe1();
|
|
217
|
+
unsubscribe2();
|
|
218
|
+
|
|
219
|
+
await client.syncManager.actuallySyncCoValue(map.core);
|
|
220
|
+
|
|
221
|
+
anyUpdateSpy.mockClear();
|
|
222
|
+
|
|
223
|
+
await waitFor(() => {
|
|
224
|
+
return client.syncManager.syncStateSubscriptionManager.getIsCoValueFullyUploadedIntoPeer(
|
|
225
|
+
"jazzCloudConnection",
|
|
226
|
+
map.core.id,
|
|
227
|
+
);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
expect(anyUpdateSpy).not.toHaveBeenCalled();
|
|
231
|
+
});
|
|
232
|
+
});
|
package/src/tests/sync.test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, expect, test } from "vitest";
|
|
1
|
+
import { describe, expect, test, vi } from "vitest";
|
|
2
2
|
import { expectMap } from "../coValue.js";
|
|
3
3
|
import { CoValueHeader } from "../coValueCore.js";
|
|
4
4
|
import { RawAccountID } from "../coValues/account.js";
|
|
@@ -10,7 +10,11 @@ import { LocalNode } from "../localNode.js";
|
|
|
10
10
|
import { getPriorityFromHeader } from "../priority.js";
|
|
11
11
|
import { connectedPeers, newQueuePair } from "../streamUtils.js";
|
|
12
12
|
import { SyncMessage } from "../sync.js";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
createTestNode,
|
|
15
|
+
randomAnonymousAccountAndSessionID,
|
|
16
|
+
waitFor,
|
|
17
|
+
} from "./testUtils.js";
|
|
14
18
|
|
|
15
19
|
const Crypto = await WasmCrypto.create();
|
|
16
20
|
|
|
@@ -1561,6 +1565,303 @@ describe("sync - extra tests", () => {
|
|
|
1561
1565
|
});
|
|
1562
1566
|
});
|
|
1563
1567
|
|
|
1568
|
+
function createTwoConnectedNodes() {
|
|
1569
|
+
// Setup nodes
|
|
1570
|
+
const client = createTestNode();
|
|
1571
|
+
const jazzCloud = createTestNode();
|
|
1572
|
+
|
|
1573
|
+
// Connect nodes initially
|
|
1574
|
+
const [connectionWithClientAsPeer, jazzCloudConnectionAsPeer] =
|
|
1575
|
+
connectedPeers("connectionWithClient", "jazzCloudConnection", {
|
|
1576
|
+
peer1role: "client",
|
|
1577
|
+
peer2role: "server",
|
|
1578
|
+
});
|
|
1579
|
+
|
|
1580
|
+
client.syncManager.addPeer(jazzCloudConnectionAsPeer);
|
|
1581
|
+
jazzCloud.syncManager.addPeer(connectionWithClientAsPeer);
|
|
1582
|
+
|
|
1583
|
+
return {
|
|
1584
|
+
client,
|
|
1585
|
+
jazzCloud,
|
|
1586
|
+
connectionWithClientAsPeer,
|
|
1587
|
+
jazzCloudConnectionAsPeer,
|
|
1588
|
+
};
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
describe("SyncManager - knownStates vs optimisticKnownStates", () => {
|
|
1592
|
+
test("knownStates and optimisticKnownStates are the same when the coValue is fully synced", async () => {
|
|
1593
|
+
const { client } = createTwoConnectedNodes();
|
|
1594
|
+
|
|
1595
|
+
// Create test data
|
|
1596
|
+
const group = client.createGroup();
|
|
1597
|
+
const map = group.createMap();
|
|
1598
|
+
map.set("key1", "value1", "trusting");
|
|
1599
|
+
|
|
1600
|
+
await client.syncManager.actuallySyncCoValue(map.core);
|
|
1601
|
+
|
|
1602
|
+
// Wait for the full sync to complete
|
|
1603
|
+
await waitFor(() => {
|
|
1604
|
+
return client.syncManager.syncStateSubscriptionManager.getIsCoValueFullyUploadedIntoPeer(
|
|
1605
|
+
"jazzCloudConnection",
|
|
1606
|
+
map.core.id,
|
|
1607
|
+
);
|
|
1608
|
+
});
|
|
1609
|
+
|
|
1610
|
+
const peerState = client.syncManager.peers["jazzCloudConnection"]!;
|
|
1611
|
+
|
|
1612
|
+
// The optimisticKnownStates should be the same as the knownStates after the full sync is complete
|
|
1613
|
+
expect(peerState.optimisticKnownStates.get(map.core.id)).toEqual(
|
|
1614
|
+
peerState.knownStates.get(map.core.id),
|
|
1615
|
+
);
|
|
1616
|
+
});
|
|
1617
|
+
|
|
1618
|
+
test("optimisticKnownStates is updated as new transactions are received, while knownStates only when the coValue is fully synced", async () => {
|
|
1619
|
+
const { client, jazzCloudConnectionAsPeer } = createTwoConnectedNodes();
|
|
1620
|
+
|
|
1621
|
+
// Create test data and sync the first change
|
|
1622
|
+
// We want that both the nodes know about the coValue so we can test
|
|
1623
|
+
// the content acknowledgement flow.
|
|
1624
|
+
const group = client.createGroup();
|
|
1625
|
+
const map = group.createMap();
|
|
1626
|
+
map.set("key1", "value1", "trusting");
|
|
1627
|
+
|
|
1628
|
+
await client.syncManager.actuallySyncCoValue(map.core);
|
|
1629
|
+
await waitFor(() => {
|
|
1630
|
+
return client.syncManager.syncStateSubscriptionManager.getIsCoValueFullyUploadedIntoPeer(
|
|
1631
|
+
"jazzCloudConnection",
|
|
1632
|
+
map.core.id,
|
|
1633
|
+
);
|
|
1634
|
+
});
|
|
1635
|
+
|
|
1636
|
+
map.set("key2", "value2", "trusting");
|
|
1637
|
+
|
|
1638
|
+
await client.syncManager.actuallySyncCoValue(map.core);
|
|
1639
|
+
|
|
1640
|
+
// Block the content messages
|
|
1641
|
+
// The main difference between optimisticKnownStates and knownStates is that
|
|
1642
|
+
// optimisticKnownStates is updated when the content messages are sent,
|
|
1643
|
+
// while knownStates is only updated when we receive the "known" messages
|
|
1644
|
+
// that are acknowledging the receipt of the content messages
|
|
1645
|
+
const push = jazzCloudConnectionAsPeer.outgoing.push;
|
|
1646
|
+
const pushSpy = vi.spyOn(jazzCloudConnectionAsPeer.outgoing, "push");
|
|
1647
|
+
|
|
1648
|
+
const blockedMessages: SyncMessage[] = [];
|
|
1649
|
+
|
|
1650
|
+
pushSpy.mockImplementation(async (msg) => {
|
|
1651
|
+
if (msg.action === "content") {
|
|
1652
|
+
blockedMessages.push(msg);
|
|
1653
|
+
return Promise.resolve();
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
return push.call(jazzCloudConnectionAsPeer.outgoing, msg);
|
|
1657
|
+
});
|
|
1658
|
+
|
|
1659
|
+
const peerState = client.syncManager.peers["jazzCloudConnection"]!;
|
|
1660
|
+
|
|
1661
|
+
expect(peerState.optimisticKnownStates.get(map.core.id)).not.toEqual(
|
|
1662
|
+
peerState.knownStates.get(map.core.id),
|
|
1663
|
+
);
|
|
1664
|
+
|
|
1665
|
+
// Restore the implementation of push and send the blocked messages
|
|
1666
|
+
// After this the full sync can be completed and the other node will
|
|
1667
|
+
// respond with a "known" message acknowledging the receipt of the content messages
|
|
1668
|
+
pushSpy.mockRestore();
|
|
1669
|
+
|
|
1670
|
+
for (const msg of blockedMessages) {
|
|
1671
|
+
await jazzCloudConnectionAsPeer.outgoing.push(msg);
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
await waitFor(() => {
|
|
1675
|
+
return client.syncManager.syncStateSubscriptionManager.getIsCoValueFullyUploadedIntoPeer(
|
|
1676
|
+
"jazzCloudConnection",
|
|
1677
|
+
map.core.id,
|
|
1678
|
+
);
|
|
1679
|
+
});
|
|
1680
|
+
|
|
1681
|
+
expect(peerState.optimisticKnownStates.get(map.core.id)).toEqual(
|
|
1682
|
+
peerState.knownStates.get(map.core.id),
|
|
1683
|
+
);
|
|
1684
|
+
});
|
|
1685
|
+
});
|
|
1686
|
+
|
|
1687
|
+
describe("SyncManager.addPeer", () => {
|
|
1688
|
+
test("new peer gets a copy of previous peer's knownStates when replacing it", async () => {
|
|
1689
|
+
const { client } = createTwoConnectedNodes();
|
|
1690
|
+
|
|
1691
|
+
// Create test data
|
|
1692
|
+
const group = client.createGroup();
|
|
1693
|
+
const map = group.createMap();
|
|
1694
|
+
map.set("key1", "value1", "trusting");
|
|
1695
|
+
|
|
1696
|
+
await client.syncManager.actuallySyncCoValue(map.core);
|
|
1697
|
+
|
|
1698
|
+
// Wait for initial sync
|
|
1699
|
+
await waitFor(() => {
|
|
1700
|
+
return client.syncManager.syncStateSubscriptionManager.getIsCoValueFullyUploadedIntoPeer(
|
|
1701
|
+
"jazzCloudConnection",
|
|
1702
|
+
map.core.id,
|
|
1703
|
+
);
|
|
1704
|
+
});
|
|
1705
|
+
|
|
1706
|
+
// Store the initial known states
|
|
1707
|
+
const initialKnownStates =
|
|
1708
|
+
client.syncManager.peers["jazzCloudConnection"]!.knownStates;
|
|
1709
|
+
|
|
1710
|
+
// Create new connection with same ID
|
|
1711
|
+
const [jazzCloudConnectionAsPeer2] = connectedPeers(
|
|
1712
|
+
"jazzCloudConnection",
|
|
1713
|
+
"unusedPeer",
|
|
1714
|
+
{
|
|
1715
|
+
peer1role: "server",
|
|
1716
|
+
peer2role: "client",
|
|
1717
|
+
},
|
|
1718
|
+
);
|
|
1719
|
+
|
|
1720
|
+
// Add new peer with same ID
|
|
1721
|
+
client.syncManager.addPeer(jazzCloudConnectionAsPeer2);
|
|
1722
|
+
|
|
1723
|
+
// Verify that the new peer has a copy of the previous known states
|
|
1724
|
+
const newPeerKnownStates =
|
|
1725
|
+
client.syncManager.peers["jazzCloudConnection"]!.knownStates;
|
|
1726
|
+
|
|
1727
|
+
expect(newPeerKnownStates).not.toBe(initialKnownStates); // Should be a different instance
|
|
1728
|
+
expect(newPeerKnownStates.get(map.core.id)).toEqual(
|
|
1729
|
+
initialKnownStates.get(map.core.id),
|
|
1730
|
+
);
|
|
1731
|
+
});
|
|
1732
|
+
|
|
1733
|
+
test("new peer with new ID starts with empty knownStates", async () => {
|
|
1734
|
+
const { client } = createTwoConnectedNodes();
|
|
1735
|
+
|
|
1736
|
+
// Create test data
|
|
1737
|
+
const group = client.createGroup();
|
|
1738
|
+
const map = group.createMap();
|
|
1739
|
+
map.set("key1", "value1", "trusting");
|
|
1740
|
+
|
|
1741
|
+
await client.syncManager.actuallySyncCoValue(map.core);
|
|
1742
|
+
|
|
1743
|
+
// Wait for initial sync
|
|
1744
|
+
await waitFor(() => {
|
|
1745
|
+
return client.syncManager.syncStateSubscriptionManager.getIsCoValueFullyUploadedIntoPeer(
|
|
1746
|
+
"jazzCloudConnection",
|
|
1747
|
+
map.core.id,
|
|
1748
|
+
);
|
|
1749
|
+
});
|
|
1750
|
+
|
|
1751
|
+
// Connect second peer with different ID
|
|
1752
|
+
const [brandNewPeer] = connectedPeers("brandNewPeer", "unusedPeer", {
|
|
1753
|
+
peer1role: "client",
|
|
1754
|
+
peer2role: "server",
|
|
1755
|
+
});
|
|
1756
|
+
|
|
1757
|
+
// Add new peer with different ID
|
|
1758
|
+
client.syncManager.addPeer(brandNewPeer);
|
|
1759
|
+
|
|
1760
|
+
// Verify that the new peer starts with empty known states
|
|
1761
|
+
const newPeerKnownStates =
|
|
1762
|
+
client.syncManager.peers["brandNewPeer"]!.knownStates;
|
|
1763
|
+
expect(newPeerKnownStates.get(map.core.id)).toBe(undefined);
|
|
1764
|
+
});
|
|
1765
|
+
|
|
1766
|
+
test("when adding a peer with the same ID as a previous peer, the previous peer is closed", async () => {
|
|
1767
|
+
const { client } = createTwoConnectedNodes();
|
|
1768
|
+
|
|
1769
|
+
// Store reference to first peer
|
|
1770
|
+
const firstPeer = client.syncManager.peers["jazzCloudConnection"]!;
|
|
1771
|
+
const closeSpy = vi.spyOn(firstPeer, "gracefulShutdown");
|
|
1772
|
+
|
|
1773
|
+
// Create and add replacement peer
|
|
1774
|
+
const [jazzCloudConnectionAsPeer2] = connectedPeers(
|
|
1775
|
+
"jazzCloudConnection",
|
|
1776
|
+
"unusedPeer",
|
|
1777
|
+
{
|
|
1778
|
+
peer1role: "server",
|
|
1779
|
+
peer2role: "client",
|
|
1780
|
+
},
|
|
1781
|
+
);
|
|
1782
|
+
|
|
1783
|
+
client.syncManager.addPeer(jazzCloudConnectionAsPeer2);
|
|
1784
|
+
|
|
1785
|
+
// Verify thet the first peer had ben closed correctly
|
|
1786
|
+
expect(closeSpy).toHaveBeenCalled();
|
|
1787
|
+
expect(firstPeer.closed).toBe(true);
|
|
1788
|
+
});
|
|
1789
|
+
|
|
1790
|
+
test("when adding a peer with the same ID as a previous peer and the previous peer is closed, do not attempt to close it again", async () => {
|
|
1791
|
+
const { client } = createTwoConnectedNodes();
|
|
1792
|
+
|
|
1793
|
+
// Store reference to first peer
|
|
1794
|
+
const firstPeer = client.syncManager.peers["jazzCloudConnection"]!;
|
|
1795
|
+
|
|
1796
|
+
firstPeer.gracefulShutdown();
|
|
1797
|
+
const closeSpy = vi.spyOn(firstPeer, "gracefulShutdown");
|
|
1798
|
+
|
|
1799
|
+
// Create and add replacement peer
|
|
1800
|
+
const [jazzCloudConnectionAsPeer2] = connectedPeers(
|
|
1801
|
+
"jazzCloudConnection",
|
|
1802
|
+
"unusedPeer",
|
|
1803
|
+
{
|
|
1804
|
+
peer1role: "server",
|
|
1805
|
+
peer2role: "client",
|
|
1806
|
+
},
|
|
1807
|
+
);
|
|
1808
|
+
|
|
1809
|
+
client.syncManager.addPeer(jazzCloudConnectionAsPeer2);
|
|
1810
|
+
|
|
1811
|
+
// Verify thet the first peer had not been closed again
|
|
1812
|
+
expect(closeSpy).not.toHaveBeenCalled();
|
|
1813
|
+
expect(firstPeer.closed).toBe(true);
|
|
1814
|
+
});
|
|
1815
|
+
});
|
|
1816
|
+
|
|
1817
|
+
describe("waitForUploadIntoPeer", () => {
|
|
1818
|
+
test("should resolve when the coValue is fully uploaded into the peer", async () => {
|
|
1819
|
+
const { client, jazzCloudConnectionAsPeer: peer } =
|
|
1820
|
+
createTwoConnectedNodes();
|
|
1821
|
+
|
|
1822
|
+
// Create test data
|
|
1823
|
+
const group = client.createGroup();
|
|
1824
|
+
const map = group.createMap();
|
|
1825
|
+
map.set("key1", "value1", "trusting");
|
|
1826
|
+
|
|
1827
|
+
await client.syncManager.actuallySyncCoValue(map.core);
|
|
1828
|
+
|
|
1829
|
+
await expect(
|
|
1830
|
+
Promise.race([
|
|
1831
|
+
client.syncManager.waitForUploadIntoPeer(peer.id, map.core.id),
|
|
1832
|
+
new Promise((_, reject) =>
|
|
1833
|
+
setTimeout(() => reject(new Error("Timeout")), 100),
|
|
1834
|
+
),
|
|
1835
|
+
]),
|
|
1836
|
+
).resolves.toBe(true);
|
|
1837
|
+
});
|
|
1838
|
+
|
|
1839
|
+
test("should not resolve when the coValue is not synced", async () => {
|
|
1840
|
+
const { client, jazzCloudConnectionAsPeer: peer } =
|
|
1841
|
+
createTwoConnectedNodes();
|
|
1842
|
+
|
|
1843
|
+
// Create test data
|
|
1844
|
+
const group = client.createGroup();
|
|
1845
|
+
const map = group.createMap();
|
|
1846
|
+
map.set("key1", "value1", "trusting");
|
|
1847
|
+
|
|
1848
|
+
vi.spyOn(peer.outgoing, "push").mockImplementation(async () => {
|
|
1849
|
+
return Promise.resolve();
|
|
1850
|
+
});
|
|
1851
|
+
|
|
1852
|
+
await client.syncManager.actuallySyncCoValue(map.core);
|
|
1853
|
+
|
|
1854
|
+
await expect(
|
|
1855
|
+
Promise.race([
|
|
1856
|
+
client.syncManager.waitForUploadIntoPeer(peer.id, map.core.id),
|
|
1857
|
+
new Promise((_, reject) =>
|
|
1858
|
+
setTimeout(() => reject(new Error("Timeout")), 100),
|
|
1859
|
+
),
|
|
1860
|
+
]),
|
|
1861
|
+
).rejects.toThrow("Timeout");
|
|
1862
|
+
});
|
|
1863
|
+
});
|
|
1864
|
+
|
|
1564
1865
|
function groupContentEx(group: RawGroup) {
|
|
1565
1866
|
return {
|
|
1566
1867
|
action: "content",
|
package/src/tests/testUtils.ts
CHANGED
|
@@ -18,6 +18,11 @@ export function randomAnonymousAccountAndSessionID(): [
|
|
|
18
18
|
return [new ControlledAgent(agentSecret, Crypto), sessionID];
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
export function createTestNode() {
|
|
22
|
+
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
23
|
+
return new LocalNode(admin, session, Crypto);
|
|
24
|
+
}
|
|
25
|
+
|
|
21
26
|
export function newGroup() {
|
|
22
27
|
const [admin, sessionID] = randomAnonymousAccountAndSessionID();
|
|
23
28
|
|
|
@@ -93,3 +98,31 @@ export function shouldNotResolve<T>(
|
|
|
93
98
|
setTimeout(resolve, ops.timeout);
|
|
94
99
|
});
|
|
95
100
|
}
|
|
101
|
+
|
|
102
|
+
export function waitFor(callback: () => boolean | void) {
|
|
103
|
+
return new Promise<void>((resolve, reject) => {
|
|
104
|
+
const checkPassed = () => {
|
|
105
|
+
try {
|
|
106
|
+
return { ok: callback(), error: null };
|
|
107
|
+
} catch (error) {
|
|
108
|
+
return { ok: false, error };
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
let retries = 0;
|
|
113
|
+
|
|
114
|
+
const interval = setInterval(() => {
|
|
115
|
+
const { ok, error } = checkPassed();
|
|
116
|
+
|
|
117
|
+
if (ok !== false) {
|
|
118
|
+
clearInterval(interval);
|
|
119
|
+
resolve();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (++retries > 10) {
|
|
123
|
+
clearInterval(interval);
|
|
124
|
+
reject(error);
|
|
125
|
+
}
|
|
126
|
+
}, 100);
|
|
127
|
+
});
|
|
128
|
+
}
|