diff options
Diffstat (limited to 'tests/Jellyfin.Server.Implementations.Tests/Item/BaseItemRepositoryByNameItemCountsTests.cs')
| -rw-r--r-- | tests/Jellyfin.Server.Implementations.Tests/Item/BaseItemRepositoryByNameItemCountsTests.cs | 215 |
1 files changed, 215 insertions, 0 deletions
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Item/BaseItemRepositoryByNameItemCountsTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Item/BaseItemRepositoryByNameItemCountsTests.cs new file mode 100644 index 0000000000..298340d1b0 --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/Item/BaseItemRepositoryByNameItemCountsTests.cs @@ -0,0 +1,215 @@ +using System; +using Emby.Server.Implementations.Data; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Server.Implementations.Item; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Querying; +using Xunit; +using BaseItemKind = Jellyfin.Data.Enums.BaseItemKind; + +namespace Jellyfin.Server.Implementations.Tests.Item; + +/// <summary> +/// The by-name listings count what a cleaned value is attached to by joining ItemValuesMap to +/// BaseItems. One item can reach the same clean value through more than one value row, so the +/// join has to be counted per distinct item; counting rows reports a multiple of the truth. +/// </summary> +public sealed class BaseItemRepositoryByNameItemCountsTests : SqliteDbTestFixture +{ + private readonly BaseItemRepository _repository; + private readonly ItemTypeLookup _itemTypeLookup; + + public BaseItemRepositoryByNameItemCountsTests() + { + _itemTypeLookup = new ItemTypeLookup(); + _repository = CreateBaseItemRepository(_itemTypeLookup); + } + + [Fact] + public void GetAllArtists_AlbumCreditedAsArtistAndAlbumArtist_CountsTheAlbumOnce() + { + // GetAllArtists spans both credit types, so an album whose artist is also its album artist + // reaches the one clean value through two rows. + SeedArtistWithAlbum(ItemValueType.Artist, ItemValueType.AlbumArtist); + + var result = _repository.GetAllArtists(CreateCountingQuery()); + + var (_, counts) = Assert.Single(result.Items); + Assert.NotNull(counts); + Assert.Equal(1, counts.AlbumCount); + Assert.Equal(1, counts.ItemCount); + } + + [Fact] + public void GetAlbumArtists_TwoValueRowsCleaningToOneName_CountsTheAlbumOnce() + { + // The shape that actually reaches users: only (Type, Value) is unique, so two differently + // cased credits of one type both clean down to a single name and both map the album. + SeedArtistWithAlbum(ItemValueType.AlbumArtist, ItemValueType.AlbumArtist); + + var result = _repository.GetAlbumArtists(CreateCountingQuery()); + + var (_, counts) = Assert.Single(result.Items); + Assert.NotNull(counts); + Assert.Equal(1, counts.AlbumCount); + } + + [Fact] + public void GetArtists_TwoValueRowsCleaningToOneName_CountsTheAlbumOnce() + { + SeedArtistWithAlbum(ItemValueType.Artist, ItemValueType.Artist); + + var result = _repository.GetArtists(CreateCountingQuery()); + + var (_, counts) = Assert.Single(result.Items); + Assert.NotNull(counts); + Assert.Equal(1, counts.AlbumCount); + } + + [Theory] + [InlineData(BaseItemKind.Book)] + [InlineData(BaseItemKind.BoxSet)] + public void GetGenres_TaggedBookOrBoxSet_CountsIt(BaseItemKind kind) + { + // The listing used to dispatch only nine of the eleven counted types, so a genre on a book + // or a box set read as zero in a list and as one on the genre's own page. + SeedGenreWith(kind); + + var result = _repository.GetGenres(CreateCountingQuery()); + + var (_, counts) = Assert.Single(result.Items); + Assert.NotNull(counts); + Assert.Equal(1, kind == BaseItemKind.Book ? counts.BookCount : counts.BoxSetCount); + Assert.Equal(1, counts.ItemCount); + } + + /// <summary> + /// Seeds one genre carried by a single item of the given kind. + /// </summary> + /// <param name="kind">The kind of the tagged item.</param> + private void SeedGenreWith(BaseItemKind kind) + { + const string Name = "Reference"; + const string CleanName = "reference"; + + using var ctx = CreateDbContext(); + + var genreId = Guid.Parse("dddddddd-0000-0000-0000-000000000001"); + var taggedId = Guid.Parse("eeeeeeee-0000-0000-0000-000000000001"); + + ctx.BaseItems.Add(new BaseItemEntity + { + Id = genreId, + Type = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Genre], + Name = Name, + CleanName = CleanName, + PresentationUniqueKey = genreId.ToString("N"), + IsFolder = true, + IsVirtualItem = false + }); + + var tagged = new BaseItemEntity + { + Id = taggedId, + Type = _itemTypeLookup.BaseItemKindNames[kind], + Name = "Tagged", + CleanName = "tagged", + PresentationUniqueKey = taggedId.ToString("N"), + IsFolder = false, + IsVirtualItem = false + }; + ctx.BaseItems.Add(tagged); + + var itemValue = new ItemValue + { + ItemValueId = Guid.Parse("ffffffff-0000-0000-0000-000000000001"), + Type = ItemValueType.Genre, + Value = Name, + CleanValue = CleanName + }; + + ctx.ItemValues.Add(itemValue); + ctx.ItemValuesMap.Add(new ItemValueMap + { + ItemId = taggedId, + ItemValueId = itemValue.ItemValueId, + Item = tagged, + ItemValue = itemValue + }); + + ctx.SaveChanges(); + } + + private static InternalItemsQuery CreateCountingQuery() + { + return new InternalItemsQuery(new User("test", "auth", "reset")) + { + DtoOptions = new DtoOptions(true) { Fields = [ItemFields.ItemCounts] } + }; + } + + /// <summary> + /// Seeds one artist and a single album mapped to that artist's clean name through two value + /// rows of the given types. + /// </summary> + /// <param name="first">The type of the first value row.</param> + /// <param name="second">The type of the second value row.</param> + private void SeedArtistWithAlbum(ItemValueType first, ItemValueType second) + { + const string Name = "Tangerine Dream"; + const string CleanName = "tangerine dream"; + + using var ctx = CreateDbContext(); + + var artistId = Guid.Parse("aaaaaaaa-0000-0000-0000-000000000001"); + var albumId = Guid.Parse("bbbbbbbb-0000-0000-0000-000000000001"); + + ctx.BaseItems.Add(new BaseItemEntity + { + Id = artistId, + Type = _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist], + Name = Name, + CleanName = CleanName, + PresentationUniqueKey = artistId.ToString("N"), + IsFolder = true, + IsVirtualItem = false + }); + + var album = new BaseItemEntity + { + Id = albumId, + Type = _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicAlbum], + Name = "Phaedra", + CleanName = "phaedra", + PresentationUniqueKey = albumId.ToString("N"), + IsFolder = true, + IsVirtualItem = false + }; + ctx.BaseItems.Add(album); + + var types = new[] { first, second }; + for (var i = 0; i < types.Length; i++) + { + var itemValue = new ItemValue + { + ItemValueId = Guid.Parse($"cccccccc-0000-0000-0000-{i:D12}"), + Type = types[i], + // Distinct values, one clean name: exactly what the unique index permits. + Value = i == 0 ? Name : Name.ToUpperInvariant(), + CleanValue = CleanName + }; + + ctx.ItemValues.Add(itemValue); + ctx.ItemValuesMap.Add(new ItemValueMap + { + ItemId = albumId, + ItemValueId = itemValue.ItemValueId, + Item = album, + ItemValue = itemValue + }); + } + + ctx.SaveChanges(); + } +} |
