refactor(db): migrate CV extraction data
Move upload artifacts and extraction runs into an additive provider-aware migration. Preserve parsed results, indexes, and nullable artifact retention semantics.
This commit is contained in:
@@ -108,6 +108,8 @@ public sealed class MigrationChainTests
|
||||
Assert.Contains("CREATE TABLE IF NOT EXISTS `GmailConnections`", script, StringComparison.Ordinal);
|
||||
Assert.Contains("CREATE TABLE IF NOT EXISTS `MicrosoftGraphConnections`", script, StringComparison.Ordinal);
|
||||
Assert.Contains("CREATE TABLE IF NOT EXISTS `ImapConnections`", script, StringComparison.Ordinal);
|
||||
Assert.Contains("CREATE TABLE IF NOT EXISTS `CvUploadArtifacts`", script, StringComparison.Ordinal);
|
||||
Assert.Contains("CREATE TABLE IF NOT EXISTS `CvExtractionRuns`", script, StringComparison.Ordinal);
|
||||
Assert.Contains("`UiLanguage` varchar(16)", script, StringComparison.Ordinal);
|
||||
Assert.All(
|
||||
Regex.Matches(script, "CONSTRAINT `([^`]+)`").Select(match => match.Groups[1].Value),
|
||||
@@ -387,6 +389,68 @@ public sealed class MigrationChainTests
|
||||
Assert.Empty(await db.Database.GetPendingMigrationsAsync());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Cv_extraction_adoption_preserves_artifacts_results_and_relationship()
|
||||
{
|
||||
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("20260830124000_AdoptEmailConnectionSchema");
|
||||
await ExecuteAsync(connection, """
|
||||
CREATE TABLE "CvUploadArtifacts" (
|
||||
"Id" INTEGER NOT NULL CONSTRAINT "PK_CvUploadArtifacts" PRIMARY KEY AUTOINCREMENT,
|
||||
"OwnerUserId" TEXT NOT NULL, "OriginalFileName" TEXT NOT NULL,
|
||||
"StoredFileName" TEXT NOT NULL, "MimeType" TEXT NOT NULL, "ByteSize" INTEGER NOT NULL,
|
||||
"Sha256" TEXT NOT NULL, "StoragePath" TEXT NOT NULL, "UploadedAtUtc" TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE "CvExtractionRuns" (
|
||||
"Id" INTEGER NOT NULL CONSTRAINT "PK_CvExtractionRuns" PRIMARY KEY AUTOINCREMENT,
|
||||
"OwnerUserId" TEXT NOT NULL, "ArtifactId" INTEGER NULL, "Trigger" TEXT NOT NULL,
|
||||
"ParserVersion" TEXT NOT NULL, "NormalizerVersion" TEXT NOT NULL,
|
||||
"LlmPromptVersion" TEXT NOT NULL, "Status" TEXT NOT NULL,
|
||||
"RawExtractedText" TEXT NULL, "NormalizedText" TEXT NULL,
|
||||
"StructuredProfileJson" TEXT NULL, "ErrorMessage" TEXT NULL,
|
||||
"StartedAtUtc" TEXT NOT NULL, "CompletedAtUtc" TEXT NULL, "AppliedAtUtc" TEXT NULL,
|
||||
CONSTRAINT "FK_CvExtractionRuns_CvUploadArtifacts_ArtifactId"
|
||||
FOREIGN KEY ("ArtifactId") REFERENCES "CvUploadArtifacts" ("Id") ON DELETE SET NULL
|
||||
);
|
||||
INSERT INTO "CvUploadArtifacts"
|
||||
("OwnerUserId", "OriginalFileName", "StoredFileName", "MimeType", "ByteSize",
|
||||
"Sha256", "StoragePath", "UploadedAtUtc")
|
||||
VALUES ('owner-fixture', 'resume.pdf', 'fixture.bin', 'application/pdf', 4096,
|
||||
'fixture-sha', 'uploads/fixture.bin', '2026-08-30T09:00:00+00:00');
|
||||
INSERT INTO "CvExtractionRuns"
|
||||
("OwnerUserId", "ArtifactId", "Trigger", "ParserVersion", "NormalizerVersion",
|
||||
"LlmPromptVersion", "Status", "StructuredProfileJson", "StartedAtUtc")
|
||||
VALUES ('owner-fixture', 1, 'upload', 'parser-v1', 'normalizer-v1', 'prompt-v1',
|
||||
'completed', '{"summary":"preserve me"}', '2026-08-30T09:01:00+00:00');
|
||||
""");
|
||||
|
||||
await migrator.MigrateAsync();
|
||||
Assert.Equal("fixture-sha", await ScalarAsync<string>(connection,
|
||||
"SELECT Sha256 FROM CvUploadArtifacts WHERE Id = 1;"));
|
||||
Assert.Equal("{\"summary\":\"preserve me\"}", await ScalarAsync<string>(connection,
|
||||
"SELECT StructuredProfileJson FROM CvExtractionRuns WHERE ArtifactId = 1;"));
|
||||
|
||||
await migrator.MigrateAsync("20260830124000_AdoptEmailConnectionSchema");
|
||||
Assert.Equal("fixture-sha", await ScalarAsync<string>(connection,
|
||||
"SELECT Sha256 FROM CvUploadArtifacts WHERE Id = 1;"));
|
||||
await migrator.MigrateAsync();
|
||||
Assert.Equal(3L, await ScalarAsync<long>(connection, """
|
||||
SELECT COUNT(*) FROM sqlite_master
|
||||
WHERE type = 'index' AND name IN (
|
||||
'IX_CvUploadArtifacts_OwnerUserId_UploadedAtUtc',
|
||||
'IX_CvExtractionRuns_OwnerUserId_StartedAtUtc',
|
||||
'IX_CvExtractionRuns_ArtifactId');
|
||||
"""));
|
||||
|
||||
await ExecuteAsync(connection, "DELETE FROM CvUploadArtifacts WHERE Id = 1;");
|
||||
Assert.Equal(1L, await ScalarAsync<long>(connection,
|
||||
"SELECT COUNT(*) FROM CvExtractionRuns WHERE ArtifactId IS NULL;"));
|
||||
Assert.Empty(await db.Database.GetPendingMigrationsAsync());
|
||||
}
|
||||
|
||||
private static JobTrackerContext Context(SqliteConnection connection)
|
||||
{
|
||||
var currentUser = new Mock<ICurrentUserService>();
|
||||
|
||||
Reference in New Issue
Block a user