cojson 0.13.5 → 0.13.7
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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +10 -0
- package/LICENSE.txt +1 -1
- package/dist/PeerState.d.ts +6 -0
- package/dist/PeerState.d.ts.map +1 -1
- package/dist/PeerState.js +43 -0
- package/dist/PeerState.js.map +1 -1
- package/dist/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore.js +1 -0
- package/dist/coValueCore.js.map +1 -1
- package/dist/coValueState.d.ts +1 -0
- package/dist/coValueState.d.ts.map +1 -1
- package/dist/coValueState.js +27 -2
- package/dist/coValueState.js.map +1 -1
- package/dist/coValues/group.d.ts +1 -0
- package/dist/coValues/group.d.ts.map +1 -1
- package/dist/coValues/group.js +45 -21
- package/dist/coValues/group.js.map +1 -1
- package/dist/crypto/crypto.d.ts +2 -2
- package/dist/crypto/crypto.d.ts.map +1 -1
- package/dist/permissions.d.ts +1 -0
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +19 -3
- package/dist/permissions.js.map +1 -1
- package/dist/storage/FileSystem.d.ts +2 -2
- package/dist/storage/FileSystem.d.ts.map +1 -1
- package/dist/sync.d.ts +14 -4
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +146 -146
- package/dist/sync.js.map +1 -1
- package/dist/tests/SyncStateManager.test.js +51 -46
- package/dist/tests/SyncStateManager.test.js.map +1 -1
- package/dist/tests/coValueCore.test.js +51 -2
- package/dist/tests/coValueCore.test.js.map +1 -1
- package/dist/tests/coValueState.test.js +31 -4
- package/dist/tests/coValueState.test.js.map +1 -1
- package/dist/tests/group.test.js +135 -2
- package/dist/tests/group.test.js.map +1 -1
- package/dist/tests/messagesTestUtils.d.ts +13 -0
- package/dist/tests/messagesTestUtils.d.ts.map +1 -0
- package/dist/tests/messagesTestUtils.js +42 -0
- package/dist/tests/messagesTestUtils.js.map +1 -0
- package/dist/tests/sync.load.test.d.ts +2 -0
- package/dist/tests/sync.load.test.d.ts.map +1 -0
- package/dist/tests/sync.load.test.js +249 -0
- package/dist/tests/sync.load.test.js.map +1 -0
- package/dist/tests/sync.mesh.test.d.ts +2 -0
- package/dist/tests/sync.mesh.test.d.ts.map +1 -0
- package/dist/tests/sync.mesh.test.js +157 -0
- package/dist/tests/sync.mesh.test.js.map +1 -0
- package/dist/tests/sync.peerReconciliation.test.d.ts +2 -0
- package/dist/tests/sync.peerReconciliation.test.d.ts.map +1 -0
- package/dist/tests/sync.peerReconciliation.test.js +130 -0
- package/dist/tests/sync.peerReconciliation.test.js.map +1 -0
- package/dist/tests/sync.storage.test.d.ts +2 -0
- package/dist/tests/sync.storage.test.d.ts.map +1 -0
- package/dist/tests/sync.storage.test.js +201 -0
- package/dist/tests/sync.storage.test.js.map +1 -0
- package/dist/tests/sync.test.js +139 -1048
- package/dist/tests/sync.test.js.map +1 -1
- package/dist/tests/sync.upload.test.d.ts +2 -0
- package/dist/tests/sync.upload.test.d.ts.map +1 -0
- package/dist/tests/sync.upload.test.js +156 -0
- package/dist/tests/sync.upload.test.js.map +1 -0
- package/dist/tests/testUtils.d.ts +76 -33
- package/dist/tests/testUtils.d.ts.map +1 -1
- package/dist/tests/testUtils.js +153 -47
- package/dist/tests/testUtils.js.map +1 -1
- package/package.json +2 -2
- package/src/PeerState.ts +59 -1
- package/src/coValueCore.ts +1 -0
- package/src/coValueState.ts +34 -3
- package/src/coValues/group.ts +83 -45
- package/src/permissions.ts +31 -3
- package/src/sync.ts +169 -185
- package/src/tests/SyncStateManager.test.ts +58 -70
- package/src/tests/coValueCore.test.ts +70 -1
- package/src/tests/coValueState.test.ts +59 -5
- package/src/tests/group.test.ts +250 -2
- package/src/tests/messagesTestUtils.ts +75 -0
- package/src/tests/sync.load.test.ts +327 -0
- package/src/tests/sync.mesh.test.ts +219 -0
- package/src/tests/sync.peerReconciliation.test.ts +201 -0
- package/src/tests/sync.storage.test.ts +259 -0
- package/src/tests/sync.test.ts +170 -1245
- package/src/tests/sync.upload.test.ts +202 -0
- package/src/tests/testUtils.ts +213 -61
package/dist/tests/sync.test.js
CHANGED
|
@@ -1,388 +1,17 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test, vi, } from "vitest";
|
|
2
2
|
import { expectMap } from "../coValue.js";
|
|
3
3
|
import { RawCoMap } from "../coValues/coMap.js";
|
|
4
4
|
import { WasmCrypto } from "../crypto/WasmCrypto.js";
|
|
5
|
-
import { stableStringify } from "../jsonStringify.js";
|
|
6
5
|
import { LocalNode } from "../localNode.js";
|
|
7
|
-
import { getPriorityFromHeader } from "../priority.js";
|
|
8
6
|
import { connectedPeers, newQueuePair } from "../streamUtils.js";
|
|
9
|
-
import { blockMessageTypeOnOutgoingPeer,
|
|
7
|
+
import { blockMessageTypeOnOutgoingPeer, connectTwoPeers, createTestMetricReader, createTestNode, loadCoValueOrFail, randomAnonymousAccountAndSessionID, setupTestAccount, setupTestNode, tearDownTestMetricReader, waitFor, } from "./testUtils.js";
|
|
10
8
|
const Crypto = await WasmCrypto.create();
|
|
11
|
-
let jazzCloud =
|
|
12
|
-
|
|
13
|
-
jazzCloud = setupSyncServer();
|
|
14
|
-
});
|
|
15
|
-
test("Node replies with initial tx and header to empty subscribe", async () => {
|
|
16
|
-
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
17
|
-
const node = new LocalNode(admin, session, Crypto);
|
|
18
|
-
const group = node.createGroup();
|
|
19
|
-
const map = group.createMap();
|
|
20
|
-
map.set("hello", "world", "trusting");
|
|
21
|
-
const [inRx, inTx] = newQueuePair();
|
|
22
|
-
const [outRx, outTx] = newQueuePair();
|
|
23
|
-
const outRxQ = outRx[Symbol.asyncIterator]();
|
|
24
|
-
node.syncManager.addPeer({
|
|
25
|
-
id: "test",
|
|
26
|
-
incoming: inRx,
|
|
27
|
-
outgoing: outTx,
|
|
28
|
-
role: "peer",
|
|
29
|
-
crashOnClose: true,
|
|
30
|
-
});
|
|
31
|
-
await inTx.push({
|
|
32
|
-
action: "load",
|
|
33
|
-
id: map.core.id,
|
|
34
|
-
header: false,
|
|
35
|
-
sessions: {},
|
|
36
|
-
});
|
|
37
|
-
// expect((await outRxQ.next()).value).toMatchObject(admStateEx(admin.id));
|
|
38
|
-
expect((await outRxQ.next()).value).toMatchObject(groupStateEx(group));
|
|
39
|
-
const mapTellKnownStateMsg = (await outRxQ.next()).value;
|
|
40
|
-
expect(mapTellKnownStateMsg).toEqual({
|
|
41
|
-
action: "known",
|
|
42
|
-
...map.core.knownState(),
|
|
43
|
-
});
|
|
44
|
-
// expect((await outRxQ.next()).value).toMatchObject(admContEx(admin.id));
|
|
45
|
-
expect((await outRxQ.next()).value).toMatchObject(groupContentEx(group));
|
|
46
|
-
const newContentMsg = (await outRxQ.next()).value;
|
|
47
|
-
const expectedHeader = {
|
|
48
|
-
type: "comap",
|
|
49
|
-
ruleset: { type: "ownedByGroup", group: group.id },
|
|
50
|
-
meta: null,
|
|
51
|
-
createdAt: map.core.header.createdAt,
|
|
52
|
-
uniqueness: map.core.header.uniqueness,
|
|
53
|
-
};
|
|
54
|
-
expect(newContentMsg).toEqual({
|
|
55
|
-
action: "content",
|
|
56
|
-
id: map.core.id,
|
|
57
|
-
header: expectedHeader,
|
|
58
|
-
new: {
|
|
59
|
-
[node.currentSessionID]: {
|
|
60
|
-
after: 0,
|
|
61
|
-
newTransactions: [
|
|
62
|
-
{
|
|
63
|
-
privacy: "trusting",
|
|
64
|
-
madeAt: map.core.sessionLogs.get(node.currentSessionID)
|
|
65
|
-
.transactions[0].madeAt,
|
|
66
|
-
changes: stableStringify([
|
|
67
|
-
{
|
|
68
|
-
op: "set",
|
|
69
|
-
key: "hello",
|
|
70
|
-
value: "world",
|
|
71
|
-
},
|
|
72
|
-
]),
|
|
73
|
-
},
|
|
74
|
-
],
|
|
75
|
-
lastSignature: map.core.sessionLogs.get(node.currentSessionID)
|
|
76
|
-
.lastSignature,
|
|
77
|
-
},
|
|
78
|
-
},
|
|
79
|
-
priority: getPriorityFromHeader(map.core.header),
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
test("Node replies with only new tx to subscribe with some known state", async () => {
|
|
83
|
-
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
84
|
-
const node = new LocalNode(admin, session, Crypto);
|
|
85
|
-
const group = node.createGroup();
|
|
86
|
-
const map = group.createMap();
|
|
87
|
-
map.set("hello", "world", "trusting");
|
|
88
|
-
map.set("goodbye", "world", "trusting");
|
|
89
|
-
const [inRx, inTx] = newQueuePair();
|
|
90
|
-
const [outRx, outTx] = newQueuePair();
|
|
91
|
-
const outRxQ = outRx[Symbol.asyncIterator]();
|
|
92
|
-
node.syncManager.addPeer({
|
|
93
|
-
id: "test",
|
|
94
|
-
incoming: inRx,
|
|
95
|
-
outgoing: outTx,
|
|
96
|
-
role: "peer",
|
|
97
|
-
crashOnClose: true,
|
|
98
|
-
});
|
|
99
|
-
await inTx.push({
|
|
100
|
-
action: "load",
|
|
101
|
-
id: map.core.id,
|
|
102
|
-
header: true,
|
|
103
|
-
sessions: {
|
|
104
|
-
[node.currentSessionID]: 1,
|
|
105
|
-
},
|
|
106
|
-
});
|
|
107
|
-
// expect((await outRxQ.next()).value).toMatchObject(admStateEx(admin.id));
|
|
108
|
-
expect((await outRxQ.next()).value).toMatchObject(groupStateEx(group));
|
|
109
|
-
const mapTellKnownStateMsg = (await outRxQ.next()).value;
|
|
110
|
-
expect(mapTellKnownStateMsg).toEqual({
|
|
111
|
-
action: "known",
|
|
112
|
-
...map.core.knownState(),
|
|
113
|
-
});
|
|
114
|
-
// expect((await outRxQ.next()).value).toMatchObject(admContEx(admin.id));
|
|
115
|
-
expect((await outRxQ.next()).value).toMatchObject(groupContentEx(group));
|
|
116
|
-
const mapNewContentMsg = (await outRxQ.next()).value;
|
|
117
|
-
expect(mapNewContentMsg).toEqual({
|
|
118
|
-
action: "content",
|
|
119
|
-
id: map.core.id,
|
|
120
|
-
header: undefined,
|
|
121
|
-
new: {
|
|
122
|
-
[node.currentSessionID]: {
|
|
123
|
-
after: 1,
|
|
124
|
-
newTransactions: [
|
|
125
|
-
{
|
|
126
|
-
privacy: "trusting",
|
|
127
|
-
madeAt: map.core.sessionLogs.get(node.currentSessionID)
|
|
128
|
-
.transactions[1].madeAt,
|
|
129
|
-
changes: stableStringify([
|
|
130
|
-
{
|
|
131
|
-
op: "set",
|
|
132
|
-
key: "goodbye",
|
|
133
|
-
value: "world",
|
|
134
|
-
},
|
|
135
|
-
]),
|
|
136
|
-
},
|
|
137
|
-
],
|
|
138
|
-
lastSignature: map.core.sessionLogs.get(node.currentSessionID)
|
|
139
|
-
.lastSignature,
|
|
140
|
-
},
|
|
141
|
-
},
|
|
142
|
-
priority: getPriorityFromHeader(map.core.header),
|
|
143
|
-
});
|
|
144
|
-
});
|
|
145
|
-
test.todo("TODO: node only replies with new tx to subscribe with some known state, even in the depended on coValues");
|
|
146
|
-
test("After subscribing, node sends own known state and new txs to peer", async () => {
|
|
147
|
-
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
148
|
-
const node = new LocalNode(admin, session, Crypto);
|
|
149
|
-
const group = node.createGroup();
|
|
150
|
-
const map = group.createMap();
|
|
151
|
-
const [inRx, inTx] = newQueuePair();
|
|
152
|
-
const [outRx, outTx] = newQueuePair();
|
|
153
|
-
const outRxQ = outRx[Symbol.asyncIterator]();
|
|
154
|
-
node.syncManager.addPeer({
|
|
155
|
-
id: "test",
|
|
156
|
-
incoming: inRx,
|
|
157
|
-
outgoing: outTx,
|
|
158
|
-
role: "peer",
|
|
159
|
-
crashOnClose: true,
|
|
160
|
-
});
|
|
161
|
-
await inTx.push({
|
|
162
|
-
action: "load",
|
|
163
|
-
id: map.core.id,
|
|
164
|
-
header: false,
|
|
165
|
-
sessions: {
|
|
166
|
-
[node.currentSessionID]: 0,
|
|
167
|
-
},
|
|
168
|
-
});
|
|
169
|
-
// expect((await outRxQ.next()).value).toMatchObject(admStateEx(admin.id));
|
|
170
|
-
expect((await outRxQ.next()).value).toMatchObject(groupStateEx(group));
|
|
171
|
-
const mapTellKnownStateMsg = (await outRxQ.next()).value;
|
|
172
|
-
expect(mapTellKnownStateMsg).toEqual({
|
|
173
|
-
action: "known",
|
|
174
|
-
...map.core.knownState(),
|
|
175
|
-
});
|
|
176
|
-
// expect((await outRxQ.next()).value).toMatchObject(admContEx(admin.id));
|
|
177
|
-
expect((await outRxQ.next()).value).toMatchObject(groupContentEx(group));
|
|
178
|
-
const mapNewContentHeaderOnlyMsg = (await outRxQ.next()).value;
|
|
179
|
-
expect(mapNewContentHeaderOnlyMsg).toEqual({
|
|
180
|
-
action: "content",
|
|
181
|
-
id: map.core.id,
|
|
182
|
-
header: map.core.header,
|
|
183
|
-
new: {},
|
|
184
|
-
priority: getPriorityFromHeader(map.core.header),
|
|
185
|
-
});
|
|
186
|
-
map.set("hello", "world", "trusting");
|
|
187
|
-
const mapEditMsg1 = (await outRxQ.next()).value;
|
|
188
|
-
expect(mapEditMsg1).toEqual({
|
|
189
|
-
action: "content",
|
|
190
|
-
id: map.core.id,
|
|
191
|
-
new: {
|
|
192
|
-
[node.currentSessionID]: {
|
|
193
|
-
after: 0,
|
|
194
|
-
newTransactions: [
|
|
195
|
-
{
|
|
196
|
-
privacy: "trusting",
|
|
197
|
-
madeAt: map.core.sessionLogs.get(node.currentSessionID)
|
|
198
|
-
.transactions[0].madeAt,
|
|
199
|
-
changes: stableStringify([
|
|
200
|
-
{
|
|
201
|
-
op: "set",
|
|
202
|
-
key: "hello",
|
|
203
|
-
value: "world",
|
|
204
|
-
},
|
|
205
|
-
]),
|
|
206
|
-
},
|
|
207
|
-
],
|
|
208
|
-
lastSignature: map.core.sessionLogs.get(node.currentSessionID)
|
|
209
|
-
.lastSignature,
|
|
210
|
-
},
|
|
211
|
-
},
|
|
212
|
-
priority: getPriorityFromHeader(map.core.header),
|
|
213
|
-
});
|
|
214
|
-
map.set("goodbye", "world", "trusting");
|
|
215
|
-
const mapEditMsg2 = (await outRxQ.next()).value;
|
|
216
|
-
expect(mapEditMsg2).toEqual({
|
|
217
|
-
action: "content",
|
|
218
|
-
id: map.core.id,
|
|
219
|
-
new: {
|
|
220
|
-
[node.currentSessionID]: {
|
|
221
|
-
after: 1,
|
|
222
|
-
newTransactions: [
|
|
223
|
-
{
|
|
224
|
-
privacy: "trusting",
|
|
225
|
-
madeAt: map.core.sessionLogs.get(node.currentSessionID)
|
|
226
|
-
.transactions[1].madeAt,
|
|
227
|
-
changes: stableStringify([
|
|
228
|
-
{
|
|
229
|
-
op: "set",
|
|
230
|
-
key: "goodbye",
|
|
231
|
-
value: "world",
|
|
232
|
-
},
|
|
233
|
-
]),
|
|
234
|
-
},
|
|
235
|
-
],
|
|
236
|
-
lastSignature: map.core.sessionLogs.get(node.currentSessionID)
|
|
237
|
-
.lastSignature,
|
|
238
|
-
},
|
|
239
|
-
},
|
|
240
|
-
priority: getPriorityFromHeader(map.core.header),
|
|
241
|
-
});
|
|
9
|
+
let jazzCloud = setupTestNode({
|
|
10
|
+
isSyncServer: true,
|
|
242
11
|
});
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const group = node.createGroup();
|
|
247
|
-
const map = group.createMap();
|
|
248
|
-
map.set("hello", "world", "trusting");
|
|
249
|
-
const [inRx, inTx] = newQueuePair();
|
|
250
|
-
const [outRx, outTx] = newQueuePair();
|
|
251
|
-
const outRxQ = outRx[Symbol.asyncIterator]();
|
|
252
|
-
node.syncManager.addPeer({
|
|
253
|
-
id: "test",
|
|
254
|
-
incoming: inRx,
|
|
255
|
-
outgoing: outTx,
|
|
256
|
-
role: "peer",
|
|
257
|
-
crashOnClose: true,
|
|
258
|
-
});
|
|
259
|
-
// expect((await outRxQ.next()).value).toMatchObject(groupStateEx(group));
|
|
260
|
-
await inTx.push({
|
|
261
|
-
action: "known",
|
|
262
|
-
id: map.core.id,
|
|
263
|
-
header: false,
|
|
264
|
-
sessions: {
|
|
265
|
-
[node.currentSessionID]: 0,
|
|
266
|
-
},
|
|
267
|
-
});
|
|
268
|
-
// expect((await outRxQ.next()).value).toMatchObject(admStateEx(admin.id));
|
|
269
|
-
expect((await outRxQ.next()).value).toMatchObject(groupStateEx(group));
|
|
270
|
-
const mapTellKnownStateMsg = (await outRxQ.next()).value;
|
|
271
|
-
expect(mapTellKnownStateMsg).toEqual({
|
|
272
|
-
action: "known",
|
|
273
|
-
...map.core.knownState(),
|
|
274
|
-
});
|
|
275
|
-
// expect((await outRxQ.next()).value).toMatchObject(admContEx(admin.id));
|
|
276
|
-
expect((await outRxQ.next()).value).toMatchObject(groupContentEx(group));
|
|
277
|
-
const mapNewContentMsg = (await outRxQ.next()).value;
|
|
278
|
-
expect(mapNewContentMsg).toEqual({
|
|
279
|
-
action: "content",
|
|
280
|
-
id: map.core.id,
|
|
281
|
-
header: map.core.header,
|
|
282
|
-
new: {
|
|
283
|
-
[node.currentSessionID]: {
|
|
284
|
-
after: 0,
|
|
285
|
-
newTransactions: [
|
|
286
|
-
{
|
|
287
|
-
privacy: "trusting",
|
|
288
|
-
madeAt: map.core.sessionLogs.get(node.currentSessionID)
|
|
289
|
-
.transactions[0].madeAt,
|
|
290
|
-
changes: stableStringify([
|
|
291
|
-
{
|
|
292
|
-
op: "set",
|
|
293
|
-
key: "hello",
|
|
294
|
-
value: "world",
|
|
295
|
-
},
|
|
296
|
-
]),
|
|
297
|
-
},
|
|
298
|
-
],
|
|
299
|
-
lastSignature: map.core.sessionLogs.get(node.currentSessionID)
|
|
300
|
-
.lastSignature,
|
|
301
|
-
},
|
|
302
|
-
},
|
|
303
|
-
priority: getPriorityFromHeader(map.core.header),
|
|
304
|
-
});
|
|
305
|
-
});
|
|
306
|
-
test("No matter the optimistic known state, node respects invalid known state messages and resyncs", async () => {
|
|
307
|
-
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
308
|
-
const node = new LocalNode(admin, session, Crypto);
|
|
309
|
-
const group = node.createGroup();
|
|
310
|
-
const map = group.createMap();
|
|
311
|
-
const [inRx, inTx] = newQueuePair();
|
|
312
|
-
const [outRx, outTx] = newQueuePair();
|
|
313
|
-
const outRxQ = outRx[Symbol.asyncIterator]();
|
|
314
|
-
node.syncManager.addPeer({
|
|
315
|
-
id: "test",
|
|
316
|
-
incoming: inRx,
|
|
317
|
-
outgoing: outTx,
|
|
318
|
-
role: "peer",
|
|
319
|
-
crashOnClose: true,
|
|
320
|
-
});
|
|
321
|
-
await inTx.push({
|
|
322
|
-
action: "load",
|
|
323
|
-
id: map.core.id,
|
|
324
|
-
header: false,
|
|
325
|
-
sessions: {
|
|
326
|
-
[node.currentSessionID]: 0,
|
|
327
|
-
},
|
|
328
|
-
});
|
|
329
|
-
// expect((await outRxQ.next()).value).toMatchObject(admStateEx(admin.id));
|
|
330
|
-
expect((await outRxQ.next()).value).toMatchObject(groupStateEx(group));
|
|
331
|
-
const mapTellKnownStateMsg = (await outRxQ.next()).value;
|
|
332
|
-
expect(mapTellKnownStateMsg).toEqual({
|
|
333
|
-
action: "known",
|
|
334
|
-
...map.core.knownState(),
|
|
335
|
-
});
|
|
336
|
-
// expect((await outRxQ.next()).value).toMatchObject(admContEx(admin.id));
|
|
337
|
-
expect((await outRxQ.next()).value).toMatchObject(groupContentEx(group));
|
|
338
|
-
const mapNewContentHeaderOnlyMsg = (await outRxQ.next()).value;
|
|
339
|
-
expect(mapNewContentHeaderOnlyMsg).toEqual({
|
|
340
|
-
action: "content",
|
|
341
|
-
id: map.core.id,
|
|
342
|
-
header: map.core.header,
|
|
343
|
-
new: {},
|
|
344
|
-
priority: getPriorityFromHeader(map.core.header),
|
|
345
|
-
});
|
|
346
|
-
map.set("hello", "world", "trusting");
|
|
347
|
-
map.set("goodbye", "world", "trusting");
|
|
348
|
-
const _mapEditMsgs = (await outRxQ.next()).value;
|
|
349
|
-
console.log("Sending correction");
|
|
350
|
-
await inTx.push({
|
|
351
|
-
action: "known",
|
|
352
|
-
isCorrection: true,
|
|
353
|
-
id: map.core.id,
|
|
354
|
-
header: true,
|
|
355
|
-
sessions: {
|
|
356
|
-
[node.currentSessionID]: 1,
|
|
357
|
-
},
|
|
358
|
-
});
|
|
359
|
-
const newContentAfterWrongAssumedState = (await outRxQ.next()).value;
|
|
360
|
-
expect(newContentAfterWrongAssumedState).toEqual({
|
|
361
|
-
action: "content",
|
|
362
|
-
id: map.core.id,
|
|
363
|
-
header: undefined,
|
|
364
|
-
new: {
|
|
365
|
-
[node.currentSessionID]: {
|
|
366
|
-
after: 1,
|
|
367
|
-
newTransactions: [
|
|
368
|
-
{
|
|
369
|
-
privacy: "trusting",
|
|
370
|
-
madeAt: map.core.sessionLogs.get(node.currentSessionID)
|
|
371
|
-
.transactions[1].madeAt,
|
|
372
|
-
changes: stableStringify([
|
|
373
|
-
{
|
|
374
|
-
op: "set",
|
|
375
|
-
key: "goodbye",
|
|
376
|
-
value: "world",
|
|
377
|
-
},
|
|
378
|
-
]),
|
|
379
|
-
},
|
|
380
|
-
],
|
|
381
|
-
lastSignature: map.core.sessionLogs.get(node.currentSessionID)
|
|
382
|
-
.lastSignature,
|
|
383
|
-
},
|
|
384
|
-
},
|
|
385
|
-
priority: getPriorityFromHeader(map.core.header),
|
|
12
|
+
beforeEach(async () => {
|
|
13
|
+
jazzCloud = setupTestNode({
|
|
14
|
+
isSyncServer: true,
|
|
386
15
|
});
|
|
387
16
|
});
|
|
388
17
|
test("If we add a peer, but it never subscribes to a coValue, it won't get any messages", async () => {
|
|
@@ -408,376 +37,59 @@ test("If we add a peer, but it never subscribes to a coValue, it won't get any m
|
|
|
408
37
|
]);
|
|
409
38
|
expect(result).toEqual("neverHappened");
|
|
410
39
|
});
|
|
411
|
-
test.todo("If we add a server peer, all updates to all coValues are sent to it, even if it doesn't subscribe", async () => {
|
|
412
|
-
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
413
|
-
const node = new LocalNode(admin, session, Crypto);
|
|
414
|
-
const group = node.createGroup();
|
|
415
|
-
const map = group.createMap();
|
|
416
|
-
const [inRx, _inTx] = newQueuePair();
|
|
417
|
-
const [outRx, outTx] = newQueuePair();
|
|
418
|
-
const outRxQ = outRx[Symbol.asyncIterator]();
|
|
419
|
-
node.syncManager.addPeer({
|
|
420
|
-
id: "test",
|
|
421
|
-
incoming: inRx,
|
|
422
|
-
outgoing: outTx,
|
|
423
|
-
role: "server",
|
|
424
|
-
crashOnClose: true,
|
|
425
|
-
});
|
|
426
|
-
// expect((await outRxQ.next()).value).toMatchObject({
|
|
427
|
-
// action: "load",
|
|
428
|
-
// id: adminID,
|
|
429
|
-
// });
|
|
430
|
-
expect((await outRxQ.next()).value).toMatchObject({
|
|
431
|
-
action: "load",
|
|
432
|
-
id: group.core.id,
|
|
433
|
-
});
|
|
434
|
-
const mapSubscribeMsg = (await outRxQ.next()).value;
|
|
435
|
-
expect(mapSubscribeMsg).toEqual({
|
|
436
|
-
action: "load",
|
|
437
|
-
id: map.core.id,
|
|
438
|
-
header: true,
|
|
439
|
-
sessions: {},
|
|
440
|
-
});
|
|
441
|
-
map.set("hello", "world", "trusting");
|
|
442
|
-
// expect((await outRxQ.next()).value).toMatchObject(admContEx(admin.id));
|
|
443
|
-
expect((await outRxQ.next()).value).toMatchObject(groupContentEx(group));
|
|
444
|
-
const mapNewContentMsg = (await outRxQ.next()).value;
|
|
445
|
-
expect(mapNewContentMsg).toEqual({
|
|
446
|
-
action: "content",
|
|
447
|
-
id: map.core.id,
|
|
448
|
-
header: map.core.header,
|
|
449
|
-
new: {
|
|
450
|
-
[node.currentSessionID]: {
|
|
451
|
-
after: 0,
|
|
452
|
-
newTransactions: [
|
|
453
|
-
{
|
|
454
|
-
privacy: "trusting",
|
|
455
|
-
madeAt: map.core.sessionLogs.get(node.currentSessionID)
|
|
456
|
-
.transactions[0].madeAt,
|
|
457
|
-
changes: stableStringify([
|
|
458
|
-
{
|
|
459
|
-
op: "set",
|
|
460
|
-
key: "hello",
|
|
461
|
-
value: "world",
|
|
462
|
-
},
|
|
463
|
-
]),
|
|
464
|
-
},
|
|
465
|
-
],
|
|
466
|
-
lastSignature: map.core.sessionLogs.get(node.currentSessionID)
|
|
467
|
-
.lastSignature,
|
|
468
|
-
},
|
|
469
|
-
},
|
|
470
|
-
priority: getPriorityFromHeader(map.core.header),
|
|
471
|
-
});
|
|
472
|
-
});
|
|
473
|
-
test.skip("If we add a server peer, newly created coValues are auto-subscribed to", async () => {
|
|
474
|
-
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
475
|
-
const node = new LocalNode(admin, session, Crypto);
|
|
476
|
-
const group = node.createGroup();
|
|
477
|
-
const [inRx, _inTx] = newQueuePair();
|
|
478
|
-
const [outRx, outTx] = newQueuePair();
|
|
479
|
-
const outRxQ = outRx[Symbol.asyncIterator]();
|
|
480
|
-
node.syncManager.addPeer({
|
|
481
|
-
id: "test",
|
|
482
|
-
incoming: inRx,
|
|
483
|
-
outgoing: outTx,
|
|
484
|
-
role: "server",
|
|
485
|
-
crashOnClose: true,
|
|
486
|
-
});
|
|
487
|
-
// expect((await outRxQ.next()).value).toMatchObject({
|
|
488
|
-
// action: "load",
|
|
489
|
-
// id: admin.id,
|
|
490
|
-
// });
|
|
491
|
-
expect((await outRxQ.next()).value).toMatchObject({
|
|
492
|
-
action: "load",
|
|
493
|
-
id: group.core.id,
|
|
494
|
-
});
|
|
495
|
-
const map = group.createMap();
|
|
496
|
-
const mapSubscribeMsg = (await outRxQ.next()).value;
|
|
497
|
-
expect(mapSubscribeMsg).toEqual({
|
|
498
|
-
action: "load",
|
|
499
|
-
...map.core.knownState(),
|
|
500
|
-
});
|
|
501
|
-
// expect((await outRxQ.next()).value).toMatchObject(admContEx(adminID));
|
|
502
|
-
expect((await outRxQ.next()).value).toMatchObject(groupContentEx(group));
|
|
503
|
-
const mapContentMsg = (await outRxQ.next()).value;
|
|
504
|
-
expect(mapContentMsg).toEqual({
|
|
505
|
-
action: "content",
|
|
506
|
-
id: map.core.id,
|
|
507
|
-
header: map.core.header,
|
|
508
|
-
new: {},
|
|
509
|
-
priority: getPriorityFromHeader(map.core.header),
|
|
510
|
-
});
|
|
511
|
-
});
|
|
512
|
-
test.todo("TODO: when receiving a subscribe response that is behind our optimistic state (due to already sent content), we ignore it");
|
|
513
|
-
test("When we connect a new server peer, we try to sync all existing coValues to it", async () => {
|
|
514
|
-
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
515
|
-
const node = new LocalNode(admin, session, Crypto);
|
|
516
|
-
const group = node.createGroup();
|
|
517
|
-
const map = group.createMap();
|
|
518
|
-
const [inRx, _inTx] = newQueuePair();
|
|
519
|
-
const [outRx, outTx] = newQueuePair();
|
|
520
|
-
const outRxQ = outRx[Symbol.asyncIterator]();
|
|
521
|
-
node.syncManager.addPeer({
|
|
522
|
-
id: "test",
|
|
523
|
-
incoming: inRx,
|
|
524
|
-
outgoing: outTx,
|
|
525
|
-
role: "server",
|
|
526
|
-
crashOnClose: true,
|
|
527
|
-
});
|
|
528
|
-
// const _adminSubscribeMessage = await outRxQ.next();
|
|
529
|
-
const groupSubscribeMessage = (await outRxQ.next()).value;
|
|
530
|
-
expect(groupSubscribeMessage).toEqual({
|
|
531
|
-
action: "load",
|
|
532
|
-
...group.core.knownState(),
|
|
533
|
-
});
|
|
534
|
-
const secondMessage = (await outRxQ.next()).value;
|
|
535
|
-
expect(secondMessage).toEqual({
|
|
536
|
-
action: "load",
|
|
537
|
-
...map.core.knownState(),
|
|
538
|
-
});
|
|
539
|
-
});
|
|
540
|
-
test("When receiving a subscribe with a known state that is ahead of our own, peers should respond with a corresponding subscribe response message", async () => {
|
|
541
|
-
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
542
|
-
const node = new LocalNode(admin, session, Crypto);
|
|
543
|
-
const group = node.createGroup();
|
|
544
|
-
const map = group.createMap();
|
|
545
|
-
const [inRx, inTx] = newQueuePair();
|
|
546
|
-
const [outRx, outTx] = newQueuePair();
|
|
547
|
-
const outRxQ = outRx[Symbol.asyncIterator]();
|
|
548
|
-
node.syncManager.addPeer({
|
|
549
|
-
id: "test",
|
|
550
|
-
incoming: inRx,
|
|
551
|
-
outgoing: outTx,
|
|
552
|
-
role: "peer",
|
|
553
|
-
crashOnClose: true,
|
|
554
|
-
});
|
|
555
|
-
await inTx.push({
|
|
556
|
-
action: "load",
|
|
557
|
-
id: map.core.id,
|
|
558
|
-
header: true,
|
|
559
|
-
sessions: {
|
|
560
|
-
[node.currentSessionID]: 1,
|
|
561
|
-
},
|
|
562
|
-
});
|
|
563
|
-
// expect((await outRxQ.next()).value).toMatchObject(admStateEx(admin.id));
|
|
564
|
-
expect((await outRxQ.next()).value).toMatchObject(groupStateEx(group));
|
|
565
|
-
const mapTellKnownState = (await outRxQ.next()).value;
|
|
566
|
-
expect(mapTellKnownState).toEqual({
|
|
567
|
-
action: "known",
|
|
568
|
-
...map.core.knownState(),
|
|
569
|
-
});
|
|
570
|
-
});
|
|
571
|
-
test.skip("When replaying creation and transactions of a coValue as new content, the receiving peer integrates this information", async () => {
|
|
572
|
-
// TODO: this test is mostly correct but also slightly unrealistic, make sure we pass all messages back and forth as expected and then it should work
|
|
573
|
-
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
574
|
-
const node1 = new LocalNode(admin, session, Crypto);
|
|
575
|
-
const group = node1.createGroup();
|
|
576
|
-
const [inRx1, inTx1] = newQueuePair();
|
|
577
|
-
const [outRx1, outTx1] = newQueuePair();
|
|
578
|
-
const outRxQ1 = outRx1[Symbol.asyncIterator]();
|
|
579
|
-
node1.syncManager.addPeer({
|
|
580
|
-
id: "test2",
|
|
581
|
-
incoming: inRx1,
|
|
582
|
-
outgoing: outTx1,
|
|
583
|
-
role: "server",
|
|
584
|
-
crashOnClose: true,
|
|
585
|
-
});
|
|
586
|
-
const node2 = new LocalNode(admin, Crypto.newRandomSessionID(admin.id), Crypto);
|
|
587
|
-
const [inRx2, inTx2] = newQueuePair();
|
|
588
|
-
const [outRx2, outTx2] = newQueuePair();
|
|
589
|
-
const outRxQ2 = outRx2[Symbol.asyncIterator]();
|
|
590
|
-
node2.syncManager.addPeer({
|
|
591
|
-
id: "test1",
|
|
592
|
-
incoming: inRx2,
|
|
593
|
-
outgoing: outTx2,
|
|
594
|
-
role: "client",
|
|
595
|
-
crashOnClose: true,
|
|
596
|
-
});
|
|
597
|
-
const adminSubscribeMessage = (await outRxQ1.next()).value;
|
|
598
|
-
expect(adminSubscribeMessage).toMatchObject({
|
|
599
|
-
action: "load",
|
|
600
|
-
id: admin.id,
|
|
601
|
-
});
|
|
602
|
-
const groupSubscribeMsg = (await outRxQ1.next()).value;
|
|
603
|
-
expect(groupSubscribeMsg).toMatchObject({
|
|
604
|
-
action: "load",
|
|
605
|
-
id: group.core.id,
|
|
606
|
-
});
|
|
607
|
-
await inTx2.push(adminSubscribeMessage);
|
|
608
|
-
await inTx2.push(groupSubscribeMsg);
|
|
609
|
-
// const adminTellKnownStateMsg = (await outRxQ2.next()).value;
|
|
610
|
-
// expect(adminTellKnownStateMsg).toMatchObject(admStateEx(admin.id));
|
|
611
|
-
const groupTellKnownStateMsg = (await outRxQ2.next()).value;
|
|
612
|
-
expect(groupTellKnownStateMsg).toMatchObject(groupStateEx(group));
|
|
613
|
-
expect(node2.syncManager.peers["test1"].optimisticKnownStates.has(group.core.id)).toBeDefined();
|
|
614
|
-
// await inTx1.push(adminTellKnownStateMsg);
|
|
615
|
-
await inTx1.push(groupTellKnownStateMsg);
|
|
616
|
-
// const adminContentMsg = (await outRxQ1.next()).value;
|
|
617
|
-
// expect(adminContentMsg).toMatchObject(admContEx(admin.id));
|
|
618
|
-
const groupContentMsg = (await outRxQ1.next()).value;
|
|
619
|
-
expect(groupContentMsg).toMatchObject(groupContentEx(group));
|
|
620
|
-
// await inTx2.push(adminContentMsg);
|
|
621
|
-
await inTx2.push(groupContentMsg);
|
|
622
|
-
const map = group.createMap();
|
|
623
|
-
const mapSubscriptionMsg = (await outRxQ1.next()).value;
|
|
624
|
-
expect(mapSubscriptionMsg).toMatchObject({
|
|
625
|
-
action: "load",
|
|
626
|
-
id: map.core.id,
|
|
627
|
-
});
|
|
628
|
-
const mapNewContentMsg = (await outRxQ1.next()).value;
|
|
629
|
-
expect(mapNewContentMsg).toEqual({
|
|
630
|
-
action: "content",
|
|
631
|
-
id: map.core.id,
|
|
632
|
-
header: map.core.header,
|
|
633
|
-
new: {},
|
|
634
|
-
priority: getPriorityFromHeader(map.core.header),
|
|
635
|
-
});
|
|
636
|
-
await inTx2.push(mapSubscriptionMsg);
|
|
637
|
-
const mapTellKnownStateMsg = (await outRxQ2.next()).value;
|
|
638
|
-
expect(mapTellKnownStateMsg).toEqual({
|
|
639
|
-
action: "known",
|
|
640
|
-
id: map.core.id,
|
|
641
|
-
header: false,
|
|
642
|
-
sessions: {},
|
|
643
|
-
});
|
|
644
|
-
expect(node2.coValuesStore.get(map.core.id).state.type).toEqual("loading");
|
|
645
|
-
await inTx2.push(mapNewContentMsg);
|
|
646
|
-
map.set("hello", "world", "trusting");
|
|
647
|
-
const mapEditMsg = (await outRxQ1.next()).value;
|
|
648
|
-
await inTx2.push(mapEditMsg);
|
|
649
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
650
|
-
expect(expectMap(node2.expectCoValueLoaded(map.core.id).getCurrentContent()).get("hello")).toEqual("world");
|
|
651
|
-
});
|
|
652
|
-
test.skip("When loading a coValue on one node, the server node it is requested from replies with all the necessary depended on coValues to make it work", async () => {
|
|
653
|
-
/*
|
|
654
|
-
// TODO: this test is mostly correct but also slightly unrealistic, make sure we pass all messages back and forth as expected and then it should work
|
|
655
|
-
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
656
|
-
|
|
657
|
-
const node1 = new LocalNode(admin, session, Crypto);
|
|
658
|
-
|
|
659
|
-
const group = node1.createGroup();
|
|
660
|
-
|
|
661
|
-
const map = group.createMap();
|
|
662
|
-
map.set("hello", "world", "trusting");
|
|
663
|
-
|
|
664
|
-
const node2 = new LocalNode(admin, newRandomSessionID(admin.id), Crypto);
|
|
665
|
-
|
|
666
|
-
const [node1asPeer, node2asPeer] = connectedPeers("peer1", "peer2");
|
|
667
|
-
|
|
668
|
-
node1.syncManager.addPeer(node2asPeer);
|
|
669
|
-
node2.syncManager.addPeer(node1asPeer);
|
|
670
|
-
|
|
671
|
-
await node2.loadCoValueCore(map.core.id);
|
|
672
|
-
|
|
673
|
-
expect(
|
|
674
|
-
expectMap(
|
|
675
|
-
node2.expectCoValueLoaded(map.core.id).getCurrentContent(),
|
|
676
|
-
).get("hello"),
|
|
677
|
-
).toEqual("world");
|
|
678
|
-
*/
|
|
679
|
-
});
|
|
680
40
|
test("Can sync a coValue through a server to another client", async () => {
|
|
681
|
-
const { node: client1 } = await
|
|
41
|
+
const { node: client1 } = await setupTestAccount({
|
|
42
|
+
connected: true,
|
|
43
|
+
});
|
|
682
44
|
const group = client1.createGroup();
|
|
683
45
|
const map = group.createMap();
|
|
684
46
|
map.set("hello", "world", "trusting");
|
|
685
|
-
const { node: client2 } = await
|
|
47
|
+
const { node: client2 } = await setupTestAccount({
|
|
48
|
+
connected: true,
|
|
49
|
+
});
|
|
686
50
|
const mapOnClient2 = await loadCoValueOrFail(client2, map.id);
|
|
687
51
|
expect(mapOnClient2.get("hello")).toEqual("world");
|
|
688
52
|
});
|
|
689
53
|
test("Can sync a coValue with private transactions through a server to another client", async () => {
|
|
690
|
-
const { node: client1 } = await
|
|
54
|
+
const { node: client1 } = await setupTestAccount({
|
|
55
|
+
connected: true,
|
|
56
|
+
});
|
|
691
57
|
const group = client1.createGroup();
|
|
692
58
|
const map = group.createMap();
|
|
693
59
|
map.set("hello", "world", "private");
|
|
694
60
|
group.addMember("everyone", "reader");
|
|
695
|
-
const { node: client2 } = await
|
|
61
|
+
const { node: client2 } = await setupTestAccount({
|
|
62
|
+
connected: true,
|
|
63
|
+
});
|
|
696
64
|
const mapOnClient2 = await loadCoValueOrFail(client2, map.id);
|
|
697
65
|
expect(mapOnClient2.get("hello")).toEqual("world");
|
|
698
66
|
});
|
|
699
|
-
test.skip("When a peer's incoming/readable stream closes, we remove the peer", async () => {
|
|
700
|
-
/*
|
|
701
|
-
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
702
|
-
const node = new LocalNode(admin, session, Crypto);
|
|
703
|
-
|
|
704
|
-
const group = node.createGroup();
|
|
705
|
-
|
|
706
|
-
const [inRx, inTx] = await Effect.runPromise(newStreamPair());
|
|
707
|
-
const [outRx, outTx] = await Effect.runPromise(newStreamPair());
|
|
708
|
-
|
|
709
|
-
node.syncManager.addPeer({
|
|
710
|
-
id: "test",
|
|
711
|
-
incoming: inRx,
|
|
712
|
-
outgoing: outTx,
|
|
713
|
-
role: "server",
|
|
714
|
-
});
|
|
715
|
-
|
|
716
|
-
// expect(yield* Queue.take(outRxQ)).toMatchObject({
|
|
717
|
-
// action: "load",
|
|
718
|
-
// id: admin.id,
|
|
719
|
-
// });
|
|
720
|
-
expect(yield * Queue.take(outRxQ)).toMatchObject({
|
|
721
|
-
action: "load",
|
|
722
|
-
id: group.core.id,
|
|
723
|
-
});
|
|
724
|
-
|
|
725
|
-
const map = group.createMap();
|
|
726
|
-
|
|
727
|
-
const mapSubscribeMsg = await reader.read();
|
|
728
|
-
|
|
729
|
-
expect(mapSubscribeMsg.value).toEqual({
|
|
730
|
-
action: "load",
|
|
731
|
-
...map.core.knownState(),
|
|
732
|
-
} satisfies SyncMessage);
|
|
733
|
-
|
|
734
|
-
// expect(yield* Queue.take(outRxQ)).toMatchObject(admContEx(admin.id));
|
|
735
|
-
expect(yield * Queue.take(outRxQ)).toMatchObject(groupContentEx(group));
|
|
736
|
-
|
|
737
|
-
const mapContentMsg = await reader.read();
|
|
738
|
-
|
|
739
|
-
expect(mapContentMsg.value).toEqual({
|
|
740
|
-
action: "content",
|
|
741
|
-
id: map.core.id,
|
|
742
|
-
header: map.core.header,
|
|
743
|
-
new: {},
|
|
744
|
-
} satisfies SyncMessage);
|
|
745
|
-
|
|
746
|
-
await inTx.abort();
|
|
747
|
-
|
|
748
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
749
|
-
|
|
750
|
-
expect(node.syncManager.peers["test"]).toBeUndefined();
|
|
751
|
-
*/
|
|
752
|
-
});
|
|
753
67
|
test("should keep the peer state when the peer closes", async () => {
|
|
754
|
-
const client =
|
|
755
|
-
const {
|
|
756
|
-
const group = jazzCloud.createGroup();
|
|
68
|
+
const client = setupTestNode();
|
|
69
|
+
const { peer, peerState } = client.connectToSyncServer();
|
|
70
|
+
const group = jazzCloud.node.createGroup();
|
|
757
71
|
const map = group.createMap();
|
|
758
72
|
map.set("hello", "world", "trusting");
|
|
759
|
-
await client.loadCoValueCore(map.core.id);
|
|
760
|
-
const syncManager = client.syncManager;
|
|
761
|
-
const peerState = syncManager.peers[nodeToServerPeer.id];
|
|
73
|
+
await client.node.loadCoValueCore(map.core.id);
|
|
74
|
+
const syncManager = client.node.syncManager;
|
|
762
75
|
// @ts-expect-error Simulating a peer closing, leveraging the direct connection between the client/server peers
|
|
763
|
-
await
|
|
76
|
+
await peer.outgoing.push("Disconnected");
|
|
764
77
|
await waitFor(() => peerState?.closed);
|
|
765
|
-
expect(syncManager.peers[
|
|
78
|
+
expect(syncManager.peers[peer.id]).not.toBeUndefined();
|
|
766
79
|
});
|
|
767
80
|
test("should delete the peer state when the peer closes if deletePeerStateOnClose is true", async () => {
|
|
768
|
-
const client =
|
|
769
|
-
const {
|
|
770
|
-
|
|
771
|
-
const group = jazzCloud.createGroup();
|
|
81
|
+
const client = setupTestNode();
|
|
82
|
+
const { peer, peerState } = client.connectToSyncServer();
|
|
83
|
+
peer.deletePeerStateOnClose = true;
|
|
84
|
+
const group = jazzCloud.node.createGroup();
|
|
772
85
|
const map = group.createMap();
|
|
773
86
|
map.set("hello", "world", "trusting");
|
|
774
|
-
await client.loadCoValueCore(map.core.id);
|
|
775
|
-
const syncManager = client.syncManager;
|
|
776
|
-
const peerState = syncManager.peers[nodeToServerPeer.id];
|
|
87
|
+
await client.node.loadCoValueCore(map.core.id);
|
|
88
|
+
const syncManager = client.node.syncManager;
|
|
777
89
|
// @ts-expect-error Simulating a peer closing, leveraging the direct connection between the client/server peers
|
|
778
|
-
await
|
|
90
|
+
await peer.outgoing.push("Disconnected");
|
|
779
91
|
await waitFor(() => peerState?.closed);
|
|
780
|
-
expect(syncManager.peers[
|
|
92
|
+
expect(syncManager.peers[peer.id]).toBeUndefined();
|
|
781
93
|
});
|
|
782
94
|
describe("sync - extra tests", () => {
|
|
783
95
|
test("Node handles disconnection and reconnection of a peer gracefully", async () => {
|
|
@@ -915,58 +227,6 @@ describe("sync - extra tests", () => {
|
|
|
915
227
|
expect(finalStateNode2.toJSON()).toEqual(expectedState);
|
|
916
228
|
expect(finalStateNode3.toJSON()).toEqual(expectedState);
|
|
917
229
|
});
|
|
918
|
-
test.skip("Large coValues are synced efficiently in chunks", async () => {
|
|
919
|
-
// Create two nodes
|
|
920
|
-
const [admin1, session1] = randomAnonymousAccountAndSessionID();
|
|
921
|
-
const node1 = new LocalNode(admin1, session1, Crypto);
|
|
922
|
-
const [admin2, session2] = randomAnonymousAccountAndSessionID();
|
|
923
|
-
const node2 = new LocalNode(admin2, session2, Crypto);
|
|
924
|
-
// Create a group and a large map on node1
|
|
925
|
-
const group = node1.createGroup();
|
|
926
|
-
group.addMember("everyone", "writer");
|
|
927
|
-
const largeMap = group.createMap();
|
|
928
|
-
// Generate a large amount of data (about 10MB)
|
|
929
|
-
const dataSize = 1 * 1024 * 1024;
|
|
930
|
-
const chunkSize = 1024; // 1KB chunks
|
|
931
|
-
const chunks = dataSize / chunkSize;
|
|
932
|
-
for (let i = 0; i < chunks; i++) {
|
|
933
|
-
const key = `key${i}`;
|
|
934
|
-
const value = Buffer.alloc(chunkSize, `value${i}`).toString("base64");
|
|
935
|
-
largeMap.set(key, value, "trusting");
|
|
936
|
-
}
|
|
937
|
-
// Connect the nodes
|
|
938
|
-
const [node1AsPeer, node2AsPeer] = connectedPeers("node1", "node2", {
|
|
939
|
-
peer1role: "server",
|
|
940
|
-
peer2role: "client",
|
|
941
|
-
});
|
|
942
|
-
node1.syncManager.addPeer(node2AsPeer);
|
|
943
|
-
node2.syncManager.addPeer(node1AsPeer);
|
|
944
|
-
await new Promise((resolve) => setTimeout(resolve, 4000));
|
|
945
|
-
// Measure sync time
|
|
946
|
-
const startSync = performance.now();
|
|
947
|
-
// Load the large map on node2
|
|
948
|
-
const largeMapOnNode2 = await node2.loadCoValueCore(largeMap.core.id);
|
|
949
|
-
if (largeMapOnNode2 === "unavailable") {
|
|
950
|
-
throw new Error("Large map is unavailable on node2");
|
|
951
|
-
}
|
|
952
|
-
const endSync = performance.now();
|
|
953
|
-
const syncTime = endSync - startSync;
|
|
954
|
-
// Verify that all data was synced correctly
|
|
955
|
-
const syncedMap = new RawCoMap(largeMapOnNode2);
|
|
956
|
-
expect(Object.keys(largeMapOnNode2.getCurrentContent().toJSON() || {}).length).toBe(chunks);
|
|
957
|
-
for (let i = 0; i < chunks; i++) {
|
|
958
|
-
const key = `key${i}`;
|
|
959
|
-
const expectedValue = Buffer.alloc(chunkSize, `value${i}`).toString("base64");
|
|
960
|
-
expect(syncedMap.get(key)).toBe(expectedValue);
|
|
961
|
-
}
|
|
962
|
-
// Check that sync time is reasonable (this threshold may need adjustment)
|
|
963
|
-
const reasonableSyncTime = 10; // 30 seconds
|
|
964
|
-
expect(syncTime).toBeLessThan(reasonableSyncTime);
|
|
965
|
-
// Check memory usage (this threshold may need adjustment)
|
|
966
|
-
const memoryUsage = process.memoryUsage().heapUsed / 1024 / 1024; // in MB
|
|
967
|
-
const reasonableMemoryUsage = 1; // 500 MB
|
|
968
|
-
expect(memoryUsage).toBeLessThan(reasonableMemoryUsage);
|
|
969
|
-
});
|
|
970
230
|
test("Node correctly handles and recovers from network partitions", async () => {
|
|
971
231
|
// Create three nodes
|
|
972
232
|
const [admin1, session1] = randomAnonymousAccountAndSessionID();
|
|
@@ -1094,7 +354,9 @@ test("a value created on one node can be loaded on anotehr node even if not dire
|
|
|
1094
354
|
});
|
|
1095
355
|
describe("SyncManager - knownStates vs optimisticKnownStates", () => {
|
|
1096
356
|
test("knownStates and optimisticKnownStates are the same when the coValue is fully synced", async () => {
|
|
1097
|
-
const { node: client } = await
|
|
357
|
+
const { node: client } = await setupTestAccount({
|
|
358
|
+
connected: true,
|
|
359
|
+
});
|
|
1098
360
|
// Create test data
|
|
1099
361
|
const group = client.createGroup();
|
|
1100
362
|
const mapOnClient = group.createMap();
|
|
@@ -1103,31 +365,31 @@ describe("SyncManager - knownStates vs optimisticKnownStates", () => {
|
|
|
1103
365
|
// Wait for the full sync to complete
|
|
1104
366
|
await mapOnClient.core.waitForSync();
|
|
1105
367
|
const peerStateClient = client.syncManager.getPeers()[0];
|
|
1106
|
-
const peerStateJazzCloud = jazzCloud.syncManager.getPeers()[0];
|
|
368
|
+
const peerStateJazzCloud = jazzCloud.node.syncManager.getPeers()[0];
|
|
1107
369
|
// The optimisticKnownStates should be the same as the knownStates after the full sync is complete
|
|
1108
370
|
expect(peerStateClient.optimisticKnownStates.get(mapOnClient.core.id)).toEqual(peerStateClient.knownStates.get(mapOnClient.core.id));
|
|
1109
371
|
// On the other node the knownStates should be updated correctly based on the messages we received
|
|
1110
372
|
expect(peerStateJazzCloud.optimisticKnownStates.get(mapOnClient.core.id)).toEqual(peerStateJazzCloud.knownStates.get(mapOnClient.core.id));
|
|
1111
373
|
});
|
|
1112
374
|
test("optimisticKnownStates is updated as new transactions are sent, while knownStates only when the updates are acknowledged", async () => {
|
|
1113
|
-
const
|
|
375
|
+
const client = await setupTestAccount();
|
|
376
|
+
const { peer, peerState } = client.connectToSyncServer();
|
|
1114
377
|
// Create test data and sync the first change
|
|
1115
378
|
// We want that both the nodes know about the coValue so we can test
|
|
1116
379
|
// the content acknowledgement flow.
|
|
1117
|
-
const group = client.createGroup();
|
|
380
|
+
const group = client.node.createGroup();
|
|
1118
381
|
const map = group.createMap();
|
|
1119
382
|
map.set("key1", "value1", "trusting");
|
|
1120
|
-
await client.syncManager.actuallySyncCoValue(map.core);
|
|
383
|
+
await client.node.syncManager.actuallySyncCoValue(map.core);
|
|
1121
384
|
await map.core.waitForSync();
|
|
1122
385
|
// Block the content messages
|
|
1123
386
|
// The main difference between optimisticKnownStates and knownStates is that
|
|
1124
387
|
// optimisticKnownStates is updated when the content messages are sent,
|
|
1125
388
|
// while knownStates is only updated when we receive the "known" messages
|
|
1126
389
|
// that are acknowledging the receipt of the content messages
|
|
1127
|
-
const outgoing = blockMessageTypeOnOutgoingPeer(
|
|
390
|
+
const outgoing = blockMessageTypeOnOutgoingPeer(peer, "content");
|
|
1128
391
|
map.set("key2", "value2", "trusting");
|
|
1129
|
-
await client.syncManager.actuallySyncCoValue(map.core);
|
|
1130
|
-
const peerState = client.syncManager.peers[nodeToServerPeer.id];
|
|
392
|
+
await client.node.syncManager.actuallySyncCoValue(map.core);
|
|
1131
393
|
expect(peerState.optimisticKnownStates.get(map.core.id)).not.toEqual(peerState.knownStates.get(map.core.id));
|
|
1132
394
|
// Restore the implementation of push and send the blocked messages
|
|
1133
395
|
// After this the full sync can be completed and the other node will
|
|
@@ -1140,37 +402,35 @@ describe("SyncManager - knownStates vs optimisticKnownStates", () => {
|
|
|
1140
402
|
});
|
|
1141
403
|
describe("SyncManager.addPeer", () => {
|
|
1142
404
|
test("new peer gets a copy of previous peer's knownStates when replacing it", async () => {
|
|
1143
|
-
const
|
|
405
|
+
const client = await setupTestAccount();
|
|
406
|
+
const { peerState: firstPeerState, getCurrentPeerState } = client.connectToSyncServer();
|
|
1144
407
|
// Create test data
|
|
1145
|
-
const group = client.createGroup();
|
|
408
|
+
const group = client.node.createGroup();
|
|
1146
409
|
const map = group.createMap();
|
|
1147
410
|
map.set("key1", "value1", "trusting");
|
|
1148
|
-
await client.syncManager.actuallySyncCoValue(map.core);
|
|
411
|
+
await client.node.syncManager.actuallySyncCoValue(map.core);
|
|
1149
412
|
// Wait for initial sync
|
|
1150
413
|
await map.core.waitForSync();
|
|
1151
|
-
const firstPeerState = client.syncManager.getPeers()[0];
|
|
1152
414
|
// Store the initial known states
|
|
1153
415
|
const initialKnownStates = firstPeerState.knownStates;
|
|
1154
416
|
// Create new connection with same ID
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
});
|
|
1159
|
-
// Add new peer with same ID
|
|
1160
|
-
client.syncManager.addPeer(secondPeer);
|
|
1161
|
-
const newPeerState = client.syncManager.getPeers()[0];
|
|
417
|
+
client.connectToSyncServer();
|
|
418
|
+
// Wait for the new peer to be added
|
|
419
|
+
await waitFor(() => expect(getCurrentPeerState()).not.toBe(firstPeerState));
|
|
1162
420
|
// Verify that the new peer has a copy of the previous known states
|
|
1163
|
-
const newPeerKnownStates =
|
|
421
|
+
const newPeerKnownStates = getCurrentPeerState().knownStates;
|
|
1164
422
|
expect(newPeerKnownStates).not.toBe(initialKnownStates); // Should be a different instance
|
|
1165
423
|
expect(newPeerKnownStates.get(map.core.id)).toEqual(initialKnownStates.get(map.core.id));
|
|
1166
424
|
});
|
|
1167
425
|
test("new peer with new ID starts with empty knownStates", async () => {
|
|
1168
|
-
const
|
|
426
|
+
const client = await setupTestAccount({
|
|
427
|
+
connected: true,
|
|
428
|
+
});
|
|
1169
429
|
// Create test data
|
|
1170
|
-
const group = client.createGroup();
|
|
430
|
+
const group = client.node.createGroup();
|
|
1171
431
|
const map = group.createMap();
|
|
1172
432
|
map.set("key1", "value1", "trusting");
|
|
1173
|
-
await client.syncManager.actuallySyncCoValue(map.core);
|
|
433
|
+
await client.node.syncManager.actuallySyncCoValue(map.core);
|
|
1174
434
|
// Wait for initial sync
|
|
1175
435
|
await map.core.waitForSync();
|
|
1176
436
|
// Connect second peer with different ID
|
|
@@ -1179,72 +439,82 @@ describe("SyncManager.addPeer", () => {
|
|
|
1179
439
|
peer2role: "server",
|
|
1180
440
|
});
|
|
1181
441
|
// Add new peer with different ID
|
|
1182
|
-
client.syncManager.addPeer(brandNewPeer);
|
|
442
|
+
client.node.syncManager.addPeer(brandNewPeer);
|
|
1183
443
|
// Verify that the new peer starts with empty known states
|
|
1184
|
-
const newPeerKnownStates = client.syncManager.peers["brandNewPeer"].knownStates;
|
|
444
|
+
const newPeerKnownStates = client.node.syncManager.peers["brandNewPeer"].knownStates;
|
|
1185
445
|
expect(newPeerKnownStates.get(map.core.id)).toBe(undefined);
|
|
1186
446
|
});
|
|
1187
447
|
test("when adding a peer with the same ID as a previous peer, the previous peer is closed", async () => {
|
|
1188
|
-
const
|
|
448
|
+
const client = await setupTestAccount({});
|
|
449
|
+
const { peerState: firstPeerState } = client.connectToSyncServer();
|
|
1189
450
|
// Store reference to first peer
|
|
1190
|
-
const
|
|
1191
|
-
const closeSpy = vi.spyOn(firstPeer, "gracefulShutdown");
|
|
451
|
+
const closeSpy = vi.spyOn(firstPeerState, "gracefulShutdown");
|
|
1192
452
|
// Create and add replacement peer
|
|
1193
|
-
const [secondPeer] = connectedPeers(
|
|
453
|
+
const [secondPeer] = connectedPeers(firstPeerState.id, "unusedPeer", {
|
|
1194
454
|
peer1role: "server",
|
|
1195
455
|
peer2role: "client",
|
|
1196
456
|
});
|
|
1197
|
-
client.syncManager.addPeer(secondPeer);
|
|
457
|
+
client.node.syncManager.addPeer(secondPeer);
|
|
1198
458
|
// Verify thet the first peer had ben closed correctly
|
|
1199
459
|
expect(closeSpy).toHaveBeenCalled();
|
|
1200
|
-
expect(
|
|
460
|
+
expect(firstPeerState.closed).toBe(true);
|
|
1201
461
|
});
|
|
1202
462
|
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 () => {
|
|
1203
|
-
const
|
|
463
|
+
const client = await setupTestAccount({
|
|
464
|
+
connected: true,
|
|
465
|
+
});
|
|
1204
466
|
// Store reference to first peer
|
|
1205
|
-
const
|
|
1206
|
-
|
|
1207
|
-
const closeSpy = vi.spyOn(
|
|
467
|
+
const { peerState: firstPeerState } = client.connectToSyncServer();
|
|
468
|
+
firstPeerState.gracefulShutdown();
|
|
469
|
+
const closeSpy = vi.spyOn(firstPeerState, "gracefulShutdown");
|
|
1208
470
|
// Create and add replacement peer
|
|
1209
|
-
const [secondPeer] = connectedPeers(
|
|
471
|
+
const [secondPeer] = connectedPeers(firstPeerState.id, "unusedPeer", {
|
|
1210
472
|
peer1role: "server",
|
|
1211
473
|
peer2role: "client",
|
|
1212
474
|
});
|
|
1213
|
-
client.syncManager.addPeer(secondPeer);
|
|
475
|
+
client.node.syncManager.addPeer(secondPeer);
|
|
1214
476
|
// Verify thet the first peer had not been closed again
|
|
1215
477
|
expect(closeSpy).not.toHaveBeenCalled();
|
|
1216
|
-
expect(
|
|
478
|
+
expect(firstPeerState.closed).toBe(true);
|
|
1217
479
|
});
|
|
1218
480
|
test("when adding a server peer the local coValues should be sent to it", async () => {
|
|
1219
|
-
const
|
|
481
|
+
const client = await setupTestAccount({
|
|
1220
482
|
connected: false,
|
|
1221
483
|
});
|
|
1222
|
-
const group = client.createGroup();
|
|
484
|
+
const group = client.node.createGroup();
|
|
1223
485
|
const map = group.createMap();
|
|
1224
486
|
map.set("key1", "value1", "trusting");
|
|
1225
|
-
|
|
487
|
+
client.connectToSyncServer();
|
|
1226
488
|
await map.core.waitForSync();
|
|
1227
|
-
expect(jazzCloud.coValuesStore.get(map.id).state.type).toBe("available");
|
|
489
|
+
expect(jazzCloud.node.coValuesStore.get(map.id).state.type).toBe("available");
|
|
1228
490
|
});
|
|
1229
491
|
});
|
|
1230
492
|
describe("loadCoValueCore with retry", () => {
|
|
1231
493
|
test("should load the value if available on the server", async () => {
|
|
1232
|
-
const
|
|
1233
|
-
|
|
1234
|
-
|
|
494
|
+
const client = await setupTestAccount({
|
|
495
|
+
connected: true,
|
|
496
|
+
});
|
|
497
|
+
const anotherClient = await setupTestAccount({
|
|
498
|
+
connected: true,
|
|
499
|
+
});
|
|
500
|
+
const group = anotherClient.node.createGroup();
|
|
1235
501
|
const map = group.createMap();
|
|
1236
502
|
map.set("key1", "value1", "trusting");
|
|
1237
|
-
const promise = client.loadCoValueCore(map.id);
|
|
503
|
+
const promise = client.node.loadCoValueCore(map.id);
|
|
1238
504
|
await expect(promise).resolves.not.toBe("unavailable");
|
|
1239
505
|
});
|
|
1240
506
|
test("should handle correctly two subsequent loads", async () => {
|
|
1241
|
-
const
|
|
1242
|
-
|
|
1243
|
-
|
|
507
|
+
const client = await setupTestAccount({
|
|
508
|
+
connected: true,
|
|
509
|
+
});
|
|
510
|
+
const anotherClient = await setupTestAccount({
|
|
511
|
+
connected: true,
|
|
512
|
+
});
|
|
513
|
+
const group = anotherClient.node.createGroup();
|
|
1244
514
|
const map = group.createMap();
|
|
1245
515
|
map.set("key1", "value1", "trusting");
|
|
1246
|
-
const promise1 = client.loadCoValueCore(map.id);
|
|
1247
|
-
const promise2 = client.loadCoValueCore(map.id);
|
|
516
|
+
const promise1 = client.node.loadCoValueCore(map.id);
|
|
517
|
+
const promise2 = client.node.loadCoValueCore(map.id);
|
|
1248
518
|
await expect(promise1).resolves.not.toBe("unavailable");
|
|
1249
519
|
await expect(promise2).resolves.not.toBe("unavailable");
|
|
1250
520
|
});
|
|
@@ -1278,46 +548,44 @@ describe("loadCoValueCore with retry", () => {
|
|
|
1278
548
|
});
|
|
1279
549
|
describe("waitForSyncWithPeer", () => {
|
|
1280
550
|
test("should resolve when the coValue is fully uploaded into the peer", async () => {
|
|
1281
|
-
const
|
|
551
|
+
const client = await setupTestAccount();
|
|
552
|
+
const { peerState } = client.connectToSyncServer();
|
|
1282
553
|
// Create test data
|
|
1283
|
-
const group = client.createGroup();
|
|
554
|
+
const group = client.node.createGroup();
|
|
1284
555
|
const map = group.createMap();
|
|
1285
556
|
map.set("key1", "value1", "trusting");
|
|
1286
|
-
await client.syncManager.actuallySyncCoValue(map.core);
|
|
1287
|
-
|
|
1288
|
-
if (!peer) {
|
|
1289
|
-
throw new Error("No peer found");
|
|
1290
|
-
}
|
|
1291
|
-
await expect(client.syncManager.waitForSyncWithPeer(peer.id, map.core.id, 100)).resolves.toBe(true);
|
|
557
|
+
await client.node.syncManager.actuallySyncCoValue(map.core);
|
|
558
|
+
await expect(client.node.syncManager.waitForSyncWithPeer(peerState.id, map.core.id, 100)).resolves.toBe(true);
|
|
1292
559
|
});
|
|
1293
560
|
test("should not resolve when the coValue is not synced", async () => {
|
|
1294
|
-
const
|
|
1295
|
-
const
|
|
1296
|
-
if (!peer) {
|
|
1297
|
-
throw new Error("No peer found");
|
|
1298
|
-
}
|
|
561
|
+
const client = await setupTestAccount();
|
|
562
|
+
const { peerState } = client.connectToSyncServer();
|
|
1299
563
|
// Create test data
|
|
1300
|
-
const group = client.createGroup();
|
|
564
|
+
const group = client.node.createGroup();
|
|
1301
565
|
const map = group.createMap();
|
|
1302
566
|
map.set("key1", "value1", "trusting");
|
|
1303
|
-
vi.spyOn(
|
|
567
|
+
vi.spyOn(peerState, "pushOutgoingMessage").mockImplementation(async () => {
|
|
1304
568
|
return Promise.resolve();
|
|
1305
569
|
});
|
|
1306
|
-
await client.syncManager.actuallySyncCoValue(map.core);
|
|
1307
|
-
await expect(client.syncManager.waitForSyncWithPeer(
|
|
570
|
+
await client.node.syncManager.actuallySyncCoValue(map.core);
|
|
571
|
+
await expect(client.node.syncManager.waitForSyncWithPeer(peerState.id, map.core.id, 100)).rejects.toThrow("Timeout");
|
|
1308
572
|
});
|
|
1309
573
|
});
|
|
1310
574
|
test("Should not crash when syncing an unknown coValue type", async () => {
|
|
1311
|
-
const
|
|
1312
|
-
|
|
575
|
+
const client = await setupTestAccount({
|
|
576
|
+
connected: true,
|
|
577
|
+
});
|
|
578
|
+
const coValue = client.node.createCoValue({
|
|
1313
579
|
type: "ooops",
|
|
1314
580
|
ruleset: { type: "unsafeAllowAll" },
|
|
1315
581
|
meta: null,
|
|
1316
582
|
...Crypto.createdNowUnique(),
|
|
1317
583
|
});
|
|
1318
584
|
await coValue.waitForSync();
|
|
1319
|
-
const
|
|
1320
|
-
|
|
585
|
+
const anotherClient = await setupTestAccount({
|
|
586
|
+
connected: true,
|
|
587
|
+
});
|
|
588
|
+
const coValueOnTheOtherNode = await loadCoValueOrFail(anotherClient.node, coValue.getCurrentContent().id);
|
|
1321
589
|
expect(coValueOnTheOtherNode.id).toBe(coValue.id);
|
|
1322
590
|
});
|
|
1323
591
|
describe("metrics", () => {
|
|
@@ -1395,183 +663,6 @@ describe("metrics", () => {
|
|
|
1395
663
|
expect(connectedServerPeers).toBe(0);
|
|
1396
664
|
});
|
|
1397
665
|
});
|
|
1398
|
-
describe("sync protocol", () => {
|
|
1399
|
-
test("should have the correct messages exchanged between client and server", async () => {
|
|
1400
|
-
// Creating the account from agent to simplify the messages exchange
|
|
1401
|
-
const { node: client, messages } = await createConnectedTestAgentNode();
|
|
1402
|
-
const group = client.createGroup();
|
|
1403
|
-
const map = group.createMap();
|
|
1404
|
-
map.set("hello", "world", "trusting");
|
|
1405
|
-
await map.core.waitForSync();
|
|
1406
|
-
const mapOnJazzCloud = await loadCoValueOrFail(jazzCloud, map.id);
|
|
1407
|
-
expect(mapOnJazzCloud.get("hello")).toEqual("world");
|
|
1408
|
-
expect(messages).toEqual([
|
|
1409
|
-
{
|
|
1410
|
-
from: "client",
|
|
1411
|
-
msg: {
|
|
1412
|
-
action: "load",
|
|
1413
|
-
header: true,
|
|
1414
|
-
id: group.id,
|
|
1415
|
-
sessions: {
|
|
1416
|
-
[client.currentSessionID]: 3,
|
|
1417
|
-
},
|
|
1418
|
-
},
|
|
1419
|
-
},
|
|
1420
|
-
{
|
|
1421
|
-
from: "server",
|
|
1422
|
-
msg: {
|
|
1423
|
-
action: "load",
|
|
1424
|
-
header: false,
|
|
1425
|
-
id: group.id,
|
|
1426
|
-
sessions: {},
|
|
1427
|
-
},
|
|
1428
|
-
},
|
|
1429
|
-
{
|
|
1430
|
-
from: "client",
|
|
1431
|
-
msg: {
|
|
1432
|
-
action: "load",
|
|
1433
|
-
header: true,
|
|
1434
|
-
id: map.id,
|
|
1435
|
-
sessions: {
|
|
1436
|
-
[client.currentSessionID]: 1,
|
|
1437
|
-
},
|
|
1438
|
-
},
|
|
1439
|
-
},
|
|
1440
|
-
{
|
|
1441
|
-
from: "server",
|
|
1442
|
-
msg: {
|
|
1443
|
-
action: "load",
|
|
1444
|
-
header: false,
|
|
1445
|
-
id: map.id,
|
|
1446
|
-
sessions: {},
|
|
1447
|
-
},
|
|
1448
|
-
},
|
|
1449
|
-
{
|
|
1450
|
-
from: "client",
|
|
1451
|
-
msg: {
|
|
1452
|
-
action: "content",
|
|
1453
|
-
header: {
|
|
1454
|
-
createdAt: expect.any(String),
|
|
1455
|
-
meta: null,
|
|
1456
|
-
ruleset: {
|
|
1457
|
-
initialAdmin: client.account.id,
|
|
1458
|
-
type: "group",
|
|
1459
|
-
},
|
|
1460
|
-
type: "comap",
|
|
1461
|
-
uniqueness: expect.any(String),
|
|
1462
|
-
},
|
|
1463
|
-
id: group.id,
|
|
1464
|
-
new: {
|
|
1465
|
-
[client.currentSessionID]: {
|
|
1466
|
-
after: 0,
|
|
1467
|
-
lastSignature: expect.any(String),
|
|
1468
|
-
newTransactions: expect.any(Array),
|
|
1469
|
-
},
|
|
1470
|
-
},
|
|
1471
|
-
priority: 0,
|
|
1472
|
-
},
|
|
1473
|
-
},
|
|
1474
|
-
{
|
|
1475
|
-
from: "client",
|
|
1476
|
-
msg: {
|
|
1477
|
-
action: "content",
|
|
1478
|
-
header: {
|
|
1479
|
-
createdAt: expect.any(String),
|
|
1480
|
-
meta: null,
|
|
1481
|
-
ruleset: {
|
|
1482
|
-
group: group.id,
|
|
1483
|
-
type: "ownedByGroup",
|
|
1484
|
-
},
|
|
1485
|
-
type: "comap",
|
|
1486
|
-
uniqueness: expect.any(String),
|
|
1487
|
-
},
|
|
1488
|
-
id: map.id,
|
|
1489
|
-
new: {
|
|
1490
|
-
[client.currentSessionID]: {
|
|
1491
|
-
after: 0,
|
|
1492
|
-
lastSignature: expect.any(String),
|
|
1493
|
-
newTransactions: expect.any(Array),
|
|
1494
|
-
},
|
|
1495
|
-
},
|
|
1496
|
-
priority: 3,
|
|
1497
|
-
},
|
|
1498
|
-
},
|
|
1499
|
-
{
|
|
1500
|
-
from: "server",
|
|
1501
|
-
msg: {
|
|
1502
|
-
action: "known",
|
|
1503
|
-
header: true,
|
|
1504
|
-
id: group.id,
|
|
1505
|
-
sessions: {
|
|
1506
|
-
[client.currentSessionID]: 3,
|
|
1507
|
-
},
|
|
1508
|
-
},
|
|
1509
|
-
},
|
|
1510
|
-
{
|
|
1511
|
-
// TODO: This is a redundant message, we should remove it
|
|
1512
|
-
from: "client",
|
|
1513
|
-
msg: {
|
|
1514
|
-
action: "content",
|
|
1515
|
-
header: {
|
|
1516
|
-
createdAt: expect.any(String),
|
|
1517
|
-
meta: null,
|
|
1518
|
-
ruleset: {
|
|
1519
|
-
group: group.id,
|
|
1520
|
-
type: "ownedByGroup",
|
|
1521
|
-
},
|
|
1522
|
-
type: "comap",
|
|
1523
|
-
uniqueness: expect.any(String),
|
|
1524
|
-
},
|
|
1525
|
-
id: map.id,
|
|
1526
|
-
new: {
|
|
1527
|
-
[client.currentSessionID]: {
|
|
1528
|
-
after: 0,
|
|
1529
|
-
lastSignature: expect.any(String),
|
|
1530
|
-
newTransactions: expect.any(Array),
|
|
1531
|
-
},
|
|
1532
|
-
},
|
|
1533
|
-
priority: 3,
|
|
1534
|
-
},
|
|
1535
|
-
},
|
|
1536
|
-
{
|
|
1537
|
-
// TODO: This is a redundant message, we should remove it
|
|
1538
|
-
from: "server",
|
|
1539
|
-
msg: {
|
|
1540
|
-
action: "known",
|
|
1541
|
-
asDependencyOf: undefined,
|
|
1542
|
-
header: true,
|
|
1543
|
-
id: group.id,
|
|
1544
|
-
sessions: {
|
|
1545
|
-
[client.currentSessionID]: 3,
|
|
1546
|
-
},
|
|
1547
|
-
},
|
|
1548
|
-
},
|
|
1549
|
-
{
|
|
1550
|
-
from: "server",
|
|
1551
|
-
msg: {
|
|
1552
|
-
action: "known",
|
|
1553
|
-
header: true,
|
|
1554
|
-
id: map.id,
|
|
1555
|
-
sessions: {
|
|
1556
|
-
[client.currentSessionID]: 1,
|
|
1557
|
-
},
|
|
1558
|
-
},
|
|
1559
|
-
},
|
|
1560
|
-
{
|
|
1561
|
-
from: "server",
|
|
1562
|
-
msg: {
|
|
1563
|
-
action: "known",
|
|
1564
|
-
asDependencyOf: undefined,
|
|
1565
|
-
header: true,
|
|
1566
|
-
id: map.id,
|
|
1567
|
-
sessions: {
|
|
1568
|
-
[client.currentSessionID]: 1,
|
|
1569
|
-
},
|
|
1570
|
-
},
|
|
1571
|
-
},
|
|
1572
|
-
]);
|
|
1573
|
-
});
|
|
1574
|
-
});
|
|
1575
666
|
function groupContentEx(group) {
|
|
1576
667
|
return {
|
|
1577
668
|
action: "content",
|
|
@@ -1586,20 +677,20 @@ function groupStateEx(group) {
|
|
|
1586
677
|
}
|
|
1587
678
|
describe("LocalNode.load", () => {
|
|
1588
679
|
test("should throw error when trying to load with undefined ID", async () => {
|
|
1589
|
-
const
|
|
680
|
+
const client = await setupTestAccount();
|
|
1590
681
|
// @ts-expect-error Testing with undefined ID
|
|
1591
|
-
await expect(node.load(undefined)).rejects.toThrow("Trying to load CoValue with undefined id");
|
|
682
|
+
await expect(client.node.load(undefined)).rejects.toThrow("Trying to load CoValue with undefined id");
|
|
1592
683
|
});
|
|
1593
684
|
test("should throw error when trying to load with invalid ID format", async () => {
|
|
1594
|
-
const
|
|
685
|
+
const client = await setupTestAccount();
|
|
1595
686
|
// @ts-expect-error Testing with invalid ID format
|
|
1596
|
-
await expect(node.load("invalid_id")).rejects.toThrow("Trying to load CoValue with invalid id invalid_id");
|
|
687
|
+
await expect(client.node.load("invalid_id")).rejects.toThrow("Trying to load CoValue with invalid id invalid_id");
|
|
1597
688
|
});
|
|
1598
689
|
});
|
|
1599
690
|
describe("SyncManager.handleSyncMessage", () => {
|
|
1600
691
|
test("should ignore messages with undefined ID", async () => {
|
|
1601
|
-
const
|
|
1602
|
-
const
|
|
692
|
+
const client = await setupTestAccount();
|
|
693
|
+
const { peerState } = client.connectToSyncServer();
|
|
1603
694
|
// Create an invalid message with undefined ID
|
|
1604
695
|
const invalidMessage = {
|
|
1605
696
|
action: "load",
|
|
@@ -1607,14 +698,14 @@ describe("SyncManager.handleSyncMessage", () => {
|
|
|
1607
698
|
header: false,
|
|
1608
699
|
sessions: {},
|
|
1609
700
|
};
|
|
1610
|
-
await client.syncManager.handleSyncMessage(invalidMessage,
|
|
701
|
+
await client.node.syncManager.handleSyncMessage(invalidMessage, peerState);
|
|
1611
702
|
// Verify that no state changes occurred
|
|
1612
|
-
expect(
|
|
1613
|
-
expect(
|
|
703
|
+
expect(peerState.knownStates.has(invalidMessage.id)).toBe(false);
|
|
704
|
+
expect(peerState.optimisticKnownStates.has(invalidMessage.id)).toBe(false);
|
|
1614
705
|
});
|
|
1615
706
|
test("should ignore messages with invalid ID format", async () => {
|
|
1616
|
-
const
|
|
1617
|
-
const
|
|
707
|
+
const client = await setupTestAccount();
|
|
708
|
+
const { peerState } = client.connectToSyncServer();
|
|
1618
709
|
// Create an invalid message with wrong ID format
|
|
1619
710
|
const invalidMessage = {
|
|
1620
711
|
action: "load",
|
|
@@ -1622,41 +713,41 @@ describe("SyncManager.handleSyncMessage", () => {
|
|
|
1622
713
|
header: false,
|
|
1623
714
|
sessions: {},
|
|
1624
715
|
};
|
|
1625
|
-
await client.syncManager.handleSyncMessage(invalidMessage,
|
|
716
|
+
await client.node.syncManager.handleSyncMessage(invalidMessage, peerState);
|
|
1626
717
|
// Verify that no state changes occurred
|
|
1627
|
-
expect(
|
|
1628
|
-
expect(
|
|
718
|
+
expect(peerState.knownStates.has(invalidMessage.id)).toBe(false);
|
|
719
|
+
expect(peerState.optimisticKnownStates.has(invalidMessage.id)).toBe(false);
|
|
1629
720
|
});
|
|
1630
721
|
test("should ignore messages for errored coValues", async () => {
|
|
1631
|
-
const
|
|
1632
|
-
const
|
|
722
|
+
const client = await setupTestAccount();
|
|
723
|
+
const { peerState } = client.connectToSyncServer();
|
|
1633
724
|
// Add a coValue to the errored set
|
|
1634
725
|
const erroredId = "co_z123";
|
|
1635
|
-
|
|
726
|
+
peerState.erroredCoValues.set(erroredId, new Error("Test error"));
|
|
1636
727
|
const message = {
|
|
1637
728
|
action: "load",
|
|
1638
729
|
id: erroredId,
|
|
1639
730
|
header: false,
|
|
1640
731
|
sessions: {},
|
|
1641
732
|
};
|
|
1642
|
-
await client.syncManager.handleSyncMessage(message,
|
|
733
|
+
await client.node.syncManager.handleSyncMessage(message, peerState);
|
|
1643
734
|
// Verify that no state changes occurred
|
|
1644
|
-
expect(
|
|
1645
|
-
expect(
|
|
735
|
+
expect(peerState.knownStates.has(message.id)).toBe(false);
|
|
736
|
+
expect(peerState.optimisticKnownStates.has(message.id)).toBe(false);
|
|
1646
737
|
});
|
|
1647
738
|
test("should process valid messages", async () => {
|
|
1648
|
-
const
|
|
1649
|
-
const
|
|
1650
|
-
const
|
|
739
|
+
const client = await setupTestAccount();
|
|
740
|
+
const { peerState } = client.connectToSyncServer();
|
|
741
|
+
const group = client.node.createGroup();
|
|
1651
742
|
const validMessage = {
|
|
1652
743
|
action: "load",
|
|
1653
744
|
id: group.id,
|
|
1654
745
|
header: false,
|
|
1655
746
|
sessions: {},
|
|
1656
747
|
};
|
|
1657
|
-
await client.syncManager.handleSyncMessage(validMessage,
|
|
748
|
+
await client.node.syncManager.handleSyncMessage(validMessage, peerState);
|
|
1658
749
|
// Verify that the message was processed
|
|
1659
|
-
expect(
|
|
750
|
+
expect(peerState.knownStates.has(group.id)).toBe(true);
|
|
1660
751
|
});
|
|
1661
752
|
});
|
|
1662
753
|
//# sourceMappingURL=sync.test.js.map
|