cojson 0.18.27 → 0.18.29
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 +17 -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
package/src/permissions.ts
CHANGED
|
@@ -115,9 +115,9 @@ export function determineValidTransactions(coValue: CoValueCore) {
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
tx.isValidated = true;
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const groupAtTime = groupContent.atTime(tx.
|
|
118
|
+
// We use the original made at to get the group at the original time when the transaction was made
|
|
119
|
+
// madeAt might be changed by the meta field (e.g. merged transactions), and so can't be used for permissions checks
|
|
120
|
+
const groupAtTime = groupContent.atTime(tx.currentMadeAt);
|
|
121
121
|
const effectiveTransactor = agentInAccountOrMemberInGroup(
|
|
122
122
|
tx.author,
|
|
123
123
|
groupAtTime,
|
|
@@ -131,6 +131,21 @@ export function determineValidTransactions(coValue: CoValueCore) {
|
|
|
131
131
|
const transactorRoleAtTxTime =
|
|
132
132
|
groupAtTime.roleOfInternal(effectiveTransactor);
|
|
133
133
|
|
|
134
|
+
if (
|
|
135
|
+
transactorRoleAtTxTime === "reader" &&
|
|
136
|
+
tx.meta?.branch &&
|
|
137
|
+
tx.meta?.ownerId
|
|
138
|
+
) {
|
|
139
|
+
// Force the changes and meta to only contain the branch pointer information
|
|
140
|
+
tx.meta = {
|
|
141
|
+
branch: tx.meta.branch,
|
|
142
|
+
ownerId: tx.meta.ownerId,
|
|
143
|
+
};
|
|
144
|
+
tx.changes = [];
|
|
145
|
+
tx.isValid = true;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
|
|
134
149
|
if (
|
|
135
150
|
transactorRoleAtTxTime !== "admin" &&
|
|
136
151
|
transactorRoleAtTxTime !== "writer" &&
|
|
@@ -217,13 +232,10 @@ function determineValidTransactionsForGroup(
|
|
|
217
232
|
initialAdmin: RawAccountID | AgentID,
|
|
218
233
|
extendChain?: Set<CoValueCore["id"]>,
|
|
219
234
|
): { memberState: MemberState } {
|
|
220
|
-
coValue.verifiedTransactions.sort(
|
|
221
|
-
return a.madeAt - b.madeAt;
|
|
222
|
-
});
|
|
235
|
+
coValue.verifiedTransactions.sort(coValue.compareTransactions);
|
|
223
236
|
|
|
224
237
|
const memberState: MemberState = {};
|
|
225
238
|
const writeOnlyKeys: Record<RawAccountID | AgentID, KeyID> = {};
|
|
226
|
-
const validTransactions: ValidTransactionsResult[] = [];
|
|
227
239
|
|
|
228
240
|
const writeKeys = new Set<string>();
|
|
229
241
|
|
|
@@ -246,20 +258,10 @@ function determineValidTransactionsForGroup(
|
|
|
246
258
|
}
|
|
247
259
|
}
|
|
248
260
|
|
|
249
|
-
|
|
261
|
+
const changes = transaction.changes;
|
|
250
262
|
|
|
251
263
|
if (!changes) {
|
|
252
|
-
|
|
253
|
-
changes = parseJSON(tx.changes);
|
|
254
|
-
transaction.changes = changes;
|
|
255
|
-
} catch (e) {
|
|
256
|
-
logPermissionError("Invalid JSON in transaction", {
|
|
257
|
-
id: coValue.id,
|
|
258
|
-
tx,
|
|
259
|
-
});
|
|
260
|
-
transaction.hasInvalidChanges = true;
|
|
261
|
-
continue;
|
|
262
|
-
}
|
|
264
|
+
continue;
|
|
263
265
|
}
|
|
264
266
|
|
|
265
267
|
const change = changes[0] as
|
package/src/sync.ts
CHANGED
|
@@ -740,11 +740,11 @@ export class SyncManager {
|
|
|
740
740
|
peer.role === "server" &&
|
|
741
741
|
!peer.loadRequestSent.has(coValue.id)
|
|
742
742
|
) {
|
|
743
|
-
const state = coValue.
|
|
743
|
+
const state = coValue.getLoadingStateForPeer(peer.id);
|
|
744
744
|
|
|
745
745
|
// Check if there is a inflight load operation and we
|
|
746
746
|
// are waiting for other peers to send the load request
|
|
747
|
-
if (state === "unknown"
|
|
747
|
+
if (state === "unknown") {
|
|
748
748
|
// Sending a load message to the peer to get to know how much content is missing
|
|
749
749
|
// before sending the new content
|
|
750
750
|
this.trySendToPeer(peer, {
|
|
@@ -1,11 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
assert,
|
|
3
|
+
beforeEach,
|
|
4
|
+
describe,
|
|
5
|
+
expect,
|
|
6
|
+
onTestFinished,
|
|
7
|
+
test,
|
|
8
|
+
vi,
|
|
9
|
+
} from "vitest";
|
|
2
10
|
import {
|
|
3
11
|
createTestNode,
|
|
4
12
|
setupTestNode,
|
|
5
13
|
loadCoValueOrFail,
|
|
14
|
+
setupTestAccount,
|
|
15
|
+
waitFor,
|
|
6
16
|
} from "./testUtils.js";
|
|
7
17
|
import { expectList, expectMap, expectPlainText } from "../coValue.js";
|
|
8
|
-
import { RawCoMap } from "../exports.js";
|
|
18
|
+
import { RawAccount, RawCoMap } from "../exports.js";
|
|
9
19
|
|
|
10
20
|
let jazzCloud: ReturnType<typeof setupTestNode>;
|
|
11
21
|
|
|
@@ -237,6 +247,7 @@ describe("Branching Logic", () => {
|
|
|
237
247
|
loadedBranch2.core.mergeBranch();
|
|
238
248
|
|
|
239
249
|
await loadedBranch2.core.waitForSync();
|
|
250
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
240
251
|
|
|
241
252
|
branch1.core.mergeBranch();
|
|
242
253
|
|
|
@@ -289,6 +300,385 @@ describe("Branching Logic", () => {
|
|
|
289
300
|
|
|
290
301
|
expect(plainText.toString()).toEqual("hello world people");
|
|
291
302
|
});
|
|
303
|
+
|
|
304
|
+
test("should preserve the conflict resolution that was applied to the branch", async () => {
|
|
305
|
+
const dateNowMock = vi.spyOn(Date, "now");
|
|
306
|
+
|
|
307
|
+
dateNowMock.mockReturnValue(1);
|
|
308
|
+
|
|
309
|
+
const client = setupTestNode({
|
|
310
|
+
connected: true,
|
|
311
|
+
});
|
|
312
|
+
const group = client.node.createGroup();
|
|
313
|
+
const map = group.createMap();
|
|
314
|
+
const branchName = "feature-branch";
|
|
315
|
+
|
|
316
|
+
onTestFinished(() => {
|
|
317
|
+
dateNowMock.mockRestore();
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// Add initial transactions to original map
|
|
321
|
+
map.set("value", 1, "trusting");
|
|
322
|
+
|
|
323
|
+
// Create branch from original map
|
|
324
|
+
const branch = expectMap(
|
|
325
|
+
map.core.createBranch(branchName, group.id).getCurrentContent(),
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
dateNowMock.mockReturnValue(2);
|
|
329
|
+
|
|
330
|
+
// Add new transaction to branch
|
|
331
|
+
branch.set("value", 2, "trusting");
|
|
332
|
+
|
|
333
|
+
const newSession = client.spawnNewSession();
|
|
334
|
+
|
|
335
|
+
const loadedBranch = await loadCoValueOrFail(newSession.node, branch.id);
|
|
336
|
+
|
|
337
|
+
dateNowMock.mockReturnValue(3);
|
|
338
|
+
|
|
339
|
+
loadedBranch.set("value", 3, "trusting");
|
|
340
|
+
|
|
341
|
+
expect(loadedBranch.get("value")).toBe(3);
|
|
342
|
+
|
|
343
|
+
await loadedBranch.core.waitForSync();
|
|
344
|
+
|
|
345
|
+
// Push back the change, so it doesn't win the conflict
|
|
346
|
+
dateNowMock.mockReturnValue(1);
|
|
347
|
+
|
|
348
|
+
branch.set("value", 4, "trusting");
|
|
349
|
+
|
|
350
|
+
expect(branch.get("value")).toBe(3);
|
|
351
|
+
|
|
352
|
+
dateNowMock.mockReturnValue(4);
|
|
353
|
+
branch.core.mergeBranch();
|
|
354
|
+
|
|
355
|
+
// The conflict resolution should be preserved, so we should have 3 and not 4
|
|
356
|
+
expect(map.get("value")).toBe(3);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
test("should preserve the original madeAt of the branch", async () => {
|
|
360
|
+
const client = setupTestNode();
|
|
361
|
+
const group = client.node.createGroup();
|
|
362
|
+
const map = group.createMap();
|
|
363
|
+
|
|
364
|
+
// Add initial transactions to original map
|
|
365
|
+
map.set("value", 1, "trusting");
|
|
366
|
+
|
|
367
|
+
// Create branch from original map
|
|
368
|
+
const branch = expectMap(
|
|
369
|
+
map.core.createBranch("feature-branch", group.id).getCurrentContent(),
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
// Add new transaction to branch
|
|
373
|
+
branch.set("value", 2, "trusting");
|
|
374
|
+
|
|
375
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
376
|
+
|
|
377
|
+
const result = branch.core.mergeBranch();
|
|
378
|
+
|
|
379
|
+
// The merge should be successful
|
|
380
|
+
expect(map.get("value")).toBe(2);
|
|
381
|
+
|
|
382
|
+
const lastBranchTransaction = branch.core
|
|
383
|
+
.getValidSortedTransactions()
|
|
384
|
+
.at(-1);
|
|
385
|
+
const lastMapTransaction = result
|
|
386
|
+
.getValidSortedTransactions()
|
|
387
|
+
.findLast((tx) => tx.txID.branch === branch.id);
|
|
388
|
+
|
|
389
|
+
expect(lastMapTransaction?.currentMadeAt).not.toBe(
|
|
390
|
+
lastMapTransaction?.madeAt,
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
expect(lastBranchTransaction?.madeAt).toBe(lastMapTransaction?.madeAt);
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
test("should not load the merged transactions into the branch (regression test)", async () => {
|
|
397
|
+
const client = setupTestNode();
|
|
398
|
+
const group = client.node.createGroup();
|
|
399
|
+
const map = group.createMap();
|
|
400
|
+
|
|
401
|
+
// Add initial transactions to original map
|
|
402
|
+
map.set("value", 1, "trusting");
|
|
403
|
+
|
|
404
|
+
// Create branch from original map
|
|
405
|
+
const branch = expectMap(
|
|
406
|
+
map.core.createBranch("feature-branch", group.id).getCurrentContent(),
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
410
|
+
|
|
411
|
+
// Add new transaction to branch
|
|
412
|
+
branch.set("value", 2, "trusting");
|
|
413
|
+
|
|
414
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
415
|
+
|
|
416
|
+
const result = branch.core.mergeBranch();
|
|
417
|
+
|
|
418
|
+
// The merge should be successful
|
|
419
|
+
expect(map.get("value")).toBe(2);
|
|
420
|
+
|
|
421
|
+
const lastBranchTransaction = branch.core
|
|
422
|
+
.getValidSortedTransactions()
|
|
423
|
+
.at(-1);
|
|
424
|
+
expect(lastBranchTransaction?.madeAt).toBe(
|
|
425
|
+
lastBranchTransaction?.currentMadeAt,
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
const lastMapTransaction = result
|
|
429
|
+
.getValidSortedTransactions()
|
|
430
|
+
.findLast((tx) => tx.txID.branch === branch.id);
|
|
431
|
+
expect(lastMapTransaction?.madeAt).not.toBe(
|
|
432
|
+
lastMapTransaction?.currentMadeAt,
|
|
433
|
+
);
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
test("write permissions should be validated against the time of the merge and not the original madeAt", async () => {
|
|
437
|
+
const alice = setupTestNode({
|
|
438
|
+
connected: true,
|
|
439
|
+
});
|
|
440
|
+
const bob = await setupTestAccount({
|
|
441
|
+
connected: true,
|
|
442
|
+
});
|
|
443
|
+
const group = alice.node.createGroup();
|
|
444
|
+
const map = group.createMap();
|
|
445
|
+
const branchName = "feature-branch";
|
|
446
|
+
|
|
447
|
+
map.set("value", 1, "trusting");
|
|
448
|
+
|
|
449
|
+
const branch = expectMap(
|
|
450
|
+
map.core.createBranch(branchName, group.id).getCurrentContent(),
|
|
451
|
+
);
|
|
452
|
+
|
|
453
|
+
branch.set("value", 2, "trusting");
|
|
454
|
+
|
|
455
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
456
|
+
|
|
457
|
+
// Grant writer rights to bob after the changes inside the branche
|
|
458
|
+
group.addMember(
|
|
459
|
+
await loadCoValueOrFail(alice.node, bob.accountID),
|
|
460
|
+
"writer",
|
|
461
|
+
);
|
|
462
|
+
|
|
463
|
+
const loadedBranch = await loadCoValueOrFail(bob.node, branch.id);
|
|
464
|
+
|
|
465
|
+
// Bob merges the branch
|
|
466
|
+
const mergeResult = loadedBranch.core.mergeBranch();
|
|
467
|
+
|
|
468
|
+
// The merge should be successful
|
|
469
|
+
expect(expectMap(mergeResult.getCurrentContent()).get("value")).toBe(2);
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
test("should reject edits from kicked out member even with timestamp manipulation", async () => {
|
|
473
|
+
const alice = setupTestNode({
|
|
474
|
+
connected: true,
|
|
475
|
+
});
|
|
476
|
+
const bob = await setupTestAccount({
|
|
477
|
+
connected: true,
|
|
478
|
+
});
|
|
479
|
+
const group = alice.node.createGroup();
|
|
480
|
+
const map = group.createMap();
|
|
481
|
+
|
|
482
|
+
group.addMember(
|
|
483
|
+
await loadCoValueOrFail(alice.node, bob.accountID),
|
|
484
|
+
"writer",
|
|
485
|
+
);
|
|
486
|
+
const timeOnInvitation = Date.now();
|
|
487
|
+
|
|
488
|
+
const bobMap = await loadCoValueOrFail(bob.node, map.id);
|
|
489
|
+
|
|
490
|
+
bobMap.set("value", 1, "trusting");
|
|
491
|
+
|
|
492
|
+
await bobMap.core.waitForSync();
|
|
493
|
+
|
|
494
|
+
const bobGroup = bob.node.createGroup();
|
|
495
|
+
const branch = expectMap(
|
|
496
|
+
bobMap.core
|
|
497
|
+
.createBranch("feature-branch", bobGroup.id)
|
|
498
|
+
.getCurrentContent(),
|
|
499
|
+
);
|
|
500
|
+
|
|
501
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
502
|
+
|
|
503
|
+
// Alice sets value to 2 and downgrade bob to reader
|
|
504
|
+
map.set("value", 2, "trusting");
|
|
505
|
+
group.addMember(
|
|
506
|
+
await loadCoValueOrFail(alice.node, bob.accountID),
|
|
507
|
+
"reader",
|
|
508
|
+
);
|
|
509
|
+
|
|
510
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
511
|
+
|
|
512
|
+
branch.set("value", 3, "trusting");
|
|
513
|
+
|
|
514
|
+
// Bob attempts to make an edit after being kicked out by modifying the merge time
|
|
515
|
+
const dateNowMock = vi.spyOn(Date, "now");
|
|
516
|
+
dateNowMock.mockReturnValue(timeOnInvitation + 1);
|
|
517
|
+
|
|
518
|
+
const mergeResult = branch.core.mergeBranch();
|
|
519
|
+
|
|
520
|
+
dateNowMock.mockRestore();
|
|
521
|
+
|
|
522
|
+
// Wait for the full sync to complete
|
|
523
|
+
await waitFor(() => {
|
|
524
|
+
expect(mergeResult.knownState().sessions).toEqual(
|
|
525
|
+
map.core.knownState().sessions,
|
|
526
|
+
);
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
expect(expectMap(map.core.getCurrentContent()).get("value")).toBe(2);
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
test("should alias the txID when a transaction comes from a merge", async () => {
|
|
533
|
+
const client = setupTestNode({
|
|
534
|
+
connected: true,
|
|
535
|
+
});
|
|
536
|
+
const group = client.node.createGroup();
|
|
537
|
+
const map = group.createMap();
|
|
538
|
+
|
|
539
|
+
map.set("key", "value");
|
|
540
|
+
|
|
541
|
+
const branch = map.core
|
|
542
|
+
.createBranch("feature-branch", group.id)
|
|
543
|
+
.getCurrentContent() as RawCoMap;
|
|
544
|
+
branch.set("branchKey", "branchValue");
|
|
545
|
+
|
|
546
|
+
const originalTxID = branch.core
|
|
547
|
+
.getValidTransactions({
|
|
548
|
+
skipBranchSource: true,
|
|
549
|
+
ignorePrivateTransactions: false,
|
|
550
|
+
})
|
|
551
|
+
.at(-1)?.txID;
|
|
552
|
+
|
|
553
|
+
branch.core.mergeBranch();
|
|
554
|
+
|
|
555
|
+
map.set("key2", "value2");
|
|
556
|
+
|
|
557
|
+
const validSortedTransactions = map.core.getValidSortedTransactions();
|
|
558
|
+
|
|
559
|
+
// Only the merged transaction should have the txId changed
|
|
560
|
+
const mergedTransactionIdx = validSortedTransactions.findIndex(
|
|
561
|
+
(tx) => tx.txID.branch,
|
|
562
|
+
);
|
|
563
|
+
|
|
564
|
+
expect(
|
|
565
|
+
validSortedTransactions[mergedTransactionIdx - 1]?.txID.branch,
|
|
566
|
+
).toBe(undefined);
|
|
567
|
+
expect(validSortedTransactions[mergedTransactionIdx]?.txID).toEqual(
|
|
568
|
+
originalTxID,
|
|
569
|
+
);
|
|
570
|
+
expect(
|
|
571
|
+
validSortedTransactions[mergedTransactionIdx + 1]?.txID.branch,
|
|
572
|
+
).toBe(undefined);
|
|
573
|
+
});
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
describe("Branching permissions", () => {
|
|
577
|
+
test("should allow the creation of private branches to accounts with read access to the source group", async () => {
|
|
578
|
+
const alice = setupTestNode({
|
|
579
|
+
connected: true,
|
|
580
|
+
});
|
|
581
|
+
const bob = await setupTestAccount({
|
|
582
|
+
connected: true,
|
|
583
|
+
});
|
|
584
|
+
const group = alice.node.createGroup();
|
|
585
|
+
group.addMember(
|
|
586
|
+
await loadCoValueOrFail(alice.node, bob.accountID),
|
|
587
|
+
"reader",
|
|
588
|
+
);
|
|
589
|
+
const map = group.createMap();
|
|
590
|
+
map.set("key", "alice");
|
|
591
|
+
|
|
592
|
+
const bobGroup = bob.node.createGroup();
|
|
593
|
+
|
|
594
|
+
const mapOnBob = await loadCoValueOrFail(bob.node, map.id);
|
|
595
|
+
|
|
596
|
+
const branch = expectMap(
|
|
597
|
+
mapOnBob.core
|
|
598
|
+
.createBranch("feature-branch", bobGroup.id)
|
|
599
|
+
.getCurrentContent(),
|
|
600
|
+
);
|
|
601
|
+
|
|
602
|
+
expect(mapOnBob.core.branches).toEqual([
|
|
603
|
+
{
|
|
604
|
+
branch: "feature-branch",
|
|
605
|
+
ownerId: bobGroup.id,
|
|
606
|
+
},
|
|
607
|
+
]);
|
|
608
|
+
|
|
609
|
+
expect(branch.id).not.toBe(map.id);
|
|
610
|
+
expect(branch.get("key")).toBe("alice");
|
|
611
|
+
expect(branch.core.getGroup().id).toBe(bobGroup.id);
|
|
612
|
+
expect(branch.core.getGroup().myRole()).toBe("admin");
|
|
613
|
+
branch.set("key", "bob");
|
|
614
|
+
|
|
615
|
+
expect(branch.get("key")).toBe("bob");
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
test("an account with write access to the source and read access to the branch should be able to merge a branch created by a reader", async () => {
|
|
619
|
+
const alice = await setupTestAccount({
|
|
620
|
+
connected: true,
|
|
621
|
+
});
|
|
622
|
+
const bob = await setupTestAccount({
|
|
623
|
+
connected: true,
|
|
624
|
+
});
|
|
625
|
+
const group = alice.node.createGroup();
|
|
626
|
+
group.addMember(
|
|
627
|
+
await loadCoValueOrFail(alice.node, bob.accountID),
|
|
628
|
+
"reader",
|
|
629
|
+
);
|
|
630
|
+
const map = group.createMap();
|
|
631
|
+
map.set("key", "alice");
|
|
632
|
+
|
|
633
|
+
const bobGroup = bob.node.createGroup();
|
|
634
|
+
bobGroup.addMember(
|
|
635
|
+
await loadCoValueOrFail(bob.node, alice.accountID),
|
|
636
|
+
"reader",
|
|
637
|
+
);
|
|
638
|
+
|
|
639
|
+
const mapOnBob = await loadCoValueOrFail(bob.node, map.id);
|
|
640
|
+
|
|
641
|
+
const branch = expectMap(
|
|
642
|
+
mapOnBob.core
|
|
643
|
+
.createBranch("feature-branch", bobGroup.id)
|
|
644
|
+
.getCurrentContent(),
|
|
645
|
+
);
|
|
646
|
+
|
|
647
|
+
branch.set("key", "bob");
|
|
648
|
+
|
|
649
|
+
expect(branch.get("key")).toBe("bob");
|
|
650
|
+
|
|
651
|
+
const branchOnAlice = await loadCoValueOrFail(alice.node, branch.id);
|
|
652
|
+
branchOnAlice.core.mergeBranch();
|
|
653
|
+
|
|
654
|
+
expect(map.get("key")).toBe("bob");
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
test("should not allow the creation of public branches to accounts with read access", async () => {
|
|
658
|
+
const alice = setupTestNode({
|
|
659
|
+
connected: true,
|
|
660
|
+
});
|
|
661
|
+
const bob = await setupTestAccount({
|
|
662
|
+
connected: true,
|
|
663
|
+
});
|
|
664
|
+
const group = alice.node.createGroup();
|
|
665
|
+
group.addMember(
|
|
666
|
+
await loadCoValueOrFail(alice.node, bob.accountID),
|
|
667
|
+
"reader",
|
|
668
|
+
);
|
|
669
|
+
const map = group.createMap();
|
|
670
|
+
map.set("key", "alice");
|
|
671
|
+
|
|
672
|
+
const mapOnBob = await loadCoValueOrFail(bob.node, map.id);
|
|
673
|
+
|
|
674
|
+
const branch = expectMap(
|
|
675
|
+
mapOnBob.core.createBranch("feature-branch").getCurrentContent(),
|
|
676
|
+
);
|
|
677
|
+
|
|
678
|
+
expect(mapOnBob.core.branches).toEqual([]);
|
|
679
|
+
|
|
680
|
+
expect(branch.id).toBe(map.id);
|
|
681
|
+
});
|
|
292
682
|
});
|
|
293
683
|
|
|
294
684
|
describe("Branch Loading and Checkout", () => {
|
|
@@ -560,49 +950,6 @@ describe("Branching Logic", () => {
|
|
|
560
950
|
});
|
|
561
951
|
});
|
|
562
952
|
|
|
563
|
-
test("should alias the txID when a transaction comes from a merge", async () => {
|
|
564
|
-
const client = setupTestNode({
|
|
565
|
-
connected: true,
|
|
566
|
-
});
|
|
567
|
-
const group = client.node.createGroup();
|
|
568
|
-
const map = group.createMap();
|
|
569
|
-
|
|
570
|
-
map.set("key", "value");
|
|
571
|
-
|
|
572
|
-
const branch = map.core
|
|
573
|
-
.createBranch("feature-branch", group.id)
|
|
574
|
-
.getCurrentContent() as RawCoMap;
|
|
575
|
-
branch.set("branchKey", "branchValue");
|
|
576
|
-
|
|
577
|
-
const originalTxID = branch.core
|
|
578
|
-
.getValidTransactions({
|
|
579
|
-
skipBranchSource: true,
|
|
580
|
-
ignorePrivateTransactions: false,
|
|
581
|
-
})
|
|
582
|
-
.at(-1)?.txID;
|
|
583
|
-
|
|
584
|
-
branch.core.mergeBranch();
|
|
585
|
-
|
|
586
|
-
map.set("key2", "value2");
|
|
587
|
-
|
|
588
|
-
const validSortedTransactions = map.core.getValidSortedTransactions();
|
|
589
|
-
|
|
590
|
-
// Only the merged transaction should have the txId changed
|
|
591
|
-
const mergedTransactionIdx = validSortedTransactions.findIndex(
|
|
592
|
-
(tx) => tx.txID.branch,
|
|
593
|
-
);
|
|
594
|
-
|
|
595
|
-
expect(validSortedTransactions[mergedTransactionIdx - 1]?.txID.branch).toBe(
|
|
596
|
-
undefined,
|
|
597
|
-
);
|
|
598
|
-
expect(validSortedTransactions[mergedTransactionIdx]?.txID).toEqual(
|
|
599
|
-
originalTxID,
|
|
600
|
-
);
|
|
601
|
-
expect(validSortedTransactions[mergedTransactionIdx + 1]?.txID.branch).toBe(
|
|
602
|
-
undefined,
|
|
603
|
-
);
|
|
604
|
-
});
|
|
605
|
-
|
|
606
953
|
describe("hasBranch", () => {
|
|
607
954
|
test("should work when the branch owner is the source owner", () => {
|
|
608
955
|
const client = setupTestNode({
|