From 0e987352bb1eaea6a3faaa8d99d69089b70636f3 Mon Sep 17 00:00:00 2001 From: shenlong <139912620+shenlong-tanwen@users.noreply.github.com> Date: Wed, 17 Sep 2025 23:50:43 +0530 Subject: [PATCH] fix: do not migrate existing users (#22146) fix: do not migrate if already on 15+ Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> --- mobile/lib/utils/migration.dart | 56 ++++---- mobile/test/modules/utils/migration_test.dart | 131 ++++++++++++++++++ 2 files changed, 163 insertions(+), 24 deletions(-) create mode 100644 mobile/test/modules/utils/migration_test.dart diff --git a/mobile/lib/utils/migration.dart b/mobile/lib/utils/migration.dart index f4f0564425..1be8647e3d 100644 --- a/mobile/lib/utils/migration.dart +++ b/mobile/lib/utils/migration.dart @@ -62,30 +62,7 @@ Future migrateDatabaseIfNeeded(Isar db, Drift drift) async { await Store.populateCache(); } - // Handle migration only for this version - // TODO: remove when old timeline is removed - final needBetaMigration = Store.tryGet(StoreKey.needBetaMigration); - if (version >= 15 && needBetaMigration == null) { - // Check both databases directly instead of relying on cache - - final isBeta = Store.tryGet(StoreKey.betaTimeline); - final isNewInstallation = await _isNewInstallation(db, drift); - - // For new installations, no migration needed - // For existing installations, only migrate if beta timeline is not enabled (null or false) - if (isNewInstallation || isBeta == true || (version > 15 && isBeta == null)) { - await Store.put(StoreKey.needBetaMigration, false); - await Store.put(StoreKey.betaTimeline, true); - } else { - await drift.reset(); - await Store.put(StoreKey.needBetaMigration, true); - } - } - - if (version < 16) { - await SyncStreamRepository(drift).reset(); - await Store.put(StoreKey.shouldResetSync, true); - } + await handleBetaMigration(version, await _isNewInstallation(db, drift), SyncStreamRepository(drift)); if (targetVersion >= 12) { await Store.put(StoreKey.version, targetVersion); @@ -99,6 +76,37 @@ Future migrateDatabaseIfNeeded(Isar db, Drift drift) async { } } +Future handleBetaMigration(int version, bool isNewInstallation, SyncStreamRepository syncStreamRepository) async { + // Handle migration only for this version + // TODO: remove when old timeline is removed + final isBeta = Store.tryGet(StoreKey.betaTimeline); + final needBetaMigration = Store.tryGet(StoreKey.needBetaMigration); + if (version <= 15 && needBetaMigration == null) { + // For new installations, no migration needed + // For existing installations, only migrate if beta timeline is not enabled (null or false) + if (isNewInstallation || isBeta == true) { + await Store.put(StoreKey.needBetaMigration, false); + await Store.put(StoreKey.betaTimeline, true); + } else { + await Store.put(StoreKey.needBetaMigration, true); + } + } + + if (version > 15) { + if (isBeta == null || isBeta) { + await Store.put(StoreKey.needBetaMigration, false); + await Store.put(StoreKey.betaTimeline, true); + } else { + await Store.put(StoreKey.needBetaMigration, false); + } + } + + if (version < 16) { + await syncStreamRepository.reset(); + await Store.put(StoreKey.shouldResetSync, true); + } +} + Future _isNewInstallation(Isar db, Drift drift) async { try { final isarUserCount = await db.users.count(); diff --git a/mobile/test/modules/utils/migration_test.dart b/mobile/test/modules/utils/migration_test.dart new file mode 100644 index 0000000000..08ab1204a6 --- /dev/null +++ b/mobile/test/modules/utils/migration_test.dart @@ -0,0 +1,131 @@ +import 'package:drift/drift.dart' hide isNull; +import 'package:drift/native.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/domain/services/store.service.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart'; +import 'package:immich_mobile/utils/migration.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../../infrastructure/repository.mock.dart'; + +void main() { + late Drift db; + late SyncStreamRepository mockSyncStreamRepository; + + setUpAll(() async { + db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true)); + await StoreService.init(storeRepository: DriftStoreRepository(db)); + mockSyncStreamRepository = MockSyncStreamRepository(); + when(() => mockSyncStreamRepository.reset()).thenAnswer((_) async => {}); + }); + + tearDown(() async { + await Store.clear(); + }); + + group('handleBetaMigration Tests', () { + group("version < 15", () { + test('already on new timeline', () async { + await Store.put(StoreKey.betaTimeline, true); + + await handleBetaMigration(14, false, mockSyncStreamRepository); + + expect(Store.tryGet(StoreKey.betaTimeline), true); + expect(Store.tryGet(StoreKey.needBetaMigration), false); + }); + + test('already on old timeline', () async { + await Store.put(StoreKey.betaTimeline, false); + + await handleBetaMigration(14, false, mockSyncStreamRepository); + + expect(Store.tryGet(StoreKey.needBetaMigration), true); + }); + + test('fresh install', () async { + await Store.delete(StoreKey.betaTimeline); + await handleBetaMigration(14, true, mockSyncStreamRepository); + + expect(Store.tryGet(StoreKey.betaTimeline), true); + expect(Store.tryGet(StoreKey.needBetaMigration), false); + }); + }); + + group("version == 15", () { + test('already on new timeline', () async { + await Store.put(StoreKey.betaTimeline, true); + + await handleBetaMigration(15, false, mockSyncStreamRepository); + + expect(Store.tryGet(StoreKey.betaTimeline), true); + expect(Store.tryGet(StoreKey.needBetaMigration), false); + }); + + test('already on old timeline', () async { + await Store.put(StoreKey.betaTimeline, false); + + await handleBetaMigration(15, false, mockSyncStreamRepository); + + expect(Store.tryGet(StoreKey.needBetaMigration), true); + }); + + test('fresh install', () async { + await Store.delete(StoreKey.betaTimeline); + await handleBetaMigration(15, true, mockSyncStreamRepository); + + expect(Store.tryGet(StoreKey.betaTimeline), true); + expect(Store.tryGet(StoreKey.needBetaMigration), false); + }); + }); + + group("version > 15", () { + test('already on new timeline', () async { + await Store.put(StoreKey.betaTimeline, true); + + await handleBetaMigration(16, false, mockSyncStreamRepository); + + expect(Store.tryGet(StoreKey.betaTimeline), true); + expect(Store.tryGet(StoreKey.needBetaMigration), false); + }); + + test('already on old timeline', () async { + await Store.put(StoreKey.betaTimeline, false); + + await handleBetaMigration(16, false, mockSyncStreamRepository); + + expect(Store.tryGet(StoreKey.betaTimeline), false); + expect(Store.tryGet(StoreKey.needBetaMigration), false); + }); + + test('fresh install', () async { + await Store.delete(StoreKey.betaTimeline); + await handleBetaMigration(16, true, mockSyncStreamRepository); + + expect(Store.tryGet(StoreKey.betaTimeline), true); + expect(Store.tryGet(StoreKey.needBetaMigration), false); + }); + }); + }); + + group('sync reset tests', () { + test('version < 16', () async { + await Store.put(StoreKey.shouldResetSync, false); + + await handleBetaMigration(15, false, mockSyncStreamRepository); + + expect(Store.tryGet(StoreKey.shouldResetSync), true); + }); + + test('version >= 16', () async { + await Store.put(StoreKey.shouldResetSync, false); + + await handleBetaMigration(16, false, mockSyncStreamRepository); + + expect(Store.tryGet(StoreKey.shouldResetSync), false); + }); + }); +}