aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorShadowghost <Ghost_of_Stone@web.de>2026-09-01 20:47:39 +0200
committerShadowghost <Ghost_of_Stone@web.de>2026-09-01 21:42:07 +0200
commitccdc69e3b012381f49900aa930d1dc876096cfe6 (patch)
tree12313e81258e9b1c42ab21691a332553de251dc7 /tests
parentc56e14d8fb559abcc73fc7c2c83533cd32cb5320 (diff)
Treat an item deleted mid-save as a no-op when saving its images
Diffstat (limited to 'tests')
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Item/ItemPersistenceServiceSaveImagesTests.cs77
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Item/SqliteDbTestFixture.cs3
2 files changed, 80 insertions, 0 deletions
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Item/ItemPersistenceServiceSaveImagesTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Item/ItemPersistenceServiceSaveImagesTests.cs
new file mode 100644
index 0000000000..7997c6d771
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Item/ItemPersistenceServiceSaveImagesTests.cs
@@ -0,0 +1,77 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Jellyfin.Database.Implementations.Entities;
+using Jellyfin.Server.Implementations.Item;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.Entities;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Logging.Abstractions;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.Server.Implementations.Tests.Item;
+
+public class ItemPersistenceServiceSaveImagesTests : SqliteDbTestFixture
+{
+ private readonly ItemPersistenceService _service;
+
+ public ItemPersistenceServiceSaveImagesTests()
+ {
+ _service = new ItemPersistenceService(
+ CreateDbContextFactory(),
+ Mock.Of<IServerApplicationHost>(),
+ NullLogger<ItemPersistenceService>.Instance);
+ }
+
+ [Fact]
+ public async Task SaveImagesAsync_ReplacesThePreviousImages()
+ {
+ var itemId = Guid.NewGuid();
+ Seed(itemId);
+
+ await _service.SaveImagesAsync(CreateItem(itemId, "/first.jpg"), TestContext.Current.CancellationToken);
+ await _service.SaveImagesAsync(CreateItem(itemId, "/second.jpg"), TestContext.Current.CancellationToken);
+
+ using var context = CreateDbContext();
+ var paths = context.BaseItemImageInfos
+ .Where(e => e.ItemId.Equals(itemId))
+ .Select(e => e.Path)
+ .ToList();
+
+ Assert.Equal(["/second.jpg"], paths);
+ }
+
+ [Fact]
+ public async Task SaveImagesAsync_ItemDeletedFromUnderIt_IsANoOp()
+ {
+ // A scan can delete the item between the refresh reading it and the images being written. That
+ // must not fail the whole refresh, and must not leave the images of an item that is gone.
+ var itemId = Guid.NewGuid();
+
+ await _service.SaveImagesAsync(CreateItem(itemId, "/gone.jpg"), TestContext.Current.CancellationToken);
+
+ using var context = CreateDbContext();
+ Assert.Empty(context.BaseItemImageInfos.Where(e => e.ItemId.Equals(itemId)));
+ }
+
+ private static BaseItem CreateItem(Guid itemId, string imagePath)
+ => new Folder
+ {
+ Id = itemId,
+ ImageInfos = [new ItemImageInfo { Path = imagePath, Type = ImageType.Primary }]
+ };
+
+ private void Seed(Guid itemId)
+ {
+ using var context = CreateDbContext();
+ context.BaseItems.Add(new BaseItemEntity
+ {
+ Id = itemId,
+ Type = "Folder",
+ IsFolder = true
+ });
+ context.SaveChanges();
+ }
+}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Item/SqliteDbTestFixture.cs b/tests/Jellyfin.Server.Implementations.Tests/Item/SqliteDbTestFixture.cs
index 87efa8fea5..cfc9c9496c 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Item/SqliteDbTestFixture.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/Item/SqliteDbTestFixture.cs
@@ -1,4 +1,5 @@
using System;
+using System.Threading;
using Emby.Server.Implementations.Data;
using Jellyfin.Database.Implementations;
using Jellyfin.Database.Implementations.Locking;
@@ -58,6 +59,8 @@ public abstract class SqliteDbTestFixture : IDisposable
{
var factory = new Mock<IDbContextFactory<JellyfinDbContext>>();
factory.Setup(f => f.CreateDbContext()).Returns(CreateDbContext);
+ factory.Setup(f => f.CreateDbContextAsync(It.IsAny<CancellationToken>()))
+ .ReturnsAsync(CreateDbContext);
return factory.Object;
}