refactor(db): adopt email settings migration

This commit is contained in:
cesnimda
2026-08-30 16:42:19 +02:00
parent 2d7a51c3fd
commit e47ad1d243
11 changed files with 324 additions and 34 deletions
@@ -43,6 +43,9 @@ public sealed class MigrationChainTests
Assert.Equal(1, await ScalarAsync<long>(connection, """
SELECT COUNT(*) FROM pragma_table_info('AspNetUsers') WHERE name = 'UiLanguage';
"""));
Assert.Equal(10, await ScalarAsync<long>(connection, """
SELECT COUNT(*) FROM pragma_table_info('SystemEmailSettings');
"""));
}
[Fact]
@@ -96,12 +99,55 @@ public sealed class MigrationChainTests
Assert.Contains("CONSTRAINT `FK_AccountDeletionFiles_Request`", script, StringComparison.Ordinal);
Assert.Contains("CREATE TABLE IF NOT EXISTS `AspNetUsers`", script, StringComparison.Ordinal);
Assert.Contains("CREATE TABLE IF NOT EXISTS `AiInteractions`", script, StringComparison.Ordinal);
Assert.Contains("CREATE TABLE IF NOT EXISTS `SystemEmailSettings`", script, StringComparison.Ordinal);
Assert.Contains("`UiLanguage` varchar(16)", script, StringComparison.Ordinal);
Assert.All(
Regex.Matches(script, "CONSTRAINT `([^`]+)`").Select(match => match.Groups[1].Value),
identifier => Assert.True(identifier.Length <= 64, $"MariaDB constraint identifier exceeds 64 characters: {identifier}"));
}
[Fact]
public async Task System_email_settings_adoption_preserves_legacy_rows()
{
await using var connection = new SqliteConnection("Data Source=:memory:");
await connection.OpenAsync();
await using var db = Context(connection);
var migrator = db.GetService<IMigrator>();
await migrator.MigrateAsync("20260828090000_AddUiLanguagePreference");
await ExecuteAsync(connection, """
CREATE TABLE "SystemEmailSettings" (
"Id" INTEGER NOT NULL CONSTRAINT "PK_SystemEmailSettings" PRIMARY KEY,
"Enabled" INTEGER NULL,
"SmtpHost" TEXT NULL,
"SmtpPort" INTEGER NULL,
"SmtpUser" TEXT NULL,
"SmtpPassword" TEXT NULL,
"From" TEXT NULL,
"FromName" TEXT NULL,
"SmtpEnableSsl" INTEGER NULL,
"SmtpTimeoutMs" INTEGER NULL
);
INSERT INTO "SystemEmailSettings" ("Id", "Enabled", "SmtpHost", "SmtpPort")
VALUES (1, 1, 'smtp.fixture.invalid', 587);
""");
await migrator.MigrateAsync();
Assert.Equal("smtp.fixture.invalid", await ScalarAsync<string>(connection,
"SELECT SmtpHost FROM SystemEmailSettings WHERE Id = 1;"));
Assert.Equal(587L, await ScalarAsync<long>(connection,
"SELECT SmtpPort FROM SystemEmailSettings WHERE Id = 1;"));
Assert.Empty(await db.Database.GetPendingMigrationsAsync());
await migrator.MigrateAsync("20260828090000_AddUiLanguagePreference");
Assert.Equal("smtp.fixture.invalid", await ScalarAsync<string>(connection,
"SELECT SmtpHost FROM SystemEmailSettings WHERE Id = 1;"));
await migrator.MigrateAsync();
Assert.Equal("smtp.fixture.invalid", await ScalarAsync<string>(connection,
"SELECT SmtpHost FROM SystemEmailSettings WHERE Id = 1;"));
Assert.Empty(await db.Database.GetPendingMigrationsAsync());
}
private static JobTrackerContext Context(SqliteConnection connection)
{
var currentUser = new Mock<ICurrentUserService>();
@@ -0,0 +1,50 @@
using JobTrackerApi.Data;
using JobTrackerApi.Services;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Moq;
using Xunit;
namespace JobTrackerApi.Tests;
public sealed class StartupSchemaOwnershipTests
{
[Fact]
public void Every_model_table_has_exactly_one_creation_owner()
{
using var connection = new SqliteConnection("Data Source=:memory:");
var currentUser = new Mock<ICurrentUserService>();
currentUser.SetupGet(service => service.UserId).Returns((string?)null);
using var db = new JobTrackerContext(
new DbContextOptionsBuilder<JobTrackerContext>().UseSqlite(connection).Options,
currentUser.Object);
var modelTables = db.Model.GetEntityTypes()
.Select(entity => entity.GetTableName())
.Where(table => table is not null)
.Cast<string>()
.ToHashSet(StringComparer.Ordinal);
var overlap = StartupSchemaOwnership.MigrationOwnedTables
.Intersect(StartupSchemaOwnership.ReconcilerOwnedTables, StringComparer.Ordinal)
.OrderBy(table => table)
.ToArray();
var classified = StartupSchemaOwnership.MigrationOwnedTables
.Concat(StartupSchemaOwnership.ReconcilerOwnedTables)
.ToHashSet(StringComparer.Ordinal);
Assert.Empty(overlap);
Assert.Empty(modelTables.Except(classified, StringComparer.Ordinal));
Assert.Empty(classified.Except(modelTables, StringComparer.Ordinal));
}
[Fact]
public void Compatibility_bootstraps_remain_reconciler_owned_until_migrated()
{
Assert.All(
StartupSchemaOwnership.MigrationCompatibilityBootstrapTables,
table => Assert.Contains(table, StartupSchemaOwnership.ReconcilerOwnedTables));
Assert.Empty(StartupSchemaOwnership.MigrationCompatibilityBootstrapTables
.Intersect(StartupSchemaOwnership.MigrationOwnedTables, StringComparer.Ordinal));
}
}