From 6adef4643ad5c18e15f4f08b11264b3cd7c10efc Mon Sep 17 00:00:00 2001 From: Timm Preetz Date: Tue, 19 Nov 2024 06:13:28 +0100 Subject: [PATCH] Add test for migrations --- CHANGELOG.md | 4 + example/pubspec.lock | 2 +- lib/src/indexed_entity_database.dart | 12 +-- pubspec.yaml | 2 +- test/migrations_test.dart | 106 +++++++++++++++++++++++++++ 5 files changed, 119 insertions(+), 7 deletions(-) create mode 100644 test/migrations_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index be41436..19e1eb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.0.1 + +* Add tests for internal schema migrations + ## 2.0.0 * Update method names diff --git a/example/pubspec.lock b/example/pubspec.lock index 0485cb3..f386325 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -121,7 +121,7 @@ packages: path: ".." relative: true source: path - version: "2.0.0" + version: "2.0.1" leak_tracker: dependency: transitive description: diff --git a/lib/src/indexed_entity_database.dart b/lib/src/indexed_entity_database.dart index 1702400..84b2f4c 100644 --- a/lib/src/indexed_entity_database.dart +++ b/lib/src/indexed_entity_database.dart @@ -15,6 +15,7 @@ class IndexedEntityDabase { IndexedEntityDabase._( String path, { + /// Up to which version the schema migrations should be run required int targetSchemaVersion, }) : _database = sqlite3.open(path) { final res = _database.select( @@ -26,19 +27,19 @@ class IndexedEntityDabase { _initialDBSetup(); } - if (_dbVersion < targetSchemaVersion) { + if (dbVersion < 2 && targetSchemaVersion >= 2) { _v2Migration(); } - if (_dbVersion < targetSchemaVersion) { + if (dbVersion < 3 && targetSchemaVersion >= 3) { _v3Migration(); } - if (_dbVersion < targetSchemaVersion) { + if (dbVersion < 4 && targetSchemaVersion >= 4) { _v4Migration(); } - assert(_dbVersion == targetSchemaVersion); + assert(dbVersion == targetSchemaVersion); // Foreign keys need to be re-enable on every open (session) // https://www.sqlite.org/foreignkeys.html#fk_enable @@ -80,7 +81,8 @@ class IndexedEntityDabase { ); } - int get _dbVersion => _database.select( + @visibleForTesting + int get dbVersion => _database.select( 'SELECT `value` FROM `metadata` WHERE `key` = ?', ['version'], ).single['value'] as int; diff --git a/pubspec.yaml b/pubspec.yaml index 08656f8..f8e41a0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: indexed_entity_store description: A fast, simple, and synchronous entity store for Flutter applications. -version: 2.0.0 +version: 2.0.1 repository: https://github.com/LunaONE/indexed_entity_store environment: diff --git a/test/migrations_test.dart b/test/migrations_test.dart new file mode 100644 index 0000000..04d4cc9 --- /dev/null +++ b/test/migrations_test.dart @@ -0,0 +1,106 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:indexed_entity_store/indexed_entity_store.dart'; + +void main() { + WidgetsFlutterBinding.ensureInitialized(); + + test('Database migrations', () async { + final path = '/tmp/index_entity_store_test_${FlutterTimeline.now}.sqlite3'; + + // v1 + { + final db = IndexedEntityDabase.open(path, targetSchemaVersion: 1); + + expect(db.dbVersion, 1); + + // can not open the store with the current code version, as index format differs + + db.dispose(); + } + + // v2 + { + final db = IndexedEntityDabase.open(path, targetSchemaVersion: 2); + + expect(db.dbVersion, 2); + + // can not open the store with the current code version, as index format differs + + db.dispose(); + } + + // v3 + { + final db = IndexedEntityDabase.open(path, targetSchemaVersion: 3); + + expect(db.dbVersion, 3); + + // can not open the store with the current code version, as index format differs + + db.dispose(); + } + + // v4 + { + final db = IndexedEntityDabase.open(path, targetSchemaVersion: 4); + + expect(db.dbVersion, 4); + + final fooStore = db.entityStore(fooConnector); + + // The entity storage did not change, so we can use the normal write method to handle this + fooStore.write(_FooEntity(id: 1, value: 'some value')); + + expect( + fooStore.readOnce(1), + isA<_FooEntity>().having((f) => f.value, 'value', 'some value'), + ); + + db.dispose(); + } + + File(path).deleteSync(); + }); +} + +class _FooEntity { + _FooEntity({ + required this.id, + required this.value, + }); + + final int id; + + final String value; + + Map toJSON() { + return { + 'id': id, + 'value': value, + }; + } + + static _FooEntity fromJSON(Map json) { + return _FooEntity( + id: json['id'], + value: json['value'], + ); + } +} + +final fooConnector = IndexedEntityConnector<_FooEntity, int, String>( + entityKey: 'foo', + getPrimaryKey: (f) => f.id, + getIndices: (index) { + index((e) => e.value, as: 'value'); + }, + serialize: (f) => jsonEncode(f.toJSON()), + deserialize: (s) => _FooEntity.fromJSON( + jsonDecode(s) as Map, + ), +);