refactor(db): migrate AI interaction history
Move append-only AI results into an additive provider-aware migration. Preserve generated content, usage counters, indexes, and application cascade semantics.
This commit is contained in:
@@ -656,6 +656,53 @@ public sealed class MigrationChainTests
|
||||
Assert.Empty(await db.Database.GetPendingMigrationsAsync());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Ai_interaction_adoption_preserves_history_usage_and_cascade()
|
||||
{
|
||||
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("20260830128000_AdoptCvVariantSchema");
|
||||
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);
|
||||
INSERT INTO AiInteractions
|
||||
(OwnerUserId, JobApplicationId, Module, Mode, Title, Provider, ResultJson,
|
||||
InputCharacterCount, OutputCharacterCount, EstimatedTokenCount, CreatedAtUtc)
|
||||
VALUES
|
||||
('owner-fixture', 1, 'cover-letter', 'professional', 'Cover letter draft',
|
||||
'fixture-provider', '{"text":"preserve me"}', 1200, 640, 460,
|
||||
'2026-08-30T09:00:00+00:00');
|
||||
""");
|
||||
|
||||
await migrator.MigrateAsync();
|
||||
Assert.Equal("{\"text\":\"preserve me\"}", await ScalarAsync<string>(connection,
|
||||
"SELECT ResultJson FROM AiInteractions WHERE Id = 1;"));
|
||||
Assert.Equal(460L, await ScalarAsync<long>(connection,
|
||||
"SELECT EstimatedTokenCount FROM AiInteractions WHERE Id = 1;"));
|
||||
|
||||
await migrator.MigrateAsync("20260830128000_AdoptCvVariantSchema");
|
||||
Assert.Equal("professional", await ScalarAsync<string>(connection,
|
||||
"SELECT Mode FROM AiInteractions WHERE Id = 1;"));
|
||||
await migrator.MigrateAsync();
|
||||
Assert.Equal(2L, await ScalarAsync<long>(connection, """
|
||||
SELECT COUNT(*) FROM sqlite_master
|
||||
WHERE type = 'index' AND name IN (
|
||||
'IX_AiInteractions_JobApplicationId',
|
||||
'IX_AiInteractions_Owner_Job_Module_Created');
|
||||
"""));
|
||||
|
||||
await ExecuteAsync(connection, "DELETE FROM JobApplications WHERE Id = 1;");
|
||||
Assert.Equal(0L, await ScalarAsync<long>(connection, "SELECT COUNT(*) FROM AiInteractions;"));
|
||||
Assert.Empty(await db.Database.GetPendingMigrationsAsync());
|
||||
}
|
||||
|
||||
private static JobTrackerContext Context(SqliteConnection connection)
|
||||
{
|
||||
var currentUser = new Mock<ICurrentUserService>();
|
||||
|
||||
Reference in New Issue
Block a user