refactor: Improve code organization and reutilization

This commit is contained in:
Marcelo Soares 2026-01-25 00:56:41 -03:00
commit eadf1f7d7c
No known key found for this signature in database
GPG key ID: CB2F09CE66E99EAD
5 changed files with 291 additions and 137 deletions

View file

@ -0,0 +1,94 @@
import 'package:drift/drift.dart' hide isNotNull, isNull;
import 'package:drift_offline_first/drift_offline_first.dart';
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 {
createdData = TableWithEveryColumnTypeData(
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'),
);
(_, crdtDb, _) = database.crdtContext;
testHlc = Hlc.now(crdtDb.nodeId);
});
group('when converting the data to CRDT data entries', () {
late Iterable<CrdtDataEntry> entries;
setUp(() async {
entries = createdData.toCrdtDataEntry(crdtDb, testHlc).toList();
});
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 {
final entriesMap = {
for (final entry in entries) entry.columnName: entry.rawValue,
};
expect(entriesMap['a_bool'], equals(createdData.aBool?.driftAny));
expect(entriesMap['a_date_time'], isNotNull);
expect(entriesMap['a_text'], equals(createdData.aText?.driftAny));
expect(entriesMap['an_int'], equals(createdData.anInt?.driftAny));
expect(entriesMap['an_int64'], equals(createdData.anInt64?.driftAny));
expect(entriesMap['a_real'], equals(createdData.aReal?.driftAny));
expect(entriesMap['a_blob'], equals(createdData.aBlob?.driftAny));
expect(
entriesMap['an_int_enum'],
equals(createdData.anIntEnum?.index.driftAny),
);
expect(
// aTextWithConverter uses 'insert' as column name
entriesMap['insert']?.rawSqlValue,
equals(const DriftAny({'data': 'test'}).rawSqlValue),
);
expect(entriesMap['a_uuid'], createdData.aUuid?.driftAny);
});
});
});
}
extension on Object {
DriftAny? get driftAny => DriftAny(this);
}

View file

@ -0,0 +1,81 @@
import 'package:drift/drift.dart' hide isNotNull, isNull;
import 'package:drift_offline_first/drift_offline_first.dart';
import 'package:test/test.dart';
import '../utils/crdt_context.dart';
import '../utils/database.dart';
import '../utils/tables.dart';
void main() {
group('Given CRDT database for a database with data with every column type', () {
late CrdtDatabase crdtDb;
late String rowId;
late TableWithEveryColumnTypeData createdData;
setUp(() async {
final createdRowId = await database.managers.tableWithEveryColumnType.create(
(t) => t(
aBool: const Value(true),
aDateTime: Value(DateTime.now()),
aText: const Value('test'),
anInt: const Value(1),
anInt64: Value(BigInt.one),
aReal: const Value(1.0),
aBlob: Value(Uint8List.fromList([1, 2, 3])),
anIntEnum: const Value(TodoStatus.open),
aTextWithConverter: Value(MyCustomObject('test')),
aUuid: Value(UuidValue.fromString('123e4567-e89b-12d3-a456-426614174000')),
),
);
(_, crdtDb, _) = database.crdtContext;
rowId = createdRowId.toString();
createdData = await database.managers.tableWithEveryColumnType.getSingle();
});
test(
'when getting the data from the CRDT data table of the CRDT database '
'then it rebuilds the data perfectly.', () async {
final data = await crdtDb.getSingleFromCrdtData<TableWithEveryColumnTypeData>(
tableName: 'table_with_every_column_type',
rowId: rowId,
);
expect(data, equals(createdData));
});
test(
'when trying to get the data from the CRDT data table with a non-existent row id '
'then it returns null.', () async {
final data = await crdtDb.getSingleFromCrdtData<TableWithEveryColumnTypeData>(
tableName: 'table_with_every_column_type',
rowId: 'non-existent-row-id',
);
expect(data, isNull);
});
test(
'when trying to get the data from the CRDT data table with multiple row ids '
'then it returns the data for the existing row ids.', () async {
final data = await crdtDb.getFromCrdtData<TableWithEveryColumnTypeData>(
tableName: 'table_with_every_column_type',
rowIds: [rowId, 'non-existent-row-id'],
);
expect(data, equals([createdData]));
});
test(
'when trying to get the data from the CRDT data table for a non-synchronized table '
'then it throws an error.', () async {
expect(
() => crdtDb.getFromCrdtData<TableWithEveryColumnTypeData>(
tableName: 'table_without_pk',
rowIds: [rowId],
),
throwsA(isA<ArgumentError>()),
);
});
});
}

View file

