diff options
Diffstat (limited to 'tests/Jellyfin.Server.Implementations.Tests/SessionManager')
| -rw-r--r-- | tests/Jellyfin.Server.Implementations.Tests/SessionManager/PlayCommandQueueTests.cs | 93 | ||||
| -rw-r--r-- | tests/Jellyfin.Server.Implementations.Tests/SessionManager/RewatchPlayStateTests.cs | 112 |
2 files changed, 205 insertions, 0 deletions
diff --git a/tests/Jellyfin.Server.Implementations.Tests/SessionManager/PlayCommandQueueTests.cs b/tests/Jellyfin.Server.Implementations.Tests/SessionManager/PlayCommandQueueTests.cs new file mode 100644 index 0000000000..13081dcff9 --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/SessionManager/PlayCommandQueueTests.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Devices; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging.Abstractions; +using Moq; +using Xunit; + +namespace Jellyfin.Server.Implementations.Tests.SessionManager; + +public class PlayCommandQueueTests : IDisposable +{ + private readonly ILibraryManager? _previousLibraryManager; + + public PlayCommandQueueTests() + { + _previousLibraryManager = BaseItem.LibraryManager; + } + + /// <summary> + /// A music genre tags its artists as well as their songs, and a by-name artist row is not a + /// folder, so the queue query cannot exclude it. Such an item has no media sources, and a + /// client that reaches it in the queue gets an error instead of the next track. + /// </summary> + /// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns> + [Fact] + public async Task SendPlayCommand_GenreTaggingAnArtist_QueuesOnlyPlayableItems() + { + var genre = new MusicGenre { Id = Guid.NewGuid(), Name = "Reggaeton" }; + var song = new Audio { Id = Guid.NewGuid(), Name = "Me Porto Bonito" }; + var artist = new MusicArtist { Id = Guid.NewGuid(), Name = "NATTI NATASHA" }; + + var libraryManager = new Mock<ILibraryManager>(); + libraryManager.Setup(i => i.GetItemById(genre.Id)).Returns(genre); + libraryManager + .Setup(i => i.GetItemList(It.IsAny<InternalItemsQuery>())) + .Returns(new List<BaseItem> { artist, song }); + BaseItem.LibraryManager = libraryManager.Object; + + await using var sessionManager = new Emby.Server.Implementations.Session.SessionManager( + NullLogger<Emby.Server.Implementations.Session.SessionManager>.Instance, + Mock.Of<IEventManager>(), + Mock.Of<IUserDataManager>(), + Mock.Of<IServerConfigurationManager>(), + libraryManager.Object, + Mock.Of<IUserManager>(), + Mock.Of<IMusicManager>(), + Mock.Of<IDtoService>(), + Mock.Of<IImageProcessor>(), + Mock.Of<IServerApplicationHost>(), + Mock.Of<IDeviceManager>(), + Mock.Of<IMediaSourceManager>(), + Mock.Of<IHostApplicationLifetime>()); + + var session = await sessionManager.LogSessionActivity("app_name", "0.0.0", "device_id", "device_name", "127.0.0.1", null); + + var command = new PlayRequest + { + ItemIds = new[] { genre.Id }, + PlayCommand = PlayCommand.PlayNow + }; + + await sessionManager.SendPlayCommand(null, session.Id, command, CancellationToken.None); + + Assert.Equal(new[] { song.Id }, command.ItemIds); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + BaseItem.LibraryManager = _previousLibraryManager!; + } + } +} diff --git a/tests/Jellyfin.Server.Implementations.Tests/SessionManager/RewatchPlayStateTests.cs b/tests/Jellyfin.Server.Implementations.Tests/SessionManager/RewatchPlayStateTests.cs new file mode 100644 index 0000000000..dd2e935feb --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/SessionManager/RewatchPlayStateTests.cs @@ -0,0 +1,112 @@ +using System; +using System.Reflection; +using System.Threading.Tasks; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Devices; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Library; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging.Abstractions; +using Moq; +using Xunit; + +namespace Jellyfin.Server.Implementations.Tests.SessionManager; + +[Collection("LibraryManagerTests")] +public sealed class RewatchPlayStateTests : IDisposable +{ + private const long ResumeThresholdTicks = 100 * TimeSpan.TicksPerSecond; + + private static readonly DateTime _previousPlayDate = new(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + private readonly ILibraryManager? _previousLibraryManager; + + public RewatchPlayStateTests() + { + _previousLibraryManager = BaseItem.LibraryManager; + + // Resolves the (empty) alternate versions of the episode when the stop propagates its played state + BaseItem.LibraryManager = Mock.Of<ILibraryManager>(); + } + + public void Dispose() + { + BaseItem.LibraryManager = _previousLibraryManager!; + } + + [Theory] + [InlineData(3 * TimeSpan.TicksPerSecond, false)] + [InlineData(600 * TimeSpan.TicksPerSecond, true)] + public async Task RewatchOfPlayedEpisode_OnlyCountsPastResumeThreshold(long stopPositionTicks, bool expectDateUpdated) + { + var data = new UserItemData { Key = "episode", Played = true, LastPlayedDate = _previousPlayDate }; + await using var sessionManager = CreateSessionManager(data); + var user = new User("test", "default", "default"); + var episode = new Episode { Id = Guid.NewGuid() }; + + Invoke(sessionManager, "OnPlaybackStart", user, episode); + Assert.Equal(_previousPlayDate, data.LastPlayedDate); + + Invoke(sessionManager, "OnPlaybackStopped", user, episode, stopPositionTicks, false); + Assert.True(data.Played); + Assert.Equal(expectDateUpdated, data.LastPlayedDate > _previousPlayDate); + } + + [Fact] + public async Task PlaybackStartOfUnplayedEpisode_UpdatesLastPlayedDate() + { + var data = new UserItemData { Key = "episode", LastPlayedDate = _previousPlayDate }; + await using var sessionManager = CreateSessionManager(data); + + Invoke(sessionManager, "OnPlaybackStart", new User("test", "default", "default"), new Episode { Id = Guid.NewGuid() }); + + Assert.True(data.LastPlayedDate > _previousPlayDate); + } + + private static Emby.Server.Implementations.Session.SessionManager CreateSessionManager(UserItemData data) + { + var userDataManager = new Mock<IUserDataManager>(); + userDataManager + .Setup(m => m.GetUserData(It.IsAny<User>(), It.IsAny<BaseItem>())) + .Returns(data); + userDataManager + .Setup(m => m.UpdatePlayState(It.IsAny<BaseItem>(), It.IsAny<UserItemData>(), It.IsAny<long?>())) + .Callback<BaseItem, UserItemData, long?>((_, d, position) => d.PlaybackPositionTicks = position >= ResumeThresholdTicks ? position.Value : 0) + .Returns(false); + + return new Emby.Server.Implementations.Session.SessionManager( + NullLogger<Emby.Server.Implementations.Session.SessionManager>.Instance, + Mock.Of<IEventManager>(), + userDataManager.Object, + Mock.Of<IServerConfigurationManager>(), + Mock.Of<ILibraryManager>(), + Mock.Of<IUserManager>(), + Mock.Of<IMusicManager>(), + Mock.Of<IDtoService>(), + Mock.Of<IImageProcessor>(), + Mock.Of<IServerApplicationHost>(), + Mock.Of<IDeviceManager>(), + Mock.Of<IMediaSourceManager>(), + Mock.Of<IHostApplicationLifetime>()); + } + + private static void Invoke(Emby.Server.Implementations.Session.SessionManager sessionManager, string name, params object?[] args) + { + var parameterTypes = Array.ConvertAll(args, a => a!.GetType()); + parameterTypes[1] = typeof(BaseItem); + if (args.Length > 2) + { + parameterTypes[2] = typeof(long?); + } + + typeof(Emby.Server.Implementations.Session.SessionManager) + .GetMethod(name, BindingFlags.Instance | BindingFlags.NonPublic, parameterTypes)! + .Invoke(sessionManager, args); + } +} |
