1c59e6afcb
Next Up walked every series in an unpaginated loop and read user data one episode at a time, so the Home screen row cost two queries per series once the cache was gone. - read the played state of every candidate episode in one query - read the versions the resume check and the last played date need in one query each - default PrefetchedUserData on IUserBaseItemComparer so plugin comparers still compile - cover the bounded query count and the plugin comparer with tests - share one database across the replica tests
95 lines
3.6 KiB
C#
95 lines
3.6 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using Emby.Server.Implementations.TV;
|
|
using Jellyfin.Database.Implementations.Entities;
|
|
using MediaBrowser.Controller.Configuration;
|
|
using MediaBrowser.Controller.Dto;
|
|
using MediaBrowser.Controller.Entities;
|
|
using MediaBrowser.Controller.Entities.TV;
|
|
using MediaBrowser.Controller.Library;
|
|
using MediaBrowser.Controller.Persistence;
|
|
using MediaBrowser.Model.Configuration;
|
|
using MediaBrowser.Model.Querying;
|
|
using Moq;
|
|
using Xunit;
|
|
|
|
namespace Jellyfin.Server.Implementations.Tests.TV;
|
|
|
|
public class TVSeriesManagerNextUpTests
|
|
{
|
|
[Theory]
|
|
[InlineData(1)]
|
|
[InlineData(25)]
|
|
[InlineData(200)]
|
|
public void GetNextUp_ReadsUserDataInABoundedNumberOfQueries(int seriesCount)
|
|
{
|
|
var user = new User("next-up", "provider", "provider");
|
|
var libraryManager = new Mock<ILibraryManager>();
|
|
var userDataManager = new Mock<IUserDataManager>();
|
|
|
|
var seriesKeys = Enumerable.Range(0, seriesCount)
|
|
.Select(i => i.ToString(CultureInfo.InvariantCulture))
|
|
.ToList();
|
|
|
|
var batch = seriesKeys.ToDictionary(
|
|
key => key,
|
|
key => new NextUpEpisodeBatchResult
|
|
{
|
|
NextUp = new Episode { Id = Guid.NewGuid(), Name = "Next " + key },
|
|
LastWatched = new Episode { Id = Guid.NewGuid(), Name = "Watched " + key }
|
|
});
|
|
|
|
libraryManager
|
|
.Setup(l => l.GetNextUpSeriesKeys(It.IsAny<InternalItemsQuery>(), It.IsAny<IReadOnlyCollection<BaseItem>>(), It.IsAny<DateTime>()))
|
|
.Returns(seriesKeys);
|
|
libraryManager
|
|
.Setup(l => l.GetNextUpEpisodesBatch(It.IsAny<InternalItemsQuery>(), It.IsAny<IReadOnlyList<string>>(), It.IsAny<bool>(), It.IsAny<bool>()))
|
|
.Returns(batch);
|
|
libraryManager.Setup(l => l.GetLinkedAlternateVersions(It.IsAny<Video>())).Returns([]);
|
|
libraryManager.Setup(l => l.GetLocalAlternateVersionIds(It.IsAny<Video>())).Returns([]);
|
|
|
|
var batchReads = 0;
|
|
userDataManager
|
|
.Setup(u => u.GetUserDataBatch(It.IsAny<IReadOnlyList<BaseItem>>(), It.IsAny<User>()))
|
|
.Returns<IReadOnlyList<BaseItem>, User>((items, _) =>
|
|
{
|
|
batchReads++;
|
|
return items.DistinctBy(i => i.Id).ToDictionary(
|
|
i => i.Id,
|
|
i => new UserItemData { Key = i.Id.ToString("N", CultureInfo.InvariantCulture) });
|
|
});
|
|
|
|
var previousLibraryManager = BaseItem.LibraryManager;
|
|
BaseItem.LibraryManager = libraryManager.Object;
|
|
|
|
try
|
|
{
|
|
var manager = new TVSeriesManager(userDataManager.Object, libraryManager.Object, CreateConfigurationManager());
|
|
|
|
var result = manager.GetNextUp(
|
|
new NextUpQuery { User = user, EnableTotalRecordCount = true },
|
|
[],
|
|
new DtoOptions(false));
|
|
|
|
Assert.Equal(seriesCount, result.TotalRecordCount);
|
|
|
|
// Selection, the resume check and the last played date: three reads whatever the library holds.
|
|
Assert.Equal(3, batchReads);
|
|
userDataManager.Verify(u => u.GetUserData(It.IsAny<User>(), It.IsAny<BaseItem>()), Times.Never);
|
|
}
|
|
finally
|
|
{
|
|
BaseItem.LibraryManager = previousLibraryManager;
|
|
}
|
|
}
|
|
|
|
private static IServerConfigurationManager CreateConfigurationManager()
|
|
{
|
|
var configurationManager = new Mock<IServerConfigurationManager>();
|
|
configurationManager.SetupGet(c => c.Configuration).Returns(new ServerConfiguration());
|
|
return configurationManager.Object;
|
|
}
|
|
}
|