cojson 0.18.27 → 0.18.28
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/dist/coValueCore/branching.d.ts +2 -1
- package/dist/coValueCore/branching.d.ts.map +1 -1
- package/dist/coValueCore/branching.js +20 -2
- package/dist/coValueCore/branching.js.map +1 -1
- package/dist/coValueCore/coValueCore.d.ts +26 -23
- package/dist/coValueCore/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore/coValueCore.js +161 -123
- package/dist/coValueCore/coValueCore.js.map +1 -1
- package/dist/coValueCore/decryptTransactionChangesAndMeta.d.ts +3 -0
- package/dist/coValueCore/decryptTransactionChangesAndMeta.d.ts.map +1 -0
- package/dist/coValueCore/decryptTransactionChangesAndMeta.js +34 -0
- package/dist/coValueCore/decryptTransactionChangesAndMeta.js.map +1 -0
- package/dist/localNode.js +1 -1
- package/dist/localNode.js.map +1 -1
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +18 -20
- package/dist/permissions.js.map +1 -1
- package/dist/sync.js +2 -2
- package/dist/sync.js.map +1 -1
- package/dist/tests/branching.test.js +237 -28
- package/dist/tests/branching.test.js.map +1 -1
- package/dist/tests/coValueCore.loadFromStorage.test.d.ts +2 -0
- package/dist/tests/coValueCore.loadFromStorage.test.d.ts.map +1 -0
- package/dist/tests/coValueCore.loadFromStorage.test.js +395 -0
- package/dist/tests/coValueCore.loadFromStorage.test.js.map +1 -0
- package/dist/tests/coValueCore.loadingState.test.d.ts +2 -0
- package/dist/tests/coValueCore.loadingState.test.d.ts.map +1 -0
- package/dist/tests/{coValueCoreLoadingState.test.js → coValueCore.loadingState.test.js} +4 -12
- package/dist/tests/coValueCore.loadingState.test.js.map +1 -0
- package/dist/tests/coValueCore.test.js +2 -5
- package/dist/tests/coValueCore.test.js.map +1 -1
- package/dist/tests/sync.mesh.test.js +4 -34
- package/dist/tests/sync.mesh.test.js.map +1 -1
- package/dist/tests/testUtils.d.ts +7 -1
- package/dist/tests/testUtils.d.ts.map +1 -1
- package/dist/tests/testUtils.js +12 -0
- package/dist/tests/testUtils.js.map +1 -1
- package/package.json +3 -3
- package/src/coValueCore/branching.ts +28 -3
- package/src/coValueCore/coValueCore.ts +215 -167
- package/src/coValueCore/decryptTransactionChangesAndMeta.ts +56 -0
- package/src/localNode.ts +1 -1
- package/src/permissions.ts +21 -19
- package/src/sync.ts +2 -2
- package/src/tests/branching.test.ts +392 -45
- package/src/tests/coValueCore.loadFromStorage.test.ts +540 -0
- package/src/tests/{coValueCoreLoadingState.test.ts → coValueCore.loadingState.test.ts} +3 -16
- package/src/tests/coValueCore.test.ts +2 -5
- package/src/tests/sync.mesh.test.ts +11 -38
- package/src/tests/testUtils.ts +21 -0
- package/dist/coValueCore/decodeTransactionChangesAndMeta.d.ts +0 -3
- package/dist/coValueCore/decodeTransactionChangesAndMeta.d.ts.map +0 -1
- package/dist/coValueCore/decodeTransactionChangesAndMeta.js +0 -59
- package/dist/coValueCore/decodeTransactionChangesAndMeta.js.map +0 -1
- package/dist/tests/coValueCoreLoadingState.test.d.ts +0 -2
- package/dist/tests/coValueCoreLoadingState.test.d.ts.map +0 -1
- package/dist/tests/coValueCoreLoadingState.test.js.map +0 -1
- package/src/coValueCore/decodeTransactionChangesAndMeta.ts +0 -81
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
2
|
+
import { RawCoID } from "../ids";
|
|
3
|
+
import { StorageAPI } from "../storage/types";
|
|
4
|
+
import {
|
|
5
|
+
createTestMetricReader,
|
|
6
|
+
createTestNode,
|
|
7
|
+
createUnloadedCoValue,
|
|
8
|
+
tearDownTestMetricReader,
|
|
9
|
+
} from "./testUtils";
|
|
10
|
+
|
|
11
|
+
let metricReader: ReturnType<typeof createTestMetricReader>;
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
metricReader = createTestMetricReader();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
tearDownTestMetricReader();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
function setup() {
|
|
22
|
+
const node = createTestNode();
|
|
23
|
+
|
|
24
|
+
const { coValue, id, header } = createUnloadedCoValue(node);
|
|
25
|
+
|
|
26
|
+
return { node, state: coValue, id, header };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function createMockStorage(
|
|
30
|
+
opts: {
|
|
31
|
+
load?: (
|
|
32
|
+
id: RawCoID,
|
|
33
|
+
callback: (data: any) => void,
|
|
34
|
+
done: (found: boolean) => void,
|
|
35
|
+
) => void;
|
|
36
|
+
store?: (data: any, correctionCallback: any) => void;
|
|
37
|
+
getKnownState?: (id: RawCoID) => any;
|
|
38
|
+
waitForSync?: (id: string, coValue: any) => Promise<void>;
|
|
39
|
+
close?: () => Promise<unknown> | undefined;
|
|
40
|
+
} = {},
|
|
41
|
+
): StorageAPI {
|
|
42
|
+
return {
|
|
43
|
+
load: opts.load || vi.fn(),
|
|
44
|
+
store: opts.store || vi.fn(),
|
|
45
|
+
getKnownState: opts.getKnownState || vi.fn(),
|
|
46
|
+
waitForSync: opts.waitForSync || vi.fn().mockResolvedValue(undefined),
|
|
47
|
+
close: opts.close || vi.fn().mockResolvedValue(undefined),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
describe("CoValueCore.loadFromStorage", () => {
|
|
52
|
+
describe("when storage is not configured", () => {
|
|
53
|
+
test("should call done callback with false immediately", () => {
|
|
54
|
+
const { state } = setup();
|
|
55
|
+
const doneSpy = vi.fn();
|
|
56
|
+
|
|
57
|
+
state.loadFromStorage(doneSpy);
|
|
58
|
+
|
|
59
|
+
expect(doneSpy).toHaveBeenCalledTimes(1);
|
|
60
|
+
expect(doneSpy).toHaveBeenCalledWith(false);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("should not crash when done callback is not provided", () => {
|
|
64
|
+
const { state } = setup();
|
|
65
|
+
|
|
66
|
+
expect(() => state.loadFromStorage()).not.toThrow();
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe("when current state is pending", () => {
|
|
71
|
+
test("should return early when done callback is not provided", () => {
|
|
72
|
+
const { state, node } = setup();
|
|
73
|
+
const loadSpy = vi.fn();
|
|
74
|
+
const storage = createMockStorage({ load: loadSpy });
|
|
75
|
+
node.setStorage(storage);
|
|
76
|
+
|
|
77
|
+
// Mark as pending
|
|
78
|
+
state.markPending("storage");
|
|
79
|
+
|
|
80
|
+
// Call without done callback
|
|
81
|
+
state.loadFromStorage();
|
|
82
|
+
|
|
83
|
+
// Should not call storage.load again
|
|
84
|
+
expect(loadSpy).not.toHaveBeenCalled();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("should wait for loading to complete and call done(true) when becomes available", async () => {
|
|
88
|
+
const { state, node, header } = setup();
|
|
89
|
+
let storageCallback: any;
|
|
90
|
+
let storageDone: any;
|
|
91
|
+
|
|
92
|
+
const storage = createMockStorage({
|
|
93
|
+
load: (id, callback, done) => {
|
|
94
|
+
storageCallback = callback;
|
|
95
|
+
storageDone = done;
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
node.setStorage(storage);
|
|
99
|
+
|
|
100
|
+
// Start initial load (will mark as pending)
|
|
101
|
+
state.loadFromStorage();
|
|
102
|
+
|
|
103
|
+
// Now try to load again with a done callback while pending
|
|
104
|
+
const doneSpy = vi.fn();
|
|
105
|
+
state.loadFromStorage(doneSpy);
|
|
106
|
+
|
|
107
|
+
// Should not call done yet
|
|
108
|
+
expect(doneSpy).not.toHaveBeenCalled();
|
|
109
|
+
|
|
110
|
+
// Simulate storage providing header and marking as found
|
|
111
|
+
const previousState = state.loadingState;
|
|
112
|
+
state.provideHeader(header);
|
|
113
|
+
state.markFoundInPeer("storage", previousState);
|
|
114
|
+
|
|
115
|
+
// Wait a tick for subscription to fire
|
|
116
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
117
|
+
|
|
118
|
+
expect(doneSpy).toHaveBeenCalledTimes(1);
|
|
119
|
+
expect(doneSpy).toHaveBeenCalledWith(true);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("should wait for loading to complete and call done(false) when becomes errored", async () => {
|
|
123
|
+
const { state, node } = setup();
|
|
124
|
+
const storage = createMockStorage({
|
|
125
|
+
load: vi.fn(),
|
|
126
|
+
});
|
|
127
|
+
node.setStorage(storage);
|
|
128
|
+
|
|
129
|
+
// Start initial load (will mark as pending)
|
|
130
|
+
state.loadFromStorage();
|
|
131
|
+
|
|
132
|
+
// Now try to load again with a done callback while pending
|
|
133
|
+
const doneSpy = vi.fn();
|
|
134
|
+
state.loadFromStorage(doneSpy);
|
|
135
|
+
|
|
136
|
+
// Should not call done yet
|
|
137
|
+
expect(doneSpy).not.toHaveBeenCalled();
|
|
138
|
+
|
|
139
|
+
// Simulate error
|
|
140
|
+
state.markErrored("storage", {} as any);
|
|
141
|
+
|
|
142
|
+
// Wait a tick for subscription to fire
|
|
143
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
144
|
+
|
|
145
|
+
expect(doneSpy).toHaveBeenCalledTimes(1);
|
|
146
|
+
expect(doneSpy).toHaveBeenCalledWith(false);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("should wait for loading to complete and call done(false) when becomes unavailable", async () => {
|
|
150
|
+
const { state, node } = setup();
|
|
151
|
+
const storage = createMockStorage({
|
|
152
|
+
load: vi.fn(),
|
|
153
|
+
});
|
|
154
|
+
node.setStorage(storage);
|
|
155
|
+
|
|
156
|
+
// Start initial load (will mark as pending)
|
|
157
|
+
state.loadFromStorage();
|
|
158
|
+
|
|
159
|
+
// Now try to load again with a done callback while pending
|
|
160
|
+
const doneSpy = vi.fn();
|
|
161
|
+
state.loadFromStorage(doneSpy);
|
|
162
|
+
|
|
163
|
+
// Should not call done yet
|
|
164
|
+
expect(doneSpy).not.toHaveBeenCalled();
|
|
165
|
+
|
|
166
|
+
// Simulate not found
|
|
167
|
+
state.markNotFoundInPeer("storage");
|
|
168
|
+
|
|
169
|
+
// Wait a tick for subscription to fire
|
|
170
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
171
|
+
|
|
172
|
+
expect(doneSpy).toHaveBeenCalledTimes(1);
|
|
173
|
+
expect(doneSpy).toHaveBeenCalledWith(false);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test("should unsubscribe after receiving result", async () => {
|
|
177
|
+
const { state, node, header } = setup();
|
|
178
|
+
const storage = createMockStorage({
|
|
179
|
+
load: vi.fn(),
|
|
180
|
+
});
|
|
181
|
+
node.setStorage(storage);
|
|
182
|
+
|
|
183
|
+
// Start initial load (will mark as pending)
|
|
184
|
+
state.loadFromStorage();
|
|
185
|
+
|
|
186
|
+
// Now try to load again with a done callback while pending
|
|
187
|
+
const doneSpy = vi.fn();
|
|
188
|
+
state.loadFromStorage(doneSpy);
|
|
189
|
+
|
|
190
|
+
// Simulate becoming available
|
|
191
|
+
const previousState = state.loadingState;
|
|
192
|
+
state.provideHeader(header);
|
|
193
|
+
state.markFoundInPeer("storage", previousState);
|
|
194
|
+
|
|
195
|
+
// Wait a tick for subscription to fire
|
|
196
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
197
|
+
|
|
198
|
+
expect(doneSpy).toHaveBeenCalledTimes(1);
|
|
199
|
+
|
|
200
|
+
// Further state changes should not trigger the callback again
|
|
201
|
+
state.markNotFoundInPeer("another_peer");
|
|
202
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
203
|
+
|
|
204
|
+
expect(doneSpy).toHaveBeenCalledTimes(1); // Still only called once
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
describe("when current state is not unknown", () => {
|
|
209
|
+
test("should call done(true) immediately when state is available", () => {
|
|
210
|
+
const { state, node, header } = setup();
|
|
211
|
+
const storage = createMockStorage();
|
|
212
|
+
node.setStorage(storage);
|
|
213
|
+
|
|
214
|
+
// Mark as available
|
|
215
|
+
const previousState = state.loadingState;
|
|
216
|
+
state.provideHeader(header);
|
|
217
|
+
state.markFoundInPeer("storage", previousState);
|
|
218
|
+
|
|
219
|
+
const doneSpy = vi.fn();
|
|
220
|
+
state.loadFromStorage(doneSpy);
|
|
221
|
+
|
|
222
|
+
expect(doneSpy).toHaveBeenCalledTimes(1);
|
|
223
|
+
expect(doneSpy).toHaveBeenCalledWith(true);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test("should call done(false) immediately when state is unavailable", () => {
|
|
227
|
+
const { state, node } = setup();
|
|
228
|
+
const storage = createMockStorage();
|
|
229
|
+
node.setStorage(storage);
|
|
230
|
+
|
|
231
|
+
// Mark as unavailable
|
|
232
|
+
state.markNotFoundInPeer("storage");
|
|
233
|
+
|
|
234
|
+
const doneSpy = vi.fn();
|
|
235
|
+
state.loadFromStorage(doneSpy);
|
|
236
|
+
|
|
237
|
+
expect(doneSpy).toHaveBeenCalledTimes(1);
|
|
238
|
+
expect(doneSpy).toHaveBeenCalledWith(false);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test("should call done(false) immediately when state is errored", () => {
|
|
242
|
+
const { state, node } = setup();
|
|
243
|
+
const storage = createMockStorage();
|
|
244
|
+
node.setStorage(storage);
|
|
245
|
+
|
|
246
|
+
// Mark as errored
|
|
247
|
+
state.markErrored("storage", {} as any);
|
|
248
|
+
|
|
249
|
+
const doneSpy = vi.fn();
|
|
250
|
+
state.loadFromStorage(doneSpy);
|
|
251
|
+
|
|
252
|
+
expect(doneSpy).toHaveBeenCalledTimes(1);
|
|
253
|
+
expect(doneSpy).toHaveBeenCalledWith(false);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test("should not call storage.load when state is already known", () => {
|
|
257
|
+
const { state, node, header } = setup();
|
|
258
|
+
const loadSpy = vi.fn();
|
|
259
|
+
const storage = createMockStorage({ load: loadSpy });
|
|
260
|
+
node.setStorage(storage);
|
|
261
|
+
|
|
262
|
+
// Mark as available
|
|
263
|
+
const previousState = state.loadingState;
|
|
264
|
+
state.provideHeader(header);
|
|
265
|
+
state.markFoundInPeer("storage", previousState);
|
|
266
|
+
|
|
267
|
+
state.loadFromStorage(vi.fn());
|
|
268
|
+
|
|
269
|
+
expect(loadSpy).not.toHaveBeenCalled();
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test("should handle missing done callback when state is available", () => {
|
|
273
|
+
const { state, node, header } = setup();
|
|
274
|
+
const storage = createMockStorage();
|
|
275
|
+
node.setStorage(storage);
|
|
276
|
+
|
|
277
|
+
// Mark as available
|
|
278
|
+
const previousState = state.loadingState;
|
|
279
|
+
state.provideHeader(header);
|
|
280
|
+
state.markFoundInPeer("storage", previousState);
|
|
281
|
+
|
|
282
|
+
expect(() => state.loadFromStorage()).not.toThrow();
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
describe("when current state is unknown", () => {
|
|
287
|
+
test("should mark as pending and call storage.load", () => {
|
|
288
|
+
const { state, node, id } = setup();
|
|
289
|
+
const loadSpy = vi.fn();
|
|
290
|
+
const storage = createMockStorage({ load: loadSpy });
|
|
291
|
+
node.setStorage(storage);
|
|
292
|
+
|
|
293
|
+
state.loadFromStorage();
|
|
294
|
+
|
|
295
|
+
expect(state.getLoadingStateForPeer("storage")).toBe("pending");
|
|
296
|
+
expect(loadSpy).toHaveBeenCalledTimes(1);
|
|
297
|
+
expect(loadSpy).toHaveBeenCalledWith(
|
|
298
|
+
id,
|
|
299
|
+
expect.any(Function),
|
|
300
|
+
expect.any(Function),
|
|
301
|
+
);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test("should call done(true) when storage finds the value", async () => {
|
|
305
|
+
const { state, node, id, header } = setup();
|
|
306
|
+
let storageCallback: any;
|
|
307
|
+
let storageDone: any;
|
|
308
|
+
|
|
309
|
+
const storage = createMockStorage({
|
|
310
|
+
load: (id, callback, done) => {
|
|
311
|
+
storageCallback = callback;
|
|
312
|
+
storageDone = done;
|
|
313
|
+
},
|
|
314
|
+
});
|
|
315
|
+
node.setStorage(storage);
|
|
316
|
+
|
|
317
|
+
const doneSpy = vi.fn();
|
|
318
|
+
state.loadFromStorage(doneSpy);
|
|
319
|
+
|
|
320
|
+
// Simulate storage finding the value
|
|
321
|
+
// First provide the content through callback
|
|
322
|
+
state.provideHeader(header);
|
|
323
|
+
|
|
324
|
+
// Then call done with true
|
|
325
|
+
storageDone(true);
|
|
326
|
+
|
|
327
|
+
expect(doneSpy).toHaveBeenCalledTimes(1);
|
|
328
|
+
expect(doneSpy).toHaveBeenCalledWith(true);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
test("should call done(false) and mark as not found when storage doesn't find the value", async () => {
|
|
332
|
+
const { state, node } = setup();
|
|
333
|
+
let storageDone: any;
|
|
334
|
+
|
|
335
|
+
const storage = createMockStorage({
|
|
336
|
+
load: (id, callback, done) => {
|
|
337
|
+
storageDone = done;
|
|
338
|
+
},
|
|
339
|
+
});
|
|
340
|
+
node.setStorage(storage);
|
|
341
|
+
|
|
342
|
+
const doneSpy = vi.fn();
|
|
343
|
+
state.loadFromStorage(doneSpy);
|
|
344
|
+
|
|
345
|
+
// Simulate storage not finding the value
|
|
346
|
+
storageDone(false);
|
|
347
|
+
|
|
348
|
+
expect(doneSpy).toHaveBeenCalledTimes(1);
|
|
349
|
+
expect(doneSpy).toHaveBeenCalledWith(false);
|
|
350
|
+
expect(state.getLoadingStateForPeer("storage")).toBe("unavailable");
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test("should pass content to syncManager when storage provides it", async () => {
|
|
354
|
+
const { state, node } = setup();
|
|
355
|
+
let storageCallback: any;
|
|
356
|
+
|
|
357
|
+
const storage = createMockStorage({
|
|
358
|
+
load: (id, callback, done) => {
|
|
359
|
+
storageCallback = callback;
|
|
360
|
+
},
|
|
361
|
+
});
|
|
362
|
+
node.setStorage(storage);
|
|
363
|
+
|
|
364
|
+
const handleNewContentSpy = vi.spyOn(
|
|
365
|
+
node.syncManager,
|
|
366
|
+
"handleNewContent",
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
state.loadFromStorage();
|
|
370
|
+
|
|
371
|
+
// Simulate storage providing content with proper format
|
|
372
|
+
const mockData = {
|
|
373
|
+
action: "content" as const,
|
|
374
|
+
id: state.id,
|
|
375
|
+
priority: 0,
|
|
376
|
+
new: {},
|
|
377
|
+
};
|
|
378
|
+
storageCallback(mockData);
|
|
379
|
+
|
|
380
|
+
expect(handleNewContentSpy).toHaveBeenCalledTimes(1);
|
|
381
|
+
expect(handleNewContentSpy).toHaveBeenCalledWith(mockData, "storage");
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
test("should handle missing done callback when loading from storage", () => {
|
|
385
|
+
const { state, node } = setup();
|
|
386
|
+
let storageDone: any;
|
|
387
|
+
|
|
388
|
+
const storage = createMockStorage({
|
|
389
|
+
load: (id, callback, done) => {
|
|
390
|
+
storageDone = done;
|
|
391
|
+
},
|
|
392
|
+
});
|
|
393
|
+
node.setStorage(storage);
|
|
394
|
+
|
|
395
|
+
expect(() => {
|
|
396
|
+
state.loadFromStorage();
|
|
397
|
+
storageDone(true);
|
|
398
|
+
}).not.toThrow();
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
test("should not mark as not found when storage finds the value", async () => {
|
|
402
|
+
const { state, node, header } = setup();
|
|
403
|
+
let storageDone: any;
|
|
404
|
+
|
|
405
|
+
const storage = createMockStorage({
|
|
406
|
+
load: (id, callback, done) => {
|
|
407
|
+
storageDone = done;
|
|
408
|
+
},
|
|
409
|
+
});
|
|
410
|
+
node.setStorage(storage);
|
|
411
|
+
|
|
412
|
+
state.loadFromStorage();
|
|
413
|
+
|
|
414
|
+
// Provide header first
|
|
415
|
+
state.provideHeader(header);
|
|
416
|
+
const previousState = state.loadingState;
|
|
417
|
+
state.markFoundInPeer("storage", previousState);
|
|
418
|
+
|
|
419
|
+
// Call done with true
|
|
420
|
+
storageDone(true);
|
|
421
|
+
|
|
422
|
+
// State should be available, not unavailable
|
|
423
|
+
expect(state.getLoadingStateForPeer("storage")).not.toBe("unavailable");
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
test("should handle multiple concurrent loadFromStorage calls", async () => {
|
|
427
|
+
const { state, node, id } = setup();
|
|
428
|
+
const loadSpy = vi.fn();
|
|
429
|
+
const storage = createMockStorage({ load: loadSpy });
|
|
430
|
+
node.setStorage(storage);
|
|
431
|
+
|
|
432
|
+
const done1 = vi.fn();
|
|
433
|
+
const done2 = vi.fn();
|
|
434
|
+
const done3 = vi.fn();
|
|
435
|
+
|
|
436
|
+
// All three calls should work together
|
|
437
|
+
state.loadFromStorage(done1);
|
|
438
|
+
state.loadFromStorage(done2);
|
|
439
|
+
state.loadFromStorage(done3);
|
|
440
|
+
|
|
441
|
+
// Storage.load should only be called once (first call)
|
|
442
|
+
expect(loadSpy).toHaveBeenCalledTimes(1);
|
|
443
|
+
|
|
444
|
+
// The other calls should be waiting (pending state)
|
|
445
|
+
expect(done1).not.toHaveBeenCalled();
|
|
446
|
+
expect(done2).not.toHaveBeenCalled();
|
|
447
|
+
expect(done3).not.toHaveBeenCalled();
|
|
448
|
+
});
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
describe("edge cases and integration", () => {
|
|
452
|
+
test("should handle transition from unknown to pending to available", async () => {
|
|
453
|
+
const { state, node, header } = setup();
|
|
454
|
+
let storageCallback: any;
|
|
455
|
+
let storageDone: any;
|
|
456
|
+
|
|
457
|
+
const storage = createMockStorage({
|
|
458
|
+
load: (id, callback, done) => {
|
|
459
|
+
storageCallback = callback;
|
|
460
|
+
storageDone = done;
|
|
461
|
+
},
|
|
462
|
+
});
|
|
463
|
+
node.setStorage(storage);
|
|
464
|
+
|
|
465
|
+
const doneSpy = vi.fn();
|
|
466
|
+
|
|
467
|
+
// Start as unknown
|
|
468
|
+
expect(state.getLoadingStateForPeer("storage")).toBe("unknown");
|
|
469
|
+
|
|
470
|
+
// Load from storage
|
|
471
|
+
state.loadFromStorage(doneSpy);
|
|
472
|
+
|
|
473
|
+
// Should now be pending
|
|
474
|
+
expect(state.getLoadingStateForPeer("storage")).toBe("pending");
|
|
475
|
+
|
|
476
|
+
// Simulate storage providing the header
|
|
477
|
+
state.provideHeader(header);
|
|
478
|
+
const previousState = state.loadingState;
|
|
479
|
+
state.markFoundInPeer("storage", previousState);
|
|
480
|
+
|
|
481
|
+
// Call done
|
|
482
|
+
storageDone(true);
|
|
483
|
+
|
|
484
|
+
// Should be available
|
|
485
|
+
expect(state.getLoadingStateForPeer("storage")).toBe("available");
|
|
486
|
+
expect(doneSpy).toHaveBeenCalledWith(true);
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
test("should properly clean up subscriptions when state becomes available through isAvailable()", async () => {
|
|
490
|
+
const { state, node, header } = setup();
|
|
491
|
+
const storage = createMockStorage({
|
|
492
|
+
load: vi.fn(),
|
|
493
|
+
});
|
|
494
|
+
node.setStorage(storage);
|
|
495
|
+
|
|
496
|
+
// Start initial load (will mark as pending)
|
|
497
|
+
state.loadFromStorage();
|
|
498
|
+
|
|
499
|
+
// Now try to load again with a done callback while pending
|
|
500
|
+
const doneSpy = vi.fn();
|
|
501
|
+
state.loadFromStorage(doneSpy);
|
|
502
|
+
|
|
503
|
+
// Make the whole state available (not just from storage peer)
|
|
504
|
+
state.provideHeader(header);
|
|
505
|
+
const previousState = state.loadingState;
|
|
506
|
+
state.markFoundInPeer("some_other_peer", previousState);
|
|
507
|
+
|
|
508
|
+
// Wait for subscription to process
|
|
509
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
510
|
+
|
|
511
|
+
// Should have called done(true) because isAvailable() is true
|
|
512
|
+
expect(doneSpy).toHaveBeenCalledTimes(1);
|
|
513
|
+
expect(doneSpy).toHaveBeenCalledWith(true);
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
test("should handle rapid state changes", async () => {
|
|
517
|
+
const { state, node, header } = setup();
|
|
518
|
+
const storage = createMockStorage({
|
|
519
|
+
load: vi.fn(),
|
|
520
|
+
});
|
|
521
|
+
node.setStorage(storage);
|
|
522
|
+
|
|
523
|
+
const doneSpy = vi.fn();
|
|
524
|
+
|
|
525
|
+
// Start loading
|
|
526
|
+
state.loadFromStorage(doneSpy);
|
|
527
|
+
|
|
528
|
+
// Rapid state changes
|
|
529
|
+
state.markPending("storage");
|
|
530
|
+
state.markNotFoundInPeer("storage");
|
|
531
|
+
|
|
532
|
+
const previousState = state.loadingState;
|
|
533
|
+
state.provideHeader(header);
|
|
534
|
+
state.markFoundInPeer("storage", previousState);
|
|
535
|
+
|
|
536
|
+
// Should have the final state
|
|
537
|
+
expect(state.getLoadingStateForPeer("storage")).toBe("available");
|
|
538
|
+
});
|
|
539
|
+
});
|
|
540
|
+
});
|
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
2
2
|
import { PeerState } from "../PeerState";
|
|
3
|
-
import { CoValueCore, idforHeader } from "../coValueCore/coValueCore";
|
|
4
|
-
import { CoValueHeader, VerifiedState } from "../coValueCore/verifiedState";
|
|
5
|
-
import { RawCoID } from "../ids";
|
|
6
|
-
import { LocalNode } from "../localNode";
|
|
7
3
|
import { Peer } from "../sync";
|
|
8
4
|
import {
|
|
9
5
|
createTestMetricReader,
|
|
10
6
|
createTestNode,
|
|
7
|
+
createUnloadedCoValue,
|
|
11
8
|
tearDownTestMetricReader,
|
|
12
9
|
} from "./testUtils";
|
|
13
|
-
import { WasmCrypto } from "../crypto/WasmCrypto";
|
|
14
10
|
|
|
15
11
|
let metricReader: ReturnType<typeof createTestMetricReader>;
|
|
16
12
|
|
|
@@ -25,18 +21,9 @@ afterEach(() => {
|
|
|
25
21
|
function setup() {
|
|
26
22
|
const node = createTestNode();
|
|
27
23
|
|
|
28
|
-
const header =
|
|
29
|
-
type: "comap",
|
|
30
|
-
ruleset: { type: "ownedByGroup", group: "co_ztest123" },
|
|
31
|
-
meta: null,
|
|
32
|
-
...node.crypto.createdNowUnique(),
|
|
33
|
-
} as CoValueHeader;
|
|
24
|
+
const { coValue, id, header } = createUnloadedCoValue(node);
|
|
34
25
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const state = CoValueCore.fromID(id, node);
|
|
38
|
-
|
|
39
|
-
return { node, state, id, header };
|
|
26
|
+
return { node, state: coValue, id, header };
|
|
40
27
|
}
|
|
41
28
|
|
|
42
29
|
describe("CoValueCore loading state", () => {
|
|
@@ -451,9 +451,7 @@ describe("markErrored and isErroredInPeer", () => {
|
|
|
451
451
|
expect(coValue.isErroredInPeer(peerId)).toBe(true);
|
|
452
452
|
|
|
453
453
|
// Verify the peer state contains the error
|
|
454
|
-
|
|
455
|
-
expect(peerState).toBeDefined();
|
|
456
|
-
expect(peerState?.type).toBe("errored");
|
|
454
|
+
expect(coValue.getLoadingStateForPeer(peerId)).toBe("errored");
|
|
457
455
|
});
|
|
458
456
|
|
|
459
457
|
test("markErrored should update loading state and notify listeners", () => {
|
|
@@ -632,8 +630,7 @@ describe("markErrored and isErroredInPeer", () => {
|
|
|
632
630
|
// Verify the peer is now errored
|
|
633
631
|
expect(coValue.isErroredInPeer(peerId)).toBe(true);
|
|
634
632
|
|
|
635
|
-
|
|
636
|
-
expect(peerState?.type).toBe("errored");
|
|
633
|
+
expect(coValue.getLoadingStateForPeer(peerId)).toBe("errored");
|
|
637
634
|
});
|
|
638
635
|
|
|
639
636
|
test("markErrored should work with different error types", () => {
|
|
@@ -276,44 +276,17 @@ describe("multiple clients syncing with the a cloud-like server mesh", () => {
|
|
|
276
276
|
expect(coValue.get("fromClient")).toEqual("updated");
|
|
277
277
|
});
|
|
278
278
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
).
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
"edge-italy -> core | CONTENT Map header: true new: After: 0 New: 1",
|
|
291
|
-
"edge-italy -> client | CONTENT Group header: true new: After: 0 New: 5",
|
|
292
|
-
"edge-italy -> client | CONTENT Map header: true new: After: 0 New: 1",
|
|
293
|
-
"core -> edge-italy | KNOWN Group sessions: header/5",
|
|
294
|
-
"core -> storage | CONTENT Group header: true new: After: 0 New: 5",
|
|
295
|
-
"core -> edge-italy | KNOWN Map sessions: header/1",
|
|
296
|
-
"core -> storage | CONTENT Map header: true new: After: 0 New: 1",
|
|
297
|
-
"client -> edge-italy | KNOWN Group sessions: header/5",
|
|
298
|
-
"client -> edge-italy | KNOWN Map sessions: header/1",
|
|
299
|
-
"client -> edge-italy | CONTENT Map header: false new: After: 0 New: 1",
|
|
300
|
-
"core -> storage | CONTENT Map header: false new: After: 0 New: 1",
|
|
301
|
-
"core -> edge-italy | CONTENT Map header: false new: After: 0 New: 1",
|
|
302
|
-
"edge-italy -> client | KNOWN CORRECTION Map sessions: empty",
|
|
303
|
-
"edge-italy -> core | KNOWN CORRECTION Map sessions: empty",
|
|
304
|
-
"client -> edge-italy | CONTENT Map header: true new: After: 0 New: 1 | After: 0 New: 1",
|
|
305
|
-
"core -> edge-italy | CONTENT Map header: true new: After: 0 New: 1 | After: 0 New: 1",
|
|
306
|
-
"edge-italy -> client | KNOWN Map sessions: header/2",
|
|
307
|
-
"edge-italy -> storage | CONTENT Map header: true new: After: 0 New: 1 | After: 0 New: 1",
|
|
308
|
-
"edge-italy -> core | CONTENT Map header: false new: After: 0 New: 1",
|
|
309
|
-
"edge-italy -> core | KNOWN Map sessions: header/3",
|
|
310
|
-
"edge-italy -> storage | CONTENT Map header: true new: After: 0 New: 1",
|
|
311
|
-
"edge-italy -> client | CONTENT Map header: false new: After: 0 New: 1",
|
|
312
|
-
"core -> edge-italy | KNOWN Map sessions: header/3",
|
|
313
|
-
"core -> storage | CONTENT Map header: false new: After: 0 New: 1",
|
|
314
|
-
"client -> edge-italy | KNOWN Map sessions: header/3",
|
|
315
|
-
]
|
|
316
|
-
`);
|
|
279
|
+
const syncLog = SyncMessagesLog.getMessages({
|
|
280
|
+
Group: group.core,
|
|
281
|
+
Map: map.core,
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
expect(syncLog).toContain(
|
|
285
|
+
"edge-italy -> client | KNOWN CORRECTION Map sessions: empty",
|
|
286
|
+
);
|
|
287
|
+
expect(syncLog).toContain(
|
|
288
|
+
"edge-italy -> core | KNOWN CORRECTION Map sessions: empty",
|
|
289
|
+
);
|
|
317
290
|
});
|
|
318
291
|
|
|
319
292
|
test("sync of changes of a coValue with bad signatures should be blocked", async () => {
|
package/src/tests/testUtils.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { ControlledAccount, ControlledAgent } from "../coValues/account.js";
|
|
|
10
10
|
import { WasmCrypto } from "../crypto/WasmCrypto.js";
|
|
11
11
|
import {
|
|
12
12
|
type AgentSecret,
|
|
13
|
+
AnyRawCoValue,
|
|
13
14
|
type CoID,
|
|
14
15
|
type CoValueCore,
|
|
15
16
|
type RawAccount,
|
|
@@ -24,6 +25,8 @@ import { expectGroup } from "../typeUtils/expectGroup.js";
|
|
|
24
25
|
import { toSimplifiedMessages } from "./messagesTestUtils.js";
|
|
25
26
|
import { createAsyncStorage, createSyncStorage } from "./testStorage.js";
|
|
26
27
|
import { PureJSCrypto } from "../crypto/PureJSCrypto.js";
|
|
28
|
+
import { CoValueHeader } from "../coValueCore/verifiedState.js";
|
|
29
|
+
import { idforHeader } from "../coValueCore/coValueCore.js";
|
|
27
30
|
|
|
28
31
|
let Crypto = await WasmCrypto.create();
|
|
29
32
|
|
|
@@ -722,3 +725,21 @@ export function createAccountInNode(node: LocalNode) {
|
|
|
722
725
|
accountOnTempNode.core.node.agentSecret,
|
|
723
726
|
);
|
|
724
727
|
}
|
|
728
|
+
|
|
729
|
+
export function createUnloadedCoValue(
|
|
730
|
+
node: LocalNode,
|
|
731
|
+
type: AnyRawCoValue["type"] = "comap",
|
|
732
|
+
) {
|
|
733
|
+
const header = {
|
|
734
|
+
type,
|
|
735
|
+
ruleset: { type: "ownedByGroup", group: node.getCurrentAccountOrAgentID() },
|
|
736
|
+
meta: null,
|
|
737
|
+
...node.crypto.createdNowUnique(),
|
|
738
|
+
} as CoValueHeader;
|
|
739
|
+
|
|
740
|
+
const id = idforHeader(header, node.crypto);
|
|
741
|
+
|
|
742
|
+
const state = node.getCoValue(id);
|
|
743
|
+
|
|
744
|
+
return { coValue: state, id, header };
|
|
745
|
+
}
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import { AvailableCoValueCore, VerifiedTransaction } from "./coValueCore.js";
|
|
2
|
-
export declare function decodeTransactionChangesAndMeta(coValue: AvailableCoValueCore, transaction: VerifiedTransaction, ignorePrivateTransactions: boolean): void;
|
|
3
|
-
//# sourceMappingURL=decodeTransactionChangesAndMeta.d.ts.map
|