import 'dart:convert'; import 'dart:io'; import 'package:flutter_test/flutter_test.dart'; import 'package:ikar/backend/modules/chats.dart'; import 'package:ikar/backend/modules/messages.dart'; import 'package:ikar/core/config/qlyra_settings.dart'; import 'package:ikar/core/media/deleted_media_keeper.dart'; import 'package:ikar/core/storage/app_database.dart'; import 'package:ikar/core/utils/media_cache.dart'; import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; class _SyntheticPathProvider extends PathProviderPlatform with MockPlatformInterfaceMixin { final String directory; _SyntheticPathProvider(this.directory); @override Future getApplicationSupportPath() async => directory; @override Future getTemporaryPath() async => directory; } const _accountId = 1; const _chatId = 900101; const _photoId = 555001; const _photoUrl = 'https://synthetic.invalid/photo/555001'; final _photoBytes = List.generate(64, (i) => i); Map _photoMessageRow(String id) => CachedMessage( id: id, accountId: _accountId, chatId: _chatId, senderId: 20, time: 1000, payload: { 'attaches': [ {'_type': 'PHOTO', 'photoId': _photoId, 'baseUrl': _photoUrl}, ], }, ).toDbRow(); Future?> _attachOf(String id) async { final row = await AppDatabase.loadMessage(_accountId, _chatId, id); final payload = jsonDecode(row!['payload'] as String) as Map; return Map.from((payload['attaches'] as List).first as Map); } void main() { TestWidgetsFlutterBinding.ensureInitialized(); setUp(() async { final directory = Directory.systemTemp.createTempSync( 'synthetic_deleted_media_test', ); PathProviderPlatform.instance = _SyntheticPathProvider(directory.path); MediaCache.resetForTesting(); addTearDown(() async { await AppDatabase.close(); MediaCache.resetForTesting(); QlyraSettings.viewDeleted.value = false; if (directory.existsSync()) directory.deleteSync(recursive: true); }); await AppDatabase.init(); await AppDatabase.saveProfile( ProfileData( id: _accountId, firstName: 'Synthetic owner', phone: 100000, country: 'ZZ', accountStatus: 0, updateTime: 1, ), ); await AppDatabase.saveChats([ CachedChat( id: _chatId, accountId: _accountId, type: 'DIALOG', title: 'Synthetic dialog', unreadCount: 0, lastEventTime: 0, cachedAt: 0, dontDisturbUntil: 0, isOnline: false, seenTime: 0, participants: const {_accountId: 0, 20: 0}, ).toDbRow()..['in_list'] = 1, ]); QlyraSettings.viewDeleted.value = true; }); test('a deleted photo keeps a pinned copy the payload points at', () async { await AppDatabase.saveMessages([_photoMessageRow('101')]); final downloaded = await MediaCache.fileFor('photo_$_photoId.jpg'); await downloaded.writeAsBytes(_photoBytes); await keepDeletedMedia(_accountId, _chatId, const ['101']); final attach = await _attachOf('101'); final kept = attach!['localPath'] as String?; expect(kept, isNotNull); expect(kept, contains(MediaCache.keptPrefix)); expect(await File(kept!).readAsBytes(), _photoBytes); }); test('without the setting nothing is copied', () async { QlyraSettings.viewDeleted.value = false; await AppDatabase.saveMessages([_photoMessageRow('102')]); final downloaded = await MediaCache.fileFor('photo_$_photoId.jpg'); await downloaded.writeAsBytes(_photoBytes); await keepDeletedMedia(_accountId, _chatId, const ['102']); expect((await _attachOf('102'))!['localPath'], isNull); }); test('media payload update cannot overwrite a concurrent edit', () async { final original = _photoMessageRow('104'); await AppDatabase.saveMessages([original]); await AppDatabase.saveMessages([ Map.from(original)..['text'] = 'edited' ..['payload'] = jsonEncode({'text': 'edited'}), ]); await AppDatabase.updateMessageMediaPayload( _accountId, _chatId, '104', original['payload'] as String, jsonEncode({'attaches': [], 'localPath': 'obsolete'}), ); final current = await AppDatabase.loadMessage(_accountId, _chatId, '104'); expect(current!['text'], 'edited'); expect(jsonDecode(current['payload'] as String), {'text': 'edited'}); }); test('a dead link without a cached copy leaves the payload alone', () async { await AppDatabase.saveMessages([_photoMessageRow('103')]); await keepDeletedMedia(_accountId, _chatId, const ['103']); expect((await _attachOf('103'))!['localPath'], isNull); }); }