Split SQLite rebuild migration from roles change

This commit is contained in:
2026-04-04 20:44:26 +02:00
parent 8c413a8ded
commit a5f8421aa8
7 changed files with 513 additions and 61 deletions

View File

@@ -199,6 +199,11 @@ public sealed class HostingCoverageTests
var rolesHistoryCount = Convert.ToInt32(rolesHistoryCommand.ExecuteScalar());
Assert.Equal(1, rolesHistoryCount);
using var authorizationRolesHistoryCommand = verifyConnection.CreateCommand();
authorizationRolesHistoryCommand.CommandText = "SELECT COUNT(*) FROM \"__EFMigrationsHistory\" WHERE \"MigrationId\" = '20260226170000_AddAuthorizationRoles';";
var authorizationRolesHistoryCount = Convert.ToInt32(authorizationRolesHistoryCommand.ExecuteScalar());
Assert.Equal(1, authorizationRolesHistoryCount);
using var rolemasterHistoryCommand = verifyConnection.CreateCommand();
rolemasterHistoryCommand.CommandText = "SELECT COUNT(*) FROM \"__EFMigrationsHistory\" WHERE \"MigrationId\" = '20260402222501_AddRolemasterFumbleRange';";
var rolemasterHistoryCount = Convert.ToInt32(rolemasterHistoryCommand.ExecuteScalar());
@@ -206,20 +211,147 @@ public sealed class HostingCoverageTests
}
[Fact]
public void AuthorizationRolesAndCampaignDeletion_MigrationScript_DoesNotMixUsersSqlIntoCharactersRebuild()
public void AuthorizationMigrations_SplitCharactersRebuildFromRolesColumnAddition()
{
var dbPath = Path.Combine(Path.GetTempPath(), $"rpgroller-migration-script-{Guid.NewGuid():N}.db");
var options = new DbContextOptionsBuilder<RpgRollerDbContext>().UseSqlite($"Data Source={dbPath}").Options;
using var db = new RpgRollerDbContext(options);
var migrator = db.GetService<IMigrator>();
var script = migrator.GenerateScript(
var charactersScript = migrator.GenerateScript(
fromMigration: "20260226131003_AddSkillGroupPrototypes",
toMigration: "20260226160859_AddAuthorizationRolesAndCampaignDeletion");
var rolesScript = migrator.GenerateScript(
fromMigration: "20260226160859_AddAuthorizationRolesAndCampaignDeletion",
toMigration: "20260226170000_AddAuthorizationRoles");
Assert.Contains("""ALTER TABLE "Users" ADD "Roles" TEXT NOT NULL DEFAULT 'admin';""", script);
Assert.Contains("""CREATE TABLE "ef_temp_Characters" (""", script);
Assert.DoesNotContain("UPDATE Users", script);
Assert.Contains("""CREATE TABLE "ef_temp_Characters" (""", charactersScript);
Assert.DoesNotContain("""ALTER TABLE "Users" ADD "Roles" TEXT NOT NULL DEFAULT 'admin';""", charactersScript);
Assert.Contains("""ALTER TABLE "Users" ADD "Roles" TEXT NOT NULL DEFAULT 'admin';""", rolesScript);
Assert.DoesNotContain("""CREATE TABLE "ef_temp_Characters" (""", rolesScript);
}
[Fact]
public void SqliteSchemaUpgrader_BackfillsSplitAuthorizationRolesMigrationHistory()
{
var dbPath = Path.Combine(Path.GetTempPath(), $"rpgroller-split-history-{Guid.NewGuid():N}.db");
using (var connection = new SqliteConnection($"Data Source={dbPath}"))
{
connection.Open();
using var command = connection.CreateCommand();
command.CommandText = """
CREATE TABLE "__EFMigrationsHistory" (
"MigrationId" TEXT NOT NULL CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY,
"ProductVersion" TEXT NOT NULL
);
INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
VALUES
('20260226084000_InitialSchema', '10.0.2'),
('20260226090000_ModelSync', '10.0.2'),
('20260226100000_AddRollLogDice', '10.0.2'),
('20260226124941_AddSkillGroupsAndCharacterOwnerTransfer', '10.0.2'),
('20260226131003_AddSkillGroupPrototypes', '10.0.2'),
('20260226160859_AddAuthorizationRolesAndCampaignDeletion', '10.0.2');
CREATE TABLE "Users" (
"Id" TEXT NOT NULL CONSTRAINT "PK_Users" PRIMARY KEY,
"Username" TEXT NOT NULL,
"UsernameNormalized" TEXT NOT NULL,
"PasswordHash" TEXT NOT NULL,
"DisplayName" TEXT NOT NULL,
"ActiveCharacterId" TEXT NULL,
"Roles" TEXT NOT NULL DEFAULT 'admin'
);
CREATE TABLE "Sessions" (
"Token" TEXT NOT NULL CONSTRAINT "PK_Sessions" PRIMARY KEY,
"UserId" TEXT NOT NULL,
"CreatedAtUtc" TEXT NOT NULL
);
CREATE TABLE "Campaigns" (
"Id" TEXT NOT NULL CONSTRAINT "PK_Campaigns" PRIMARY KEY,
"GmUserId" TEXT NOT NULL,
"Name" TEXT NOT NULL,
"Ruleset" TEXT NOT NULL,
"Version" INTEGER NOT NULL
);
CREATE TABLE "Characters" (
"Id" TEXT NOT NULL CONSTRAINT "PK_Characters" PRIMARY KEY,
"OwnerUserId" TEXT NOT NULL,
"CampaignId" TEXT NULL,
"Name" TEXT NOT NULL
);
CREATE INDEX "IX_Characters_OwnerUserId" ON "Characters" ("OwnerUserId");
CREATE INDEX "IX_Characters_CampaignId" ON "Characters" ("CampaignId");
CREATE TABLE "SkillGroups" (
"Id" TEXT NOT NULL CONSTRAINT "PK_SkillGroups" PRIMARY KEY,
"CharacterId" TEXT NOT NULL,
"Name" TEXT NOT NULL,
"AllowFumble" INTEGER NOT NULL,
"DiceRollDefinition" TEXT NOT NULL,
"WildDice" INTEGER NOT NULL
);
CREATE INDEX "IX_SkillGroups_CharacterId" ON "SkillGroups" ("CharacterId");
CREATE TABLE "Skills" (
"Id" TEXT NOT NULL CONSTRAINT "PK_Skills" PRIMARY KEY,
"CharacterId" TEXT NOT NULL,
"Name" TEXT NOT NULL,
"DiceRollDefinition" TEXT NOT NULL,
"WildDice" INTEGER NOT NULL,
"AllowFumble" INTEGER NOT NULL,
"SkillGroupId" TEXT NULL
);
CREATE INDEX "IX_Skills_CharacterId" ON "Skills" ("CharacterId");
CREATE INDEX "IX_Skills_SkillGroupId" ON "Skills" ("SkillGroupId");
CREATE TABLE "RollLogEntries" (
"Id" TEXT NOT NULL CONSTRAINT "PK_RollLogEntries" PRIMARY KEY,
"CampaignId" TEXT NOT NULL,
"CharacterId" TEXT NOT NULL,
"SkillId" TEXT NOT NULL,
"RollerUserId" TEXT NOT NULL,
"Visibility" TEXT NOT NULL,
"Result" INTEGER NOT NULL,
"Breakdown" TEXT NOT NULL,
"TimestampUtc" TEXT NOT NULL,
"Dice" TEXT NOT NULL
);
CREATE INDEX "IX_RollLogEntries_CampaignId" ON "RollLogEntries" ("CampaignId");
CREATE INDEX "IX_RollLogEntries_CharacterId" ON "RollLogEntries" ("CharacterId");
CREATE INDEX "IX_RollLogEntries_RollerUserId" ON "RollLogEntries" ("RollerUserId");
CREATE INDEX "IX_RollLogEntries_SkillId" ON "RollLogEntries" ("SkillId");
""";
_ = command.ExecuteNonQuery();
}
var options = new DbContextOptionsBuilder<RpgRollerDbContext>().UseSqlite($"Data Source={dbPath}").Options;
using (var db = new RpgRollerDbContext(options))
{
SqliteSchemaUpgrader.ApplyPendingChanges(db);
}
using var verifyConnection = new SqliteConnection($"Data Source={dbPath}");
verifyConnection.Open();
using var authorizationRolesHistoryCommand = verifyConnection.CreateCommand();
authorizationRolesHistoryCommand.CommandText = "SELECT COUNT(*) FROM \"__EFMigrationsHistory\" WHERE \"MigrationId\" = '20260226170000_AddAuthorizationRoles';";
var authorizationRolesHistoryCount = Convert.ToInt32(authorizationRolesHistoryCommand.ExecuteScalar());
Assert.Equal(1, authorizationRolesHistoryCount);
using var rolemasterHistoryCommand = verifyConnection.CreateCommand();
rolemasterHistoryCommand.CommandText = "SELECT COUNT(*) FROM \"__EFMigrationsHistory\" WHERE \"MigrationId\" = '20260402222501_AddRolemasterFumbleRange';";
var rolemasterHistoryCount = Convert.ToInt32(rolemasterHistoryCommand.ExecuteScalar());
Assert.Equal(1, rolemasterHistoryCount);
}
[Fact]
@@ -343,5 +475,10 @@ public sealed class HostingCoverageTests
skillGroupColumns.Add(skillGroupsTableInfoReader.GetString(1));
Assert.Contains("FumbleRange", skillGroupColumns);
using var authorizationRolesHistoryCommand = verifyConnection.CreateCommand();
authorizationRolesHistoryCommand.CommandText = "SELECT COUNT(*) FROM \"__EFMigrationsHistory\" WHERE \"MigrationId\" = '20260226170000_AddAuthorizationRoles';";
var authorizationRolesHistoryCount = Convert.ToInt32(authorizationRolesHistoryCommand.ExecuteScalar());
Assert.Equal(1, authorizationRolesHistoryCount);
}
}