//
// 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 Microsoft.EntityFrameworkCore;
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();
}
}
}