refactor(db): migrate application checklist

Move workflow checklist state into an additive provider-aware migration. Preserve system-key idempotency, manual progress, ordering, and application cascade semantics.
This commit is contained in:
cesnimda
2026-08-30 18:52:08 +02:00
parent ae57d1afde
commit e933409698
9 changed files with 165 additions and 63 deletions
@@ -115,6 +115,7 @@ public sealed class MigrationChainTests
Assert.Contains("CREATE TABLE IF NOT EXISTS `AiWorkspaceNotes`", script, StringComparison.Ordinal);
Assert.Contains("CREATE TABLE IF NOT EXISTS `CvVariants`", script, StringComparison.Ordinal);
Assert.Contains("CREATE TABLE IF NOT EXISTS `CvVariantVersions`", script, StringComparison.Ordinal);
Assert.Contains("CREATE TABLE IF NOT EXISTS `ApplicationChecklistItems`", script, StringComparison.Ordinal);
Assert.Contains("`UiLanguage` varchar(16)", script, StringComparison.Ordinal);
Assert.All(
Regex.Matches(script, "CONSTRAINT `([^`]+)`").Select(match => match.Groups[1].Value),
@@ -703,6 +704,68 @@ public sealed class MigrationChainTests
Assert.Empty(await db.Database.GetPendingMigrationsAsync());
}
[Fact]
public async Task Checklist_adoption_preserves_system_and_manual_workflow_state()
{
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("20260830129000_AdoptAiInteractionSchema");
await ExecuteAsync(connection, """
INSERT INTO Companies (Name) VALUES ('Fixture company');
INSERT INTO JobApplications
(CompanyId, JobTitle, DateApplied, SavedAt, Status, ResponseReceived, OwnerUserId,
HasResume, HasCoverLetter, HasPortfolio, HasOtherAttachment, IsDeleted)
VALUES
(1, 'Fixture role', '2026-08-01T09:00:00', '2026-07-20T09:00:00',
'Applied', 0, 'owner-fixture', 0, 0, 0, 0, 0);
CREATE TABLE "ApplicationChecklistItems" (
"Id" INTEGER NOT NULL CONSTRAINT "PK_ApplicationChecklistItems" PRIMARY KEY AUTOINCREMENT,
"OwnerUserId" TEXT NOT NULL, "JobApplicationId" INTEGER NOT NULL,
"SystemKey" TEXT NULL, "AutoSignal" TEXT NULL, "Title" TEXT NOT NULL,
"Description" TEXT NULL, "Category" TEXT NOT NULL, "Status" TEXT NOT NULL,
"Section" TEXT NULL, "SortOrder" INTEGER NOT NULL,
"IsSystemGenerated" INTEGER NOT NULL, "IsAutoCompleted" INTEGER NOT NULL,
"CompletedAt" TEXT NULL, "CreatedAtUtc" TEXT NOT NULL, "UpdatedAtUtc" TEXT NOT NULL,
CONSTRAINT "FK_ApplicationChecklistItems_JobApplications_JobApplicationId"
FOREIGN KEY ("JobApplicationId") REFERENCES "JobApplications" ("Id") ON DELETE CASCADE
);
INSERT INTO "ApplicationChecklistItems"
("OwnerUserId", "JobApplicationId", "SystemKey", "AutoSignal", "Title",
"Category", "Status", "Section", "SortOrder", "IsSystemGenerated",
"IsAutoCompleted", "CompletedAt", "CreatedAtUtc", "UpdatedAtUtc")
VALUES
('owner-fixture', 1, 'prepare-cv', 'cv-ready', 'Prepare CV', 'preparation', 'done',
'cv', 10, 1, 1, '2026-08-30T09:10:00+00:00',
'2026-08-30T09:00:00+00:00', '2026-08-30T09:10:00+00:00'),
('owner-fixture', 1, NULL, NULL, 'Call recruiter', 'custom', 'pending',
NULL, 20, 0, 0, NULL, '2026-08-30T09:00:00+00:00',
'2026-08-30T09:00:00+00:00');
""");
await migrator.MigrateAsync();
Assert.Equal("done", await ScalarAsync<string>(connection,
"SELECT Status FROM ApplicationChecklistItems WHERE SystemKey = 'prepare-cv';"));
Assert.Equal("Call recruiter", await ScalarAsync<string>(connection,
"SELECT Title FROM ApplicationChecklistItems WHERE SystemKey IS NULL;"));
await migrator.MigrateAsync("20260830129000_AdoptAiInteractionSchema");
Assert.Equal(2L, await ScalarAsync<long>(connection,
"SELECT COUNT(*) FROM ApplicationChecklistItems WHERE JobApplicationId = 1;"));
await migrator.MigrateAsync();
Assert.Equal(2L, await ScalarAsync<long>(connection, """
SELECT COUNT(*) FROM sqlite_master
WHERE type = 'index' AND name IN (
'IX_ApplicationChecklistItems_JobApplicationId_SystemKey',
'IX_ApplicationChecklistItems_Owner_Job_Sort');
"""));
await ExecuteAsync(connection, "DELETE FROM JobApplications WHERE Id = 1;");
Assert.Equal(0L, await ScalarAsync<long>(connection, "SELECT COUNT(*) FROM ApplicationChecklistItems;"));
Assert.Empty(await db.Database.GetPendingMigrationsAsync());
}
private static JobTrackerContext Context(SqliteConnection connection)
{
var currentUser = new Mock<ICurrentUserService>();