aboutsummaryrefslogtreecommitdiff
path: root/tests/Jellyfin.Server.Implementations.Tests/SessionManager
diff options
context:
space:
mode:
Diffstat (limited to 'tests/Jellyfin.Server.Implementations.Tests/SessionManager')
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/SessionManager/PlayCommandQueueTests.cs93
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/SessionManager/RewatchPlayStateTests.cs112
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);
+ }
+}