fix(cv): preserve attachment flags across generic letter writers

This commit is contained in:
cesnimda
2026-10-26 08:47:47 +01:00
parent 7bf06ede52
commit fc93dfb950
5 changed files with 109 additions and 2 deletions
@@ -62,17 +62,22 @@ public sealed partial class CoverLetterWriterTests
var created = await Controller(db).Create(Request<CreateJobApplicationRequest>(new { jobTitle = "Created", companyId = seed.CompanyId, coverLetterText = " initial body " }), default);
Assert.IsType<CreatedAtActionResult>(created.Result);
var job = await db.JobApplications.SingleAsync(x => x.JobTitle == "Created");
Assert.False(job.HasCoverLetter);
Assert.Equal(" initial body ", (await db.CoverLetterVersions.SingleAsync(x => x.JobApplicationId == job.Id)).Text);
Assert.IsType<NoContentResult>(await Update(db, job, " generic body "));
Assert.Equal(" generic body ", job.CoverLetterText);
Assert.False(job.HasCoverLetter);
Assert.IsType<NoContentResult>(await Controller(db).SaveApplicationDrafts(job.Id,
new SaveApplicationDraftsRequest(" draft body ", null, null, ExpectedVersion: job.Version), default));
Assert.Equal("draft body", job.CoverLetterText);
Assert.False(job.HasCoverLetter);
var saved = await Assets(db).SaveCoverLetterAsync(Owner, job.Id, " approved AI body ", CoverLetterSources.Ai, "improve", job.Version, default);
Assert.Equal(4, saved!.CurrentVersion);
Assert.True(job.HasCoverLetter);
var restored = await Assets(db).RestoreCoverLetterAsync(Owner, job.Id, 3, job.Version, default);
Assert.Equal("draft body", restored!.Text);
Assert.Equal(5, restored.CurrentVersion);
Assert.True(job.HasCoverLetter);
var history = await db.CoverLetterVersions.Where(x => x.JobApplicationId == job.Id).OrderBy(x => x.Version).ToListAsync();
Assert.Equal(new[] { " initial body ", " generic body ", "draft body", "approved AI body", "draft body" }, history.Select(x => x.Text));
Assert.Equal(CoverLetterSources.Ai, history[3].Source);
@@ -80,6 +85,49 @@ public sealed partial class CoverLetterWriterTests
Assert.Equal(CoverLetterSources.Restore, history[4].Source);
}
[Theory]
[InlineData("generic", false)]
[InlineData("generic", true)]
[InlineData("draft", false)]
[InlineData("draft", true)]
public async Task Generic_and_draft_letter_changes_preserve_existing_attachment_flag(string writer, bool hasCoverLetter)
{
await using var connection = new SqliteConnection("Data Source=:memory:");
await connection.OpenAsync();
await using var db = Context(connection);
var job = await Seed(db);
job.HasCoverLetter = hasCoverLetter;
await db.SaveChangesAsync();
await Write(db, job, writer, "Intended body");
Assert.Equal(hasCoverLetter, job.HasCoverLetter);
if (writer == "generic") Assert.IsType<NoContentResult>(await Update(db, job, null));
else await Write(db, job, writer, "");
Assert.Null(job.CoverLetterText);
Assert.Equal(hasCoverLetter, job.HasCoverLetter);
Assert.Equal(new[] { "Intended body", "" }, await db.CoverLetterVersions.OrderBy(value => value.Version).Select(value => value.Text).ToArrayAsync());
}
[Fact]
public async Task Dedicated_letter_no_op_keeps_flag_but_changed_nonempty_body_sets_it_and_clear_preserves_it()
{
await using var connection = new SqliteConnection("Data Source=:memory:");
await connection.OpenAsync();
await using var db = Context(connection);
var job = await Seed(db, "Existing body");
var service = Assets(db);
var revision = job.Version;
var unchanged = await service.SaveCoverLetterAsync(Owner, job.Id, " Existing body ", CoverLetterSources.Manual, null, revision, default);
Assert.False(job.HasCoverLetter);
Assert.Equal(revision + 1, unchanged!.Revision);
Assert.Empty(await db.CoverLetterVersions.ToListAsync());
await service.SaveCoverLetterAsync(Owner, job.Id, "Changed body", CoverLetterSources.Manual, null, job.Version, default);
Assert.True(job.HasCoverLetter);
var cleared = await service.SaveCoverLetterAsync(Owner, job.Id, null, CoverLetterSources.Manual, null, job.Version, default);
Assert.Null(job.CoverLetterText);
Assert.True(job.HasCoverLetter);
Assert.Equal(3, cleared!.CurrentVersion);
}
[Fact]
public async Task Draft_null_preserves_generic_null_clears_and_no_ops_acknowledge_without_duplicate_history()
{