using System.Threading.Tasks;
using DotNet.Testcontainers.Builders;
using Jellyfin.Database.Implementations;
using Jellyfin.Database.Implementations.DbConfiguration;
using Jellyfin.Database.Implementations.Locking;
using Jellyfin.Database.Providers.PostgreSQL;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging.Abstractions;
using Npgsql;
using Testcontainers.PostgreSql;
using Xunit;
namespace Jellyfin.Database.Tests.PostgreSQL;
///
/// Integration tests that validate PostgreSQL migrations against a real container.
///
[Xunit.Trait("Category", "RequiresDocker")]
public sealed class PostgreSqlMigrationTests : IAsyncLifetime
{
private readonly PostgreSqlContainer _container;
///
/// Initializes a new instance of the class.
///
public PostgreSqlMigrationTests()
{
_container = new PostgreSqlBuilder()
.WithImage("postgres:16-alpine")
.WithWaitStrategy(Wait.ForUnixContainer().UntilCommandIsCompleted("pg_isready"))
.Build();
}
///
/// Starts the PostgreSQL container before any tests in the class run.
///
/// A representing the asynchronous operation.
public async Task InitializeAsync()
{
await _container.StartAsync().ConfigureAwait(false);
}
///
/// Stops and removes the PostgreSQL container after all tests in the class have run.
///
/// A representing the asynchronous operation.
public async Task DisposeAsync()
{
await _container.DisposeAsync().ConfigureAwait(false);
}
///
/// Verifies that the InitialPostgreSql migration applies cleanly to a fresh PostgreSQL 16 container.
///
/// A representing the asynchronous operation.
[Fact]
public async Task MigrateAsync_AppliesInitialMigrationCleanly()
{
await using var dataSource = new NpgsqlDataSourceBuilder(_container.GetConnectionString()).Build();
var context = CreateContext(dataSource);
await using (context)
{
await context.Database.MigrateAsync();
var pendingMigrations = await context.Database.GetPendingMigrationsAsync();
Assert.Empty(pendingMigrations);
}
}
///
/// Verifies that no pending model changes exist for the PostgreSQL provider,
/// acting as a CI gate that fails when model changes are added without a corresponding migration.
///
[Fact]
public void CheckForUnappliedMigrations_PostgreSql()
{
// Use a dummy connection string; HasPendingModelChanges() is a purely in-memory check
// that compares the current compiled model with the migration snapshots — no real DB needed.
const string dummyConnectionString = "Host=localhost;Database=jellyfin;Username=postgres;Password=postgres";
using var dataSource = new NpgsqlDataSourceBuilder(dummyConnectionString).Build();
using var context = CreateContext(dataSource);
Assert.False(
context.Database.HasPendingModelChanges(),
"There are unapplied changes to the EFCore model for PostgreSQL. Please create a Migration.");
}
private static JellyfinDbContext CreateContext(NpgsqlDataSource dataSource)
{
var optionsBuilder = new DbContextOptionsBuilder();
var provider = new PostgreSqlDatabaseProvider(dataSource);
provider.Initialise(optionsBuilder, new DatabaseConfigurationOptions { DatabaseType = "PostgreSQL" });
return new JellyfinDbContext(
optionsBuilder.Options,
NullLogger.Instance,
provider,
new NoLockBehavior(NullLogger.Instance));
}
}