cojson 0.19.21 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +67 -0
- package/dist/CojsonMessageChannel/CojsonMessageChannel.d.ts +42 -0
- package/dist/CojsonMessageChannel/CojsonMessageChannel.d.ts.map +1 -0
- package/dist/CojsonMessageChannel/CojsonMessageChannel.js +261 -0
- package/dist/CojsonMessageChannel/CojsonMessageChannel.js.map +1 -0
- package/dist/CojsonMessageChannel/MessagePortOutgoingChannel.d.ts +18 -0
- package/dist/CojsonMessageChannel/MessagePortOutgoingChannel.d.ts.map +1 -0
- package/dist/CojsonMessageChannel/MessagePortOutgoingChannel.js +37 -0
- package/dist/CojsonMessageChannel/MessagePortOutgoingChannel.js.map +1 -0
- package/dist/CojsonMessageChannel/index.d.ts +3 -0
- package/dist/CojsonMessageChannel/index.d.ts.map +1 -0
- package/dist/CojsonMessageChannel/index.js +2 -0
- package/dist/CojsonMessageChannel/index.js.map +1 -0
- package/dist/CojsonMessageChannel/types.d.ts +149 -0
- package/dist/CojsonMessageChannel/types.d.ts.map +1 -0
- package/dist/CojsonMessageChannel/types.js +36 -0
- package/dist/CojsonMessageChannel/types.js.map +1 -0
- package/dist/GarbageCollector.d.ts +4 -2
- package/dist/GarbageCollector.d.ts.map +1 -1
- package/dist/GarbageCollector.js +5 -3
- package/dist/GarbageCollector.js.map +1 -1
- package/dist/SyncStateManager.d.ts +3 -3
- package/dist/SyncStateManager.d.ts.map +1 -1
- package/dist/SyncStateManager.js +4 -4
- package/dist/SyncStateManager.js.map +1 -1
- package/dist/coValueContentMessage.d.ts +0 -2
- package/dist/coValueContentMessage.d.ts.map +1 -1
- package/dist/coValueContentMessage.js +0 -8
- package/dist/coValueContentMessage.js.map +1 -1
- package/dist/coValueCore/SessionMap.d.ts +4 -2
- package/dist/coValueCore/SessionMap.d.ts.map +1 -1
- package/dist/coValueCore/SessionMap.js +30 -0
- package/dist/coValueCore/SessionMap.js.map +1 -1
- package/dist/coValueCore/coValueCore.d.ts +86 -4
- package/dist/coValueCore/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore/coValueCore.js +318 -17
- package/dist/coValueCore/coValueCore.js.map +1 -1
- package/dist/coValueCore/verifiedState.d.ts +6 -1
- package/dist/coValueCore/verifiedState.d.ts.map +1 -1
- package/dist/coValueCore/verifiedState.js +9 -0
- package/dist/coValueCore/verifiedState.js.map +1 -1
- package/dist/coValues/coList.d.ts +3 -2
- package/dist/coValues/coList.d.ts.map +1 -1
- package/dist/coValues/coList.js.map +1 -1
- package/dist/coValues/group.d.ts.map +1 -1
- package/dist/coValues/group.js +3 -6
- package/dist/coValues/group.js.map +1 -1
- package/dist/config.d.ts +0 -6
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +0 -8
- package/dist/config.js.map +1 -1
- package/dist/crypto/NapiCrypto.d.ts +1 -2
- package/dist/crypto/NapiCrypto.d.ts.map +1 -1
- package/dist/crypto/NapiCrypto.js +19 -4
- package/dist/crypto/NapiCrypto.js.map +1 -1
- package/dist/crypto/RNCrypto.d.ts.map +1 -1
- package/dist/crypto/RNCrypto.js +19 -4
- package/dist/crypto/RNCrypto.js.map +1 -1
- package/dist/crypto/WasmCrypto.d.ts +11 -4
- package/dist/crypto/WasmCrypto.d.ts.map +1 -1
- package/dist/crypto/WasmCrypto.js +52 -10
- package/dist/crypto/WasmCrypto.js.map +1 -1
- package/dist/crypto/WasmCryptoEdge.d.ts +1 -0
- package/dist/crypto/WasmCryptoEdge.d.ts.map +1 -1
- package/dist/crypto/WasmCryptoEdge.js +4 -1
- package/dist/crypto/WasmCryptoEdge.js.map +1 -1
- package/dist/crypto/crypto.d.ts +3 -3
- package/dist/crypto/crypto.d.ts.map +1 -1
- package/dist/crypto/crypto.js +6 -1
- package/dist/crypto/crypto.js.map +1 -1
- package/dist/exports.d.ts +3 -2
- package/dist/exports.d.ts.map +1 -1
- package/dist/exports.js +3 -1
- package/dist/exports.js.map +1 -1
- package/dist/ids.d.ts +4 -1
- package/dist/ids.d.ts.map +1 -1
- package/dist/ids.js +4 -0
- package/dist/ids.js.map +1 -1
- package/dist/knownState.d.ts +2 -0
- package/dist/knownState.d.ts.map +1 -1
- package/dist/localNode.d.ts +13 -3
- package/dist/localNode.d.ts.map +1 -1
- package/dist/localNode.js +17 -2
- package/dist/localNode.js.map +1 -1
- package/dist/platformUtils.d.ts +3 -0
- package/dist/platformUtils.d.ts.map +1 -0
- package/dist/platformUtils.js +24 -0
- package/dist/platformUtils.js.map +1 -0
- package/dist/storage/DeletedCoValuesEraserScheduler.d.ts +30 -0
- package/dist/storage/DeletedCoValuesEraserScheduler.d.ts.map +1 -0
- package/dist/storage/DeletedCoValuesEraserScheduler.js +84 -0
- package/dist/storage/DeletedCoValuesEraserScheduler.js.map +1 -0
- package/dist/storage/sqlite/client.d.ts +3 -0
- package/dist/storage/sqlite/client.d.ts.map +1 -1
- package/dist/storage/sqlite/client.js +44 -0
- package/dist/storage/sqlite/client.js.map +1 -1
- package/dist/storage/sqlite/sqliteMigrations.d.ts.map +1 -1
- package/dist/storage/sqlite/sqliteMigrations.js +7 -0
- package/dist/storage/sqlite/sqliteMigrations.js.map +1 -1
- package/dist/storage/sqliteAsync/client.d.ts +3 -0
- package/dist/storage/sqliteAsync/client.d.ts.map +1 -1
- package/dist/storage/sqliteAsync/client.js +42 -0
- package/dist/storage/sqliteAsync/client.js.map +1 -1
- package/dist/storage/storageAsync.d.ts +15 -3
- package/dist/storage/storageAsync.d.ts.map +1 -1
- package/dist/storage/storageAsync.js +60 -3
- package/dist/storage/storageAsync.js.map +1 -1
- package/dist/storage/storageSync.d.ts +14 -3
- package/dist/storage/storageSync.d.ts.map +1 -1
- package/dist/storage/storageSync.js +54 -3
- package/dist/storage/storageSync.js.map +1 -1
- package/dist/storage/types.d.ts +64 -0
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/storage/types.js +12 -1
- package/dist/storage/types.js.map +1 -1
- package/dist/sync.d.ts +6 -0
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +69 -15
- package/dist/sync.js.map +1 -1
- package/dist/tests/CojsonMessageChannel.test.d.ts +2 -0
- package/dist/tests/CojsonMessageChannel.test.d.ts.map +1 -0
- package/dist/tests/CojsonMessageChannel.test.js +236 -0
- package/dist/tests/CojsonMessageChannel.test.js.map +1 -0
- package/dist/tests/DeletedCoValuesEraserScheduler.test.d.ts +2 -0
- package/dist/tests/DeletedCoValuesEraserScheduler.test.d.ts.map +1 -0
- package/dist/tests/DeletedCoValuesEraserScheduler.test.js +149 -0
- package/dist/tests/DeletedCoValuesEraserScheduler.test.js.map +1 -0
- package/dist/tests/GarbageCollector.test.js +91 -18
- package/dist/tests/GarbageCollector.test.js.map +1 -1
- package/dist/tests/StorageApiAsync.test.js +510 -146
- package/dist/tests/StorageApiAsync.test.js.map +1 -1
- package/dist/tests/StorageApiSync.test.js +531 -130
- package/dist/tests/StorageApiSync.test.js.map +1 -1
- package/dist/tests/SyncManager.processQueues.test.js +1 -1
- package/dist/tests/SyncManager.processQueues.test.js.map +1 -1
- package/dist/tests/SyncStateManager.test.js +1 -1
- package/dist/tests/SyncStateManager.test.js.map +1 -1
- package/dist/tests/WasmCrypto.test.js +6 -3
- package/dist/tests/WasmCrypto.test.js.map +1 -1
- package/dist/tests/coPlainText.test.js +1 -1
- package/dist/tests/coPlainText.test.js.map +1 -1
- package/dist/tests/coValueCore.loadFromStorage.test.js +4 -0
- package/dist/tests/coValueCore.loadFromStorage.test.js.map +1 -1
- package/dist/tests/coValueCore.test.js +34 -13
- package/dist/tests/coValueCore.test.js.map +1 -1
- package/dist/tests/coreWasm.test.js +127 -4
- package/dist/tests/coreWasm.test.js.map +1 -1
- package/dist/tests/crypto.test.js +89 -93
- package/dist/tests/crypto.test.js.map +1 -1
- package/dist/tests/deleteCoValue.test.d.ts +2 -0
- package/dist/tests/deleteCoValue.test.d.ts.map +1 -0
- package/dist/tests/deleteCoValue.test.js +313 -0
- package/dist/tests/deleteCoValue.test.js.map +1 -0
- package/dist/tests/group.removeMember.test.js +18 -30
- package/dist/tests/group.removeMember.test.js.map +1 -1
- package/dist/tests/knownState.lazyLoading.test.js +4 -0
- package/dist/tests/knownState.lazyLoading.test.js.map +1 -1
- package/dist/tests/sync.deleted.test.d.ts +2 -0
- package/dist/tests/sync.deleted.test.d.ts.map +1 -0
- package/dist/tests/sync.deleted.test.js +214 -0
- package/dist/tests/sync.deleted.test.js.map +1 -0
- package/dist/tests/sync.garbageCollection.test.js +56 -32
- package/dist/tests/sync.garbageCollection.test.js.map +1 -1
- package/dist/tests/sync.load.test.js +3 -5
- package/dist/tests/sync.load.test.js.map +1 -1
- package/dist/tests/sync.mesh.test.js +4 -3
- package/dist/tests/sync.mesh.test.js.map +1 -1
- package/dist/tests/sync.peerReconciliation.test.js +3 -3
- package/dist/tests/sync.peerReconciliation.test.js.map +1 -1
- package/dist/tests/sync.storage.test.js +12 -11
- package/dist/tests/sync.storage.test.js.map +1 -1
- package/dist/tests/sync.storageAsync.test.js +7 -7
- package/dist/tests/sync.storageAsync.test.js.map +1 -1
- package/dist/tests/sync.test.js +3 -2
- package/dist/tests/sync.test.js.map +1 -1
- package/dist/tests/sync.tracking.test.js +35 -4
- package/dist/tests/sync.tracking.test.js.map +1 -1
- package/dist/tests/testStorage.d.ts +3 -0
- package/dist/tests/testStorage.d.ts.map +1 -1
- package/dist/tests/testStorage.js +16 -2
- package/dist/tests/testStorage.js.map +1 -1
- package/dist/tests/testUtils.d.ts +29 -4
- package/dist/tests/testUtils.d.ts.map +1 -1
- package/dist/tests/testUtils.js +84 -9
- package/dist/tests/testUtils.js.map +1 -1
- package/package.json +6 -16
- package/src/CojsonMessageChannel/CojsonMessageChannel.ts +332 -0
- package/src/CojsonMessageChannel/MessagePortOutgoingChannel.ts +52 -0
- package/src/CojsonMessageChannel/index.ts +9 -0
- package/src/CojsonMessageChannel/types.ts +200 -0
- package/src/GarbageCollector.ts +5 -5
- package/src/SyncStateManager.ts +6 -6
- package/src/coValueContentMessage.ts +0 -14
- package/src/coValueCore/SessionMap.ts +43 -1
- package/src/coValueCore/coValueCore.ts +430 -15
- package/src/coValueCore/verifiedState.ts +26 -3
- package/src/coValues/coList.ts +5 -3
- package/src/coValues/group.ts +5 -6
- package/src/config.ts +0 -9
- package/src/crypto/NapiCrypto.ts +29 -13
- package/src/crypto/RNCrypto.ts +29 -11
- package/src/crypto/WasmCrypto.ts +67 -20
- package/src/crypto/WasmCryptoEdge.ts +5 -1
- package/src/crypto/crypto.ts +16 -4
- package/src/exports.ts +3 -0
- package/src/ids.ts +11 -1
- package/src/localNode.ts +18 -5
- package/src/platformUtils.ts +26 -0
- package/src/storage/DeletedCoValuesEraserScheduler.ts +124 -0
- package/src/storage/sqlite/client.ts +77 -0
- package/src/storage/sqlite/sqliteMigrations.ts +7 -0
- package/src/storage/sqliteAsync/client.ts +75 -0
- package/src/storage/storageAsync.ts +77 -4
- package/src/storage/storageSync.ts +73 -4
- package/src/storage/types.ts +75 -0
- package/src/sync.ts +84 -15
- package/src/tests/CojsonMessageChannel.test.ts +306 -0
- package/src/tests/DeletedCoValuesEraserScheduler.test.ts +185 -0
- package/src/tests/GarbageCollector.test.ts +119 -22
- package/src/tests/StorageApiAsync.test.ts +615 -156
- package/src/tests/StorageApiSync.test.ts +623 -137
- package/src/tests/SyncManager.processQueues.test.ts +1 -1
- package/src/tests/SyncStateManager.test.ts +1 -1
- package/src/tests/WasmCrypto.test.ts +8 -3
- package/src/tests/coPlainText.test.ts +1 -1
- package/src/tests/coValueCore.loadFromStorage.test.ts +8 -0
- package/src/tests/coValueCore.test.ts +49 -14
- package/src/tests/coreWasm.test.ts +319 -10
- package/src/tests/crypto.test.ts +141 -150
- package/src/tests/deleteCoValue.test.ts +528 -0
- package/src/tests/group.removeMember.test.ts +35 -35
- package/src/tests/knownState.lazyLoading.test.ts +8 -0
- package/src/tests/sync.deleted.test.ts +294 -0
- package/src/tests/sync.garbageCollection.test.ts +69 -36
- package/src/tests/sync.load.test.ts +3 -5
- package/src/tests/sync.mesh.test.ts +6 -3
- package/src/tests/sync.peerReconciliation.test.ts +3 -3
- package/src/tests/sync.storage.test.ts +14 -11
- package/src/tests/sync.storageAsync.test.ts +7 -7
- package/src/tests/sync.test.ts +5 -2
- package/src/tests/sync.tracking.test.ts +54 -4
- package/src/tests/testStorage.ts +30 -3
- package/src/tests/testUtils.ts +113 -15
- package/dist/crypto/PureJSCrypto.d.ts +0 -77
- package/dist/crypto/PureJSCrypto.d.ts.map +0 -1
- package/dist/crypto/PureJSCrypto.js +0 -236
- package/dist/crypto/PureJSCrypto.js.map +0 -1
- package/dist/tests/PureJSCrypto.test.d.ts +0 -2
- package/dist/tests/PureJSCrypto.test.d.ts.map +0 -1
- package/dist/tests/PureJSCrypto.test.js +0 -145
- package/dist/tests/PureJSCrypto.test.js.map +0 -1
- package/src/crypto/PureJSCrypto.ts +0 -429
- package/src/tests/PureJSCrypto.test.ts +0 -217
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
import { assert, beforeEach, expect, test } from "vitest";
|
|
2
|
+
import { WasmCrypto } from "../crypto/WasmCrypto.js";
|
|
3
|
+
import { type SessionID, isDeleteSessionID } from "../ids.js";
|
|
4
|
+
import type { CoValueCore } from "../exports.js";
|
|
5
|
+
import {
|
|
6
|
+
fillCoMapWithLargeData,
|
|
7
|
+
importContentIntoNode,
|
|
8
|
+
setupTestAccount,
|
|
9
|
+
setupTestNode,
|
|
10
|
+
loadCoValueOrFail,
|
|
11
|
+
nodeWithRandomAgentAndSessionID,
|
|
12
|
+
hotSleep,
|
|
13
|
+
waitFor,
|
|
14
|
+
} from "./testUtils.js";
|
|
15
|
+
import { CO_VALUE_PRIORITY } from "../priority.js";
|
|
16
|
+
|
|
17
|
+
const Crypto = await WasmCrypto.create();
|
|
18
|
+
|
|
19
|
+
function makeDeleteMarkerTransaction(core: CoValueCore, madeAt?: number) {
|
|
20
|
+
core.makeTransaction([], "trusting", { deleted: core.id }, madeAt);
|
|
21
|
+
const deleteSessionID = Object.keys(core.knownState().sessions).find(
|
|
22
|
+
(sessionID) => isDeleteSessionID(sessionID as SessionID),
|
|
23
|
+
) as SessionID;
|
|
24
|
+
assert(deleteSessionID);
|
|
25
|
+
const log = core.verified?.sessions.get(deleteSessionID);
|
|
26
|
+
assert(log?.lastSignature);
|
|
27
|
+
const tx = log.transactions.at(-1);
|
|
28
|
+
assert(tx);
|
|
29
|
+
return { tx, signature: log.lastSignature, deleteSessionID };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let jazzCloud: ReturnType<typeof setupTestNode>;
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
jazzCloud = setupTestNode({ isSyncServer: true });
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("deleteCoValue is blocked for Account and Group CoValues", async () => {
|
|
38
|
+
const client = await setupTestAccount();
|
|
39
|
+
|
|
40
|
+
const account = client.node.expectCurrentAccount("to test deleteCoValue");
|
|
41
|
+
expect(() => account.core.deleteCoValue()).toThrow(
|
|
42
|
+
/Cannot delete Group or Account coValues/,
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const group = client.node.createGroup();
|
|
46
|
+
expect(() => group.core.deleteCoValue()).toThrow(
|
|
47
|
+
/Cannot delete Group or Account coValues/,
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("deleteCoValue throws when called by a non-admin on a group-owned CoValue", async () => {
|
|
52
|
+
const alice = await setupTestAccount({ connected: true });
|
|
53
|
+
const bob = await setupTestAccount({ connected: true });
|
|
54
|
+
|
|
55
|
+
const bobAccountOnAlice = await loadCoValueOrFail(alice.node, bob.accountID);
|
|
56
|
+
|
|
57
|
+
const group = alice.node.createGroup();
|
|
58
|
+
group.addMember(bobAccountOnAlice, "writer");
|
|
59
|
+
|
|
60
|
+
const map = group.createMap();
|
|
61
|
+
|
|
62
|
+
// Give sync a moment to propagate the group ownership + membership
|
|
63
|
+
const mapOnBob = await loadCoValueOrFail(bob.node, map.id);
|
|
64
|
+
|
|
65
|
+
await waitFor(() => {
|
|
66
|
+
expect(mapOnBob.core.safeGetGroup()?.myRole()).toBe("writer");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
expect(() => mapOnBob.core.deleteCoValue()).toThrow(
|
|
70
|
+
/The current account lacks admin permissions to delete this coValue/,
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("deleteCoValue creates a trusting {deleted:id} tombstone tx, marks the session, and flips core.isDeleted", async () => {
|
|
75
|
+
const alice = await setupTestAccount({ connected: true });
|
|
76
|
+
|
|
77
|
+
const group = alice.node.createGroup();
|
|
78
|
+
const map = group.createMap();
|
|
79
|
+
|
|
80
|
+
expect(map.core.isDeleted).toBe(false);
|
|
81
|
+
|
|
82
|
+
map.core.deleteCoValue();
|
|
83
|
+
|
|
84
|
+
expect(map.core.isDeleted).toBe(true);
|
|
85
|
+
|
|
86
|
+
const txs = map.core.getValidSortedTransactions();
|
|
87
|
+
const last = txs.at(-1);
|
|
88
|
+
expect(last).toBeTruthy();
|
|
89
|
+
|
|
90
|
+
expect(last!.tx.privacy).toBe("trusting");
|
|
91
|
+
expect(last!.changes).toEqual([]);
|
|
92
|
+
expect(last!.meta).toMatchObject({ deleted: map.id });
|
|
93
|
+
expect(last!.txID.sessionID).toMatch(/_session_d[1-9A-HJ-NP-Za-km-z]+\$$/); // Delete session format
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("rejects delete marker ingestion from non-admin (ownedByGroup, skipVerify=false)", async () => {
|
|
97
|
+
const alice = await setupTestAccount({ connected: true });
|
|
98
|
+
const bob = await setupTestAccount({ connected: true });
|
|
99
|
+
|
|
100
|
+
const bobAccount = await loadCoValueOrFail(alice.node, bob.accountID);
|
|
101
|
+
await loadCoValueOrFail(bob.node, alice.accountID);
|
|
102
|
+
|
|
103
|
+
const group = alice.node.createGroup();
|
|
104
|
+
group.addMember(bobAccount, "writer");
|
|
105
|
+
await group.core.waitForSync();
|
|
106
|
+
|
|
107
|
+
const map = group.createMap();
|
|
108
|
+
const mapOnBob = await loadCoValueOrFail(bob.node, map.id);
|
|
109
|
+
|
|
110
|
+
const { tx, signature, deleteSessionID } = makeDeleteMarkerTransaction(
|
|
111
|
+
mapOnBob.core,
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const error = map.core.tryAddTransactions(
|
|
115
|
+
deleteSessionID,
|
|
116
|
+
[tx],
|
|
117
|
+
signature,
|
|
118
|
+
false,
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
expect(error).toMatchObject({
|
|
122
|
+
type: "DeleteTransactionRejected",
|
|
123
|
+
reason: "NotAdmin",
|
|
124
|
+
});
|
|
125
|
+
expect(map.core.isDeleted).toBe(false);
|
|
126
|
+
expect(map.core.verified?.sessions.get(deleteSessionID)).toBeUndefined();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("accepts delete marker ingestion from admin (ownedByGroup, skipVerify=false) and marks deleted", async () => {
|
|
130
|
+
const alice = await setupTestAccount({ connected: true });
|
|
131
|
+
const bob = await setupTestAccount({ connected: true });
|
|
132
|
+
|
|
133
|
+
await loadCoValueOrFail(alice.node, bob.accountID);
|
|
134
|
+
await loadCoValueOrFail(bob.node, alice.accountID);
|
|
135
|
+
|
|
136
|
+
const group = alice.node.createGroup();
|
|
137
|
+
const bobAccount = await loadCoValueOrFail(alice.node, bob.accountID);
|
|
138
|
+
group.addMember(bobAccount, "writer");
|
|
139
|
+
await group.core.waitForSync();
|
|
140
|
+
|
|
141
|
+
const map = group.createMap();
|
|
142
|
+
await loadCoValueOrFail(bob.node, group.id);
|
|
143
|
+
const mapOnBob = await loadCoValueOrFail(bob.node, map.id);
|
|
144
|
+
|
|
145
|
+
const { tx, signature, deleteSessionID } = makeDeleteMarkerTransaction(
|
|
146
|
+
map.core,
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const error = mapOnBob.core.tryAddTransactions(
|
|
150
|
+
deleteSessionID,
|
|
151
|
+
[tx],
|
|
152
|
+
signature,
|
|
153
|
+
false,
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
expect(error).toBeUndefined();
|
|
157
|
+
expect(mapOnBob.core.isDeleted).toBe(true);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("rejects delete session ingestion when attempting to append a second transaction (txCount > 0)", async () => {
|
|
161
|
+
const alice = await setupTestAccount({ connected: true });
|
|
162
|
+
const bob = await setupTestAccount({ connected: true });
|
|
163
|
+
|
|
164
|
+
await loadCoValueOrFail(alice.node, bob.accountID);
|
|
165
|
+
await loadCoValueOrFail(bob.node, alice.accountID);
|
|
166
|
+
|
|
167
|
+
const group = alice.node.createGroup();
|
|
168
|
+
const bobAccount = await loadCoValueOrFail(alice.node, bob.accountID);
|
|
169
|
+
group.addMember(bobAccount, "writer");
|
|
170
|
+
await group.core.waitForSync();
|
|
171
|
+
|
|
172
|
+
const map = group.createMap();
|
|
173
|
+
await loadCoValueOrFail(bob.node, group.id);
|
|
174
|
+
const mapOnBob = await loadCoValueOrFail(bob.node, map.id);
|
|
175
|
+
|
|
176
|
+
const { tx, signature, deleteSessionID } = makeDeleteMarkerTransaction(
|
|
177
|
+
map.core,
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
const first = mapOnBob.core.tryAddTransactions(
|
|
181
|
+
deleteSessionID,
|
|
182
|
+
[tx],
|
|
183
|
+
signature,
|
|
184
|
+
false,
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
expect(first).toBeUndefined();
|
|
188
|
+
expect(mapOnBob.core.isDeleted).toBe(true);
|
|
189
|
+
expect(
|
|
190
|
+
mapOnBob.core.verified?.sessions.get(deleteSessionID)?.transactions,
|
|
191
|
+
).toHaveLength(1);
|
|
192
|
+
|
|
193
|
+
const second = mapOnBob.core.tryAddTransactions(
|
|
194
|
+
deleteSessionID,
|
|
195
|
+
[tx],
|
|
196
|
+
signature,
|
|
197
|
+
false,
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
expect(second).toMatchObject({
|
|
201
|
+
type: "DeleteTransactionRejected",
|
|
202
|
+
reason: "InvalidDeleteTransaction",
|
|
203
|
+
});
|
|
204
|
+
expect(second && "error" in second).toBe(true);
|
|
205
|
+
const secondErr = (second as { error: unknown }).error;
|
|
206
|
+
expect(secondErr).toBeInstanceOf(Error);
|
|
207
|
+
if (secondErr instanceof Error) {
|
|
208
|
+
expect(secondErr.message).toMatch(
|
|
209
|
+
/Delete transaction must be the only transaction in the session/,
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
expect(
|
|
213
|
+
mapOnBob.core.verified?.sessions.get(deleteSessionID)?.transactions,
|
|
214
|
+
).toHaveLength(1);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test("rejects delete session ingestion when attempting to add multiple delete transactions", async () => {
|
|
218
|
+
const alice = await setupTestAccount({ connected: true });
|
|
219
|
+
const bob = await setupTestAccount({ connected: true });
|
|
220
|
+
|
|
221
|
+
await loadCoValueOrFail(alice.node, bob.accountID);
|
|
222
|
+
await loadCoValueOrFail(bob.node, alice.accountID);
|
|
223
|
+
|
|
224
|
+
const group = alice.node.createGroup();
|
|
225
|
+
const bobAccount = await loadCoValueOrFail(alice.node, bob.accountID);
|
|
226
|
+
group.addMember(bobAccount, "writer");
|
|
227
|
+
await group.core.waitForSync();
|
|
228
|
+
|
|
229
|
+
const map = group.createMap();
|
|
230
|
+
await loadCoValueOrFail(bob.node, group.id);
|
|
231
|
+
const mapOnBob = await loadCoValueOrFail(bob.node, map.id);
|
|
232
|
+
|
|
233
|
+
const { tx, signature, deleteSessionID } = makeDeleteMarkerTransaction(
|
|
234
|
+
map.core,
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
const first = mapOnBob.core.tryAddTransactions(
|
|
238
|
+
deleteSessionID,
|
|
239
|
+
[tx, tx],
|
|
240
|
+
signature,
|
|
241
|
+
false,
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
expect(first).toMatchObject({
|
|
245
|
+
type: "DeleteTransactionRejected",
|
|
246
|
+
reason: "InvalidDeleteTransaction",
|
|
247
|
+
});
|
|
248
|
+
expect(first && "error" in first).toBe(true);
|
|
249
|
+
const err = (first as { error: unknown }).error;
|
|
250
|
+
expect(err).toBeInstanceOf(Error);
|
|
251
|
+
if (err instanceof Error) {
|
|
252
|
+
expect(err.message).toMatch(
|
|
253
|
+
/Delete transaction must be the only transaction in the session/,
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
expect(mapOnBob.core.verified?.sessions.get(deleteSessionID)).toBeUndefined();
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
test("skipVerify=true ingestion marks deleted even for non-admin delete marker", async () => {
|
|
260
|
+
const alice = await setupTestAccount({ connected: true });
|
|
261
|
+
const bob = await setupTestAccount({ connected: true });
|
|
262
|
+
|
|
263
|
+
const bobAccount = await loadCoValueOrFail(alice.node, bob.accountID);
|
|
264
|
+
await loadCoValueOrFail(bob.node, alice.accountID);
|
|
265
|
+
|
|
266
|
+
const group = alice.node.createGroup();
|
|
267
|
+
group.addMember(bobAccount, "writer");
|
|
268
|
+
await group.core.waitForSync();
|
|
269
|
+
|
|
270
|
+
const map = group.createMap();
|
|
271
|
+
const mapOnBob = await loadCoValueOrFail(bob.node, map.id);
|
|
272
|
+
|
|
273
|
+
const { tx, signature, deleteSessionID } = makeDeleteMarkerTransaction(
|
|
274
|
+
mapOnBob.core,
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
const error = map.core.tryAddTransactions(
|
|
278
|
+
deleteSessionID,
|
|
279
|
+
[tx],
|
|
280
|
+
signature,
|
|
281
|
+
true,
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
expect(error).toBeUndefined();
|
|
285
|
+
expect(map.core.isDeleted).toBe(true);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
test("rejects delete marker ingestion when tx.madeAt predates admin rights (time travel permission check)", async () => {
|
|
289
|
+
const alice = await setupTestAccount({ connected: true });
|
|
290
|
+
const bob = await setupTestAccount({ connected: true });
|
|
291
|
+
|
|
292
|
+
const bobAccount = await loadCoValueOrFail(alice.node, bob.accountID);
|
|
293
|
+
await loadCoValueOrFail(bob.node, alice.accountID);
|
|
294
|
+
|
|
295
|
+
const group = alice.node.createGroup();
|
|
296
|
+
group.addMember(bobAccount, "writer");
|
|
297
|
+
await group.core.waitForSync();
|
|
298
|
+
|
|
299
|
+
const map = group.createMap();
|
|
300
|
+
const mapOnBob = await loadCoValueOrFail(bob.node, map.id);
|
|
301
|
+
|
|
302
|
+
// Ensure Bob is still a writer at tx creation time
|
|
303
|
+
await waitFor(() => {
|
|
304
|
+
expect(mapOnBob.core.safeGetGroup()?.myRole()).toBe("writer");
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
308
|
+
|
|
309
|
+
const { tx, signature, deleteSessionID } = makeDeleteMarkerTransaction(
|
|
310
|
+
mapOnBob.core,
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
// Later, Bob gets admin rights...
|
|
314
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
315
|
+
|
|
316
|
+
group.addMember(bobAccount, "admin");
|
|
317
|
+
await group.core.waitForSync();
|
|
318
|
+
|
|
319
|
+
// ...but ingestion should still validate permissions at tx.madeAt (writer), not "now" (admin).
|
|
320
|
+
expect(group.roleOf(bob.accountID)).toBe("admin");
|
|
321
|
+
|
|
322
|
+
const error = map.core.tryAddTransactions(
|
|
323
|
+
deleteSessionID,
|
|
324
|
+
[tx],
|
|
325
|
+
signature,
|
|
326
|
+
false,
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
expect(error).toMatchObject({
|
|
330
|
+
type: "DeleteTransactionRejected",
|
|
331
|
+
reason: "NotAdmin",
|
|
332
|
+
});
|
|
333
|
+
expect(map.core.isDeleted).toBe(false);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
test("rejects delete marker ingestion for non-owned covalue when verifying (skipVerify=false)", () => {
|
|
337
|
+
const node = nodeWithRandomAgentAndSessionID();
|
|
338
|
+
|
|
339
|
+
const coValue = node.createCoValue({
|
|
340
|
+
type: "costream",
|
|
341
|
+
ruleset: { type: "unsafeAllowAll" },
|
|
342
|
+
meta: null,
|
|
343
|
+
...Crypto.createdNowUnique(),
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
const { tx, signature, deleteSessionID } =
|
|
347
|
+
makeDeleteMarkerTransaction(coValue);
|
|
348
|
+
|
|
349
|
+
node.internalDeleteCoValue(coValue.id);
|
|
350
|
+
node.syncManager.handleNewContent(
|
|
351
|
+
{
|
|
352
|
+
action: "content",
|
|
353
|
+
id: coValue.id,
|
|
354
|
+
header: coValue.verified!.header,
|
|
355
|
+
priority: CO_VALUE_PRIORITY.LOW,
|
|
356
|
+
new: {},
|
|
357
|
+
},
|
|
358
|
+
"import",
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
const newEntry = node.getCoValue(coValue.id);
|
|
362
|
+
const error = newEntry.tryAddTransactions(
|
|
363
|
+
deleteSessionID,
|
|
364
|
+
[tx],
|
|
365
|
+
signature,
|
|
366
|
+
false,
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
expect(error).toMatchObject({
|
|
370
|
+
type: "DeleteTransactionRejected",
|
|
371
|
+
reason: "CannotVerifyPermissions",
|
|
372
|
+
});
|
|
373
|
+
expect(newEntry.isDeleted).toBe(false);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
test("deleted coValues return only the deleted session/transaction on the knownState", async () => {
|
|
377
|
+
const client = setupTestNode({
|
|
378
|
+
connected: true,
|
|
379
|
+
});
|
|
380
|
+
const group = client.node.createGroup();
|
|
381
|
+
const map = group.createMap();
|
|
382
|
+
map.set("hello", "world", "trusting");
|
|
383
|
+
map.core.deleteCoValue();
|
|
384
|
+
|
|
385
|
+
const knownState = map.core.knownState();
|
|
386
|
+
expect(
|
|
387
|
+
Object.keys(knownState.sessions).every((sessionID) =>
|
|
388
|
+
isDeleteSessionID(sessionID as SessionID),
|
|
389
|
+
),
|
|
390
|
+
).toBe(true);
|
|
391
|
+
expect(Object.keys(knownState.sessions)).toHaveLength(1);
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
test("deleted coValues return only the deleted session/transaction on the knownStateWithStreaming", async () => {
|
|
395
|
+
const streamingClient = setupTestNode();
|
|
396
|
+
const client = await setupTestAccount({ connected: true });
|
|
397
|
+
const group = streamingClient.node.createGroup();
|
|
398
|
+
|
|
399
|
+
group.addMemberInternal(client.account, "admin");
|
|
400
|
+
|
|
401
|
+
// Import the group content into the client
|
|
402
|
+
importContentIntoNode(group.core, client.node);
|
|
403
|
+
|
|
404
|
+
const map = group.createMap();
|
|
405
|
+
fillCoMapWithLargeData(map);
|
|
406
|
+
|
|
407
|
+
// Import only partially the map content into the client, to keep it in streaming state
|
|
408
|
+
importContentIntoNode(map.core, client.node, 1);
|
|
409
|
+
|
|
410
|
+
const mapOnClient = await loadCoValueOrFail(client.node, map.id);
|
|
411
|
+
|
|
412
|
+
expect(mapOnClient.core.isStreaming()).toBe(true);
|
|
413
|
+
|
|
414
|
+
mapOnClient.core.deleteCoValue();
|
|
415
|
+
|
|
416
|
+
const streamingSessions = mapOnClient.core.knownStateWithStreaming().sessions;
|
|
417
|
+
|
|
418
|
+
expect(
|
|
419
|
+
Object.keys(streamingSessions).every((sessionID) =>
|
|
420
|
+
isDeleteSessionID(sessionID as SessionID),
|
|
421
|
+
),
|
|
422
|
+
).toBe(true);
|
|
423
|
+
expect(Object.keys(streamingSessions)).toHaveLength(1);
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
test("waitForSync should wait only for the delete session/transaction", async () => {
|
|
427
|
+
const client = setupTestNode({
|
|
428
|
+
connected: true,
|
|
429
|
+
});
|
|
430
|
+
const group = client.node.createGroup();
|
|
431
|
+
const map = group.createMap();
|
|
432
|
+
map.set("hello", "world", "trusting");
|
|
433
|
+
map.core.deleteCoValue();
|
|
434
|
+
|
|
435
|
+
await map.core.waitForSync();
|
|
436
|
+
|
|
437
|
+
expect(jazzCloud.node.expectCoValueLoaded(map.id).isDeleted).toBe(true);
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
test("waitForSync should wait only for the delete session/transaction even if the coValue loading was in streaming", async () => {
|
|
441
|
+
const streamingClient = setupTestNode();
|
|
442
|
+
const client = await setupTestAccount({ connected: true });
|
|
443
|
+
const group = streamingClient.node.createGroup();
|
|
444
|
+
|
|
445
|
+
group.addMemberInternal(client.account, "admin");
|
|
446
|
+
|
|
447
|
+
// Import the group content into the client
|
|
448
|
+
importContentIntoNode(group.core, client.node);
|
|
449
|
+
|
|
450
|
+
const map = group.createMap();
|
|
451
|
+
fillCoMapWithLargeData(map);
|
|
452
|
+
|
|
453
|
+
// Import only partially the map content into the client, to keep it in streaming state
|
|
454
|
+
importContentIntoNode(map.core, client.node, 1);
|
|
455
|
+
|
|
456
|
+
const mapOnClient = await loadCoValueOrFail(client.node, map.id);
|
|
457
|
+
|
|
458
|
+
expect(mapOnClient.core.isStreaming()).toBe(true);
|
|
459
|
+
|
|
460
|
+
mapOnClient.core.deleteCoValue();
|
|
461
|
+
|
|
462
|
+
await mapOnClient.core.waitForSync();
|
|
463
|
+
|
|
464
|
+
const mapOnSyncServer = jazzCloud.node.expectCoValueLoaded(map.id);
|
|
465
|
+
|
|
466
|
+
expect(
|
|
467
|
+
Object.keys(mapOnSyncServer.knownState().sessions).every((sessionID) =>
|
|
468
|
+
isDeleteSessionID(sessionID as SessionID),
|
|
469
|
+
),
|
|
470
|
+
).toBe(true);
|
|
471
|
+
expect(jazzCloud.node.expectCoValueLoaded(map.id).isDeleted).toBe(true);
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
test("rejects delete transaction with mismatched coValueId", async () => {
|
|
475
|
+
const alice = await setupTestAccount({ connected: true });
|
|
476
|
+
const bob = await setupTestAccount({ connected: true });
|
|
477
|
+
|
|
478
|
+
await loadCoValueOrFail(alice.node, bob.accountID);
|
|
479
|
+
await loadCoValueOrFail(bob.node, alice.accountID);
|
|
480
|
+
|
|
481
|
+
const group = alice.node.createGroup();
|
|
482
|
+
const bobAccount = await loadCoValueOrFail(alice.node, bob.accountID);
|
|
483
|
+
group.addMember(bobAccount, "admin");
|
|
484
|
+
await group.core.waitForSync();
|
|
485
|
+
|
|
486
|
+
// Create two maps owned by the same group
|
|
487
|
+
const mapA = group.createMap();
|
|
488
|
+
const mapB = group.createMap();
|
|
489
|
+
|
|
490
|
+
await loadCoValueOrFail(bob.node, group.id);
|
|
491
|
+
const mapAOnBob = await loadCoValueOrFail(bob.node, mapA.id);
|
|
492
|
+
const mapBOnBob = await loadCoValueOrFail(bob.node, mapB.id);
|
|
493
|
+
|
|
494
|
+
// Create a delete transaction for mapA
|
|
495
|
+
const { tx, signature, deleteSessionID } = makeDeleteMarkerTransaction(
|
|
496
|
+
mapA.core,
|
|
497
|
+
);
|
|
498
|
+
|
|
499
|
+
// Try to apply mapA's delete transaction to mapB - should be rejected due to ID mismatch
|
|
500
|
+
const error = mapBOnBob.core.tryAddTransactions(
|
|
501
|
+
deleteSessionID,
|
|
502
|
+
[tx],
|
|
503
|
+
signature,
|
|
504
|
+
false,
|
|
505
|
+
);
|
|
506
|
+
|
|
507
|
+
expect(error).toMatchObject({
|
|
508
|
+
type: "DeleteTransactionRejected",
|
|
509
|
+
reason: "InvalidDeleteTransaction",
|
|
510
|
+
});
|
|
511
|
+
expect(error && "error" in error).toBe(true);
|
|
512
|
+
const err = (error as { error: unknown }).error;
|
|
513
|
+
expect(err).toBeInstanceOf(Error);
|
|
514
|
+
if (err instanceof Error) {
|
|
515
|
+
expect(err.message).toMatch(/Delete transaction ID mismatch/);
|
|
516
|
+
}
|
|
517
|
+
expect(mapBOnBob.core.isDeleted).toBe(false);
|
|
518
|
+
|
|
519
|
+
// Verify mapA's delete transaction can still be applied correctly to mapA
|
|
520
|
+
const successError = mapAOnBob.core.tryAddTransactions(
|
|
521
|
+
deleteSessionID,
|
|
522
|
+
[tx],
|
|
523
|
+
signature,
|
|
524
|
+
false,
|
|
525
|
+
);
|
|
526
|
+
expect(successError).toBeUndefined();
|
|
527
|
+
expect(mapAOnBob.core.isDeleted).toBe(true);
|
|
528
|
+
});
|
|
@@ -168,45 +168,45 @@ describe("Group.removeMember", () => {
|
|
|
168
168
|
|
|
169
169
|
const loadedGroup = await loadCoValueOrFail(client.node, group.id);
|
|
170
170
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
171
|
+
expect(async () => {
|
|
172
|
+
loadedGroup.removeMember(
|
|
173
|
+
await loadCoValueOrFail(client.node, reader.accountID),
|
|
174
|
+
);
|
|
175
|
+
}).rejects.toThrow(
|
|
176
|
+
`Failed to revoke role to ${reader.accountID} (role of current account is ${member})`,
|
|
174
177
|
);
|
|
175
|
-
// }).rejects.toThrow(
|
|
176
|
-
// `Failed to revoke role to ${reader.accountID} (role of current account is ${member})`,
|
|
177
|
-
// );
|
|
178
178
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
179
|
+
expect(async () => {
|
|
180
|
+
loadedGroup.removeMember(
|
|
181
|
+
await loadCoValueOrFail(client.node, writeOnly.accountID),
|
|
182
|
+
);
|
|
183
|
+
}).rejects.toThrow(
|
|
184
|
+
`Failed to revoke role to ${writeOnly.accountID} (role of current account is ${member})`,
|
|
182
185
|
);
|
|
183
|
-
// }).rejects.toThrow(
|
|
184
|
-
// `Failed to revoke role to ${writeOnly.accountID} (role of current account is ${member})`,
|
|
185
|
-
// );
|
|
186
186
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
187
|
+
expect(async () => {
|
|
188
|
+
loadedGroup.removeMember(
|
|
189
|
+
await loadCoValueOrFail(client.node, writer.accountID),
|
|
190
|
+
);
|
|
191
|
+
}).rejects.toThrow(
|
|
192
|
+
`Failed to revoke role to ${writer.accountID} (role of current account is ${member})`,
|
|
190
193
|
);
|
|
191
|
-
// }).rejects.toThrow(
|
|
192
|
-
// `Failed to revoke role to ${writer.accountID} (role of current account is ${member})`,
|
|
193
|
-
// );
|
|
194
194
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
195
|
+
expect(async () => {
|
|
196
|
+
loadedGroup.removeMember(
|
|
197
|
+
await loadCoValueOrFail(client.node, admin.accountID),
|
|
198
|
+
);
|
|
199
|
+
}).rejects.toThrow(
|
|
200
|
+
`Failed to revoke role to ${admin.accountID} (role of current account is ${member})`,
|
|
198
201
|
);
|
|
199
|
-
// }).rejects.toThrow(
|
|
200
|
-
// `Failed to revoke role to ${admin.accountID} (role of current account is ${member})`,
|
|
201
|
-
// );
|
|
202
202
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
203
|
+
expect(async () => {
|
|
204
|
+
loadedGroup.removeMember(
|
|
205
|
+
await loadCoValueOrFail(client.node, manager.accountID),
|
|
206
|
+
);
|
|
207
|
+
}).rejects.toThrow(
|
|
208
|
+
`Failed to revoke role to ${manager.accountID} (role of current account is ${member})`,
|
|
206
209
|
);
|
|
207
|
-
// }).rejects.toThrow(
|
|
208
|
-
// `Failed to revoke role to ${manager.accountID} (role of current account is ${member})`,
|
|
209
|
-
// );
|
|
210
210
|
expect(loadedGroup.roleOf(reader.accountID)).toEqual("reader");
|
|
211
211
|
expect(loadedGroup.roleOf(writer.accountID)).toEqual("writer");
|
|
212
212
|
expect(loadedGroup.roleOf(writeOnly.accountID)).toEqual("writeOnly");
|
|
@@ -254,11 +254,11 @@ describe("Group.removeMember", () => {
|
|
|
254
254
|
admin.accountID,
|
|
255
255
|
);
|
|
256
256
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
257
|
+
expect(() => {
|
|
258
|
+
loadedGroup.removeMember(adminOnClientNode);
|
|
259
|
+
}).toThrow(
|
|
260
|
+
`Failed to revoke role to ${admin.accountID} (role of current account is admin)`,
|
|
261
|
+
);
|
|
262
262
|
|
|
263
263
|
expect(loadedGroup.roleOf(admin.accountID)).toEqual("admin");
|
|
264
264
|
|
|
@@ -23,10 +23,17 @@ function createMockStorage(
|
|
|
23
23
|
callback: (unsyncedCoValueIDs: RawCoID[]) => void,
|
|
24
24
|
) => void;
|
|
25
25
|
stopTrackingSyncState?: (id: RawCoID) => void;
|
|
26
|
+
onCoValueUnmounted?: (id: RawCoID) => void;
|
|
26
27
|
close?: () => Promise<unknown> | undefined;
|
|
28
|
+
markDeleteAsValid?: (id: RawCoID) => void;
|
|
29
|
+
enableDeletedCoValuesErasure?: () => void;
|
|
30
|
+
eraseAllDeletedCoValues?: () => Promise<void>;
|
|
27
31
|
} = {},
|
|
28
32
|
): StorageAPI {
|
|
29
33
|
return {
|
|
34
|
+
markDeleteAsValid: opts.markDeleteAsValid || vi.fn(),
|
|
35
|
+
enableDeletedCoValuesErasure: opts.enableDeletedCoValuesErasure || vi.fn(),
|
|
36
|
+
eraseAllDeletedCoValues: opts.eraseAllDeletedCoValues || vi.fn(),
|
|
30
37
|
load: opts.load || vi.fn(),
|
|
31
38
|
store: opts.store || vi.fn(),
|
|
32
39
|
getKnownState: opts.getKnownState || vi.fn(),
|
|
@@ -36,6 +43,7 @@ function createMockStorage(
|
|
|
36
43
|
trackCoValuesSyncState: opts.trackCoValuesSyncState || vi.fn(),
|
|
37
44
|
getUnsyncedCoValueIDs: opts.getUnsyncedCoValueIDs || vi.fn(),
|
|
38
45
|
stopTrackingSyncState: opts.stopTrackingSyncState || vi.fn(),
|
|
46
|
+
onCoValueUnmounted: opts.onCoValueUnmounted || vi.fn(),
|
|
39
47
|
close: opts.close || vi.fn().mockResolvedValue(undefined),
|
|
40
48
|
};
|
|
41
49
|
}
|