cojson 0.8.39 → 0.8.44
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 +12 -0
- package/CHANGELOG.md +14 -0
- package/dist/native/coValueCore.js +22 -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 +2 -0
- 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 +77 -19
- package/dist/native/permissions.js.map +1 -1
- package/dist/web/coValueCore.js +22 -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 +2 -0
- 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 +77 -19
- package/dist/web/permissions.js.map +1 -1
- package/package.json +3 -5
- package/src/coValueCore.ts +37 -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 -0
- package/src/localNode.ts +5 -2
- package/src/permissions.ts +105 -24
- package/src/tests/coMap.test.ts +2 -2
- package/src/tests/group.test.ts +332 -38
- package/src/tests/permissions.test.ts +324 -0
- package/src/tests/testUtils.ts +18 -13
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,33 +60,44 @@ 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();
|
|
66
70
|
|
|
67
|
-
group.addMember(
|
|
68
|
-
|
|
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
|
+
);
|
|
79
|
+
|
|
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
102
|
await group.core.waitForSync();
|
|
91
103
|
|
|
@@ -97,26 +109,37 @@ test("Remove a member from a group where the admin role is inherited", async ()
|
|
|
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();
|
|
110
125
|
|
|
111
|
-
group.addMember(
|
|
112
|
-
|
|
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
|
+
);
|
|
113
134
|
|
|
114
|
-
|
|
135
|
+
await group.core.waitForSync();
|
|
136
|
+
|
|
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
145
|
await childGroup.core.waitForSync();
|
|
@@ -124,78 +147,349 @@ test("An admin should be able to rotate the readKey on child groups and keep acc
|
|
|
124
147
|
// The node1 account removes the reader from the group
|
|
125
148
|
// In this case we want to ensure that node1 is still able to read new coValues
|
|
126
149
|
// Even if some childs are not available when the readKey is rotated
|
|
127
|
-
await group.removeMember(node3.account);
|
|
150
|
+
await group.removeMember(node3.node.account);
|
|
128
151
|
await group.core.waitForSync();
|
|
129
152
|
|
|
130
153
|
const map = childGroup.createMap();
|
|
131
154
|
map.set("test", "Available to node1");
|
|
132
155
|
|
|
133
|
-
const mapOnNode1 = await loadCoValueOrFail(node1, map.id);
|
|
156
|
+
const mapOnNode1 = await loadCoValueOrFail(node1.node, map.id);
|
|
134
157
|
expect(mapOnNode1.get("test")).toEqual("Available to node1");
|
|
135
158
|
});
|
|
136
159
|
|
|
137
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 () => {
|
|
138
|
-
const { node1, node2, node3
|
|
139
|
-
|
|
161
|
+
const { node1, node2, node3 } = await createThreeConnectedNodes(
|
|
162
|
+
"server",
|
|
163
|
+
"server",
|
|
164
|
+
"server",
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
const group = node1.node.createGroup();
|
|
140
168
|
|
|
141
|
-
|
|
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
|
+
);
|
|
142
177
|
|
|
143
|
-
group.
|
|
144
|
-
group.addMember(node3.account, "reader");
|
|
178
|
+
await group.core.waitForSync();
|
|
145
179
|
|
|
146
|
-
const groupOnNode2 = await loadCoValueOrFail(node2, group.id);
|
|
180
|
+
const groupOnNode2 = await loadCoValueOrFail(node2.node, group.id);
|
|
147
181
|
|
|
148
182
|
// The account of node2 create a child group and extend the initial group
|
|
149
183
|
// This way the node1 account should become "admin" of the child group
|
|
150
184
|
// by inheriting the admin role from the initial group
|
|
151
|
-
const childGroup = node2.createGroup();
|
|
185
|
+
const childGroup = node2.node.createGroup();
|
|
152
186
|
childGroup.extend(groupOnNode2);
|
|
153
187
|
|
|
154
188
|
// The node1 account removes the reader from the group
|
|
155
189
|
// In this case we want to ensure that node1 is still able to read new coValues
|
|
156
190
|
// Even if some childs are not available when the readKey is rotated
|
|
157
|
-
await group.removeMember(node3.account);
|
|
191
|
+
await group.removeMember(node3.node.account);
|
|
158
192
|
await group.core.waitForSync();
|
|
159
193
|
|
|
160
194
|
const map = childGroup.createMap();
|
|
161
195
|
map.set("test", "Available to node1");
|
|
162
196
|
|
|
163
|
-
const mapOnNode1 = await loadCoValueOrFail(node1, map.id);
|
|
197
|
+
const mapOnNode1 = await loadCoValueOrFail(node1.node, map.id);
|
|
164
198
|
expect(mapOnNode1.get("test")).toEqual("Available to node1");
|
|
165
199
|
});
|
|
166
200
|
|
|
167
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 () => {
|
|
168
|
-
const { node1, node2, node3
|
|
202
|
+
const { node1, node2, node3 } = await createThreeConnectedNodes(
|
|
169
203
|
"server",
|
|
170
204
|
"server",
|
|
171
205
|
"server",
|
|
172
206
|
);
|
|
173
207
|
|
|
174
|
-
const group = node1.createGroup();
|
|
208
|
+
const group = node1.node.createGroup();
|
|
175
209
|
|
|
176
|
-
group.addMember(
|
|
177
|
-
|
|
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
|
-
|
|
219
|
+
await group.core.waitForSync();
|
|
220
|
+
|
|
221
|
+
const groupOnNode2 = await loadCoValueOrFail(node2.node, group.id);
|
|
180
222
|
|
|
181
223
|
// The account of node2 create a child group and extend the initial group
|
|
182
224
|
// This way the node1 account should become "admin" of the child group
|
|
183
225
|
// by inheriting the admin role from the initial group
|
|
184
|
-
const childGroup = node2.createGroup();
|
|
226
|
+
const childGroup = node2.node.createGroup();
|
|
185
227
|
childGroup.extend(groupOnNode2);
|
|
186
|
-
const grandChildGroup = node2.createGroup();
|
|
228
|
+
const grandChildGroup = node2.node.createGroup();
|
|
187
229
|
grandChildGroup.extend(childGroup);
|
|
188
230
|
|
|
189
231
|
// The node1 account removes the reader from the group
|
|
190
232
|
// In this case we want to ensure that node1 is still able to read new coValues
|
|
191
233
|
// Even if some childs are not available when the readKey is rotated
|
|
192
|
-
await group.removeMember(node3.account);
|
|
234
|
+
await group.removeMember(node3.node.account);
|
|
193
235
|
await group.core.waitForSync();
|
|
194
236
|
|
|
195
237
|
const map = childGroup.createMap();
|
|
196
238
|
map.set("test", "Available to node1");
|
|
197
239
|
|
|
198
|
-
const mapOnNode1 = await loadCoValueOrFail(node1, map.id);
|
|
240
|
+
const mapOnNode1 = await loadCoValueOrFail(node1.node, map.id);
|
|
199
241
|
|
|
200
242
|
expect(mapOnNode1.get("test")).toEqual("Available to node1");
|
|
201
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
|
+
});
|