Share the SQLite fixture across the item tests

This commit is contained in:
Shadowghost
2026-08-12 07:42:29 +02:00
parent fa7fdf5884
commit 9ae6ffe441
8 changed files with 115 additions and 288 deletions
@@ -1,14 +1,9 @@
#pragma warning disable RS0030 // Do not use banned APIs: Guid == is required inside EF expression trees to mirror the production query shapes.
#pragma warning disable RS0030 // Do not use banned APIs: Guid == is required inside EF expression trees to mirror the production query shapes.
using System;
using System.Linq;
using Jellyfin.Database.Implementations;
using Jellyfin.Database.Implementations.Entities;
using Jellyfin.Database.Implementations.Locking;
using Jellyfin.Database.Providers.Sqlite;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;
namespace Jellyfin.Server.Implementations.Tests.Item;
@@ -18,22 +13,10 @@ namespace Jellyfin.Server.Implementations.Tests.Item;
/// (BaseItemRepository.TranslateQuery) and the DatePlayed ordering (OrderMapper) translate
/// and evaluate correctly on the SQLite provider.
/// </summary>
public sealed class AlternateVersionQueryTranslationTests : IDisposable
public sealed class AlternateVersionQueryTranslationTests : SqliteDbTestFixture
{
private readonly SqliteConnection _connection;
private readonly DbContextOptions<JellyfinDbContext> _dbOptions;
public AlternateVersionQueryTranslationTests()
{
_connection = new SqliteConnection("Data Source=:memory:");
_connection.Open();
_dbOptions = new DbContextOptionsBuilder<JellyfinDbContext>()
.UseSqlite(_connection)
.Options;
using var ctx = CreateDbContext();
ctx.Database.EnsureCreated();
}
[Fact]
@@ -220,18 +203,4 @@ public sealed class AlternateVersionQueryTranslationTests : IDisposable
ctx.SaveChanges();
return (user.Id, primary.Id, versionA.Id, versionB.Id);
}
private JellyfinDbContext CreateDbContext()
{
return new JellyfinDbContext(
_dbOptions,
NullLogger<JellyfinDbContext>.Instance,
new SqliteDatabaseProvider(null!, NullLogger<SqliteDatabaseProvider>.Instance),
new NoLockBehavior(NullLogger<NoLockBehavior>.Instance));
}
public void Dispose()
{
_connection.Dispose();
}
}
@@ -1,19 +1,10 @@
using System;
using System;
using System.Linq;
using Emby.Server.Implementations.Data;
using Jellyfin.Database.Implementations;
using Jellyfin.Database.Implementations.Entities;
using Jellyfin.Database.Implementations.Locking;
using Jellyfin.Database.Providers.Sqlite;
using Jellyfin.Server.Implementations.Item;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Configuration;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Xunit;
using BaseItemKind = Jellyfin.Data.Enums.BaseItemKind;
@@ -24,46 +15,16 @@ namespace Jellyfin.Server.Implementations.Tests.Item;
/// <c>GetItemValues</c>. A query without a <c>Limit</c> used to have its total record count
/// silently disabled, so callers got a populated <c>Items</c> array next to a zero total.
/// </summary>
public sealed class BaseItemRepositoryByNameTotalCountTests : IDisposable
public sealed class BaseItemRepositoryByNameTotalCountTests : SqliteDbTestFixture
{
private readonly SqliteConnection _connection;
private readonly DbContextOptions<JellyfinDbContext> _dbOptions;
private readonly BaseItemRepository _repository;
private readonly ItemTypeLookup _itemTypeLookup;
public BaseItemRepositoryByNameTotalCountTests()
{
_connection = new SqliteConnection("Data Source=:memory:");
_connection.Open();
_dbOptions = new DbContextOptionsBuilder<JellyfinDbContext>()
.UseSqlite(_connection)
.Options;
using (var ctx = CreateDbContext())
{
ctx.Database.EnsureCreated();
}
var factory = new Mock<IDbContextFactory<JellyfinDbContext>>();
factory.Setup(f => f.CreateDbContext()).Returns(CreateDbContext);
_itemTypeLookup = new ItemTypeLookup();
var serverConfigurationManager = new Mock<IServerConfigurationManager>();
serverConfigurationManager.Setup(c => c.Configuration).Returns(new ServerConfiguration());
_repository = new BaseItemRepository(
factory.Object,
new Mock<IServerApplicationHost>().Object,
_itemTypeLookup,
serverConfigurationManager.Object,
NullLogger<BaseItemRepository>.Instance);
}
public void Dispose()
{
_connection.Dispose();
_repository = CreateBaseItemRepository(_itemTypeLookup);
}
[Fact]
@@ -187,13 +148,4 @@ public sealed class BaseItemRepositoryByNameTotalCountTests : IDisposable
ctx.SaveChanges();
}
private JellyfinDbContext CreateDbContext()
{
return new JellyfinDbContext(
_dbOptions,
NullLogger<JellyfinDbContext>.Instance,
new SqliteDatabaseProvider(null!, NullLogger<SqliteDatabaseProvider>.Instance),
new NoLockBehavior(NullLogger<NoLockBehavior>.Instance));
}
}
@@ -1,66 +1,27 @@
using System;
using System;
using System.Linq;
using Emby.Server.Implementations.Data;
using Jellyfin.Database.Implementations;
using Jellyfin.Database.Implementations.Entities;
using Jellyfin.Database.Implementations.Locking;
using Jellyfin.Database.Providers.Sqlite;
using Jellyfin.Server.Implementations.Item;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Xunit;
using BaseItemKind = Jellyfin.Data.Enums.BaseItemKind;
namespace Jellyfin.Server.Implementations.Tests.Item;
public sealed class BaseItemRepositoryGroupingTests : IDisposable
public sealed class BaseItemRepositoryGroupingTests : SqliteDbTestFixture
{
private readonly SqliteConnection _connection;
private readonly DbContextOptions<JellyfinDbContext> _dbOptions;
private readonly BaseItemRepository _repository;
private readonly string _movieTypeName;
public BaseItemRepositoryGroupingTests()
{
_connection = new SqliteConnection("Data Source=:memory:");
_connection.Open();
_dbOptions = new DbContextOptionsBuilder<JellyfinDbContext>()
.UseSqlite(_connection)
.Options;
using (var ctx = CreateDbContext())
{
ctx.Database.EnsureCreated();
}
var factory = new Mock<IDbContextFactory<JellyfinDbContext>>();
factory.Setup(f => f.CreateDbContext()).Returns(CreateDbContext);
var itemTypeLookup = new ItemTypeLookup();
_movieTypeName = itemTypeLookup.BaseItemKindNames[BaseItemKind.Movie];
var serverConfigurationManager = new Mock<IServerConfigurationManager>();
serverConfigurationManager.Setup(c => c.Configuration).Returns(new ServerConfiguration());
_repository = new BaseItemRepository(
factory.Object,
new Mock<IServerApplicationHost>().Object,
itemTypeLookup,
serverConfigurationManager.Object,
NullLogger<BaseItemRepository>.Instance);
}
public void Dispose()
{
_connection.Dispose();
_repository = CreateBaseItemRepository(itemTypeLookup);
}
[Fact]
@@ -132,13 +93,4 @@ public sealed class BaseItemRepositoryGroupingTests : IDisposable
IsVirtualItem = false
};
}
private JellyfinDbContext CreateDbContext()
{
return new JellyfinDbContext(
_dbOptions,
NullLogger<JellyfinDbContext>.Instance,
new SqliteDatabaseProvider(null!, NullLogger<SqliteDatabaseProvider>.Instance),
new NoLockBehavior(NullLogger<NoLockBehavior>.Instance));
}
}
@@ -1,37 +1,25 @@
using System;
using System;
using System.Linq;
using Emby.Server.Implementations.Data;
using Jellyfin.Database.Implementations;
using Jellyfin.Database.Implementations.Entities;
using Jellyfin.Database.Implementations.Locking;
using Jellyfin.Database.Providers.Sqlite;
using Jellyfin.Server.Implementations.Item;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Configuration;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Xunit;
using LinkedChildType = Jellyfin.Database.Implementations.Entities.LinkedChildType;
namespace Jellyfin.Server.Implementations.Tests.Item;
/// <summary>
/// Covers the filters that resolve "folders with a matching descendant" through
/// <see cref="DescendantQueryHelper.GetFolderIdsMatching"/>, both in their positive and their
/// negated form, so the sub-selects they build stay translatable on the SQLite provider.
/// Covers the filters resolving "folders with a matching descendant" through
/// <see cref="DescendantQueryHelper.GetFolderIdsMatching"/>, positive and negated.
/// </summary>
public sealed class BaseItemRepositoryStreamFilterTests : IDisposable
public sealed class BaseItemRepositoryStreamFilterTests : SqliteDbTestFixture
{
private const string FolderType = "MediaBrowser.Controller.Entities.Folder";
private const string BoxSetType = "MediaBrowser.Controller.Entities.Movies.BoxSet";
private const string MovieType = "MediaBrowser.Controller.Entities.Movies.Movie";
private readonly SqliteConnection _connection;
private readonly DbContextOptions<JellyfinDbContext> _dbOptions;
private readonly BaseItemRepository _repository;
private readonly Guid _library = Guid.NewGuid();
@@ -43,35 +31,14 @@ public sealed class BaseItemRepositoryStreamFilterTests : IDisposable
public BaseItemRepositoryStreamFilterTests()
{
_connection = new SqliteConnection("Data Source=:memory:");
_connection.Open();
_dbOptions = new DbContextOptionsBuilder<JellyfinDbContext>()
.UseSqlite(_connection)
.Options;
using (var ctx = CreateDbContext())
{
ctx.Database.EnsureCreated();
Seed(ctx);
}
var factory = new Mock<IDbContextFactory<JellyfinDbContext>>();
factory.Setup(f => f.CreateDbContext()).Returns(CreateDbContext);
var serverConfigurationManager = new Mock<IServerConfigurationManager>();
serverConfigurationManager.Setup(c => c.Configuration).Returns(new ServerConfiguration());
_repository = new BaseItemRepository(
factory.Object,
new Mock<IServerApplicationHost>().Object,
new ItemTypeLookup(),
serverConfigurationManager.Object,
NullLogger<BaseItemRepository>.Instance);
_repository = CreateBaseItemRepository(new ItemTypeLookup());
}
public void Dispose() => _connection.Dispose();
[Fact]
public void HasSubtitles_MatchesTheItemAndItsParentFolder()
{
@@ -115,7 +82,6 @@ public sealed class BaseItemRepositoryStreamFilterTests : IDisposable
{
var ids = _repository.GetItemIdsList(new InternalItemsQuery { HasSubtitles = true });
// The collection links the series, and the subtitles hang off the series' episode.
Assert.Contains(_linkedSeries, ids);
Assert.Contains(_collection, ids);
}
@@ -214,11 +180,4 @@ public sealed class BaseItemRepositoryStreamFilterTests : IDisposable
context.SaveChanges();
}
private JellyfinDbContext CreateDbContext()
=> new JellyfinDbContext(
_dbOptions,
NullLogger<JellyfinDbContext>.Instance,
new SqliteDatabaseProvider(null!, NullLogger<SqliteDatabaseProvider>.Instance),
new NoLockBehavior(NullLogger<NoLockBehavior>.Instance));
}
@@ -1,14 +1,10 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using Jellyfin.Database.Implementations;
using Jellyfin.Database.Implementations.Entities;
using Jellyfin.Database.Implementations.Locking;
using Jellyfin.Database.Implementations.MatchCriteria;
using Jellyfin.Database.Providers.Sqlite;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;
namespace Jellyfin.Server.Implementations.Tests.Item;
@@ -17,31 +13,18 @@ namespace Jellyfin.Server.Implementations.Tests.Item;
/// Verifies the descendant traversals against the SQLite provider: the sets they resolve, and that
/// they stay sub-selects instead of inlining every descendant id into the statement.
/// </summary>
public sealed class DescendantQueryHelperTests : IDisposable
public sealed class DescendantQueryHelperTests : SqliteDbTestFixture
{
private const string FolderType = "MediaBrowser.Controller.Entities.Folder";
private const string BoxSetType = "MediaBrowser.Controller.Entities.Movies.BoxSet";
private const string MovieType = "MediaBrowser.Controller.Entities.Movies.Movie";
private readonly Dictionary<Guid, int> _linkCounters = new();
private readonly SqliteConnection _connection;
private readonly DbContextOptions<JellyfinDbContext> _dbOptions;
public DescendantQueryHelperTests()
{
_connection = new SqliteConnection("Data Source=:memory:");
_connection.Open();
_dbOptions = new DbContextOptionsBuilder<JellyfinDbContext>()
.UseSqlite(_connection)
.Options;
using var ctx = CreateDbContext();
ctx.Database.EnsureCreated();
}
public void Dispose() => _connection.Dispose();
[Fact]
public void GetAllDescendantIds_Hierarchy_ReturnsEveryLevelWithoutTheParent()
{
@@ -95,7 +78,6 @@ public sealed class DescendantQueryHelperTests : IDisposable
{
var descendants = DescendantQueryHelper.GetAllDescendantIds(ctx, boxSet).ToHashSet();
// The link reaches the series, and the series' own closure reaches the episode.
Assert.Contains(series, descendants);
Assert.Contains(episode, descendants);
}
@@ -117,7 +99,7 @@ public sealed class DescendantQueryHelperTests : IDisposable
AddLink(ctx, outer, inner);
AddLink(ctx, inner, movie);
// Cycle back to the outer set: the traversal must not spin on it.
// The traversal must not spin on this cycle.
AddLink(ctx, inner, outer);
ctx.SaveChanges();
}
@@ -179,8 +161,8 @@ public sealed class DescendantQueryHelperTests : IDisposable
AddItem(ctx, boxSet, BoxSetType, isFolder: true);
AddItem(ctx, linkedMovie, MovieType);
// How production writes it: an item carries its own chain plus its collection folder, but
// not the user root above that folder - so one hop from the user root stops there.
// An item carries its own chain plus its collection folder, but not the user root above
// it, so one hop from the user root stops at the collection folder.
AddAncestors(ctx, collectionFolder, userRoot);
AddAncestors(ctx, series, collectionFolder);
AddAncestors(ctx, episode, series, collectionFolder);
@@ -264,7 +246,6 @@ public sealed class DescendantQueryHelperTests : IDisposable
AddLink(ctx, boxSet, series);
AddStream(ctx, episode, MediaStreamTypeEntity.Subtitle);
// A second collection, over an item without subtitles, must not be picked up.
AddFolder(ctx, otherLibrary);
AddItem(ctx, otherBoxSet, BoxSetType, isFolder: true);
AddItem(ctx, silentMovie, MovieType);
@@ -302,7 +283,7 @@ public sealed class DescendantQueryHelperTests : IDisposable
AddLink(ctx, outer, inner);
AddLink(ctx, inner, movie);
// Cycle back to the outer set: resolving the link parents must not spin on it.
// Resolving the link parents must not spin on this cycle.
AddLink(ctx, inner, outer);
AddStream(ctx, movie, MediaStreamTypeEntity.Subtitle);
@@ -337,8 +318,7 @@ public sealed class DescendantQueryHelperTests : IDisposable
AddFolder(ctx, series);
AddItem(ctx, episode, MovieType);
// How production writes it: an item carries its own chain plus its collection folder, but
// not the user root above that folder - so the closure is not transitive at this seam.
// The closure is not transitive at this seam: no item records the user root.
AddAncestors(ctx, episode, series, collectionFolder);
AddAncestors(ctx, series, collectionFolder);
AddAncestors(ctx, collectionFolder, userRoot);
@@ -419,8 +399,8 @@ public sealed class DescendantQueryHelperTests : IDisposable
var smallSql = CountingQuery(ctx, small).ToQueryString();
var largeSql = CountingQuery(ctx, large).ToQueryString();
// Reading the ids into memory and passing them back as AsQueryable() makes EF inline one
// literal per descendant, which is what made a large library allocate megabytes per call.
// Reading the ids into memory and handing them back as AsQueryable() makes EF inline one
// literal per descendant, which is what allocated megabytes per call.
Assert.Equal(smallSql.Length, largeSql.Length);
Assert.Contains("AncestorIds", smallSql, StringComparison.Ordinal);
Assert.Equal(10, CountingQuery(ctx, small).Count());
@@ -505,11 +485,4 @@ public sealed class DescendantQueryHelperTests : IDisposable
SortOrder = sortOrder
});
}
private JellyfinDbContext CreateDbContext()
=> new JellyfinDbContext(
_dbOptions,
NullLogger<JellyfinDbContext>.Instance,
new SqliteDatabaseProvider(null!, NullLogger<SqliteDatabaseProvider>.Instance),
new NoLockBehavior(NullLogger<NoLockBehavior>.Instance));
}
@@ -1,51 +1,29 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Jellyfin.Database.Implementations;
using Jellyfin.Database.Implementations.Locking;
using Jellyfin.Database.Providers.Sqlite;
using Jellyfin.Server.Implementations.Item;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Xunit;
namespace Jellyfin.Server.Implementations.Tests.Item;
public sealed class ItemPersistenceOwnedRowTests : IDisposable
public sealed class ItemPersistenceOwnedRowTests : SqliteDbTestFixture
{
private readonly SqliteConnection _connection;
private readonly DbContextOptions<JellyfinDbContext> _dbOptions;
private readonly ItemPersistenceService _service;
private readonly IApplicationPaths _applicationPaths;
private readonly ILibraryManager? _previousLibraryManager;
private readonly IServerConfigurationManager? _previousConfigurationManager;
public ItemPersistenceOwnedRowTests()
{
_applicationPaths = new Mock<IApplicationPaths>().Object;
_connection = new SqliteConnection("Data Source=:memory:");
_connection.Open();
_dbOptions = new DbContextOptionsBuilder<JellyfinDbContext>()
.UseSqlite(_connection)
.Options;
using (var ctx = CreateDbContext())
{
ctx.Database.EnsureCreated();
}
// BaseItem resolves these through process-wide statics; restored in Dispose.
_previousLibraryManager = BaseItem.LibraryManager;
_previousConfigurationManager = BaseItem.ConfigurationManager;
@@ -59,20 +37,17 @@ public sealed class ItemPersistenceOwnedRowTests : IDisposable
configurationManager.Setup(c => c.Configuration).Returns(new ServerConfiguration());
BaseItem.ConfigurationManager = configurationManager.Object;
var factory = new Mock<IDbContextFactory<JellyfinDbContext>>();
factory.Setup(f => f.CreateDbContext()).Returns(CreateDbContext);
_service = new ItemPersistenceService(
factory.Object,
CreateDbContextFactory(),
new Mock<IServerApplicationHost>().Object,
NullLogger<ItemPersistenceService>.Instance);
}
public void Dispose()
protected override void Dispose(bool disposing)
{
BaseItem.LibraryManager = _previousLibraryManager!;
BaseItem.ConfigurationManager = _previousConfigurationManager!;
_connection.Dispose();
base.Dispose(disposing);
}
[Fact]
@@ -140,10 +115,4 @@ public sealed class ItemPersistenceOwnedRowTests : IDisposable
book.SetImage(new ItemImageInfo { Path = "/img/primary.jpg", Type = ImageType.Primary }, 0);
return book;
}
private JellyfinDbContext CreateDbContext() => new(
_dbOptions,
NullLogger<JellyfinDbContext>.Instance,
new SqliteDatabaseProvider(_applicationPaths, NullLogger<SqliteDatabaseProvider>.Instance),
new NoLockBehavior(NullLogger<NoLockBehavior>.Instance));
}
@@ -1,45 +1,30 @@
using System;
using System;
using System.Linq;
using Emby.Server.Implementations.Data;
using Jellyfin.Data.Enums;
using Jellyfin.Database.Implementations;
using Jellyfin.Database.Implementations.Entities;
using Jellyfin.Database.Implementations.Locking;
using Jellyfin.Database.Providers.Sqlite;
using Jellyfin.Server.Implementations.Item;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Persistence;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Xunit;
using BaseItemKind = Jellyfin.Data.Enums.BaseItemKind;
namespace Jellyfin.Server.Implementations.Tests.Item;
public sealed class PeopleRepositoryUpdatePeopleTests : IDisposable
public sealed class PeopleRepositoryUpdatePeopleTests : SqliteDbTestFixture
{
private static readonly Guid _itemId = Guid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa");
private readonly SqliteConnection _connection;
private readonly DbContextOptions<JellyfinDbContext> _dbOptions;
private readonly PeopleRepository _repository;
public PeopleRepositoryUpdatePeopleTests()
{
_connection = new SqliteConnection("Data Source=:memory:");
_connection.Open();
_dbOptions = new DbContextOptionsBuilder<JellyfinDbContext>()
.UseSqlite(_connection)
.Options;
var itemTypeLookup = new ItemTypeLookup();
using (var ctx = CreateDbContext())
{
ctx.Database.EnsureCreated();
ctx.BaseItems.Add(new BaseItemEntity
{
Id = _itemId,
@@ -53,20 +38,12 @@ public sealed class PeopleRepositoryUpdatePeopleTests : IDisposable
ctx.SaveChanges();
}
var factory = new Mock<IDbContextFactory<JellyfinDbContext>>();
factory.Setup(f => f.CreateDbContext()).Returns(CreateDbContext);
_repository = new PeopleRepository(
factory.Object,
CreateDbContextFactory(),
itemTypeLookup,
new Mock<IItemQueryHelpers>().Object);
}
public void Dispose()
{
_connection.Dispose();
}
[Fact]
public void UpdatePeople_SamePersonAndTypeWithDifferentRoles_KeepsEveryCredit()
{
@@ -174,13 +151,4 @@ public sealed class PeopleRepositoryUpdatePeopleTests : IDisposable
Role = role
};
}
private JellyfinDbContext CreateDbContext()
{
return new JellyfinDbContext(
_dbOptions,
NullLogger<JellyfinDbContext>.Instance,
new SqliteDatabaseProvider(null!, NullLogger<SqliteDatabaseProvider>.Instance),
new NoLockBehavior(NullLogger<NoLockBehavior>.Instance));
}
}
@@ -0,0 +1,85 @@
using System;
using Emby.Server.Implementations.Data;
using Jellyfin.Database.Implementations;
using Jellyfin.Database.Implementations.Locking;
using Jellyfin.Database.Providers.Sqlite;
using Jellyfin.Server.Implementations.Item;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.Configuration;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
namespace Jellyfin.Server.Implementations.Tests.Item;
/// <summary>
/// Base fixture for the item tests that run against the SQLite provider: one in-memory database per
/// test class, plus the wiring the repositories under test need. The connection owns the database, so
/// it stays open for the lifetime of the fixture. Derived classes seed in their own constructor.
/// </summary>
public abstract class SqliteDbTestFixture : IDisposable
{
private readonly SqliteConnection _connection;
private readonly DbContextOptions<JellyfinDbContext> _dbOptions;
protected SqliteDbTestFixture()
{
ApplicationPaths = new Mock<IApplicationPaths>().Object;
_connection = new SqliteConnection("Data Source=:memory:");
_connection.Open();
_dbOptions = new DbContextOptionsBuilder<JellyfinDbContext>()
.UseSqlite(_connection)
.Options;
using var context = CreateDbContext();
context.Database.EnsureCreated();
}
protected IApplicationPaths ApplicationPaths { get; }
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected JellyfinDbContext CreateDbContext() => new(
_dbOptions,
NullLogger<JellyfinDbContext>.Instance,
new SqliteDatabaseProvider(ApplicationPaths, NullLogger<SqliteDatabaseProvider>.Instance),
new NoLockBehavior(NullLogger<NoLockBehavior>.Instance));
protected IDbContextFactory<JellyfinDbContext> CreateDbContextFactory()
{
var factory = new Mock<IDbContextFactory<JellyfinDbContext>>();
factory.Setup(f => f.CreateDbContext()).Returns(CreateDbContext);
return factory.Object;
}
protected BaseItemRepository CreateBaseItemRepository(ItemTypeLookup itemTypeLookup)
{
var serverConfigurationManager = new Mock<IServerConfigurationManager>();
serverConfigurationManager.Setup(c => c.Configuration).Returns(new ServerConfiguration());
return new BaseItemRepository(
CreateDbContextFactory(),
new Mock<IServerApplicationHost>().Object,
itemTypeLookup,
serverConfigurationManager.Object,
NullLogger<BaseItemRepository>.Instance);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_connection.Dispose();
}
}
}