@ -1,4 +1,3 @@
import 'package:drift/drift.dart' hide isNotNull, isNull;
import 'package:drift_offline_first/drift_offline_first.dart';
import 'package:test/test.dart';
@ -23,7 +22,7 @@ void main() {
targetDate: DateTime.now(),
);
changeset = todo.toCrdtDataEntry(nodeId);
changeset = todo.toCrdtDataEntry(crdtDb, Hlc.now(nodeId));
});
test(
@ -50,10 +49,10 @@ void main() {
targetDate: DateTime.now(),
);
firstChangeset = todo.toCrdtDataEntry(nodeId);
firstChangeset = todo.toCrdtDataEntry(crdtDb, Hlc.now(nodeId));
await crdt.merge(firstChangeset);
lateOldChangeset = todo.toCrdtDataEntry(nodeId, hlc: Hlc.zero(nodeId));
lateOldChangeset = todo.toCrdtDataEntry(crdtDb, Hlc.zero(nodeId));
});
test(
@ -80,11 +79,12 @@ void main() {
targetDate: DateTime.now(),
);
firstChangeset = todo.toCrdtDataEntry(nodeId);
firstChangeset = todo.toCrdtDataEntry(crdtDb, Hlc.now(nodeId));
await crdt.merge(firstChangeset);
newerChangeset =
todo.toCrdtDataEntry(nodeId).where((e) => e.columnName == 'content');
newerChangeset = todo
.toCrdtDataEntry(crdtDb, Hlc.now(nodeId))
.where((e) => e.columnName == 'content');
});
test(
@ -101,26 +101,3 @@ void main() {
});
});
}
extension on TodoEntry {
Iterable<CrdtDataEntry> toCrdtDataEntry(String nodeId, {Hlc? hlc}) {
final hlcTimestamp = hlc ?? Hlc.now(nodeId);
return database.todosTable.$columns.map(
(c) => CrdtDataEntry(
userId: 'test',
tblName: 'todos',
columnName: c.$name,
rowId: id.toString(),
rawValue: (toJson()[c.$name] as Object?)?.toDriftAny(),
hlcTimestamp: hlcTimestamp,
),
);
}
}
extension on Object {
DriftAny? toDriftAny() {
return DriftAny(this);
}
}

View file

@ -1,4 +1,3 @@
import 'package:drift/drift.dart' hide isNotNull, isNull;
import 'package:drift_offline_first/drift_offline_first.dart';
import 'package:test/test.dart';
import 'package:uuid/uuid.dart';
@ -26,7 +25,7 @@ void main() {
targetDate: DateTime.now(),
);
changeset = todo.toCrdtDataEntry(otherNodeId);
changeset = todo.toCrdtDataEntry(crdtDb, Hlc.now(otherNodeId));
lastHlc = Hlc.now(otherNodeId);
});
@ -66,11 +65,11 @@ void main() {
);
lastHlc = Hlc.now(otherNodeId);
firstChangeset = todo.toCrdtDataEntry(otherNodeId, lastHlc);
firstChangeset = todo.toCrdtDataEntry(crdtDb, lastHlc);
await crdt.saveChangeset(firstChangeset, [lastHlc]);
lateOldHlc = Hlc.zero(otherNodeId);
lateOldChangeset = todo.toCrdtDataEntry(otherNodeId, lateOldHlc);
lateOldChangeset = todo.toCrdtDataEntry(crdtDb, lateOldHlc);
});
group('when saving a changeset with older HLC', () {
@ -109,12 +108,12 @@ void main() {
);
lastHlc = Hlc.now(otherNodeId);
firstChangeset = todo.toCrdtDataEntry(otherNodeId);
firstChangeset = todo.toCrdtDataEntry(crdtDb, lastHlc);
await crdt.saveChangeset(firstChangeset, [lastHlc]);
newerHlc = Hlc.now(otherNodeId);
newerChangeset = todo
.toCrdtDataEntry(otherNodeId, newerHlc)
.toCrdtDataEntry(crdtDb, newerHlc)
.where((e) => e.columnName == 'content');
});
@ -139,26 +138,3 @@ void main() {
});
});
}
extension on TodoEntry {
Iterable<CrdtDataEntry> toCrdtDataEntry(String nodeId, [Hlc? hlc]) {
final hlcTimestamp = hlc ?? Hlc.now(nodeId);
return database.todosTable.$columns.map(
(c) => CrdtDataEntry(
userId: 'test',
tblName: 'todos',
columnName: c.$name,
rowId: id.toString(),
rawValue: (toJson()[c.$name] as Object?)?.toDriftAny(),
hlcTimestamp: hlcTimestamp,
),
);
}
}
extension on Object {
DriftAny? toDriftAny() {
return DriftAny(this);
}
}

View file

@ -16,7 +16,7 @@ void main() {
late List<String> triggers;
setUp(() async {
final generator = Sqlite3OfflineSyncTriggers(migrator);
final generator = Sqlite3OfflineSyncTriggers(migrator.crdtDb);
triggers = generator.generateCreateTriggerStatements(database.todosTable);
});