Share the SQLite fixture across the item tests
This commit is contained in:
+2
-33
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
+3
-51
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
+3
-51
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
+5
-46
@@ -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));
|
||||
}
|
||||
|
||||
+3
-35
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user