mirror of
https://github.com/immich-app/immich.git
synced 2026-02-04 08:49:01 +03:00
feat: migrate store to sqlite (#21078)
* add store entity and migration * make store service take both isar and drift repos * migrate and switch store on beta timeline state change * chore: make drift variables final * dispose old store before switching repos * use store to update values for beta timeline * change log service to use the proper store * migrate store when beta already enabled * use isar repository to check beta timeline in store service * remove unused update method from store repo * dispose after create * change watchAll signature in store repo * fix test * rename init isar to initDB * request user to close and reopen on beta migration * fix tests * handle empty version in migration * wait for cache to be populated after migration --------- Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
@@ -64,12 +64,12 @@ void main() {
|
||||
|
||||
group("Log Service Set Level:", () {
|
||||
setUp(() async {
|
||||
when(() => mockStoreRepo.insert<int>(StoreKey.logLevel, any())).thenAnswer((_) async => true);
|
||||
when(() => mockStoreRepo.upsert<int>(StoreKey.logLevel, any())).thenAnswer((_) async => true);
|
||||
await sut.setLogLevel(LogLevel.shout);
|
||||
});
|
||||
|
||||
test('Updates the log level in store', () {
|
||||
final index = verify(() => mockStoreRepo.insert<int>(StoreKey.logLevel, captureAny())).captured.firstOrNull;
|
||||
final index = verify(() => mockStoreRepo.upsert<int>(StoreKey.logLevel, captureAny())).captured.firstOrNull;
|
||||
expect(index, LogLevel.shout.index);
|
||||
});
|
||||
|
||||
|
||||
@@ -16,11 +16,13 @@ final _kBackupFailedSince = DateTime.utc(2023);
|
||||
void main() {
|
||||
late StoreService sut;
|
||||
late IsarStoreRepository mockStoreRepo;
|
||||
late StreamController<StoreDto<Object>> controller;
|
||||
late DriftStoreRepository mockDriftStoreRepo;
|
||||
late StreamController<List<StoreDto<Object>>> controller;
|
||||
|
||||
setUp(() async {
|
||||
controller = StreamController<StoreDto<Object>>.broadcast();
|
||||
controller = StreamController<List<StoreDto<Object>>>.broadcast();
|
||||
mockStoreRepo = MockStoreRepository();
|
||||
mockDriftStoreRepo = MockDriftStoreRepository();
|
||||
// For generics, we need to provide fallback to each concrete type to avoid runtime errors
|
||||
registerFallbackValue(StoreKey.accessToken);
|
||||
registerFallbackValue(StoreKey.backupTriggerDelay);
|
||||
@@ -37,6 +39,16 @@ void main() {
|
||||
);
|
||||
when(() => mockStoreRepo.watchAll()).thenAnswer((_) => controller.stream);
|
||||
|
||||
when(() => mockDriftStoreRepo.getAll()).thenAnswer(
|
||||
(_) async => [
|
||||
const StoreDto(StoreKey.accessToken, _kAccessToken),
|
||||
const StoreDto(StoreKey.backgroundBackup, _kBackgroundBackup),
|
||||
const StoreDto(StoreKey.groupAssetsBy, _kGroupAssetsBy),
|
||||
StoreDto(StoreKey.backupFailedSince, _kBackupFailedSince),
|
||||
],
|
||||
);
|
||||
when(() => mockDriftStoreRepo.watchAll()).thenAnswer((_) => controller.stream);
|
||||
|
||||
sut = await StoreService.create(storeRepository: mockStoreRepo);
|
||||
});
|
||||
|
||||
@@ -58,7 +70,7 @@ void main() {
|
||||
|
||||
test('Listens to stream of store updates', () async {
|
||||
final event = StoreDto(StoreKey.accessToken, _kAccessToken.toUpperCase());
|
||||
controller.add(event);
|
||||
controller.add([event]);
|
||||
|
||||
await pumpEventQueue();
|
||||
|
||||
@@ -83,18 +95,19 @@ void main() {
|
||||
|
||||
group('Store Service put:', () {
|
||||
setUp(() {
|
||||
when(() => mockStoreRepo.insert<String>(any<StoreKey<String>>(), any())).thenAnswer((_) async => true);
|
||||
when(() => mockStoreRepo.upsert<String>(any<StoreKey<String>>(), any())).thenAnswer((_) async => true);
|
||||
when(() => mockDriftStoreRepo.upsert<String>(any<StoreKey<String>>(), any())).thenAnswer((_) async => true);
|
||||
});
|
||||
|
||||
test('Skip insert when value is not modified', () async {
|
||||
await sut.put(StoreKey.accessToken, _kAccessToken);
|
||||
verifyNever(() => mockStoreRepo.insert<String>(StoreKey.accessToken, any()));
|
||||
verifyNever(() => mockStoreRepo.upsert<String>(StoreKey.accessToken, any()));
|
||||
});
|
||||
|
||||
test('Insert value when modified', () async {
|
||||
final newAccessToken = _kAccessToken.toUpperCase();
|
||||
await sut.put(StoreKey.accessToken, newAccessToken);
|
||||
verify(() => mockStoreRepo.insert<String>(StoreKey.accessToken, newAccessToken)).called(1);
|
||||
verify(() => mockStoreRepo.upsert<String>(StoreKey.accessToken, newAccessToken)).called(1);
|
||||
expect(sut.tryGet(StoreKey.accessToken), newAccessToken);
|
||||
});
|
||||
});
|
||||
@@ -105,6 +118,7 @@ void main() {
|
||||
setUp(() {
|
||||
valueController = StreamController<String?>.broadcast();
|
||||
when(() => mockStoreRepo.watch<String>(any<StoreKey<String>>())).thenAnswer((_) => valueController.stream);
|
||||
when(() => mockDriftStoreRepo.watch<String>(any<StoreKey<String>>())).thenAnswer((_) => valueController.stream);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
@@ -129,6 +143,7 @@ void main() {
|
||||
group('Store Service delete:', () {
|
||||
setUp(() {
|
||||
when(() => mockStoreRepo.delete<String>(any<StoreKey<String>>())).thenAnswer((_) async => true);
|
||||
when(() => mockDriftStoreRepo.delete<String>(any<StoreKey<String>>())).thenAnswer((_) async => true);
|
||||
});
|
||||
|
||||
test('Removes the value from the DB', () async {
|
||||
@@ -145,6 +160,7 @@ void main() {
|
||||
group('Store Service clear:', () {
|
||||
setUp(() {
|
||||
when(() => mockStoreRepo.deleteAll()).thenAnswer((_) async => true);
|
||||
when(() => mockDriftStoreRepo.deleteAll()).thenAnswer((_) async => true);
|
||||
});
|
||||
|
||||
test('Clears all values from the store', () async {
|
||||
|
||||
5
mobile/test/drift/main/generated/schema.dart
generated
5
mobile/test/drift/main/generated/schema.dart
generated
@@ -10,6 +10,7 @@ import 'schema_v4.dart' as v4;
|
||||
import 'schema_v5.dart' as v5;
|
||||
import 'schema_v6.dart' as v6;
|
||||
import 'schema_v7.dart' as v7;
|
||||
import 'schema_v8.dart' as v8;
|
||||
|
||||
class GeneratedHelper implements SchemaInstantiationHelper {
|
||||
@override
|
||||
@@ -29,10 +30,12 @@ class GeneratedHelper implements SchemaInstantiationHelper {
|
||||
return v6.DatabaseAtV6(db);
|
||||
case 7:
|
||||
return v7.DatabaseAtV7(db);
|
||||
case 8:
|
||||
return v8.DatabaseAtV8(db);
|
||||
default:
|
||||
throw MissingSchemaException(version, versions);
|
||||
}
|
||||
}
|
||||
|
||||
static const versions = const [1, 2, 3, 4, 5, 6, 7];
|
||||
static const versions = const [1, 2, 3, 4, 5, 6, 7, 8];
|
||||
}
|
||||
|
||||
6663
mobile/test/drift/main/generated/schema_v8.dart
generated
Normal file
6663
mobile/test/drift/main/generated/schema_v8.dart
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -44,7 +44,7 @@ void main() {
|
||||
test('converts int', () async {
|
||||
int? version = await sut.tryGet(StoreKey.version);
|
||||
expect(version, isNull);
|
||||
await sut.insert(StoreKey.version, _kTestVersion);
|
||||
await sut.upsert(StoreKey.version, _kTestVersion);
|
||||
version = await sut.tryGet(StoreKey.version);
|
||||
expect(version, _kTestVersion);
|
||||
});
|
||||
@@ -52,7 +52,7 @@ void main() {
|
||||
test('converts string', () async {
|
||||
String? accessToken = await sut.tryGet(StoreKey.accessToken);
|
||||
expect(accessToken, isNull);
|
||||
await sut.insert(StoreKey.accessToken, _kTestAccessToken);
|
||||
await sut.upsert(StoreKey.accessToken, _kTestAccessToken);
|
||||
accessToken = await sut.tryGet(StoreKey.accessToken);
|
||||
expect(accessToken, _kTestAccessToken);
|
||||
});
|
||||
@@ -60,7 +60,7 @@ void main() {
|
||||
test('converts datetime', () async {
|
||||
DateTime? backupFailedSince = await sut.tryGet(StoreKey.backupFailedSince);
|
||||
expect(backupFailedSince, isNull);
|
||||
await sut.insert(StoreKey.backupFailedSince, _kTestBackupFailed);
|
||||
await sut.upsert(StoreKey.backupFailedSince, _kTestBackupFailed);
|
||||
backupFailedSince = await sut.tryGet(StoreKey.backupFailedSince);
|
||||
expect(backupFailedSince, _kTestBackupFailed);
|
||||
});
|
||||
@@ -68,7 +68,7 @@ void main() {
|
||||
test('converts bool', () async {
|
||||
bool? colorfulInterface = await sut.tryGet(StoreKey.colorfulInterface);
|
||||
expect(colorfulInterface, isNull);
|
||||
await sut.insert(StoreKey.colorfulInterface, _kTestColorfulInterface);
|
||||
await sut.upsert(StoreKey.colorfulInterface, _kTestColorfulInterface);
|
||||
colorfulInterface = await sut.tryGet(StoreKey.colorfulInterface);
|
||||
expect(colorfulInterface, _kTestColorfulInterface);
|
||||
});
|
||||
@@ -76,7 +76,7 @@ void main() {
|
||||
test('converts user', () async {
|
||||
UserDto? user = await sut.tryGet(StoreKey.currentUser);
|
||||
expect(user, isNull);
|
||||
await sut.insert(StoreKey.currentUser, _kTestUser);
|
||||
await sut.upsert(StoreKey.currentUser, _kTestUser);
|
||||
user = await sut.tryGet(StoreKey.currentUser);
|
||||
expect(user, _kTestUser);
|
||||
});
|
||||
@@ -108,10 +108,10 @@ void main() {
|
||||
await _populateStore(db);
|
||||
});
|
||||
|
||||
test('update()', () async {
|
||||
test('upsert()', () async {
|
||||
int? version = await sut.tryGet(StoreKey.version);
|
||||
expect(version, _kTestVersion);
|
||||
await sut.update(StoreKey.version, _kTestVersion + 10);
|
||||
await sut.upsert(StoreKey.version, _kTestVersion + 10);
|
||||
version = await sut.tryGet(StoreKey.version);
|
||||
expect(version, _kTestVersion + 10);
|
||||
});
|
||||
@@ -126,22 +126,29 @@ void main() {
|
||||
final stream = sut.watch(StoreKey.version);
|
||||
expectLater(stream, emitsInOrder([_kTestVersion, _kTestVersion + 10]));
|
||||
await pumpEventQueue();
|
||||
await sut.update(StoreKey.version, _kTestVersion + 10);
|
||||
await sut.upsert(StoreKey.version, _kTestVersion + 10);
|
||||
});
|
||||
|
||||
test('watchAll()', () async {
|
||||
final stream = sut.watchAll();
|
||||
expectLater(
|
||||
stream,
|
||||
emitsInAnyOrder([
|
||||
emits(const StoreDto<Object>(StoreKey.version, _kTestVersion)),
|
||||
emits(StoreDto<Object>(StoreKey.backupFailedSince, _kTestBackupFailed)),
|
||||
emits(const StoreDto<Object>(StoreKey.accessToken, _kTestAccessToken)),
|
||||
emits(const StoreDto<Object>(StoreKey.colorfulInterface, _kTestColorfulInterface)),
|
||||
emits(const StoreDto<Object>(StoreKey.version, _kTestVersion + 10)),
|
||||
emitsInOrder([
|
||||
[
|
||||
const StoreDto<Object>(StoreKey.version, _kTestVersion),
|
||||
StoreDto<Object>(StoreKey.backupFailedSince, _kTestBackupFailed),
|
||||
const StoreDto<Object>(StoreKey.accessToken, _kTestAccessToken),
|
||||
const StoreDto<Object>(StoreKey.colorfulInterface, _kTestColorfulInterface),
|
||||
],
|
||||
[
|
||||
const StoreDto<Object>(StoreKey.version, _kTestVersion + 10),
|
||||
StoreDto<Object>(StoreKey.backupFailedSince, _kTestBackupFailed),
|
||||
const StoreDto<Object>(StoreKey.accessToken, _kTestAccessToken),
|
||||
const StoreDto<Object>(StoreKey.colorfulInterface, _kTestColorfulInterface),
|
||||
],
|
||||
]),
|
||||
);
|
||||
await sut.update(StoreKey.version, _kTestVersion + 10);
|
||||
await sut.upsert(StoreKey.version, _kTestVersion + 10);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ import 'package:mocktail/mocktail.dart';
|
||||
|
||||
class MockStoreRepository extends Mock implements IsarStoreRepository {}
|
||||
|
||||
class MockDriftStoreRepository extends Mock implements DriftStoreRepository {}
|
||||
|
||||
class MockLogRepository extends Mock implements LogRepository {}
|
||||
|
||||
class MockIsarUserRepository extends Mock implements IsarUserRepository {}
|
||||
|
||||
Reference in New Issue
Block a user