diff options
Diffstat (limited to 'tests/Jellyfin.Server.Implementations.Tests/Item/BaseItemRepositoryPlayedVersionTests.cs')
| -rw-r--r-- | tests/Jellyfin.Server.Implementations.Tests/Item/BaseItemRepositoryPlayedVersionTests.cs | 173 |
1 files changed, 173 insertions, 0 deletions
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Item/BaseItemRepositoryPlayedVersionTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Item/BaseItemRepositoryPlayedVersionTests.cs new file mode 100644 index 0000000000..0958512b1a --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/Item/BaseItemRepositoryPlayedVersionTests.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Emby.Server.Implementations.Data; +using Jellyfin.Database.Implementations; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Server.Implementations.Item; +using MediaBrowser.Controller.Entities; +using Xunit; +using BaseItemKind = Jellyfin.Data.Enums.BaseItemKind; + +namespace Jellyfin.Server.Implementations.Tests.Item; + +/// <summary> +/// Covers the isPlayed filter over items with alternate versions: playback is recorded against the +/// version that was actually played, so the played state belongs to the version group rather than to +/// the row that happens to carry it. +/// </summary> +public sealed class BaseItemRepositoryPlayedVersionTests : SqliteDbTestFixture +{ + private const string MovieType = "MediaBrowser.Controller.Entities.Movies.Movie"; + private const string SeriesType = "MediaBrowser.Controller.Entities.TV.Series"; + private const string EpisodeType = "MediaBrowser.Controller.Entities.TV.Episode"; + + private readonly BaseItemRepository _repository; + private readonly User _user = new("test", "auth-provider", "reset-provider"); + + private readonly Guid _playedViaAlternate = Guid.NewGuid(); + private readonly Guid _playedOnPrimary = Guid.NewGuid(); + private readonly Guid _unplayedWithAlternate = Guid.NewGuid(); + private readonly Guid _unplayedWithoutAlternate = Guid.NewGuid(); + + private readonly Guid _seriesPlayedViaAlternate = Guid.NewGuid(); + private readonly Guid _unplayedSeries = Guid.NewGuid(); + + public BaseItemRepositoryPlayedVersionTests() + { + using (var context = CreateDbContext()) + { + Seed(context); + } + + _repository = CreateBaseItemRepository(new ItemTypeLookup()); + } + + [Fact] + public void IsPlayed_CountsAMoviePlayedThroughItsAlternateVersion() + { + Assert.Equal( + new HashSet<Guid> { _playedOnPrimary, _playedViaAlternate }, + Ids(BaseItemKind.Movie, isPlayed: true)); + } + + [Fact] + public void IsUnplayed_DropsAMoviePlayedThroughItsAlternateVersion() + { + Assert.Equal( + new HashSet<Guid> { _unplayedWithAlternate, _unplayedWithoutAlternate }, + Ids(BaseItemKind.Movie, isPlayed: false)); + } + + [Fact] + public void IsPlayed_KeepsAPlayedPrimaryWhoseAlternateHasNoRowOfItsOwn() + { + Assert.Contains(_playedOnPrimary, Ids(BaseItemKind.Movie, isPlayed: true)); + } + + [Fact] + public void IsPlayed_CountsASeriesWatchedThroughAnEpisodeAlternateVersion() + { + Assert.Equal(new HashSet<Guid> { _seriesPlayedViaAlternate }, Ids(BaseItemKind.Series, isPlayed: true)); + Assert.Equal(new HashSet<Guid> { _unplayedSeries }, Ids(BaseItemKind.Series, isPlayed: false)); + } + + private HashSet<Guid> Ids(BaseItemKind kind, bool isPlayed) + => _repository + .GetItemList(new InternalItemsQuery(_user) + { + IncludeItemTypes = [kind], + IsPlayed = isPlayed + }) + .Select(i => i.Id) + .ToHashSet(); + + private void Seed(JellyfinDbContext context) + { + context.Users.Add(_user); + + // Only the alternate carries the played row, which is what playing that version records. + AddMovieWithAlternate(context, _playedViaAlternate, "A", playedPrimary: false, playedAlternate: true); + AddMovieWithAlternate(context, _playedOnPrimary, "B", playedPrimary: true, playedAlternate: false); + AddMovieWithAlternate(context, _unplayedWithAlternate, "C", playedPrimary: false, playedAlternate: false); + AddItem(context, _unplayedWithoutAlternate, MovieType, "D"); + + AddSeriesWithAlternateEpisode(context, _seriesPlayedViaAlternate, "E", playedAlternate: true); + AddSeriesWithAlternateEpisode(context, _unplayedSeries, "F", playedAlternate: false); + + context.SaveChanges(); + } + + private void AddMovieWithAlternate(JellyfinDbContext context, Guid primaryId, string name, bool playedPrimary, bool playedAlternate) + { + AddItem(context, primaryId, MovieType, name); + AddAlternateVersion(context, primaryId, MovieType, $"{name} 4K", playedAlternate); + + if (playedPrimary) + { + AddPlayedUserData(context, primaryId); + } + } + + private void AddSeriesWithAlternateEpisode(JellyfinDbContext context, Guid seriesId, string name, bool playedAlternate) + { + var episodeId = Guid.NewGuid(); + + context.BaseItems.Add(new BaseItemEntity + { + Id = seriesId, + Type = SeriesType, + Name = name, + SortName = name, + PresentationUniqueKey = seriesId.ToString("N"), + IsFolder = true + }); + + AddItem(context, episodeId, EpisodeType, $"{name} 1"); + context.AncestorIds.Add(new AncestorId { ItemId = episodeId, ParentItemId = seriesId, Item = null!, ParentItem = null! }); + + AddAlternateVersion(context, episodeId, EpisodeType, $"{name} 1 4K", playedAlternate); + } + + private void AddItem(JellyfinDbContext context, Guid id, string type, string name) + => context.BaseItems.Add(new BaseItemEntity + { + Id = id, + Type = type, + Name = name, + SortName = name, + PresentationUniqueKey = id.ToString("N") + }); + + private void AddAlternateVersion(JellyfinDbContext context, Guid primaryId, string type, string name, bool played) + { + var alternateId = Guid.NewGuid(); + + // An alternate presents under its primary's key, which is what collapses the group in listings. + context.BaseItems.Add(new BaseItemEntity + { + Id = alternateId, + Type = type, + Name = name, + SortName = name, + PresentationUniqueKey = primaryId.ToString("N"), + PrimaryVersionId = primaryId + }); + + if (played) + { + AddPlayedUserData(context, alternateId); + } + } + + private void AddPlayedUserData(JellyfinDbContext context, Guid itemId) + => context.UserData.Add(new UserData + { + ItemId = itemId, + UserId = _user.Id, + CustomDataKey = itemId.ToString("N"), + Played = true, + Item = null!, + User = null! + }); +} |
