diff --git a/test/bloc/list_album_share_outlier_test.dart b/test/bloc/list_album_share_outlier_test.dart index 662b3956..f2f59294 100644 --- a/test/bloc/list_album_share_outlier_test.dart +++ b/test/bloc/list_album_share_outlier_test.dart @@ -11,12 +11,38 @@ void main() { test("intial state", _initialState); group("ListAlbumShareOutlierBlocQuery", () { group("unshared album", () { - _testQueryUnsharedAlbumExtraFileShare(); - _testQueryUnsharedAlbumExtraJsonShare(); + _testQueryUnsharedAlbumExtraShare("extra share"); + _testQueryUnsharedAlbumExtraJsonShare("extra json share"); }); group("shared album", () { - _testQuerySharedAlbumMissingFileShare(); - _testQuerySharedAlbumMissingJsonShare(); + group("owned", () { + _testQuerySharedAlbumMissingShare("missing share"); + _testQuerySharedAlbumMissingManagedOtherShare( + "missing managed other share"); + _testQuerySharedAlbumMissingUnmanagedOtherShare( + "missing unmanaged other share"); + _testQuerySharedAlbumMissingJsonShare("missing json share"); + + _testQuerySharedAlbumExtraShare("extra share"); + _testQuerySharedAlbumExtraManagedOtherShare( + "extra managed other share"); + _testQuerySharedAlbumExtraUnmanagedOtherShare( + "extra unmanaged other share"); + _testQuerySharedAlbumExtraJsonShare("extra json share"); + }); + group("not owned", () { + _testQuerySharedAlbumNotOwnedMissingShareToOwner("missing share"); + _testQuerySharedAlbumNotOwnedMissingManagedShare( + "missing managed share"); + _testQuerySharedAlbumNotOwnedMissingUnmanagedShare( + "missing unmanaged share"); + _testQuerySharedAlbumNotOwnedMissingJsonShare("missing json share"); + + _testQuerySharedAlbumNotOwnedExtraManagedShare("extra managed share"); + _testQuerySharedAlbumNotOwnedExtraUnmanagedShare( + "extra unmanaged share"); + _testQuerySharedAlbumNotOwnedExtraJsonShare("extra json share"); + }); }); }); }); @@ -33,7 +59,7 @@ void _initialState() { /// Query an album that is not shared, but with shared file (admin -> user1) /// /// Expect: emit the file with extra share (admin -> user1) -void _testQueryUnsharedAlbumExtraFileShare() { +void _testQueryUnsharedAlbumExtraShare(String description) { final account = util.buildAccount(); final files = (util.FilesBuilder(initialFileId: 1)..addJpeg("admin/test1.jpg")).build(); @@ -46,7 +72,7 @@ void _testQueryUnsharedAlbumExtraFileShare() { util.buildSharee(shareWith: "user1".toCi()), ]); blocTest( - "extra file share", + description, build: () => ListAlbumShareOutlierBloc(shareRepo, shareeRepo), act: (bloc) => bloc.add(ListAlbumShareOutlierBlocQuery(account, album)), wait: const Duration(milliseconds: 500), @@ -66,7 +92,7 @@ void _testQueryUnsharedAlbumExtraFileShare() { /// (admin -> user1) /// /// Expect: emit the json file with extra share (admin -> user1) -void _testQueryUnsharedAlbumExtraJsonShare() { +void _testQueryUnsharedAlbumExtraJsonShare(String description) { final account = util.buildAccount(); final album = util.AlbumBuilder().build(); final albumFile = album.albumFile!; @@ -77,7 +103,7 @@ void _testQueryUnsharedAlbumExtraJsonShare() { util.buildSharee(shareWith: "user1".toCi()), ]); blocTest( - "extra json share", + description, build: () => ListAlbumShareOutlierBloc(shareRepo, shareeRepo), act: (bloc) => bloc.add(ListAlbumShareOutlierBlocQuery(account, album)), wait: const Duration(milliseconds: 500), @@ -93,10 +119,10 @@ void _testQueryUnsharedAlbumExtraJsonShare() { ); } -/// Query a shared album (admin -> user1), with file not shared +/// Query a shared album (admin -> user1), with file not shared (admin -> user1) /// /// Expect: emit the file with missing share (admin -> user1) -void _testQuerySharedAlbumMissingFileShare() { +void _testQuerySharedAlbumMissingShare(String description) { final account = util.buildAccount(); final files = (util.FilesBuilder(initialFileId: 1)..addJpeg("admin/test1.jpg")).build(); @@ -113,7 +139,7 @@ void _testQuerySharedAlbumMissingFileShare() { util.buildSharee(shareWith: "user1".toCi()), ]); blocTest( - "missing file share", + description, build: () => ListAlbumShareOutlierBloc(shareRepo, shareeRepo), act: (bloc) => bloc.add(ListAlbumShareOutlierBlocQuery(account, album)), wait: const Duration(milliseconds: 500), @@ -128,10 +154,91 @@ void _testQuerySharedAlbumMissingFileShare() { ); } +/// Query a shared album (admin -> user1, user2), with file added by user1, +/// managed by admin, not shared (admin -> user2) +/// +/// Expect: emit empty list +void _testQuerySharedAlbumMissingManagedOtherShare(String description) { + final account = util.buildAccount(); + final files = (util.FilesBuilder(initialFileId: 1) + ..addJpeg("user1/test1.jpg", ownerId: "user1")) + .build(); + final album = (util.AlbumBuilder() + // added before album shared, thus managed by album owner + ..addFileItem(files[0], addedBy: "user1") + ..addShare("user1", sharedAt: DateTime.utc(2021, 1, 2, 3, 4, 5)) + ..addShare("user2", sharedAt: DateTime.utc(2021, 1, 2, 3, 4, 5))) + .build(); + final albumFile = album.albumFile!; + final shareRepo = MockShareMemoryRepo([ + util.buildShare(id: "0", file: albumFile, shareWith: "user1"), + util.buildShare(id: "1", file: albumFile, shareWith: "user2"), + util.buildShare( + id: "2", file: files[0], uidOwner: "user1", shareWith: "admin"), + ]); + final shareeRepo = MockShareeMemoryRepo([ + util.buildSharee(shareWith: "user1".toCi()), + util.buildSharee(shareWith: "user2".toCi()), + ]); + blocTest( + description, + build: () => ListAlbumShareOutlierBloc(shareRepo, shareeRepo), + act: (bloc) => bloc.add(ListAlbumShareOutlierBlocQuery(account, album)), + wait: const Duration(milliseconds: 500), + expect: () => [ + ListAlbumShareOutlierBlocLoading(account, []), + ListAlbumShareOutlierBlocSuccess(account, [ + ListAlbumShareOutlierItem(files[0], [ + ListAlbumShareOutlierMissingShareItem("user2".toCi(), "user2"), + ]), + ]), + ], + ); +} + +/// Query a shared album (admin -> user1, user2), with file added by user1, +/// managed by user1, not shared (user1 -> user2) +/// +/// Expect: emit empty list +void _testQuerySharedAlbumMissingUnmanagedOtherShare(String description) { + final account = util.buildAccount(); + final files = (util.FilesBuilder(initialFileId: 1) + ..addJpeg("user1/test1.jpg", ownerId: "user1")) + .build(); + final album = (util.AlbumBuilder() + // added after album shared, thus managed by adder + ..addFileItem(files[0], + addedBy: "user1", addedAt: DateTime.utc(2021, 1, 2, 3, 4, 5)) + ..addShare("user1") + ..addShare("user2")) + .build(); + final albumFile = album.albumFile!; + final shareRepo = MockShareMemoryRepo([ + util.buildShare(id: "0", file: albumFile, shareWith: "user1"), + util.buildShare(id: "1", file: albumFile, shareWith: "user2"), + util.buildShare( + id: "2", file: files[0], uidOwner: "user1", shareWith: "admin"), + ]); + final shareeRepo = MockShareeMemoryRepo([ + util.buildSharee(shareWith: "user1".toCi()), + util.buildSharee(shareWith: "user2".toCi()), + ]); + blocTest( + description, + build: () => ListAlbumShareOutlierBloc(shareRepo, shareeRepo), + act: (bloc) => bloc.add(ListAlbumShareOutlierBlocQuery(account, album)), + wait: const Duration(milliseconds: 500), + expect: () => [ + ListAlbumShareOutlierBlocLoading(account, []), + ListAlbumShareOutlierBlocSuccess(account, []), + ], + ); +} + /// Query a shared album (admin -> user1), with album json not shared /// /// Expect: emit the file with missing share (admin -> user1) -void _testQuerySharedAlbumMissingJsonShare() { +void _testQuerySharedAlbumMissingJsonShare(String description) { final account = util.buildAccount(); final album = (util.AlbumBuilder()..addShare("user1")).build(); final albumFile = album.albumFile!; @@ -140,7 +247,7 @@ void _testQuerySharedAlbumMissingJsonShare() { util.buildSharee(shareWith: "user1".toCi()), ]); blocTest( - "missing json share", + description, build: () => ListAlbumShareOutlierBloc(shareRepo, shareeRepo), act: (bloc) => bloc.add(ListAlbumShareOutlierBlocQuery(account, album)), wait: const Duration(milliseconds: 500), @@ -154,3 +261,415 @@ void _testQuerySharedAlbumMissingJsonShare() { ], ); } + +/// Query a shared album (admin -> user1), with file shared +/// (admin -> user1, user2) +/// +/// Expect: emit the file with extra share (admin -> user2) +void _testQuerySharedAlbumExtraShare(String description) { + final account = util.buildAccount(); + final files = + (util.FilesBuilder(initialFileId: 1)..addJpeg("admin/test1.jpg")).build(); + final album = (util.AlbumBuilder() + ..addFileItem(files[0]) + ..addShare("user1")) + .build(); + final file1 = files[0]; + final albumFile = album.albumFile!; + final shareRepo = MockShareMemoryRepo([ + util.buildShare(id: "0", file: albumFile, shareWith: "user1"), + util.buildShare(id: "1", file: files[0], shareWith: "user1"), + util.buildShare(id: "2", file: files[0], shareWith: "user2"), + ]); + final shareeRepo = MockShareeMemoryRepo([ + util.buildSharee(shareWith: "user1".toCi()), + util.buildSharee(shareWith: "user2".toCi()), + ]); + blocTest( + description, + build: () => ListAlbumShareOutlierBloc(shareRepo, shareeRepo), + act: (bloc) => bloc.add(ListAlbumShareOutlierBlocQuery(account, album)), + wait: const Duration(milliseconds: 500), + expect: () => [ + ListAlbumShareOutlierBlocLoading(account, []), + ListAlbumShareOutlierBlocSuccess(account, [ + ListAlbumShareOutlierItem(file1, [ + ListAlbumShareOutlierExtraShareItem( + util.buildShare(id: "2", file: files[0], shareWith: "user2")), + ]), + ]), + ], + ); +} + +/// Query a shared album (admin -> user1), with file added by user1, +/// managed by admin, shared (admin -> user1, user2) +/// +/// Expect: emit the file with extra share (admin -> user2) +void _testQuerySharedAlbumExtraManagedOtherShare(String description) { + final account = util.buildAccount(); + final files = (util.FilesBuilder(initialFileId: 1) + ..addJpeg("user1/test1.jpg", ownerId: "user1")) + .build(); + final album = (util.AlbumBuilder() + // added before album shared, thus managed by album owner + ..addFileItem(files[0], addedBy: "user1") + ..addShare("user1", sharedAt: DateTime.utc(2021, 1, 2, 3, 4, 5))) + .build(); + final albumFile = album.albumFile!; + final shareRepo = MockShareMemoryRepo([ + util.buildShare(id: "0", file: albumFile, shareWith: "user1"), + util.buildShare( + id: "1", file: files[0], uidOwner: "user1", shareWith: "admin"), + util.buildShare(id: "2", file: files[0], shareWith: "user2"), + ]); + final shareeRepo = MockShareeMemoryRepo([ + util.buildSharee(shareWith: "user1".toCi()), + util.buildSharee(shareWith: "user2".toCi()), + ]); + blocTest( + description, + build: () => ListAlbumShareOutlierBloc(shareRepo, shareeRepo), + act: (bloc) => bloc.add(ListAlbumShareOutlierBlocQuery(account, album)), + wait: const Duration(milliseconds: 500), + expect: () => [ + ListAlbumShareOutlierBlocLoading(account, []), + ListAlbumShareOutlierBlocSuccess(account, [ + ListAlbumShareOutlierItem(files[0], [ + ListAlbumShareOutlierExtraShareItem( + util.buildShare(id: "2", file: files[0], shareWith: "user2")), + ]), + ]), + ], + ); +} + +/// Query a shared album (admin -> user1), with file added by user1, managed by +/// user1, shared (user1 -> admin, user2) +/// +/// Expect: emit empty list +void _testQuerySharedAlbumExtraUnmanagedOtherShare(String description) { + final account = util.buildAccount(); + final files = (util.FilesBuilder(initialFileId: 1) + ..addJpeg("user1/test1.jpg", ownerId: "user1")) + .build(); + final album = (util.AlbumBuilder() + // added after album shared, thus managed by adder + ..addFileItem(files[0], + addedBy: "user1", addedAt: DateTime.utc(2021, 1, 2, 3, 4, 5)) + ..addShare("user1")) + .build(); + final albumFile = album.albumFile!; + final shareRepo = MockShareMemoryRepo([ + util.buildShare(id: "0", file: albumFile, shareWith: "user1"), + util.buildShare( + id: "1", file: files[0], uidOwner: "user1", shareWith: "admin"), + util.buildShare( + id: "2", file: files[0], uidOwner: "user1", shareWith: "user2"), + ]); + final shareeRepo = MockShareeMemoryRepo([ + util.buildSharee(shareWith: "user1".toCi()), + util.buildSharee(shareWith: "user2".toCi()), + ]); + blocTest( + description, + build: () => ListAlbumShareOutlierBloc(shareRepo, shareeRepo), + act: (bloc) => bloc.add(ListAlbumShareOutlierBlocQuery(account, album)), + wait: const Duration(milliseconds: 500), + expect: () => [ + ListAlbumShareOutlierBlocLoading(account, []), + ListAlbumShareOutlierBlocSuccess(account, []), + ], + ); +} + +/// Query a shared album (admin -> user1), with album json shared +/// (admin -> user1, user2) +/// +/// Expect: emit the file with extra share (admin -> user2) +void _testQuerySharedAlbumExtraJsonShare(String description) { + final account = util.buildAccount(); + final album = (util.AlbumBuilder()..addShare("user1")).build(); + final albumFile = album.albumFile!; + final shareRepo = MockShareMemoryRepo([ + util.buildShare(id: "0", file: albumFile, shareWith: "user1"), + util.buildShare(id: "1", file: albumFile, shareWith: "user2"), + ]); + final shareeRepo = MockShareeMemoryRepo([ + util.buildSharee(shareWith: "user1".toCi()), + util.buildSharee(shareWith: "user2".toCi()), + ]); + blocTest( + description, + build: () => ListAlbumShareOutlierBloc(shareRepo, shareeRepo), + act: (bloc) => bloc.add(ListAlbumShareOutlierBlocQuery(account, album)), + wait: const Duration(milliseconds: 500), + expect: () => [ + ListAlbumShareOutlierBlocLoading(account, []), + ListAlbumShareOutlierBlocSuccess(account, [ + ListAlbumShareOutlierItem(albumFile, [ + ListAlbumShareOutlierExtraShareItem( + util.buildShare(id: "1", file: albumFile, shareWith: "user2")), + ]), + ]), + ], + ); +} + +/// Query a shared album (user1 -> admin), with file added by admin not shared +/// (admin -> user1) +/// +/// Expect: emit the file with missing share (admin -> user1) +void _testQuerySharedAlbumNotOwnedMissingShareToOwner(String description) { + final account = util.buildAccount(); + final files = + (util.FilesBuilder(initialFileId: 1)..addJpeg("admin/test1.jpg")).build(); + final album = (util.AlbumBuilder(ownerId: "user1") + ..addFileItem(files[0]) + ..addShare("admin")) + .build(); + final albumFile = album.albumFile!; + final shareRepo = MockShareMemoryRepo([ + util.buildShare( + id: "0", file: albumFile, uidOwner: "user1", shareWith: "admin"), + ]); + final shareeRepo = MockShareeMemoryRepo([ + util.buildSharee(shareWith: "user1".toCi()), + ]); + blocTest( + description, + build: () => ListAlbumShareOutlierBloc(shareRepo, shareeRepo), + act: (bloc) => bloc.add(ListAlbumShareOutlierBlocQuery(account, album)), + wait: const Duration(milliseconds: 500), + expect: () => [ + ListAlbumShareOutlierBlocLoading(account, []), + ListAlbumShareOutlierBlocSuccess(account, [ + ListAlbumShareOutlierItem(files[0], [ + ListAlbumShareOutlierMissingShareItem("user1".toCi(), "user1"), + ]), + ]), + ], + ); +} + +/// Query a shared album (user1 -> admin, user2), with file added by admin, +/// managed by admin, not shared (admin -> user2) +/// +/// Expect: emit the file with missing share (admin -> user2) +void _testQuerySharedAlbumNotOwnedMissingManagedShare(String description) { + final account = util.buildAccount(); + final files = + (util.FilesBuilder(initialFileId: 1)..addJpeg("admin/test1.jpg")).build(); + final album = (util.AlbumBuilder(ownerId: "user1") + // added after album shared, thus managed by adder + ..addFileItem(files[0], addedAt: DateTime.utc(2021, 1, 2, 3, 4, 5)) + ..addShare("admin") + ..addShare("user2")) + .build(); + final albumFile = album.albumFile!; + final shareRepo = MockShareMemoryRepo([ + util.buildShare( + id: "0", file: albumFile, uidOwner: "user1", shareWith: "admin"), + util.buildShare( + id: "1", file: albumFile, uidOwner: "user1", shareWith: "user2"), + util.buildShare(id: "2", file: files[0], shareWith: "user1"), + ]); + final shareeRepo = MockShareeMemoryRepo([ + util.buildSharee(shareWith: "user1".toCi()), + util.buildSharee(shareWith: "user2".toCi()), + ]); + blocTest( + description, + build: () => ListAlbumShareOutlierBloc(shareRepo, shareeRepo), + act: (bloc) => bloc.add(ListAlbumShareOutlierBlocQuery(account, album)), + wait: const Duration(milliseconds: 500), + expect: () => [ + ListAlbumShareOutlierBlocLoading(account, []), + ListAlbumShareOutlierBlocSuccess(account, [ + ListAlbumShareOutlierItem(files[0], [ + ListAlbumShareOutlierMissingShareItem("user2".toCi(), "user2"), + ]), + ]), + ], + ); +} + +/// Query a shared album (user1 -> admin, user2), with file added by admin, +/// managed by user1, not shared (user1 -> user2) +/// +/// Expect: emit empty list +void _testQuerySharedAlbumNotOwnedMissingUnmanagedShare(String description) { + final account = util.buildAccount(); + final files = + (util.FilesBuilder(initialFileId: 1)..addJpeg("admin/test1.jpg")).build(); + final album = (util.AlbumBuilder(ownerId: "user1") + // added before album shared, thus managed by album owner + ..addFileItem(files[0], addedBy: "admin") + ..addShare("admin", sharedAt: DateTime.utc(2021, 1, 2, 3, 4, 5)) + ..addShare("user2", sharedAt: DateTime.utc(2021, 1, 2, 3, 4, 5))) + .build(); + final albumFile = album.albumFile!; + final shareRepo = MockShareMemoryRepo([ + util.buildShare(id: "0", file: albumFile, shareWith: "user1"), + util.buildShare(id: "1", file: albumFile, shareWith: "user2"), + util.buildShare(id: "2", file: files[0], shareWith: "user1"), + ]); + final shareeRepo = MockShareeMemoryRepo([ + util.buildSharee(shareWith: "user1".toCi()), + util.buildSharee(shareWith: "user2".toCi()), + ]); + blocTest( + description, + build: () => ListAlbumShareOutlierBloc(shareRepo, shareeRepo), + act: (bloc) => bloc.add(ListAlbumShareOutlierBlocQuery(account, album)), + wait: const Duration(milliseconds: 500), + expect: () => [ + ListAlbumShareOutlierBlocLoading(account, []), + ListAlbumShareOutlierBlocSuccess(account, []), + ], + ); +} + +/// Query a shared album (user1 -> admin, user2), with missing album json share +/// (user1 -> user2) +/// +/// Expect: emit empty list +void _testQuerySharedAlbumNotOwnedMissingJsonShare(String description) { + final account = util.buildAccount(); + final album = (util.AlbumBuilder(ownerId: "user1") + ..addShare("admin") + ..addShare("user2")) + .build(); + final albumFile = album.albumFile!; + final shareRepo = MockShareMemoryRepo([ + util.buildShare( + id: "0", file: albumFile, uidOwner: "user1", shareWith: "admin"), + ]); + final shareeRepo = MockShareeMemoryRepo([ + util.buildSharee(shareWith: "user1".toCi()), + util.buildSharee(shareWith: "user2".toCi()), + ]); + blocTest( + description, + build: () => ListAlbumShareOutlierBloc(shareRepo, shareeRepo), + act: (bloc) => bloc.add(ListAlbumShareOutlierBlocQuery(account, album)), + wait: const Duration(milliseconds: 500), + expect: () => [ + ListAlbumShareOutlierBlocLoading(account, []), + ListAlbumShareOutlierBlocSuccess(account, []), + ], + ); +} + +/// Query a shared album (user1 -> admin), with file added by admin, managed by +/// admin, shared (admin -> user1, user2) +/// +/// Expect: emit the file with missing share (admin -> user2) +void _testQuerySharedAlbumNotOwnedExtraManagedShare(String description) { + final account = util.buildAccount(); + final files = + (util.FilesBuilder(initialFileId: 1)..addJpeg("admin/test1.jpg")).build(); + final album = (util.AlbumBuilder(ownerId: "user1") + // added after album shared, thus managed by adder + ..addFileItem(files[0], addedAt: DateTime.utc(2021, 1, 2, 3, 4, 5)) + ..addShare("admin")) + .build(); + final albumFile = album.albumFile!; + final shareRepo = MockShareMemoryRepo([ + util.buildShare( + id: "0", file: albumFile, uidOwner: "user1", shareWith: "admin"), + util.buildShare(id: "1", file: files[0], shareWith: "user1"), + util.buildShare(id: "2", file: files[0], shareWith: "user2"), + ]); + final shareeRepo = MockShareeMemoryRepo([ + util.buildSharee(shareWith: "user1".toCi()), + util.buildSharee(shareWith: "user2".toCi()), + ]); + blocTest( + description, + build: () => ListAlbumShareOutlierBloc(shareRepo, shareeRepo), + act: (bloc) => bloc.add(ListAlbumShareOutlierBlocQuery(account, album)), + wait: const Duration(milliseconds: 500), + expect: () => [ + ListAlbumShareOutlierBlocLoading(account, []), + ListAlbumShareOutlierBlocSuccess(account, [ + ListAlbumShareOutlierItem(files[0], [ + ListAlbumShareOutlierExtraShareItem( + util.buildShare(id: "2", file: files[0], shareWith: "user2")), + ]), + ]), + ], + ); +} + +/// Query a shared album (user1 -> admin), with file added by admin, managed by +/// user1, shared (admin -> user1, user2) +/// +/// Expect: emit the file with missing share (admin -> user2) +void _testQuerySharedAlbumNotOwnedExtraUnmanagedShare(String description) { + final account = util.buildAccount(); + final files = + (util.FilesBuilder(initialFileId: 1)..addJpeg("admin/test1.jpg")).build(); + final album = (util.AlbumBuilder(ownerId: "user1") + // added before album shared, thus managed by album owner + ..addFileItem(files[0], addedBy: "admin") + ..addShare("admin", sharedAt: DateTime.utc(2021, 1, 2, 3, 4, 5))) + .build(); + final albumFile = album.albumFile!; + final shareRepo = MockShareMemoryRepo([ + util.buildShare( + id: "0", file: albumFile, uidOwner: "user1", shareWith: "admin"), + util.buildShare(id: "1", file: files[0], shareWith: "user1"), + util.buildShare(id: "2", file: files[0], shareWith: "user2"), + ]); + final shareeRepo = MockShareeMemoryRepo([ + util.buildSharee(shareWith: "user1".toCi()), + util.buildSharee(shareWith: "user2".toCi()), + ]); + blocTest( + description, + build: () => ListAlbumShareOutlierBloc(shareRepo, shareeRepo), + act: (bloc) => bloc.add(ListAlbumShareOutlierBlocQuery(account, album)), + wait: const Duration(milliseconds: 500), + expect: () => [ + ListAlbumShareOutlierBlocLoading(account, []), + ListAlbumShareOutlierBlocSuccess(account, [ + ListAlbumShareOutlierItem(files[0], [ + ListAlbumShareOutlierExtraShareItem( + util.buildShare(id: "2", file: files[0], shareWith: "user2")), + ]), + ]), + ], + ); +} + +/// Query a shared album (user1 -> admin), with album json share +/// (user1 -> admin, user2) +/// +/// Expect: emit empty list +void _testQuerySharedAlbumNotOwnedExtraJsonShare(String description) { + final account = util.buildAccount(); + final album = + (util.AlbumBuilder(ownerId: "user1")..addShare("admin")).build(); + final albumFile = album.albumFile!; + final shareRepo = MockShareMemoryRepo([ + util.buildShare( + id: "0", file: albumFile, uidOwner: "user1", shareWith: "admin"), + util.buildShare( + id: "1", file: albumFile, uidOwner: "user1", shareWith: "user2"), + ]); + final shareeRepo = MockShareeMemoryRepo([ + util.buildSharee(shareWith: "user1".toCi()), + util.buildSharee(shareWith: "user2".toCi()), + ]); + blocTest( + description, + build: () => ListAlbumShareOutlierBloc(shareRepo, shareeRepo), + act: (bloc) => bloc.add(ListAlbumShareOutlierBlocQuery(account, album)), + wait: const Duration(milliseconds: 500), + expect: () => [ + ListAlbumShareOutlierBlocLoading(account, []), + ListAlbumShareOutlierBlocSuccess(account, []), + ], + ); +} diff --git a/test/entity/album_test.dart b/test/entity/album_test.dart index 76a3fab2..7988b9a0 100644 --- a/test/entity/album_test.dart +++ b/test/entity/album_test.dart @@ -1674,6 +1674,7 @@ void _fromJsonShares() { { "userId": "admin", "displayName": "admin", + "sharedAt": "2020-01-02T03:04:05.000Z", }, ], }; @@ -1727,6 +1728,7 @@ void _toRemoteJsonShares() { { "userId": "admin", "displayName": "admin", + "sharedAt": "2020-01-02T03:04:05.000Z", }, ], }); @@ -1765,6 +1767,7 @@ void _toAppDbJsonShares() { { "userId": "admin", "displayName": "admin", + "sharedAt": "2020-01-02T03:04:05.000Z", }, ], }); diff --git a/test/test_util.dart b/test/test_util.dart index 46243566..6c4ef268 100644 --- a/test/test_util.dart +++ b/test/test_util.dart @@ -105,8 +105,16 @@ class AlbumBuilder { } /// Add an album share - void addShare(String userId) { - shares.add(buildAlbumShare(userId: userId)); + /// + /// By default, the album will be shared at 2020-01-02 03:04:05 + void addShare( + String userId, { + DateTime? sharedAt, + }) { + shares.add(buildAlbumShare( + userId: userId, + sharedAt: sharedAt, + )); } static fileItemsOf(Album album) => @@ -170,10 +178,12 @@ String buildAlbumFilePath( AlbumShare buildAlbumShare({ required String userId, String? displayName, + DateTime? sharedAt, }) => AlbumShare( userId: userId.toCi(), displayName: displayName ?? userId, + sharedAt: sharedAt ?? DateTime.utc(2020, 1, 2, 3, 4, 5), ); /// Build a mock [File] pointing to a JPEG image file diff --git a/test/use_case/add_to_album_test.dart b/test/use_case/add_to_album_test.dart index 10265dff..46a14cca 100644 --- a/test/use_case/add_to_album_test.dart +++ b/test/use_case/add_to_album_test.dart @@ -22,6 +22,7 @@ void main() { group("AddToAlbum", () { test("file", _addFile); + test("ignore existing file", _addExistingFile); group("shared album (owned)", () { test("file", _addFileToSharedAlbumOwned); test("file owned by user", _addFileOwnedByUserToSharedAlbumOwned); @@ -94,6 +95,88 @@ Future _addFile() async { ); } +/// Add a [File], already included in the [Album], to an [Album] +/// +/// Expect: file not added to album +Future _addExistingFile() async { + final account = util.buildAccount(); + final pref = Pref.scoped(PrefMemoryProvider()); + final files = + (util.FilesBuilder(initialFileId: 1)..addJpeg("admin/test1.jpg")).build(); + final album = (util.AlbumBuilder()..addFileItem(files[0])).build(); + final oldFile = files[0]; + final newFile = files[0].copyWith(); + final albumFile = album.albumFile!; + final appDb = MockAppDb(); + await appDb.use((db) async { + final transaction = db.transaction(AppDb.fileDbStoreName, idbModeReadWrite); + final store = transaction.objectStore(AppDb.fileDbStoreName); + await store.put(AppDbFileDbEntry.fromFile(account, files[0]).toJson(), + AppDbFileDbEntry.toPrimaryKey(account, files[0])); + }); + final albumRepo = MockAlbumMemoryRepo([album]); + final shareRepo = MockShareRepo(); + + await AddToAlbum(albumRepo, shareRepo, appDb, pref)( + account, + albumRepo.findAlbumByPath(albumFile.path), + [ + AlbumFileItem( + addedBy: "admin".toCi(), + addedAt: DateTime.utc(2020, 1, 2, 3, 4, 5), + file: newFile, + ), + ], + ); + expect( + albumRepo.albums + .map((e) => e.copyWith( + // we need to set a known value to lastUpdated + lastUpdated: OrNull(DateTime.utc(2020, 1, 2, 3, 4, 5)), + )) + .toList(), + [ + Album( + lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5), + name: "test", + provider: AlbumStaticProvider( + items: [ + AlbumFileItem( + addedBy: "admin".toCi(), + addedAt: DateTime.utc(2020, 1, 2, 3, 4, 5), + file: files[0], + ), + ], + latestItemTime: DateTime.utc(2020, 1, 2, 3, 4, 5), + ), + coverProvider: AlbumAutoCoverProvider(coverFile: files[0]), + sortProvider: const AlbumNullSortProvider(), + albumFile: albumFile, + ), + ], + ); + // when there's a conflict, it's guaranteed that the original file in the + // album is kept and the incoming file dropped + expect( + identical( + AlbumStaticProvider.of(albumRepo.albums[0]) + .items + .whereType() + .first + .file, + oldFile), + true); + expect( + identical( + AlbumStaticProvider.of(albumRepo.albums[0]) + .items + .whereType() + .first + .file, + newFile), + false); +} + /// Add a file to a shared album (admin -> user1) /// /// Expect: a new share (admin -> user1) is created for the file diff --git a/test/use_case/share_album_with_user_test.dart b/test/use_case/share_album_with_user_test.dart index 360262de..ffe6c444 100644 --- a/test/use_case/share_album_with_user_test.dart +++ b/test/use_case/share_album_with_user_test.dart @@ -1,6 +1,7 @@ import 'package:event_bus/event_bus.dart'; import 'package:kiwi/kiwi.dart'; import 'package:nc_photos/ci_string.dart'; +import 'package:nc_photos/or_null.dart'; import 'package:nc_photos/use_case/share_album_with_user.dart'; import 'package:test/test.dart'; @@ -35,7 +36,14 @@ Future _shareWithoutFile() async { util.buildSharee(shareWith: "user1".toCi()), ); expect( - albumRepo.findAlbumByPath(albumFile.path).shares, + albumRepo + .findAlbumByPath(albumFile.path) + .shares + ?.map((s) => s.copyWith( + // we need to set a known value to sharedAt + sharedAt: OrNull(DateTime.utc(2020, 1, 2, 3, 4, 5)), + )) + .toList(), [util.buildAlbumShare(userId: "user1")], ); expect( @@ -65,7 +73,14 @@ Future _shareWithFile() async { util.buildSharee(shareWith: "user1".toCi()), ); expect( - albumRepo.findAlbumByPath(albumFile.path).shares, + albumRepo + .findAlbumByPath(albumFile.path) + .shares + ?.map((s) => s.copyWith( + // we need to set a known value to sharedAt + sharedAt: OrNull(DateTime.utc(2020, 1, 2, 3, 4, 5)), + )) + .toList(), [util.buildAlbumShare(userId: "user1")], ); expect( @@ -97,7 +112,14 @@ Future _shareWithFileOwnedByUser() async { util.buildSharee(shareWith: "user1".toCi()), ); expect( - albumRepo.findAlbumByPath(albumFile.path).shares, + albumRepo + .findAlbumByPath(albumFile.path) + .shares + ?.map((s) => s.copyWith( + // we need to set a known value to sharedAt + sharedAt: OrNull(DateTime.utc(2020, 1, 2, 3, 4, 5)), + )) + .toList(), [util.buildAlbumShare(userId: "user1")], ); expect( @@ -127,7 +149,14 @@ Future _shareSharedAlbum() async { util.buildSharee(shareWith: "user2".toCi()), ); expect( - albumRepo.findAlbumByPath(albumFile.path).shares, + albumRepo + .findAlbumByPath(albumFile.path) + .shares + ?.map((s) => s.copyWith( + // we need to set a known value to sharedAt + sharedAt: OrNull(DateTime.utc(2020, 1, 2, 3, 4, 5)), + )) + .toList(), [ util.buildAlbumShare(userId: "user1"), util.buildAlbumShare(userId: "user2"),