aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/Jellyfin.Providers.Tests/Manager/MetadataServiceRefreshTests.cs99
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Dto/DtoServiceTests.cs57
2 files changed, 155 insertions, 1 deletions
diff --git a/tests/Jellyfin.Providers.Tests/Manager/MetadataServiceRefreshTests.cs b/tests/Jellyfin.Providers.Tests/Manager/MetadataServiceRefreshTests.cs
index 1d2fb2e760..3b4d6fc9bb 100644
--- a/tests/Jellyfin.Providers.Tests/Manager/MetadataServiceRefreshTests.cs
+++ b/tests/Jellyfin.Providers.Tests/Manager/MetadataServiceRefreshTests.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
@@ -11,6 +12,7 @@ using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Providers.Manager;
@@ -228,6 +230,88 @@ namespace Jellyfin.Providers.Tests.Manager
Assert.Equal("nm0000123", mergedPerson.GetProviderId(MetadataProvider.Imdb));
}
+ [Theory]
+ [InlineData(MetadataRefreshMode.FullRefresh, true)]
+ [InlineData(MetadataRefreshMode.Default, false)]
+ public async Task RefreshMetadata_ProvidersFoundNothing_PersistsRefreshDateOnFullRefresh(MetadataRefreshMode mode, bool expectSaved)
+ {
+ var item = new TestItem
+ {
+ Id = Guid.NewGuid(),
+ Name = "Test Item",
+ PreferredMetadataLanguage = "en",
+ PreferredMetadataCountryCode = "US",
+ DateLastRefreshed = DateTime.UtcNow.AddDays(-60),
+ DateLastSaved = DateTime.UtcNow.AddDays(-60)
+ };
+ item.PresentationUniqueKey = item.CreatePresentationUniqueKey();
+
+ var stampBefore = item.DateLastRefreshed;
+
+ var provider = new Mock<IRemoteMetadataProvider<TestItem, ItemLookupInfo>>(MockBehavior.Loose);
+ provider.Setup(p => p.Name).Returns("Provider");
+ provider.Setup(p => p.GetMetadata(It.IsAny<ItemLookupInfo>(), It.IsAny<CancellationToken>()))
+ .ReturnsAsync(new MetadataResult<TestItem> { HasMetadata = false });
+
+ var libraryManager = new Mock<ILibraryManager>(MockBehavior.Loose);
+ libraryManager.Setup(l => l.GetLibraryOptions(It.IsAny<BaseItem>())).Returns(new LibraryOptions());
+
+ var providerManager = new Mock<IProviderManager>(MockBehavior.Loose);
+ providerManager.Setup(p => p.GetImageProviders(It.IsAny<BaseItem>(), It.IsAny<ImageRefreshOptions>()))
+ .Returns(Array.Empty<IImageProvider>());
+ providerManager.Setup(p => p.GetMetadataProviders<TestItem>(It.IsAny<BaseItem>(), It.IsAny<LibraryOptions>()))
+ .Returns(new[] { (IMetadataProvider<TestItem>)provider.Object });
+ providerManager.Setup(p => p.GetMetadataSavers(It.IsAny<BaseItem>(), It.IsAny<LibraryOptions>()))
+ .Returns(Array.Empty<IMetadataSaver>());
+
+ var itemRepository = new Mock<IItemRepository>(MockBehavior.Loose);
+ itemRepository.Setup(r => r.ItemExistsAsync(It.IsAny<Guid>())).ReturnsAsync(true);
+
+ var service = new TestItemMetadataService(libraryManager.Object, providerManager.Object, itemRepository.Object);
+
+ await service.RefreshMetadata(
+ item,
+ new MetadataRefreshOptions(Mock.Of<IDirectoryService>())
+ {
+ MetadataRefreshMode = mode,
+ ImageRefreshMode = mode
+ },
+ CancellationToken.None).ConfigureAwait(true);
+
+ // Nothing was found, so on a full refresh the advanced stamp is the only reason to write the row.
+ Assert.Equal(expectSaved, item.Saved);
+
+ if (expectSaved)
+ {
+ Assert.True(item.DateLastRefreshed > stampBefore);
+ }
+ }
+
+ /// <summary>
+ /// Stands in for a real item so the refresh stays off the shared BaseItem statics, which other
+ /// test classes in this assembly overwrite while xUnit runs them in parallel.
+ /// </summary>
+ internal sealed class TestItem : BaseItem
+ {
+ public bool Saved { get; private set; }
+
+ public override bool RequiresRefresh() => false;
+
+ public override bool IsSaveLocalMetadataEnabled() => false;
+
+ public override string CreatePresentationUniqueKey() => Id.ToString("N", CultureInfo.InvariantCulture);
+
+ public override ItemUpdateType OnMetadataChanged() => ItemUpdateType.None;
+
+ public override bool BeforeMetadataRefresh(bool replaceAllMetadata) => false;
+
+ public override Task UpdateToRepositoryAsync(ItemUpdateType updateReason, CancellationToken cancellationToken)
+ {
+ Saved = true;
+ return Task.CompletedTask;
+ }
+ }
+
private sealed class TestMetadataService : MetadataService<Movie, MovieInfo>
{
public TestMetadataService()
@@ -249,5 +333,20 @@ namespace Jellyfin.Providers.Tests.Manager
ICollection<IMetadataProvider> providers)
=> RefreshWithProviders(metadata, id, options, providers, ImageProvider, false, CancellationToken.None);
}
+
+ private sealed class TestItemMetadataService : MetadataService<TestItem, ItemLookupInfo>
+ {
+ public TestItemMetadataService(ILibraryManager libraryManager, IProviderManager providerManager, IItemRepository itemRepository)
+ : base(
+ Mock.Of<IServerConfigurationManager>(),
+ NullLogger<MetadataService<TestItem, ItemLookupInfo>>.Instance,
+ providerManager,
+ Mock.Of<IFileSystem>(),
+ libraryManager,
+ Mock.Of<IExternalDataManager>(),
+ itemRepository)
+ {
+ }
+ }
}
}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Dto/DtoServiceTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Dto/DtoServiceTests.cs
index 9c247d54b9..bdac59c013 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Dto/DtoServiceTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/Dto/DtoServiceTests.cs
@@ -1,5 +1,7 @@
using System;
+using System.Collections.Generic;
using Emby.Server.Implementations.Dto;
+using Jellyfin.Database.Implementations.Entities;
using MediaBrowser.Common;
using MediaBrowser.Controller.Chapters;
using MediaBrowser.Controller.Drawing;
@@ -21,11 +23,13 @@ namespace Jellyfin.Server.Implementations.Tests.Dto;
public class DtoServiceTests
{
private readonly Mock<ILibraryManager> _libraryManagerMock;
+ private readonly Mock<IUserDataManager> _userDataManagerMock;
private readonly DtoService _dtoService;
public DtoServiceTests()
{
_libraryManagerMock = new Mock<ILibraryManager>();
+ _userDataManagerMock = new Mock<IUserDataManager>();
var imageProcessor = new Mock<IImageProcessor>();
// Deterministic tag derived from the image so each item gets a distinct, assertable tag.
@@ -42,7 +46,7 @@ public class DtoServiceTests
_dtoService = new DtoService(
NullLogger<DtoService>.Instance,
_libraryManagerMock.Object,
- new Mock<IUserDataManager>().Object,
+ _userDataManagerMock.Object,
imageProcessor.Object,
new Mock<IProviderManager>().Object,
new Mock<IRecordingsManager>().Object,
@@ -105,6 +109,57 @@ public class DtoServiceTests
Assert.Null(dto.ParentPrimaryImageItemId);
}
+ [Fact]
+ public void GetBaseItemDtos_SeasonWithNoRealEpisodes_ReportsVirtualEpisodesAsChildCount()
+ {
+ // No episode has aired yet, so RecursiveItemCount is 0. ChildCount must still report the
+ // virtual episodes clients get back for the season. This deliberately does not track
+ // Season.IsVirtualItem: that flag is recomputed only on a full refresh, so a season can
+ // carry it while already holding real episodes.
+ var (season, user) = BuildSeason(playedCount: 0, totalCount: 0, childCount: 10);
+ var options = new DtoOptions(false) { EnableImages = false, Fields = [ItemFields.ChildCount, ItemFields.RecursiveItemCount] };
+
+ var dto = _dtoService.GetBaseItemDtos([season], options, user, skipVisibilityCheck: true)[0];
+
+ Assert.Equal(0, dto.RecursiveItemCount);
+ Assert.Equal(10, dto.ChildCount);
+ }
+
+ [Fact]
+ public void GetBaseItemDtos_SeasonWithRealEpisodes_KeepsRecursiveItemCountAsChildCount()
+ {
+ var (season, user) = BuildSeason(playedCount: 2, totalCount: 9, childCount: 11);
+ var options = new DtoOptions(false) { EnableImages = false, Fields = [ItemFields.ChildCount, ItemFields.RecursiveItemCount] };
+
+ var dto = _dtoService.GetBaseItemDtos([season], options, user, skipVisibilityCheck: true)[0];
+
+ Assert.Equal(9, dto.RecursiveItemCount);
+ // The shortcut still wins over the batched child count, which also counts virtual episodes.
+ Assert.Equal(9, dto.ChildCount);
+ }
+
+ private (Season Season, User User) BuildSeason(int playedCount, int totalCount, int childCount)
+ {
+ var user = new User("user", "auth-provider", "reset-provider");
+ var season = new Season { Id = Guid.NewGuid(), Name = "Season 2", SeriesId = Guid.NewGuid() };
+
+ _userDataManagerMock
+ .Setup(x => x.GetUserDataBatch(It.IsAny<IReadOnlyList<BaseItem>>(), user))
+ .Returns(new Dictionary<Guid, UserItemData> { [season.Id] = new UserItemData { Key = "key" } });
+ _userDataManagerMock
+ .Setup(x => x.GetResumeUserDataBatch(It.IsAny<IReadOnlyList<BaseItem>>(), user))
+ .Returns(new Dictionary<Guid, VersionResumeData>());
+
+ _libraryManagerMock
+ .Setup(x => x.GetPlayedAndTotalCountBatch(It.IsAny<IReadOnlyList<Guid>>(), user))
+ .Returns(new Dictionary<Guid, (int Played, int Total)> { [season.Id] = (playedCount, totalCount) });
+ _libraryManagerMock
+ .Setup(x => x.GetChildCountBatch(It.IsAny<IReadOnlyList<Guid>>(), It.IsAny<Guid?>()))
+ .Returns(new Dictionary<Guid, int> { [season.Id] = childCount });
+
+ return (season, user);
+ }
+
private (Episode Episode, Season Season, Series Series) BuildEpisode(bool seasonHasPoster, bool seriesHasPoster = true)
{
// Non-local (http) paths keep aspect-ratio resolution off the image processor and on the