diff --git a/app/test/entity/file/data_source_test.dart b/app/test/entity/file/data_source2_test.dart similarity index 66% rename from app/test/entity/file/data_source_test.dart rename to app/test/entity/file/data_source2_test.dart index 4c3cab2f..8f47911f 100644 --- a/app/test/entity/file/data_source_test.dart +++ b/app/test/entity/file/data_source2_test.dart @@ -1,7 +1,8 @@ import 'package:nc_photos/db/entity_converter.dart'; import 'package:nc_photos/di_container.dart'; import 'package:nc_photos/entity/file.dart'; -import 'package:nc_photos/entity/file/data_source.dart'; +import 'package:nc_photos/entity/file/data_source2.dart'; +import 'package:nc_photos/entity/file_util.dart' as file_util; import 'package:np_collection/np_collection.dart'; import 'package:np_common/or_null.dart'; import 'package:np_db_sqlite/np_db_sqlite_compat.dart' as compat; @@ -10,9 +11,13 @@ import 'package:test/test.dart'; import '../../test_util.dart' as util; void main() { - group("FileSqliteDbDataSource", () { - test("list", _list); - test("listSingle", _listSingle); + group("FileNpDbDataSource", () { + group("getFileDescriptors", () { + test("normal", _getFileDescriptors); + test("multiple account", _getFileDescriptorsMultipleAccount); + test("share folder", _getFileDescriptorsShareFolder); + test("extra share folder", _getFileDescriptorsExtraShareFolder); + }); group("remove", () { test("file", _removeFile); test("empty dir", _removeEmptyDir); @@ -28,14 +33,11 @@ void main() { }); } -/// List a dir +/// Return files of an account /// /// Files: admin/test1.jpg, admin/test/test2.jpg -/// List: admin -/// Expect: admin/test1.jpg -/// List: admin/test -/// Expect: admin/test/test2.jpg -Future _list() async { +/// Expect: admin/test1.jpg, admin/test/test2.jpg +Future _getFileDescriptors() async { final account = util.buildAccount(); final files = (util.FilesBuilder() ..addDir("admin") @@ -55,17 +57,77 @@ Future _list() async { await util.insertDirRelation(c.sqliteDb, account, files[2], [files[3]]); }); - final src = FileSqliteDbDataSource(c); - expect(await src.list(account, files[0]), files.slice(0, 3)); - expect(await src.list(account, files[2]), files.slice(2, 4)); + final src = FileNpDbDataSource(c.npDb); + expect( + await src + .getFileDescriptors(account, file_util.unstripPath(account, "")) + .last, + [files[3].toDescriptor(), files[1].toDescriptor()], + ); } -/// List a single dir +/// Return files of an account /// -/// Expect: throw UnimplementedError -Future _listSingle() async { +/// Files: admin/test1.jpg, admin/test/test2.jpg, user1/test3.jpg +/// Expect: admin/test1.jpg, admin/test/test2.jpg +Future _getFileDescriptorsMultipleAccount() async { final account = util.buildAccount(); - final files = (util.FilesBuilder()..addDir("admin")).build(); + final user1Account = util.buildAccount(userId: "user1"); + final files = (util.FilesBuilder() + ..addDir("admin") + ..addJpeg("admin/test1.jpg") + ..addDir("admin/test") + ..addJpeg("admin/test/test2.jpg")) + .build(); + final user1Files = (util.FilesBuilder(initialFileId: files.length) + ..addDir("user1", ownerId: "user1") + ..addJpeg("user1/test3.jpg")) + .build(); + final c = DiContainer( + npDb: util.buildTestDb(), + ); + addTearDown(() => c.sqliteDb.close()); + await c.sqliteDb.transaction(() async { + await c.sqliteDb.insertAccounts([account.toDb()]); + await c.sqliteDb.insertAccounts([user1Account.toDb()]); + await util.insertFiles(c.sqliteDb, account, files); + await util.insertDirRelation( + c.sqliteDb, account, files[0], files.slice(1, 3)); + await util.insertDirRelation(c.sqliteDb, account, files[2], [files[3]]); + await util.insertFiles(c.sqliteDb, user1Account, user1Files); + await util.insertDirRelation( + c.sqliteDb, user1Account, user1Files[0], [user1Files[1]]); + }); + + final src = FileNpDbDataSource(c.npDb); + expect( + await src + .getFileDescriptors(account, file_util.unstripPath(account, "")) + .last, + [files[3].toDescriptor(), files[1].toDescriptor()], + ); + expect( + await src + .getFileDescriptors( + user1Account, file_util.unstripPath(user1Account, "")) + .last, + [user1Files[1].toDescriptor()], + ); +} + +/// Return files of an account +/// +/// Files: admin/test1/test1.jpg, admin/test2/test2.jpg +/// Expect: admin/test1/test1.jpg +Future _getFileDescriptorsShareFolder() async { + final account = util.buildAccount(roots: ["test1"]); + final files = (util.FilesBuilder() + ..addDir("admin") + ..addDir("admin/test1") + ..addJpeg("admin/test1/test1.jpg") + ..addDir("admin/test2") + ..addJpeg("admin/test2/test2.jpg")) + .build(); final c = DiContainer( npDb: util.buildTestDb(), ); @@ -73,12 +135,54 @@ Future _listSingle() async { await c.sqliteDb.transaction(() async { await c.sqliteDb.insertAccounts([account.toDb()]); await util.insertFiles(c.sqliteDb, account, files); - await util.insertDirRelation(c.sqliteDb, account, files[0], const []); + await util + .insertDirRelation(c.sqliteDb, account, files[0], [files[1], files[3]]); + await util.insertDirRelation(c.sqliteDb, account, files[1], [files[2]]); + await util.insertDirRelation(c.sqliteDb, account, files[3], [files[4]]); }); - final src = FileSqliteDbDataSource(c); - expect(() async => await src.listSingle(account, files[0]), - throwsUnimplementedError); + final src = FileNpDbDataSource(c.npDb); + expect( + await src + .getFileDescriptors(account, file_util.unstripPath(account, "test1")) + .last, + [files[2].toDescriptor()], + ); +} + +/// Return files of an account +/// +/// Files: admin/test1/test1.jpg, admin/test2/test2.jpg +/// Expect: admin/test1/test1.jpg, admin/test2/test2.jpg +Future _getFileDescriptorsExtraShareFolder() async { + final account = util.buildAccount(roots: ["test1"]); + final files = (util.FilesBuilder() + ..addDir("admin") + ..addDir("admin/test1") + ..addJpeg("admin/test1/test1.jpg") + ..addDir("admin/test2") + ..addJpeg("admin/test2/test2.jpg")) + .build(); + final c = DiContainer( + npDb: util.buildTestDb(), + ); + addTearDown(() => c.sqliteDb.close()); + await c.sqliteDb.transaction(() async { + await c.sqliteDb.insertAccounts([account.toDb()]); + await util.insertFiles(c.sqliteDb, account, files); + await util + .insertDirRelation(c.sqliteDb, account, files[0], [files[1], files[3]]); + await util.insertDirRelation(c.sqliteDb, account, files[1], [files[2]]); + await util.insertDirRelation(c.sqliteDb, account, files[3], [files[4]]); + }); + + final src = FileNpDbDataSource(c.npDb); + expect( + await src + .getFileDescriptors(account, file_util.unstripPath(account, "test2")) + .last, + [files[2].toDescriptor(), files[4].toDescriptor()], + ); } /// Remove a file @@ -100,7 +204,7 @@ Future _removeFile() async { await util.insertDirRelation(c.sqliteDb, account, files[0], [files[1]]); }); - final src = FileSqliteDbDataSource(c); + final src = FileNpDbDataSource(c.npDb); await src.remove(account, files[1]); expect( await util.listSqliteDbFiles(c.sqliteDb), @@ -128,7 +232,7 @@ Future _removeEmptyDir() async { await util.insertDirRelation(c.sqliteDb, account, files[1], const []); }); - final src = FileSqliteDbDataSource(c); + final src = FileNpDbDataSource(c.npDb); await src.remove(account, files[1]); // parent dir is not updated, parent dir is only updated when syncing with // remote @@ -161,7 +265,7 @@ Future _removeDir() async { await util.insertDirRelation(c.sqliteDb, account, files[1], [files[2]]); }); - final src = FileSqliteDbDataSource(c); + final src = FileNpDbDataSource(c.npDb); await src.remove(account, files[1]); expect( await util.listSqliteDbFiles(c.sqliteDb), @@ -192,7 +296,7 @@ Future _removeDirWithSubDir() async { await util.insertDirRelation(c.sqliteDb, account, files[2], [files[3]]); }); - final src = FileSqliteDbDataSource(c); + final src = FileNpDbDataSource(c.npDb); await src.remove(account, files[1]); expect( await util.listSqliteDbDirs(c.sqliteDb), @@ -225,7 +329,7 @@ Future _updateFileProperty() async { await util.insertDirRelation(c.sqliteDb, account, files[0], [files[1]]); }); - final src = FileSqliteDbDataSource(c); + final src = FileNpDbDataSource(c.npDb); await src.updateProperty( account, files[1], @@ -267,7 +371,7 @@ Future _updateMetadata() async { await util.insertDirRelation(c.sqliteDb, account, files[0], [files[1]]); }); - final src = FileSqliteDbDataSource(c); + final src = FileNpDbDataSource(c.npDb); await src.updateProperty( account, files[1], @@ -309,7 +413,7 @@ Future _updateAddMetadata() async { await util.insertDirRelation(c.sqliteDb, account, files[0], [files[1]]); }); - final src = FileSqliteDbDataSource(c); + final src = FileNpDbDataSource(c.npDb); await src.updateProperty( account, files[1], @@ -355,7 +459,7 @@ Future _updateDeleteMetadata() async { await util.insertDirRelation(c.sqliteDb, account, files[0], [files[1]]); }); - final src = FileSqliteDbDataSource(c); + final src = FileNpDbDataSource(c.npDb); await src.updateProperty( account, files[1], diff --git a/app/test/mock_type.dart b/app/test/mock_type.dart index 48611d1c..b7a94953 100644 --- a/app/test/mock_type.dart +++ b/app/test/mock_type.dart @@ -12,6 +12,7 @@ import 'package:nc_photos/entity/face_recognition_person/repo.dart'; import 'package:nc_photos/entity/favorite.dart'; import 'package:nc_photos/entity/file.dart'; import 'package:nc_photos/entity/file/data_source.dart'; +import 'package:nc_photos/entity/file/repo.dart'; import 'package:nc_photos/entity/file_descriptor.dart'; import 'package:nc_photos/entity/file_util.dart' as file_util; import 'package:nc_photos/entity/share.dart'; @@ -303,6 +304,72 @@ class MockFileMemoryRepo extends FileRepo { } } +class MockFileDataSource2 implements FileDataSource2 { + @override + Stream> getFileDescriptors( + Account account, String shareDirPath) { + throw UnimplementedError(); + } + + @override + Future remove(Account account, FileDescriptor f) { + throw UnimplementedError(); + } + + @override + Future updateProperty( + Account account, + FileDescriptor f, { + OrNull? metadata, + OrNull? isArchived, + OrNull? overrideDateTime, + bool? favorite, + OrNull? location, + }) { + throw UnimplementedError(); + } +} + +class MockFileMemoryDataSource2 extends MockFileDataSource2 { + MockFileMemoryDataSource2([ + List initialData = const [], + ]) : files = initialData.map((f) => f.copyWith()).toList(); + + @override + Stream> getFileDescriptors( + Account account, String shareDirPath) async* { + yield files.where((f) { + if (account.roots.any((r) => file_util.isOrUnderDirPath( + f.fdPath, file_util.unstripPath(account, r)))) { + return true; + } else if (file_util.isOrUnderDirPath( + f.fdPath, file_util.unstripPath(account, shareDirPath))) { + return true; + } else { + return false; + } + }).toList(); + } + + @override + Future remove(Account account, FileDescriptor file) async { + files.removeWhere((f) => f.compareServerIdentity(file)); + } + + final List files; +} + +/// [FileRepo2] mock that support some ops with an internal List +class MockFileMemoryRepo2 extends BasicFileRepo { + MockFileMemoryRepo2([ + List initialData = const [], + ]) : super(MockFileMemoryDataSource2(initialData)); + + List get files { + return (dataSrc as MockFileMemoryDataSource2).files; + } +} + /// Mock of [ShareRepo] where all methods will throw UnimplementedError class MockShareRepo implements ShareRepo { @override @@ -489,6 +556,7 @@ class MockFaceRecognitionPersonMemoryRepo extension MockDiContainerExtension on DiContainer { MockAlbumMemoryRepo get albumMemoryRepo => albumRepo as MockAlbumMemoryRepo; MockFileMemoryRepo get fileMemoryRepo => fileRepo as MockFileMemoryRepo; + MockFileMemoryRepo2 get fileMemoryRepo2 => fileRepo2 as MockFileMemoryRepo2; MockShareMemoryRepo get shareMemoryRepo => shareRepo as MockShareMemoryRepo; MockShareeMemoryRepo get shareeMemoryRepo => shareeRepo as MockShareeMemoryRepo; diff --git a/app/test/test_util.dart b/app/test/test_util.dart index 9b049a69..e01db815 100644 --- a/app/test/test_util.dart +++ b/app/test/test_util.dart @@ -317,7 +317,7 @@ void initLog() { } Account buildAccount({ - String id = "123456-000000", + String? id, String scheme = "http", String address = "example.com", String userId = "admin", @@ -326,7 +326,7 @@ Account buildAccount({ List roots = const [""], }) => Account( - id: id, + id: id ?? "$userId-000000", scheme: scheme, address: address, userId: userId.toCi(), diff --git a/app/test/use_case/remove_album_test.dart b/app/test/use_case/remove_album_test.dart index 561f2f10..ae590d0c 100644 --- a/app/test/use_case/remove_album_test.dart +++ b/app/test/use_case/remove_album_test.dart @@ -2,6 +2,7 @@ import 'package:event_bus/event_bus.dart'; import 'package:kiwi/kiwi.dart'; import 'package:nc_photos/db/entity_converter.dart'; import 'package:nc_photos/di_container.dart'; +import 'package:nc_photos/entity/file.dart'; import 'package:nc_photos/entity/pref.dart'; import 'package:nc_photos/entity/pref/provider/memory.dart'; import 'package:nc_photos/use_case/album/remove_album.dart'; @@ -36,6 +37,7 @@ Future _removeAlbum() async { final c = DiContainer( albumRepo: MockAlbumMemoryRepo([album1, album2]), fileRepo: MockFileMemoryRepo([albumFile1, albumFile2]), + fileRepo2: MockFileMemoryRepo2([albumFile1, albumFile2]), shareRepo: MockShareRepo(), npDb: util.buildTestDb(), pref: Pref.scoped(PrefMemoryProvider()), @@ -44,7 +46,7 @@ Future _removeAlbum() async { await RemoveAlbum(c)( account, c.albumMemoryRepo.findAlbumByPath(albumFile1.path)); - expect(c.fileMemoryRepo.files, [albumFile2]); + expect(c.fileMemoryRepo2.files, [albumFile2.toDescriptor()]); } /// Remove a shared album (admin -> user1) @@ -64,6 +66,7 @@ Future _removeSharedAlbum() async { final c = DiContainer( albumRepo: MockAlbumMemoryRepo([album]), fileRepo: MockFileMemoryRepo([albumFile, ...files]), + fileRepo2: MockFileMemoryRepo2([albumFile, ...files]), shareRepo: MockShareMemoryRepo([ util.buildShare(id: "0", file: albumFile, shareWith: "user1"), util.buildShare(id: "1", file: files[0], shareWith: "user1"), @@ -77,7 +80,7 @@ Future _removeSharedAlbum() async { await RemoveAlbum(c)( account, c.albumMemoryRepo.findAlbumByPath(albumFile.path)); - expect(c.fileMemoryRepo.files, [files[0]]); + expect(c.fileMemoryRepo2.files, [files[0].toDescriptor()]); expect(c.shareMemoryRepo.shares, const []); } @@ -105,6 +108,7 @@ Future _removeSharedAlbumFileInOtherAlbum() async { final c = DiContainer( albumRepo: MockAlbumMemoryRepo(albums), fileRepo: MockFileMemoryRepo([...albumFiles, ...files]), + fileRepo2: MockFileMemoryRepo2([...albumFiles, ...files]), shareRepo: MockShareMemoryRepo([ util.buildShare(id: "0", file: albumFiles[0], shareWith: "user1"), util.buildShare(id: "1", file: files[0], shareWith: "user1"), @@ -119,7 +123,10 @@ Future _removeSharedAlbumFileInOtherAlbum() async { await RemoveAlbum(c)( account, c.albumMemoryRepo.findAlbumByPath(albumFiles[0].path)); - expect(c.fileMemoryRepo.files, [albumFiles[1], files[0]]); + expect( + c.fileMemoryRepo2.files, + [albumFiles[1].toDescriptor(), files[0].toDescriptor()], + ); expect(c.shareMemoryRepo.shares, [ util.buildShare(id: "1", file: files[0], shareWith: "user1"), util.buildShare(id: "2", file: albumFiles[1], shareWith: "user1"), @@ -147,6 +154,7 @@ Future _removeSharedAlbumResyncedFile() async { final c = DiContainer( albumRepo: MockAlbumMemoryRepo([album]), fileRepo: MockFileMemoryRepo([albumFile, ...files, ...user1Files]), + fileRepo2: MockFileMemoryRepo2([albumFile, ...files, ...user1Files]), shareRepo: MockShareMemoryRepo([ util.buildShare(id: "0", file: albumFile, shareWith: "user1"), util.buildShare(id: "1", file: files[0], shareWith: "user1"), @@ -167,6 +175,9 @@ Future _removeSharedAlbumResyncedFile() async { await RemoveAlbum(c)( account, c.albumMemoryRepo.findAlbumByPath(albumFile.path)); - expect(c.fileMemoryRepo.files, [...files, ...user1Files]); + expect( + c.fileMemoryRepo2.files, + [...files, ...user1Files].map((e) => e.toDescriptor()), + ); expect(c.shareMemoryRepo.shares, []); } diff --git a/app/test/use_case/remove_test.dart b/app/test/use_case/remove_test.dart index 94c3cd16..98176085 100644 --- a/app/test/use_case/remove_test.dart +++ b/app/test/use_case/remove_test.dart @@ -6,6 +6,7 @@ import 'package:nc_photos/entity/album.dart'; import 'package:nc_photos/entity/album/cover_provider.dart'; import 'package:nc_photos/entity/album/provider.dart'; import 'package:nc_photos/entity/album/sort_provider.dart'; +import 'package:nc_photos/entity/file.dart'; import 'package:nc_photos/entity/pref.dart'; import 'package:nc_photos/entity/pref/provider/memory.dart'; import 'package:nc_photos/use_case/remove.dart'; @@ -46,6 +47,7 @@ Future _removeFile() async { final c = DiContainer( albumRepo: MockAlbumMemoryRepo(), fileRepo: MockFileMemoryRepo(files), + fileRepo2: MockFileMemoryRepo2(files), shareRepo: MockShareMemoryRepo(), npDb: util.buildTestDb(), pref: Pref.scoped(PrefMemoryProvider()), @@ -57,7 +59,7 @@ Future _removeFile() async { }); await Remove(c)(account, [files[0]]); - expect(c.fileMemoryRepo.files, [files[1]]); + expect(c.fileMemoryRepo2.files, [files[1].toDescriptor()]); } /// Remove a file, skip clean up @@ -72,6 +74,7 @@ Future _removeFileNoCleanUp() async { final c = DiContainer( albumRepo: MockAlbumMemoryRepo(), fileRepo: MockFileMemoryRepo(files), + fileRepo2: MockFileMemoryRepo2(files), shareRepo: MockShareMemoryRepo(), npDb: util.buildTestDb(), pref: Pref.scoped(PrefMemoryProvider()), @@ -83,7 +86,7 @@ Future _removeFileNoCleanUp() async { }); await Remove(c)(account, [files[0]], shouldCleanUp: false); - expect(c.fileMemoryRepo.files, [files[1]]); + expect(c.fileMemoryRepo2.files, [files[1].toDescriptor()]); } /// Remove a file included in an album