2e3d24cc05
The v12.0 rebase replaced the PostgreSQL provider's initial migration instead of adding to it, so an existing database kept the pre-12.0 schema while the code migrations ran against it and startup aborted on a missing NormalizedUsername. - restore 20260305010333_InitialPostgreSql as the baseline - add 20260306000000_UpgradeToServer12Schema carrying it to the 12.0 model - convert OwnerId and PrimaryVersionId to uuid with explicit casts, clear unparseable ids and repoint dangling owners at the placeholder item - drop orphaned permissions and preferences before UserId becomes non-nullable - add 20260524120336_AddUniqueNormalizedUsernameIndex after the code migration that fills the column in - read an empty or unknown encoding.xml EncoderPreset as the default - cover both paths against a real PostgreSQL and guard the migration ordering
81 lines
3.7 KiB
C#
81 lines
3.7 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using Jellyfin.Database.Implementations;
|
|
using Jellyfin.Database.Implementations.DbConfiguration;
|
|
using Jellyfin.Database.Implementations.Locking;
|
|
using Jellyfin.Database.Providers.PostgreSQL;
|
|
using Jellyfin.Server.Migrations;
|
|
using Jellyfin.Server.Migrations.Stages;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
using Microsoft.EntityFrameworkCore.Migrations;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using Npgsql;
|
|
using Xunit;
|
|
|
|
namespace Jellyfin.Server.Tests.Migrations;
|
|
|
|
/// <summary>
|
|
/// Schema migrations and the code migrations of the CoreInitialisation stage are sorted into one list by id, so the
|
|
/// ids the PostgreSQL provider ships decide which schema those code migrations see.
|
|
/// </summary>
|
|
public class PostgreSqlMigrationOrderingTests
|
|
{
|
|
private const string UsernameCodeMigrationId = "20260522092304_UpdateNormalizedUsername";
|
|
private const string UsernameIndexMigrationId = "20260524120336_AddUniqueNormalizedUsernameIndex";
|
|
|
|
[Fact]
|
|
public void SchemaMigrations_AreOrderedBeforeTheCodeMigrationsThatNeedThem()
|
|
{
|
|
var schemaMigrationIds = PostgreSqlMigrationIds().Where(id => !string.Equals(id, UsernameIndexMigrationId, StringComparison.Ordinal));
|
|
|
|
foreach (var codeMigrationId in CoreInitialisationCodeMigrationIds())
|
|
{
|
|
foreach (var schemaMigrationId in schemaMigrationIds)
|
|
{
|
|
Assert.True(
|
|
string.CompareOrdinal(schemaMigrationId, codeMigrationId) < 0,
|
|
$"Schema migration {schemaMigrationId} has to be ordered before code migration {codeMigrationId}, "
|
|
+ "otherwise the code migration runs against a schema that predates it.");
|
|
}
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void UniqueUsernameIndex_IsOrderedAfterTheUsernameCodeMigration()
|
|
{
|
|
Assert.Contains(UsernameIndexMigrationId, PostgreSqlMigrationIds());
|
|
Assert.Contains(UsernameCodeMigrationId, CoreInitialisationCodeMigrationIds());
|
|
Assert.True(
|
|
string.CompareOrdinal(UsernameIndexMigrationId, UsernameCodeMigrationId) > 0,
|
|
"The unique index can only be created once the code migration has filled in the normalized usernames.");
|
|
}
|
|
|
|
private static IReadOnlyList<string> PostgreSqlMigrationIds()
|
|
{
|
|
const string DummyConnectionString = "Host=localhost;Database=jellyfin;Username=postgres;Password=postgres";
|
|
using var dataSource = new NpgsqlDataSourceBuilder(DummyConnectionString).Build();
|
|
var optionsBuilder = new DbContextOptionsBuilder<JellyfinDbContext>();
|
|
var provider = new PostgreSqlDatabaseProvider(dataSource);
|
|
provider.Initialise(optionsBuilder, new DatabaseConfigurationOptions { DatabaseType = "PostgreSQL" });
|
|
using var context = new JellyfinDbContext(
|
|
optionsBuilder.Options,
|
|
NullLogger<JellyfinDbContext>.Instance,
|
|
provider,
|
|
new NoLockBehavior(NullLogger<NoLockBehavior>.Instance));
|
|
|
|
return [.. context.GetService<IMigrationsAssembly>().Migrations.Keys.OrderBy(id => id, StringComparer.Ordinal)];
|
|
}
|
|
|
|
private static IReadOnlyList<string> CoreInitialisationCodeMigrationIds()
|
|
{
|
|
return [.. typeof(JellyfinMigrationAttribute).Assembly.GetTypes()
|
|
.Select(type => (Type: type, Metadata: type.GetCustomAttribute<JellyfinMigrationAttribute>()))
|
|
.Where(candidate => candidate.Metadata?.Stage == JellyfinMigrationStageTypes.CoreInitialisation)
|
|
.Select(candidate => new CodeMigration(candidate.Type, candidate.Metadata!, null).BuildCodeMigrationId())
|
|
.OrderBy(id => id, StringComparer.Ordinal)];
|
|
}
|
|
}
|