2026-01-25 00:56:41 -03:00
|
|
|
import 'package:drift/drift.dart' hide isNotNull, isNull;
|
2026-01-26 00:34:08 -03:00
|
|
|
import 'package:drift_offline_sync/drift_offline_sync.dart';
|
2026-01-25 00:56:41 -03:00
|
|
|
import 'package:test/test.dart';
|
|
|
|
|
|
|
|
|
|
import '../utils/crdt_context.dart';
|
|
|
|
|
import '../utils/database.dart';
|
|
|
|
|
import '../utils/tables.dart';
|
|
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
|
group('Given a data object with every column type', () {
|
|
|
|
|
late CrdtDatabase crdtDb;
|
|
|
|
|
late TableWithEveryColumnTypeData createdData;
|
|
|
|
|
late Hlc testHlc;
|
|
|
|
|
|
|
|
|
|
setUp(() async {
|
2026-01-25 22:52:16 -03:00
|
|
|
createdData = _createTestData();
|
2026-01-25 00:56:41 -03:00
|
|
|
(_, crdtDb, _) = database.crdtContext;
|
|
|
|
|
testHlc = Hlc.now(crdtDb.nodeId);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
group('when converting the data to CRDT data entries', () {
|
|
|
|
|
late Iterable<CrdtDataEntry> entries;
|
|
|
|
|
|
|
|
|
|
setUp(() async {
|
2026-01-25 22:52:16 -03:00
|
|
|
entries = crdtDb.convertToCrdtDataEntry(createdData, testHlc);
|
2026-01-25 00:56:41 -03:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('then it creates an entry for each column.', () async {
|
|
|
|
|
expect(
|
|
|
|
|
entries.map((e) => e.columnName).toSet(),
|
|
|
|
|
equals(
|
|
|
|
|
database.tableWithEveryColumnType.$columns.map((c) => c.$name).toSet(),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('then each entry has the correct user ID.', () async {
|
|
|
|
|
expect(entries.map((e) => e.userId), everyElement(equals(crdtDb.userId)));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('then each entry has the correct table name.', () async {
|
|
|
|
|
expect(
|
|
|
|
|
entries.map((e) => e.tblName),
|
|
|
|
|
everyElement(equals('table_with_every_column_type')),
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('then each entry has the correct HLC timestamp.', () async {
|
|
|
|
|
expect(entries.map((e) => e.hlcTimestamp), everyElement(equals(testHlc)));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('then all entries have expected column names and values.', () async {
|
2026-01-25 23:23:47 -03:00
|
|
|
final mappedData = await database.tableWithEveryColumnType.fromCrdtDataEntries(
|
|
|
|
|
entries,
|
|
|
|
|
);
|
2026-01-25 22:52:16 -03:00
|
|
|
expect(mappedData.single, equals(createdData));
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
group('Given a changeset converted from a data object with every column type', () {
|
|
|
|
|
late OfflineSyncCrdt crdt;
|
|
|
|
|
late CrdtDatabase crdtDb;
|
|
|
|
|
late TableWithEveryColumnTypeData createdData;
|
|
|
|
|
late List<CrdtDataEntry> expectedChangeset;
|
|
|
|
|
late Hlc testHlc;
|
|
|
|
|
|
|
|
|
|
setUp(() async {
|
|
|
|
|
createdData = _createTestData();
|
|
|
|
|
(crdt, crdtDb, _) = database.crdtContext;
|
|
|
|
|
|
|
|
|
|
// Perform an insert to the database to get the exact changeset that would
|
|
|
|
|
// be generated by the CRDT triggers.
|
|
|
|
|
await database.tableWithEveryColumnType.insertOne(createdData);
|
2026-01-27 15:31:55 -03:00
|
|
|
expectedChangeset = await crdtDb.managers.crdtDataTable
|
|
|
|
|
.filter(
|
|
|
|
|
(f) => f.columnName.equals(crdtDb.sqlBuilder.isDeletedColumnName).not(),
|
|
|
|
|
)
|
|
|
|
|
.get();
|
2026-01-25 22:52:16 -03:00
|
|
|
testHlc = expectedChangeset.first.hlcTimestamp;
|
|
|
|
|
await crdtDb.crdtDataTable.deleteAll();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
group('when saving the changeset to the CRDT database', () {
|
|
|
|
|
late Iterable<CrdtDataEntry> entries;
|
|
|
|
|
|
|
|
|
|
setUp(() async {
|
|
|
|
|
entries = crdtDb.convertToCrdtDataEntry(createdData, testHlc);
|
|
|
|
|
await crdt.saveChangeset(entries, [testHlc]);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('then the saved data is equal to the generated by CRDT triggers.', () async {
|
|
|
|
|
final savedChangeset = await crdtDb.managers.crdtDataTable.get();
|
|
|
|
|
|
|
|
|
|
// The list object is not equal to the other objects because it is a
|
|
|
|
|
// different instance. So we remove it from the list and compare the
|
|
|
|
|
// it separately.
|
|
|
|
|
final savedListObject = savedChangeset.removeAt(7);
|
|
|
|
|
final expectedListObject = expectedChangeset.removeAt(7);
|
|
|
|
|
|
|
|
|
|
expect(savedChangeset, equals(expectedChangeset));
|
2026-01-25 00:56:41 -03:00
|
|
|
expect(
|
2026-01-25 22:52:16 -03:00
|
|
|
savedListObject.unwrappedValue,
|
|
|
|
|
equals(expectedListObject.unwrappedValue),
|
2026-01-25 00:56:41 -03:00
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-25 22:52:16 -03:00
|
|
|
TableWithEveryColumnTypeData _createTestData() => TableWithEveryColumnTypeData(
|
2026-01-25 23:23:47 -03:00
|
|
|
id: const RowId(1),
|
|
|
|
|
aBool: true,
|
|
|
|
|
aDateTime: DateTime.now(),
|
|
|
|
|
aText: 'test',
|
|
|
|
|
anInt: 1,
|
|
|
|
|
anInt64: BigInt.one,
|
|
|
|
|
aReal: 1.0,
|
|
|
|
|
aBlob: Uint8List.fromList([1, 2, 3]),
|
|
|
|
|
anIntEnum: TodoStatus.open,
|
|
|
|
|
aTextWithConverter: MyCustomObject('test'),
|
|
|
|
|
aUuid: UuidValue.fromString('123e4567-e89b-12d3-a456-426614174000'),
|
|
|
|
|
);
|