fix: Fix missing entries when syncing multiple nodes (#58)
This commit is contained in:
parent
b8ae05c76d
commit
60cca9cc85
3 changed files with 305 additions and 73 deletions
|
|
@ -537,6 +537,88 @@ void main() {
|
|||
},
|
||||
);
|
||||
|
||||
withServerpod(
|
||||
'Given a server and two client CRDT sessions for the same user that are initialized with the same sync tables',
|
||||
rollbackDatabase: RollbackDatabase.disabled,
|
||||
(sessionBuilder, _) {
|
||||
final rawServerSession = sessionBuilder.build();
|
||||
late CrdtDatabaseSession secondClientSession;
|
||||
|
||||
rawServerSession.serverpod
|
||||
..initializeCrdtSync(syncTables: serverSyncTables)
|
||||
..authenticationHandler = (session, token) async => AuthenticationInfo(
|
||||
testCrdtUserId.toString(),
|
||||
<Scope>{},
|
||||
authId: const Uuid().v4(),
|
||||
);
|
||||
|
||||
setUp(() async {
|
||||
testClient = client.Client(
|
||||
'http://localhost:${rawServerSession.server.port}',
|
||||
)..authKeyProvider = TestClientAuthKeyProvider();
|
||||
|
||||
clientSession = CrdtDatabaseSession.wraps(
|
||||
testSession,
|
||||
syncTables: clientSyncTables,
|
||||
persistentUserId: testCrdtUserId,
|
||||
);
|
||||
await clientSession.db.initialize();
|
||||
|
||||
secondClientSession = CrdtDatabaseSession.wraps(
|
||||
await createAdditionalTestSession(),
|
||||
syncTables: clientSyncTables,
|
||||
persistentUserId: testCrdtUserId,
|
||||
);
|
||||
await secondClientSession.db.initialize();
|
||||
|
||||
serverSession = CrdtDatabaseSession.wraps(
|
||||
rawServerSession,
|
||||
syncTables: serverSyncTables,
|
||||
);
|
||||
await serverSession.db.initialize();
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await serverSession.clearUserTables();
|
||||
});
|
||||
|
||||
test(
|
||||
'when the first node uploads an offline deletion after the second node synchronized a newer change '
|
||||
'then the second node receives the deletion on its next sync.',
|
||||
() async {
|
||||
final rowToDelete = await client.Unique.db.insertRow(
|
||||
clientSession,
|
||||
client.Unique(name: 'deleted-by-first-node'),
|
||||
);
|
||||
await testClient.crdt.syncOnce(clientSession);
|
||||
await testClient.crdt.syncOnce(secondClientSession);
|
||||
|
||||
// After both nodes have synchronized, the first node deletes the row.
|
||||
await client.Unique.db.deleteRow(clientSession, rowToDelete);
|
||||
|
||||
// The second node inserts a new row and synchronizes it.
|
||||
await client.Unique.db.insertRow(
|
||||
secondClientSession,
|
||||
client.Unique(name: 'inserted-by-second-node'),
|
||||
);
|
||||
await testClient.crdt.syncOnce(secondClientSession);
|
||||
|
||||
// The first node synchronizes the deletion with the server.
|
||||
await testClient.crdt.syncOnce(clientSession);
|
||||
|
||||
// This last sync from the second node will only receive the deletion
|
||||
// from the first node if checkpoints are tracked per known node.
|
||||
await testClient.crdt.syncOnce(secondClientSession);
|
||||
final deletedRow = await client.Unique.db.findById(
|
||||
secondClientSession,
|
||||
rowToDelete.id!,
|
||||
);
|
||||
expect(deletedRow, isNull);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
withServerpod(
|
||||
'Given a server and client CRDT session that are initialized with the same sync tables and a continuous sync interval',
|
||||
rollbackDatabase: RollbackDatabase.disabled,
|
||||
|
|
|
|||
|
|
@ -65,8 +65,9 @@ void main() {
|
|||
final mergeSet = await crdtSync
|
||||
.collectPendingChanges(
|
||||
testSession,
|
||||
otherNodeId: const Uuid().v7obj(),
|
||||
peerNodeId: const Uuid().v7obj(),
|
||||
userId: testCrdtUserId,
|
||||
nodeCheckpoints: const [],
|
||||
)
|
||||
.toList();
|
||||
|
||||
|
|
@ -117,7 +118,7 @@ void main() {
|
|||
testSession,
|
||||
userId: testCrdtUserId,
|
||||
peerNodeId: const Uuid().v7obj(),
|
||||
sinceHlc: Hlc.zero(const Uuid().v7obj()),
|
||||
nodeCheckpoints: const [],
|
||||
)
|
||||
.toList();
|
||||
|
||||
|
|
@ -185,8 +186,9 @@ void main() {
|
|||
final mergeSet = await crdtSync
|
||||
.collectPendingChanges(
|
||||
testSession,
|
||||
otherNodeId: const Uuid().v7obj(),
|
||||
peerNodeId: const Uuid().v7obj(),
|
||||
userId: testCrdtUserId,
|
||||
nodeCheckpoints: const [],
|
||||
)
|
||||
.toList();
|
||||
|
||||
|
|
@ -208,6 +210,27 @@ void main() {
|
|||
},
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'Given a CRDT node without local changes '
|
||||
'when synchronization checkpoints are created '
|
||||
'then one fresh checkpoint for the local node is included.',
|
||||
() async {
|
||||
final user = await CrdtUserManager(testSession).getOrCreate(testCrdtUserId);
|
||||
final sinceHlc = await crdtSync.createSyncSinceHlc(
|
||||
testSession,
|
||||
userId: testCrdtUserId,
|
||||
peerNodeId: const Uuid().v7obj(),
|
||||
);
|
||||
|
||||
expect(sinceHlc.nodeCheckpoints, hasLength(1));
|
||||
expect(sinceHlc.nodeCheckpoints.single.nodeId, user.currentNode!.uuidNodeId);
|
||||
expect(
|
||||
sinceHlc.nodeCheckpoints.single,
|
||||
greaterThan(Hlc.zero(sinceHlc.nodeCheckpoints.single.nodeId)),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
extension on List<int> {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ late CrdtDatabaseSession _crdtSession;
|
|||
late Directory _tempDir;
|
||||
late UuidValue _testCrdtUserId;
|
||||
late bool _withPersistentUser;
|
||||
var _additionalTestSessionCount = 0;
|
||||
|
||||
/// A fresh [ClientDatabaseSession] for each test that is automatically closed
|
||||
/// and removed once no longer needed.
|
||||
|
|
@ -63,6 +64,17 @@ void initTestClientSession({bool withPersistentUser = false}) {
|
|||
});
|
||||
}
|
||||
|
||||
/// Creates an additional client database session in the shared test temporary
|
||||
/// directory and closes it when the current test completes.
|
||||
Future<ClientDatabaseSession> createAdditionalTestSession() async {
|
||||
final additionalSession = await _testClient.createSession(
|
||||
p.join(_tempDir.path, 'test-${++_additionalTestSessionCount}.db'),
|
||||
isDebugMode: true,
|
||||
);
|
||||
addTearDown(additionalSession.close);
|
||||
return additionalSession;
|
||||
}
|
||||
|
||||
Future<void> _initialize() async {
|
||||
_crdtSession = CrdtDatabaseSession.wraps(
|
||||
testSession,
|
||||
|
|
|
|||
Loading…
Reference in a new issue