cojson 0.8.38 → 0.8.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/dist/native/PeerState.js +11 -2
- package/dist/native/PeerState.js.map +1 -1
- package/dist/native/{SyncStateSubscriptionManager.js → SyncStateManager.js} +35 -24
- package/dist/native/SyncStateManager.js.map +1 -0
- package/dist/native/coValueCore.js +25 -5
- package/dist/native/coValueCore.js.map +1 -1
- package/dist/native/coValues/coMap.js +98 -103
- package/dist/native/coValues/coMap.js.map +1 -1
- package/dist/native/coValues/coStream.js +17 -6
- package/dist/native/coValues/coStream.js.map +1 -1
- package/dist/native/coValues/group.js +127 -39
- package/dist/native/coValues/group.js.map +1 -1
- package/dist/native/exports.js.map +1 -1
- package/dist/native/localNode.js +5 -2
- package/dist/native/localNode.js.map +1 -1
- package/dist/native/permissions.js +51 -3
- package/dist/native/permissions.js.map +1 -1
- package/dist/native/sync.js +34 -10
- package/dist/native/sync.js.map +1 -1
- package/dist/web/PeerState.js +11 -2
- package/dist/web/PeerState.js.map +1 -1
- package/dist/web/{SyncStateSubscriptionManager.js → SyncStateManager.js} +35 -24
- package/dist/web/SyncStateManager.js.map +1 -0
- package/dist/web/coValueCore.js +25 -5
- package/dist/web/coValueCore.js.map +1 -1
- package/dist/web/coValues/coMap.js +98 -103
- package/dist/web/coValues/coMap.js.map +1 -1
- package/dist/web/coValues/coStream.js +17 -6
- package/dist/web/coValues/coStream.js.map +1 -1
- package/dist/web/coValues/group.js +127 -39
- package/dist/web/coValues/group.js.map +1 -1
- package/dist/web/exports.js.map +1 -1
- package/dist/web/localNode.js +5 -2
- package/dist/web/localNode.js.map +1 -1
- package/dist/web/permissions.js +51 -3
- package/dist/web/permissions.js.map +1 -1
- package/dist/web/sync.js +34 -10
- package/dist/web/sync.js.map +1 -1
- package/package.json +3 -5
- package/src/PeerState.ts +12 -2
- package/src/{SyncStateSubscriptionManager.ts → SyncStateManager.ts} +48 -35
- package/src/coValueCore.ts +43 -9
- package/src/coValues/coMap.ts +126 -127
- package/src/coValues/coStream.ts +27 -10
- package/src/coValues/group.ts +218 -50
- package/src/exports.ts +2 -1
- package/src/localNode.ts +5 -2
- package/src/permissions.ts +71 -8
- package/src/sync.ts +57 -23
- package/src/tests/PeerState.test.ts +49 -0
- package/src/tests/PriorityBasedMessageQueue.test.ts +6 -73
- package/src/tests/{SyncStateSubscriptionManager.test.ts → SyncStateManager.test.ts} +109 -25
- package/src/tests/coMap.test.ts +2 -2
- package/src/tests/group.test.ts +338 -47
- package/src/tests/permissions.test.ts +324 -0
- package/src/tests/sync.test.ts +112 -71
- package/src/tests/testUtils.ts +126 -17
- package/dist/native/SyncStateSubscriptionManager.js.map +0 -1
- package/dist/web/SyncStateSubscriptionManager.js.map +0 -1
package/src/tests/group.test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { expect, test } from "vitest";
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
2
|
import { RawCoList } from "../coValues/coList.js";
|
|
3
3
|
import { RawCoMap } from "../coValues/coMap.js";
|
|
4
4
|
import { RawCoStream } from "../coValues/coStream.js";
|
|
@@ -7,6 +7,7 @@ import { WasmCrypto } from "../crypto/WasmCrypto.js";
|
|
|
7
7
|
import { LocalNode } from "../localNode.js";
|
|
8
8
|
import {
|
|
9
9
|
createThreeConnectedNodes,
|
|
10
|
+
createTwoConnectedNodes,
|
|
10
11
|
loadCoValueOrFail,
|
|
11
12
|
randomAnonymousAccountAndSessionID,
|
|
12
13
|
} from "./testUtils.js";
|
|
@@ -59,146 +60,436 @@ test("Can create a FileStream in a group", () => {
|
|
|
59
60
|
});
|
|
60
61
|
|
|
61
62
|
test("Remove a member from a group where the admin role is inherited", async () => {
|
|
62
|
-
const { node1, node2, node3
|
|
63
|
-
|
|
63
|
+
const { node1, node2, node3 } = await createThreeConnectedNodes(
|
|
64
|
+
"server",
|
|
65
|
+
"server",
|
|
66
|
+
"server",
|
|
67
|
+
);
|
|
64
68
|
|
|
65
|
-
const group = node1.createGroup();
|
|
69
|
+
const group = node1.node.createGroup();
|
|
70
|
+
|
|
71
|
+
group.addMember(
|
|
72
|
+
await loadCoValueOrFail(node1.node, node2.accountID),
|
|
73
|
+
"admin",
|
|
74
|
+
);
|
|
75
|
+
group.addMember(
|
|
76
|
+
await loadCoValueOrFail(node1.node, node3.accountID),
|
|
77
|
+
"reader",
|
|
78
|
+
);
|
|
66
79
|
|
|
67
|
-
group.
|
|
68
|
-
group.addMember(node3.account, "reader");
|
|
80
|
+
await group.core.waitForSync();
|
|
69
81
|
|
|
70
|
-
const groupOnNode2 = await loadCoValueOrFail(node2, group.id);
|
|
82
|
+
const groupOnNode2 = await loadCoValueOrFail(node2.node, group.id);
|
|
71
83
|
|
|
72
84
|
// The account of node2 create a child group and extend the initial group
|
|
73
85
|
// This way the node1 account should become "admin" of the child group
|
|
74
86
|
// by inheriting the admin role from the initial group
|
|
75
|
-
const childGroup = node2.createGroup();
|
|
87
|
+
const childGroup = node2.node.createGroup();
|
|
76
88
|
childGroup.extend(groupOnNode2);
|
|
77
89
|
|
|
78
90
|
const map = childGroup.createMap();
|
|
79
91
|
map.set("test", "Available to everyone");
|
|
80
92
|
|
|
81
|
-
const mapOnNode3 = await loadCoValueOrFail(node3, map.id);
|
|
93
|
+
const mapOnNode3 = await loadCoValueOrFail(node3.node, map.id);
|
|
82
94
|
|
|
83
95
|
// Check that the sync between node2 and node3 worked
|
|
84
96
|
expect(mapOnNode3.get("test")).toEqual("Available to everyone");
|
|
85
97
|
|
|
86
98
|
// The node1 account removes the reader from the group
|
|
87
99
|
// The reader should be automatically kicked out of the child group
|
|
88
|
-
await group.removeMember(node3.account);
|
|
100
|
+
await group.removeMember(node3.node.account);
|
|
89
101
|
|
|
90
|
-
await
|
|
102
|
+
await group.core.waitForSync();
|
|
91
103
|
|
|
92
104
|
// Update the map to check that node3 can't read updates anymore
|
|
93
105
|
map.set("test", "Hidden to node3");
|
|
94
106
|
|
|
95
|
-
await
|
|
107
|
+
await map.core.waitForSync();
|
|
96
108
|
|
|
97
109
|
// Check that the value has not been updated on node3
|
|
98
110
|
expect(mapOnNode3.get("test")).toEqual("Available to everyone");
|
|
99
111
|
|
|
100
|
-
const mapOnNode1 = await loadCoValueOrFail(node1, map.id);
|
|
112
|
+
const mapOnNode1 = await loadCoValueOrFail(node1.node, map.id);
|
|
101
113
|
|
|
102
114
|
expect(mapOnNode1.get("test")).toEqual("Hidden to node3");
|
|
103
115
|
});
|
|
104
116
|
|
|
105
117
|
test("An admin should be able to rotate the readKey on child groups and keep access to new coValues", async () => {
|
|
106
|
-
const { node1, node2, node3
|
|
107
|
-
|
|
118
|
+
const { node1, node2, node3 } = await createThreeConnectedNodes(
|
|
119
|
+
"server",
|
|
120
|
+
"server",
|
|
121
|
+
"server",
|
|
122
|
+
);
|
|
108
123
|
|
|
109
|
-
const group = node1.createGroup();
|
|
124
|
+
const group = node1.node.createGroup();
|
|
125
|
+
|
|
126
|
+
group.addMember(
|
|
127
|
+
await loadCoValueOrFail(node1.node, node2.accountID),
|
|
128
|
+
"admin",
|
|
129
|
+
);
|
|
130
|
+
group.addMember(
|
|
131
|
+
await loadCoValueOrFail(node1.node, node3.accountID),
|
|
132
|
+
"reader",
|
|
133
|
+
);
|
|
110
134
|
|
|
111
|
-
group.
|
|
112
|
-
group.addMember(node3.account, "reader");
|
|
135
|
+
await group.core.waitForSync();
|
|
113
136
|
|
|
114
|
-
const groupOnNode2 = await loadCoValueOrFail(node2, group.id);
|
|
137
|
+
const groupOnNode2 = await loadCoValueOrFail(node2.node, group.id);
|
|
115
138
|
|
|
116
139
|
// The account of node2 create a child group and extend the initial group
|
|
117
140
|
// This way the node1 account should become "admin" of the child group
|
|
118
141
|
// by inheriting the admin role from the initial group
|
|
119
|
-
const childGroup = node2.createGroup();
|
|
142
|
+
const childGroup = node2.node.createGroup();
|
|
120
143
|
childGroup.extend(groupOnNode2);
|
|
121
144
|
|
|
122
|
-
await
|
|
123
|
-
node2ToNode1Peer.id,
|
|
124
|
-
childGroup.id,
|
|
125
|
-
);
|
|
145
|
+
await childGroup.core.waitForSync();
|
|
126
146
|
|
|
127
147
|
// The node1 account removes the reader from the group
|
|
128
148
|
// In this case we want to ensure that node1 is still able to read new coValues
|
|
129
149
|
// Even if some childs are not available when the readKey is rotated
|
|
130
|
-
await group.removeMember(node3.account);
|
|
131
|
-
await
|
|
150
|
+
await group.removeMember(node3.node.account);
|
|
151
|
+
await group.core.waitForSync();
|
|
132
152
|
|
|
133
153
|
const map = childGroup.createMap();
|
|
134
154
|
map.set("test", "Available to node1");
|
|
135
155
|
|
|
136
|
-
const mapOnNode1 = await loadCoValueOrFail(node1, map.id);
|
|
156
|
+
const mapOnNode1 = await loadCoValueOrFail(node1.node, map.id);
|
|
137
157
|
expect(mapOnNode1.get("test")).toEqual("Available to node1");
|
|
138
158
|
});
|
|
139
159
|
|
|
140
160
|
test("An admin should be able to rotate the readKey on child groups even if it was unavailable when kicking out a member from a parent group", async () => {
|
|
141
|
-
const { node1, node2, node3
|
|
142
|
-
|
|
161
|
+
const { node1, node2, node3 } = await createThreeConnectedNodes(
|
|
162
|
+
"server",
|
|
163
|
+
"server",
|
|
164
|
+
"server",
|
|
165
|
+
);
|
|
143
166
|
|
|
144
|
-
const group = node1.createGroup();
|
|
167
|
+
const group = node1.node.createGroup();
|
|
145
168
|
|
|
146
|
-
group.addMember(
|
|
147
|
-
|
|
169
|
+
group.addMember(
|
|
170
|
+
await loadCoValueOrFail(node1.node, node2.accountID),
|
|
171
|
+
"admin",
|
|
172
|
+
);
|
|
173
|
+
group.addMember(
|
|
174
|
+
await loadCoValueOrFail(node1.node, node3.accountID),
|
|
175
|
+
"reader",
|
|
176
|
+
);
|
|
148
177
|
|
|
149
|
-
|
|
178
|
+
await group.core.waitForSync();
|
|
179
|
+
|
|
180
|
+
const groupOnNode2 = await loadCoValueOrFail(node2.node, group.id);
|
|
150
181
|
|
|
151
182
|
// The account of node2 create a child group and extend the initial group
|
|
152
183
|
// This way the node1 account should become "admin" of the child group
|
|
153
184
|
// by inheriting the admin role from the initial group
|
|
154
|
-
const childGroup = node2.createGroup();
|
|
185
|
+
const childGroup = node2.node.createGroup();
|
|
155
186
|
childGroup.extend(groupOnNode2);
|
|
156
187
|
|
|
157
188
|
// The node1 account removes the reader from the group
|
|
158
189
|
// In this case we want to ensure that node1 is still able to read new coValues
|
|
159
190
|
// Even if some childs are not available when the readKey is rotated
|
|
160
|
-
await group.removeMember(node3.account);
|
|
161
|
-
await
|
|
191
|
+
await group.removeMember(node3.node.account);
|
|
192
|
+
await group.core.waitForSync();
|
|
162
193
|
|
|
163
194
|
const map = childGroup.createMap();
|
|
164
195
|
map.set("test", "Available to node1");
|
|
165
196
|
|
|
166
|
-
const mapOnNode1 = await loadCoValueOrFail(node1, map.id);
|
|
197
|
+
const mapOnNode1 = await loadCoValueOrFail(node1.node, map.id);
|
|
167
198
|
expect(mapOnNode1.get("test")).toEqual("Available to node1");
|
|
168
199
|
});
|
|
169
200
|
|
|
170
201
|
test("An admin should be able to rotate the readKey on child groups even if it was unavailable when kicking out a member from a parent group (grandChild)", async () => {
|
|
171
|
-
const { node1, node2, node3
|
|
202
|
+
const { node1, node2, node3 } = await createThreeConnectedNodes(
|
|
172
203
|
"server",
|
|
173
204
|
"server",
|
|
174
205
|
"server",
|
|
175
206
|
);
|
|
176
207
|
|
|
177
|
-
const group = node1.createGroup();
|
|
208
|
+
const group = node1.node.createGroup();
|
|
209
|
+
|
|
210
|
+
group.addMember(
|
|
211
|
+
await loadCoValueOrFail(node1.node, node2.accountID),
|
|
212
|
+
"admin",
|
|
213
|
+
);
|
|
214
|
+
group.addMember(
|
|
215
|
+
await loadCoValueOrFail(node1.node, node3.accountID),
|
|
216
|
+
"reader",
|
|
217
|
+
);
|
|
178
218
|
|
|
179
|
-
group.
|
|
180
|
-
group.addMember(node3.account, "reader");
|
|
219
|
+
await group.core.waitForSync();
|
|
181
220
|
|
|
182
|
-
const groupOnNode2 = await loadCoValueOrFail(node2, group.id);
|
|
221
|
+
const groupOnNode2 = await loadCoValueOrFail(node2.node, group.id);
|
|
183
222
|
|
|
184
223
|
// The account of node2 create a child group and extend the initial group
|
|
185
224
|
// This way the node1 account should become "admin" of the child group
|
|
186
225
|
// by inheriting the admin role from the initial group
|
|
187
|
-
const childGroup = node2.createGroup();
|
|
226
|
+
const childGroup = node2.node.createGroup();
|
|
188
227
|
childGroup.extend(groupOnNode2);
|
|
189
|
-
const grandChildGroup = node2.createGroup();
|
|
228
|
+
const grandChildGroup = node2.node.createGroup();
|
|
190
229
|
grandChildGroup.extend(childGroup);
|
|
191
230
|
|
|
192
231
|
// The node1 account removes the reader from the group
|
|
193
232
|
// In this case we want to ensure that node1 is still able to read new coValues
|
|
194
233
|
// Even if some childs are not available when the readKey is rotated
|
|
195
|
-
await group.removeMember(node3.account);
|
|
196
|
-
await
|
|
234
|
+
await group.removeMember(node3.node.account);
|
|
235
|
+
await group.core.waitForSync();
|
|
197
236
|
|
|
198
237
|
const map = childGroup.createMap();
|
|
199
238
|
map.set("test", "Available to node1");
|
|
200
239
|
|
|
201
|
-
const mapOnNode1 = await loadCoValueOrFail(node1, map.id);
|
|
240
|
+
const mapOnNode1 = await loadCoValueOrFail(node1.node, map.id);
|
|
202
241
|
|
|
203
242
|
expect(mapOnNode1.get("test")).toEqual("Available to node1");
|
|
204
243
|
});
|
|
244
|
+
|
|
245
|
+
test("A user add after a key rotation should have access to the old transactions", async () => {
|
|
246
|
+
const { node1, node2, node3 } = await createThreeConnectedNodes(
|
|
247
|
+
"server",
|
|
248
|
+
"server",
|
|
249
|
+
"server",
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
const group = node1.node.createGroup();
|
|
253
|
+
|
|
254
|
+
group.addMember(
|
|
255
|
+
await loadCoValueOrFail(node1.node, node2.accountID),
|
|
256
|
+
"writer",
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
await group.core.waitForSync();
|
|
260
|
+
|
|
261
|
+
const groupOnNode2 = await loadCoValueOrFail(node2.node, group.id);
|
|
262
|
+
|
|
263
|
+
const map = groupOnNode2.createMap();
|
|
264
|
+
map.set("test", "Written from node2");
|
|
265
|
+
|
|
266
|
+
await map.core.waitForSync();
|
|
267
|
+
|
|
268
|
+
await group.removeMember(node3.node.account);
|
|
269
|
+
group.addMember(
|
|
270
|
+
await loadCoValueOrFail(node1.node, node3.accountID),
|
|
271
|
+
"reader",
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
await group.core.waitForSync();
|
|
275
|
+
|
|
276
|
+
const mapOnNode3 = await loadCoValueOrFail(node3.node, map.id);
|
|
277
|
+
expect(mapOnNode3.get("test")).toEqual("Written from node2");
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
test("Invites should have access to the new keys", async () => {
|
|
281
|
+
const { node1, node2, node3 } = await createThreeConnectedNodes(
|
|
282
|
+
"server",
|
|
283
|
+
"server",
|
|
284
|
+
"server",
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
const group = node1.node.createGroup();
|
|
288
|
+
group.addMember(
|
|
289
|
+
await loadCoValueOrFail(node1.node, node3.accountID),
|
|
290
|
+
"reader",
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
const invite = group.createInvite("admin");
|
|
294
|
+
|
|
295
|
+
await group.removeMember(node3.node.account);
|
|
296
|
+
|
|
297
|
+
const map = group.createMap();
|
|
298
|
+
map.set("test", "Written from node1");
|
|
299
|
+
|
|
300
|
+
await map.core.waitForSync();
|
|
301
|
+
|
|
302
|
+
await node2.node.acceptInvite(group.id, invite);
|
|
303
|
+
|
|
304
|
+
const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
|
|
305
|
+
expect(mapOnNode2.get("test")).toEqual("Written from node1");
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
describe("writeOnly", () => {
|
|
309
|
+
test("Admins can invite writeOnly members", async () => {
|
|
310
|
+
const { node1, node2 } = await createTwoConnectedNodes("server", "server");
|
|
311
|
+
|
|
312
|
+
const group = node1.node.createGroup();
|
|
313
|
+
|
|
314
|
+
const invite = group.createInvite("writeOnly");
|
|
315
|
+
|
|
316
|
+
await node2.node.acceptInvite(group.id, invite);
|
|
317
|
+
|
|
318
|
+
const groupOnNode2 = await loadCoValueOrFail(node2.node, group.id);
|
|
319
|
+
expect(groupOnNode2.myRole()).toEqual("writeOnly");
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
test("writeOnly roles are not inherited", async () => {
|
|
323
|
+
const { node1, node2 } = await createTwoConnectedNodes("server", "server");
|
|
324
|
+
|
|
325
|
+
const group = node1.node.createGroup();
|
|
326
|
+
group.addMember(
|
|
327
|
+
await loadCoValueOrFail(node1.node, node2.accountID),
|
|
328
|
+
"writeOnly",
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
const childGroup = node1.node.createGroup();
|
|
332
|
+
childGroup.extend(group);
|
|
333
|
+
expect(childGroup.roleOf(node2.accountID)).toEqual(undefined);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
test("writeOnly roles are not overridded by reader roles", async () => {
|
|
337
|
+
const { node1, node2 } = await createTwoConnectedNodes("server", "server");
|
|
338
|
+
|
|
339
|
+
const group = node1.node.createGroup();
|
|
340
|
+
group.addMember(
|
|
341
|
+
await loadCoValueOrFail(node1.node, node2.accountID),
|
|
342
|
+
"reader",
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
const childGroup = node1.node.createGroup();
|
|
346
|
+
childGroup.extend(group);
|
|
347
|
+
childGroup.addMember(
|
|
348
|
+
await loadCoValueOrFail(node1.node, node2.accountID),
|
|
349
|
+
"writeOnly",
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
expect(childGroup.roleOf(node2.accountID)).toEqual("writeOnly");
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
test("writeOnly roles are overridded by writer roles", async () => {
|
|
356
|
+
const { node1, node2 } = await createTwoConnectedNodes("server", "server");
|
|
357
|
+
|
|
358
|
+
const group = node1.node.createGroup();
|
|
359
|
+
group.addMember(
|
|
360
|
+
await loadCoValueOrFail(node1.node, node2.accountID),
|
|
361
|
+
"writer",
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
const childGroup = node1.node.createGroup();
|
|
365
|
+
childGroup.extend(group);
|
|
366
|
+
childGroup.addMember(
|
|
367
|
+
await loadCoValueOrFail(node1.node, node2.accountID),
|
|
368
|
+
"writeOnly",
|
|
369
|
+
);
|
|
370
|
+
|
|
371
|
+
expect(childGroup.roleOf(node2.accountID)).toEqual("writer");
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
test("Edits by writeOnly members are visible to other members", async () => {
|
|
375
|
+
const { node1, node2, node3 } = await createThreeConnectedNodes(
|
|
376
|
+
"server",
|
|
377
|
+
"server",
|
|
378
|
+
"server",
|
|
379
|
+
);
|
|
380
|
+
|
|
381
|
+
const group = node1.node.createGroup();
|
|
382
|
+
|
|
383
|
+
group.addMember(
|
|
384
|
+
await loadCoValueOrFail(node1.node, node2.accountID),
|
|
385
|
+
"writeOnly",
|
|
386
|
+
);
|
|
387
|
+
group.addMember(
|
|
388
|
+
await loadCoValueOrFail(node1.node, node3.accountID),
|
|
389
|
+
"reader",
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
await group.core.waitForSync();
|
|
393
|
+
|
|
394
|
+
const groupOnNode2 = await loadCoValueOrFail(node2.node, group.id);
|
|
395
|
+
const map = groupOnNode2.createMap();
|
|
396
|
+
|
|
397
|
+
map.set("test", "Written from a writeOnly member");
|
|
398
|
+
expect(map.get("test")).toEqual("Written from a writeOnly member");
|
|
399
|
+
|
|
400
|
+
await map.core.waitForSync();
|
|
401
|
+
|
|
402
|
+
const mapOnNode1 = await loadCoValueOrFail(node1.node, map.id);
|
|
403
|
+
expect(mapOnNode1.get("test")).toEqual("Written from a writeOnly member");
|
|
404
|
+
|
|
405
|
+
const mapOnNode3 = await loadCoValueOrFail(node3.node, map.id);
|
|
406
|
+
expect(mapOnNode3.get("test")).toEqual("Written from a writeOnly member");
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
test("Edits by other members are not visible to writeOnly members", async () => {
|
|
410
|
+
const { node1, node2 } = await createTwoConnectedNodes("server", "server");
|
|
411
|
+
|
|
412
|
+
const group = node1.node.createGroup();
|
|
413
|
+
|
|
414
|
+
group.addMember(
|
|
415
|
+
await loadCoValueOrFail(node1.node, node2.accountID),
|
|
416
|
+
"writeOnly",
|
|
417
|
+
);
|
|
418
|
+
const map = group.createMap();
|
|
419
|
+
map.set("test", "Written from the admin");
|
|
420
|
+
|
|
421
|
+
await map.core.waitForSync();
|
|
422
|
+
|
|
423
|
+
const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
|
|
424
|
+
expect(mapOnNode2.get("test")).toEqual(undefined);
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
test("Write only member keys are rotated when a member is kicked out", async () => {
|
|
428
|
+
const { node1, node2, node3 } = await createThreeConnectedNodes(
|
|
429
|
+
"server",
|
|
430
|
+
"server",
|
|
431
|
+
"server",
|
|
432
|
+
);
|
|
433
|
+
|
|
434
|
+
const group = node1.node.createGroup();
|
|
435
|
+
|
|
436
|
+
group.addMember(
|
|
437
|
+
await loadCoValueOrFail(node1.node, node2.accountID),
|
|
438
|
+
"writeOnly",
|
|
439
|
+
);
|
|
440
|
+
group.addMember(
|
|
441
|
+
await loadCoValueOrFail(node1.node, node3.accountID),
|
|
442
|
+
"reader",
|
|
443
|
+
);
|
|
444
|
+
|
|
445
|
+
await group.core.waitForSync();
|
|
446
|
+
|
|
447
|
+
const groupOnNode2 = await loadCoValueOrFail(node2.node, group.id);
|
|
448
|
+
const map = groupOnNode2.createMap();
|
|
449
|
+
|
|
450
|
+
map.set("test", "Written from a writeOnly member");
|
|
451
|
+
|
|
452
|
+
await map.core.waitForSync();
|
|
453
|
+
|
|
454
|
+
await group.removeMember(node3.node.account);
|
|
455
|
+
|
|
456
|
+
await group.core.waitForSync();
|
|
457
|
+
|
|
458
|
+
map.set("test", "Updated after key rotation");
|
|
459
|
+
|
|
460
|
+
await map.core.waitForSync();
|
|
461
|
+
|
|
462
|
+
const mapOnNode1 = await loadCoValueOrFail(node1.node, map.id);
|
|
463
|
+
expect(mapOnNode1.get("test")).toEqual("Updated after key rotation");
|
|
464
|
+
|
|
465
|
+
const mapOnNode3 = await loadCoValueOrFail(node3.node, map.id);
|
|
466
|
+
expect(mapOnNode3.get("test")).toEqual("Written from a writeOnly member");
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
test("inherited writer roles should work correctly", async () => {
|
|
470
|
+
const { node1, node2 } = await createTwoConnectedNodes("server", "server");
|
|
471
|
+
|
|
472
|
+
const group = node1.node.createGroup();
|
|
473
|
+
group.addMember(
|
|
474
|
+
await loadCoValueOrFail(node1.node, node2.accountID),
|
|
475
|
+
"writer",
|
|
476
|
+
);
|
|
477
|
+
|
|
478
|
+
const childGroup = node1.node.createGroup();
|
|
479
|
+
childGroup.extend(group);
|
|
480
|
+
childGroup.addMember(
|
|
481
|
+
await loadCoValueOrFail(node1.node, node2.accountID),
|
|
482
|
+
"writeOnly",
|
|
483
|
+
);
|
|
484
|
+
|
|
485
|
+
const map = childGroup.createMap();
|
|
486
|
+
map.set("test", "Written from the admin");
|
|
487
|
+
|
|
488
|
+
await map.core.waitForSync();
|
|
489
|
+
|
|
490
|
+
const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
|
|
491
|
+
|
|
492
|
+
// The writer role should be able to see the edits from the admin
|
|
493
|
+
expect(mapOnNode2.get("test")).toEqual("Written from the admin");
|
|
494
|
+
});
|
|
495
|
+
});
|