aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Item/ItemCountServiceTests.cs130
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/SessionManager/IdlePlaybackTests.cs80
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/SessionManager/SessionInfoTests.cs92
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Users/UserManagerProfileImageTests.cs23
4 files changed, 325 insertions, 0 deletions
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Item/ItemCountServiceTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Item/ItemCountServiceTests.cs
new file mode 100644
index 0000000000..0766ca8d1e
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Item/ItemCountServiceTests.cs
@@ -0,0 +1,130 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Jellyfin.Database.Implementations;
+using Jellyfin.Database.Implementations.Entities;
+using Jellyfin.Database.Implementations.Locking;
+using Jellyfin.Database.Providers.Sqlite;
+using Jellyfin.Server.Implementations.Item;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Persistence;
+using Microsoft.Data.Sqlite;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Logging.Abstractions;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.Server.Implementations.Tests.Item;
+
+public sealed class ItemCountServiceTests : IDisposable
+{
+ private readonly SqliteConnection _connection;
+ private readonly DbContextOptions<JellyfinDbContext> _dbOptions;
+ private readonly IApplicationPaths _applicationPaths;
+ private readonly ItemCountService _service;
+
+ public ItemCountServiceTests()
+ {
+ _applicationPaths = new Mock<IApplicationPaths>().Object;
+
+ _connection = new SqliteConnection("Data Source=:memory:");
+ _connection.Open();
+
+ _dbOptions = new DbContextOptionsBuilder<JellyfinDbContext>()
+ .UseSqlite(_connection)
+ .Options;
+
+ using (var context = CreateDbContext())
+ {
+ context.Database.EnsureCreated();
+ }
+
+ var factory = new Mock<IDbContextFactory<JellyfinDbContext>>();
+ factory.Setup(f => f.CreateDbContext()).Returns(CreateDbContext);
+
+ _service = new ItemCountService(
+ factory.Object,
+ new Mock<IItemTypeLookup>().Object,
+ new Mock<IItemQueryHelpers>().Object);
+ }
+
+ public void Dispose()
+ {
+ _connection.Dispose();
+ }
+
+ [Fact]
+ public void GetChildCountBatch_LargeParentIdSet_DoesNotExceedSqliteVariableLimit()
+ {
+ var hierarchicalParentId = Guid.NewGuid();
+ var linkedParentId = Guid.NewGuid();
+
+ var hierarchicalChildId = Guid.NewGuid();
+ var linkedChildId1 = Guid.NewGuid();
+ var linkedChildId2 = Guid.NewGuid();
+
+ using (var context = CreateDbContext())
+ {
+ context.BaseItems.AddRange(
+ CreateItem(hierarchicalParentId),
+ CreateItem(linkedParentId),
+ CreateItem(hierarchicalChildId, hierarchicalParentId),
+ CreateItem(linkedChildId1),
+ CreateItem(linkedChildId2));
+
+ context.LinkedChildren.AddRange(
+ new LinkedChildEntity
+ {
+ ParentId = linkedParentId,
+ ChildId = linkedChildId1,
+ ChildType = LinkedChildType.Manual,
+ SortOrder = 0
+ },
+ new LinkedChildEntity
+ {
+ ParentId = linkedParentId,
+ ChildId = linkedChildId2,
+ ChildType = LinkedChildType.Manual,
+ SortOrder = 1
+ });
+
+ context.SaveChanges();
+ }
+
+ var parentIds = Enumerable.Range(0, 40_000)
+ .Select(_ => Guid.NewGuid())
+ .ToList();
+
+ parentIds.Add(hierarchicalParentId);
+ parentIds.Add(linkedParentId);
+
+ var result = _service.GetChildCountBatch(parentIds, null);
+
+ Assert.Equal(1, result[hierarchicalParentId]);
+ Assert.Equal(2, result[linkedParentId]);
+ Assert.Equal(parentIds.Count, result.Count);
+ }
+
+ private static BaseItemEntity CreateItem(Guid id, Guid? parentId = null)
+ {
+ return new BaseItemEntity
+ {
+ Id = id,
+ Type = "Folder",
+ ParentId = parentId,
+ IsFolder = true
+ };
+ }
+
+ private JellyfinDbContext CreateDbContext()
+ {
+ return new JellyfinDbContext(
+ _dbOptions,
+ NullLogger<JellyfinDbContext>.Instance,
+ new SqliteDatabaseProvider(
+ _applicationPaths,
+ NullLogger<SqliteDatabaseProvider>.Instance),
+ new NoLockBehavior(NullLogger<NoLockBehavior>.Instance));
+ }
+}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/SessionManager/IdlePlaybackTests.cs b/tests/Jellyfin.Server.Implementations.Tests/SessionManager/IdlePlaybackTests.cs
new file mode 100644
index 0000000000..7722707cbe
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/SessionManager/IdlePlaybackTests.cs
@@ -0,0 +1,80 @@
+using System;
+using System.Reflection;
+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.Events;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.Dto;
+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 IdlePlaybackTests
+{
+ [Theory]
+ [InlineData(null, null)]
+ [InlineData(123456789L, 123456789L)]
+ public async Task CheckForIdlePlayback_StopsAtLastClientReportedPosition(long? clientPositionTicks, long? expectedPositionTicks)
+ {
+ var playbackStopped = new TaskCompletionSource<long?>(TaskCreationOptions.RunContinuationsAsynchronously);
+ var eventManager = new Mock<IEventManager>();
+ eventManager
+ .Setup(manager => manager.PublishAsync(It.IsAny<PlaybackStopEventArgs>()))
+ .Callback<PlaybackStopEventArgs>(eventArgs => playbackStopped.TrySetResult(eventArgs.PlaybackPositionTicks))
+ .Returns(Task.CompletedTask);
+ await using var sessionManager = new Emby.Server.Implementations.Session.SessionManager(
+ NullLogger<Emby.Server.Implementations.Session.SessionManager>.Instance,
+ eventManager.Object,
+ Mock.Of<IUserDataManager>(),
+ 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>());
+ var session = await sessionManager.LogSessionActivity(
+ "Test Client",
+ "1.0.0",
+ "test-device",
+ "Test Device",
+ "127.0.0.1",
+ null);
+ session.NowPlayingItem = new BaseItemDto
+ {
+ Id = Guid.NewGuid(),
+ Name = "Test Item"
+ };
+ session.PlayState.PositionTicks = 987654321;
+
+ if (clientPositionTicks.HasValue)
+ {
+ session.StartAutomaticProgress(new PlaybackProgressInfo
+ {
+ IsPaused = true,
+ PositionTicks = clientPositionTicks
+ });
+ session.StopAutomaticProgress();
+ }
+
+ var idlePlaybackCallback = typeof(Emby.Server.Implementations.Session.SessionManager)
+ .GetMethod("CheckForIdlePlayback", BindingFlags.Instance | BindingFlags.NonPublic)!;
+ idlePlaybackCallback.Invoke(sessionManager, new object?[] { null });
+
+ var stoppedPositionTicks = await playbackStopped.Task.WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
+
+ Assert.Equal(expectedPositionTicks, stoppedPositionTicks);
+ }
+}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/SessionManager/SessionInfoTests.cs b/tests/Jellyfin.Server.Implementations.Tests/SessionManager/SessionInfoTests.cs
new file mode 100644
index 0000000000..c5b8f661b5
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/SessionManager/SessionInfoTests.cs
@@ -0,0 +1,92 @@
+using System;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.Session;
+using Microsoft.Extensions.Logging.Abstractions;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.Server.Implementations.Tests.SessionManager;
+
+public class SessionInfoTests
+{
+ [Fact]
+ public async Task StartAutomaticProgress_SnapshotsClientReportedPosition()
+ {
+ await using var session = new SessionInfo(Mock.Of<ISessionManager>(), NullLogger.Instance);
+ var progressInfo = new PlaybackProgressInfo
+ {
+ IsPaused = true,
+ PositionTicks = 123456789
+ };
+
+ session.StartAutomaticProgress(progressInfo);
+
+ Assert.Equal(progressInfo.PositionTicks, session.LastPlaybackCheckInPositionTicks);
+ }
+
+ [Fact]
+ public async Task AutomaticProgress_AdvancesEstimatedPositionWithoutAdvancingSnapshot()
+ {
+ var sessionManager = new Mock<ISessionManager>();
+ await using var session = new SessionInfo(sessionManager.Object, NullLogger.Instance);
+ var automaticProgress = new TaskCompletionSource<long?>(TaskCreationOptions.RunContinuationsAsynchronously);
+ const long reportedPositionTicks = 123456789;
+
+ sessionManager
+ .Setup(manager => manager.OnPlaybackProgress(It.IsAny<PlaybackProgressInfo>(), true))
+ .Callback<PlaybackProgressInfo, bool>((info, _) =>
+ {
+ session.PlayState.PositionTicks = info.PositionTicks;
+ automaticProgress.TrySetResult(info.PositionTicks);
+ })
+ .Returns(Task.CompletedTask);
+ session.PlayState.PositionTicks = reportedPositionTicks;
+
+ session.StartAutomaticProgress(new PlaybackProgressInfo
+ {
+ PositionTicks = reportedPositionTicks
+ });
+
+ var estimatedPositionTicks = await automaticProgress.Task.WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
+ session.StopAutomaticProgress();
+
+ Assert.Equal(reportedPositionTicks + TimeSpan.TicksPerSecond, estimatedPositionTicks);
+ Assert.Equal(estimatedPositionTicks, session.PlayState.PositionTicks);
+ Assert.Equal(reportedPositionTicks, session.LastPlaybackCheckInPositionTicks);
+ }
+
+ [Fact]
+ public async Task StartAutomaticProgress_ReplacesSnapshotOnLaterClientReport()
+ {
+ await using var session = new SessionInfo(Mock.Of<ISessionManager>(), NullLogger.Instance);
+ session.StartAutomaticProgress(new PlaybackProgressInfo
+ {
+ IsPaused = true,
+ PositionTicks = 123456789
+ });
+
+ session.StartAutomaticProgress(new PlaybackProgressInfo
+ {
+ IsPaused = true,
+ PositionTicks = 987654321
+ });
+
+ Assert.Equal(987654321, session.LastPlaybackCheckInPositionTicks);
+ }
+
+ [Fact]
+ public async Task StartAutomaticProgress_PreservesExactPausedPosition()
+ {
+ await using var session = new SessionInfo(Mock.Of<ISessionManager>(), NullLogger.Instance);
+ var pausedProgress = new PlaybackProgressInfo
+ {
+ IsPaused = true,
+ PositionTicks = 314159265
+ };
+
+ session.StartAutomaticProgress(pausedProgress);
+
+ Assert.Equal(pausedProgress.PositionTicks, session.LastPlaybackCheckInPositionTicks);
+ }
+}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Users/UserManagerProfileImageTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Users/UserManagerProfileImageTests.cs
index cb714a4014..778b888735 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Users/UserManagerProfileImageTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/Users/UserManagerProfileImageTests.cs
@@ -1,5 +1,6 @@
using System;
using System.IO;
+using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Database.Implementations;
@@ -92,6 +93,28 @@ namespace Jellyfin.Server.Implementations.Tests.Users
}
[Fact]
+ public async Task UpdateUserAsync_DoesNotLeaveOrphanedPermissionsOrPreferences()
+ {
+ var user = await _userManager.CreateUserAsync("updateduser");
+ var permissionCount = user.Permissions.Count;
+ var preferenceCount = user.Preferences.Count;
+
+ user.LastActivityDate = DateTime.UtcNow;
+ await _userManager.UpdateUserAsync(user);
+ await _userManager.UpdateUserAsync(user);
+
+ await using var context = CreateDbContext();
+ Assert.Empty(await context.Permissions
+ .Where(permission => !permission.UserId.HasValue)
+ .ToListAsync(TestContext.Current.CancellationToken));
+ Assert.Empty(await context.Preferences
+ .Where(preference => !preference.UserId.HasValue)
+ .ToListAsync(TestContext.Current.CancellationToken));
+ Assert.Equal(permissionCount, await context.Permissions.CountAsync(TestContext.Current.CancellationToken));
+ Assert.Equal(preferenceCount, await context.Preferences.CountAsync(TestContext.Current.CancellationToken));
+ }
+
+ [Fact]
public async Task ClearProfileImageAsync_WhenInMemoryImageHasTemporaryKey_RemovesPersistedImage()
{
var user = await _userManager.CreateUserAsync("profileimageuser");