feat(jobs): complete workspace draft parity
CI and Deploy / test (pull_request) Successful in 5m4s
CI and Deploy / deploy (pull_request) Has been skipped

This commit is contained in:
cesnimda
2026-08-15 17:34:32 +02:00
parent 3b86ea2da0
commit deed948183
28 changed files with 676 additions and 131 deletions
@@ -83,6 +83,24 @@ public sealed class ApplicationWorkspaceTests
Assert.Equal(0, o.DocumentCount);
}
[Fact]
public async Task Overview_separates_application_answers_from_human_notes()
{
var (db, svc) = New("user-1");
await using var _ = db;
var job = await SeedAsync(db, "user-1", j =>
{
j.Notes = "Ask about the platform team.\n\n<<<APPLICATION_ANSWER_DRAFT>>>\nI enjoy solving customer problems.\n<<<END_APPLICATION_ANSWER_DRAFT>>>";
j.RecruiterMessageDraft = "Hello Maria";
});
var o = await svc.GetOverviewAsync("user-1", job.Id, default);
Assert.Equal("Ask about the platform team.", o!.Notes);
Assert.Equal("I enjoy solving customer problems.", o.ApplicationAnswerDraft);
Assert.Equal("Hello Maria", o.RecruiterMessageDraft);
}
[Fact]
public async Task Overview_surfaces_the_attached_cv_variant()
{
@@ -46,6 +46,77 @@ public sealed class JobApplicationsApplicationPackageTests
Assert.Equal("Updated notes block", saved.Notes);
}
[Fact]
public async Task Save_application_drafts_updates_and_clears_answer_without_changing_human_notes()
{
await using var db = CreateDb();
var company = new Company { Name = "Acme", OwnerUserId = "user-1" };
db.Companies.Add(company);
await db.SaveChangesAsync();
var job = new JobApplication
{
JobTitle = "Backend Developer",
CompanyId = company.Id,
OwnerUserId = "user-1",
Notes = "Human note\n\n<<<APPLICATION_ANSWER_DRAFT>>>\nOld answer\n<<<END_APPLICATION_ANSWER_DRAFT>>>",
RecruiterMessageDraft = "Old recruiter message"
};
db.JobApplications.Add(job);
await db.SaveChangesAsync();
var controller = CreateController(db, Mock.Of<ISummarizerService>(), "user-1");
var updated = await controller.SaveApplicationDrafts(
job.Id,
new SaveApplicationDraftsRequest(null, null, " New recruiter message ", " New answer "),
CancellationToken.None);
Assert.IsType<NoContentResult>(updated);
Assert.Equal("Human note", JobApplicationHelpers.RemoveSavedApplicationAnswerDraft(job.Notes));
Assert.Equal("New answer", JobApplicationHelpers.ExtractSavedApplicationAnswerDraft(job.Notes));
Assert.Equal("New recruiter message", job.RecruiterMessageDraft);
job.Notes += "\n\n<<<APPLICATION_ANSWER_DRAFT>>>\nDuplicate stale answer\n<<<END_APPLICATION_ANSWER_DRAFT>>>";
var cleared = await controller.SaveApplicationDrafts(
job.Id,
new SaveApplicationDraftsRequest(null, null, "", ""),
CancellationToken.None);
Assert.IsType<NoContentResult>(cleared);
Assert.Equal("Human note", job.Notes);
Assert.Null(job.RecruiterMessageDraft);
}
[Fact]
public async Task Save_application_drafts_cannot_mutate_another_users_application()
{
await using var db = CreateDb();
var company = new Company { Name = "Other Acme", OwnerUserId = "user-2" };
db.Companies.Add(company);
await db.SaveChangesAsync();
var otherUsersJob = new JobApplication
{
JobTitle = "Private role",
CompanyId = company.Id,
OwnerUserId = "user-2",
Notes = "Private note",
RecruiterMessageDraft = "Private draft"
};
db.JobApplications.Add(otherUsersJob);
await db.SaveChangesAsync();
var controller = CreateController(db, Mock.Of<ISummarizerService>(), "user-1");
var result = await controller.SaveApplicationDrafts(
otherUsersJob.Id,
new SaveApplicationDraftsRequest(null, null, "Changed", "Changed"),
CancellationToken.None);
Assert.IsType<NotFoundResult>(result);
Assert.Equal("Private note", otherUsersJob.Notes);
Assert.Equal("Private draft", otherUsersJob.RecruiterMessageDraft);
}
[Fact]
public async Task Generate_application_package_uses_imported_correspondence_and_recruiter_context()
{
@@ -21,7 +21,7 @@ public sealed class JobApplicationsControllerTests
}
[Fact]
public void Save_application_drafts_request_supports_cover_letter_and_notes()
public void Save_application_drafts_request_supports_the_complete_application_package()
{
var type = typeof(SaveApplicationDraftsRequest);
Assert.NotNull(type);
@@ -30,5 +30,7 @@ public sealed class JobApplicationsControllerTests
var parameters = ctor.GetParameters().Select(x => x.Name).Where(x => x is not null).Select(x => x!).ToHashSet(StringComparer.OrdinalIgnoreCase);
Assert.Contains("coverLetterText", parameters);
Assert.Contains("notes", parameters);
Assert.Contains("applicationAnswerDraft", parameters);
Assert.Contains("recruiterMessageDraft", parameters);
}
}
@@ -225,7 +225,7 @@ public sealed class JobApplicationsEndpointBehaviorTests
}
[Fact]
public async Task Update_drops_invalid_salary_period_and_negative_values()
public async Task Update_normalizes_salary_and_preserves_the_separate_application_answer()
{
await using var db = CreateDb();
var company = new Company { Name = "Acme", OwnerUserId = "user-1" };
@@ -241,6 +241,7 @@ public sealed class JobApplicationsEndpointBehaviorTests
SalaryMax = 60000m,
SalaryCurrency = "NOK",
SalaryPeriod = "year",
Notes = "Old human note\n\n<<<APPLICATION_ANSWER_DRAFT>>>\nSaved answer\n<<<END_APPLICATION_ANSWER_DRAFT>>>",
Job = new Job { CompanyId = company.Id, JobTitle = "Backend Dev", Source = "nav", CountryCode = "NO" },
};
db.JobApplications.Add(job);
@@ -261,7 +262,7 @@ public sealed class JobApplicationsEndpointBehaviorTests
SalaryPeriod: "fortnight",
NextAction: null,
FollowUpAt: null,
Notes: null,
Notes: "Updated human note",
Description: null,
TranslatedDescription: null,
DescriptionLanguage: null,
@@ -281,6 +282,8 @@ public sealed class JobApplicationsEndpointBehaviorTests
Assert.Null(saved.SalaryMax);
Assert.Null(saved.SalaryCurrency);
Assert.Null(saved.SalaryPeriod);
Assert.Equal("Updated human note", JobApplicationHelpers.RemoveSavedApplicationAnswerDraft(saved.Notes));
Assert.Equal("Saved answer", JobApplicationHelpers.ExtractSavedApplicationAnswerDraft(saved.Notes));
var opportunity = await db.Jobs.SingleAsync();
Assert.Equal(saved.JobTitle, opportunity.JobTitle);
Assert.Null(opportunity.SalaryMin);