// // Copyright (c) PlaceholderCompany. All rights reserved. // namespace Jellyfin.Server.Implementations.Tests.Item; using System; using System.Threading; using System.Threading.Tasks; using Jellyfin.Database.Implementations; using Jellyfin.Database.Implementations.Entities; using Jellyfin.Database.Implementations.Locking; using Jellyfin.Server.Implementations.Item; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Persistence; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Moq; using Xunit; public class BaseItemRepositoryTests { [Fact] public void DeserializeBaseItem_WithUnknownType_ReturnsNull() { // Arrange var entity = new BaseItemEntity { Id = Guid.NewGuid(), Type = "NonExistent.Plugin.CustomItemType" }; // Act var result = BaseItemRepository.DeserializeBaseItem(entity, NullLogger.Instance, null, false); // Assert Assert.Null(result); } [Fact] public void DeserializeBaseItem_WithUnknownType_LogsWarning() { // Arrange var entity = new BaseItemEntity { Id = Guid.NewGuid(), Type = "NonExistent.Plugin.CustomItemType" }; var loggerMock = new Mock(); // Act BaseItemRepository.DeserializeBaseItem(entity, loggerMock.Object, null, false); // Assert loggerMock.Verify( x => x.Log( LogLevel.Warning, It.IsAny(), It.Is((v, t) => v.ToString()!.Contains("unknown type", StringComparison.OrdinalIgnoreCase)), It.IsAny(), It.IsAny>()), Times.Once); } [Fact] public void DeserializeBaseItem_WithKnownType_ReturnsItem() { // Arrange var entity = new BaseItemEntity { Id = Guid.NewGuid(), Type = "MediaBrowser.Controller.Entities.Movies.Movie" }; // Act var result = BaseItemRepository.DeserializeBaseItem(entity, NullLogger.Instance, null, false); // Assert Assert.NotNull(result); } [Fact] public async Task DeleteItemAsync_RecreatesMissingPlaceholderBeforeDetachingUserData() { await using var dbFactory = new TestDbContextFactory(); Guid itemId; await using (var context = await dbFactory.CreateDbContextAsync(CancellationToken.None)) { await context.Database.EnsureCreatedAsync(); var user = new User("test-user", "Default", "Default"); var item = new BaseItemEntity { Id = Guid.NewGuid(), Type = "TestItem", Name = "Delete me" }; context.Users.Add(user); context.BaseItems.Add(item); context.UserData.Add(new UserData { CustomDataKey = "userdata-key", ItemId = item.Id, Item = item, UserId = user.Id, User = user, Played = true, PlayCount = 1 }); await context.SaveChangesAsync(); var placeholder = await context.BaseItems.SingleAsync(e => e.Id == BaseItemRepository.PlaceholderId); context.BaseItems.Remove(placeholder); await context.SaveChangesAsync(); itemId = item.Id; } var repository = new BaseItemRepository( dbFactory, Mock.Of(), Mock.Of(), Mock.Of(), NullLogger.Instance); await repository.DeleteItemAsync([itemId], CancellationToken.None); await using var verificationContext = await dbFactory.CreateDbContextAsync(CancellationToken.None); var recreatedPlaceholder = await verificationContext.BaseItems.SingleOrDefaultAsync(e => e.Id == BaseItemRepository.PlaceholderId); var detachedUserData = await verificationContext.UserData.SingleAsync(); Assert.NotNull(recreatedPlaceholder); Assert.Null(await verificationContext.BaseItems.SingleOrDefaultAsync(e => e.Id == itemId)); Assert.Equal(BaseItemRepository.PlaceholderId, detachedUserData.ItemId); Assert.NotNull(detachedUserData.RetentionDate); } private sealed class TestDbContextFactory : IDbContextFactory, IAsyncDisposable { private readonly SqliteConnection _connection; private readonly DbContextOptions _options; private readonly Mock _databaseProviderMock; private readonly Mock _lockingBehaviorMock; public TestDbContextFactory() { _connection = new SqliteConnection("Data Source=:memory:"); _connection.Open(); _options = new DbContextOptionsBuilder() .UseSqlite(_connection) .Options; _databaseProviderMock = new Mock(); _databaseProviderMock.Setup(m => m.OnModelCreating(It.IsAny())); _databaseProviderMock.Setup(m => m.ConfigureConventions(It.IsAny())); _lockingBehaviorMock = new Mock(); _lockingBehaviorMock .Setup(m => m.OnSaveChanges(It.IsAny(), It.IsAny())) .Callback((_, saveChanges) => saveChanges()); _lockingBehaviorMock .Setup(m => m.OnSaveChangesAsync(It.IsAny(), It.IsAny>())) .Returns>((_, saveChanges) => saveChanges()); } public JellyfinDbContext CreateDbContext() { return new JellyfinDbContext( _options, NullLogger.Instance, _databaseProviderMock.Object, _lockingBehaviorMock.Object); } public ValueTask CreateDbContextAsync(CancellationToken cancellationToken = default) { return ValueTask.FromResult(CreateDbContext()); } public async ValueTask DisposeAsync() { await _connection.DisposeAsync(); } } }