fix: preserve blank CVs and reliable editor undo
This commit is contained in:
@@ -364,6 +364,20 @@ public sealed class ApplicationIntelligenceTests
|
||||
Assert.Empty(match.RelevantExperience);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Match_does_not_inherit_experience_from_an_explicit_blank_cv()
|
||||
{
|
||||
var (db, intelligence, _) = New("user-1");
|
||||
await using var dispose = db;
|
||||
var job = await SeedJobAsync(db, "user-1");
|
||||
await SeedProfileAsync(db, "user-1");
|
||||
await AttachCvAsync(db, "user-1", job.Id, CvVariantSettingsJson.Deserialize("{\"sectionsExplicit\":true,\"includeProfileContact\":false,\"sections\":[]}"));
|
||||
var match = await intelligence.MatchAsync("user-1", job.Id, default);
|
||||
Assert.True(match!.HasSelectedCv);
|
||||
Assert.Empty(match.RelevantExperience);
|
||||
Assert.Empty(match.RelevantProjects);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Match_is_not_readable_for_another_users_application()
|
||||
{
|
||||
|
||||
@@ -19,6 +19,20 @@ public sealed class CvAiReadinessTests
|
||||
{
|
||||
private const string Source = "Built and maintained internal web applications.";
|
||||
private const string Paraphrase = "Developed and maintained internal web applications.";
|
||||
|
||||
[Fact]
|
||||
public async Task Explicit_blank_document_AI_does_not_send_unselected_profile_facts()
|
||||
{
|
||||
await using var fixture = new Fixture();
|
||||
await fixture.Open();
|
||||
await fixture.Variants.SaveAsync("owner", fixture.Cv.Id, null,
|
||||
CvVariantSettingsJson.Deserialize("{\"sectionsExplicit\":true,\"includeProfileContact\":false,\"sections\":[]}"), "manual", 1, default);
|
||||
var error = await Assert.ThrowsAsync<CvAiSuggestionException>(() => fixture.Suggestions.GenerateAsync(
|
||||
"owner", fixture.Cv.Id, 2, null, "document-improve", null, null, "Rewrite", default));
|
||||
Assert.Equal("cv_ai_source_empty", error.Code);
|
||||
fixture.Ai.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
private sealed class Fixture : IAsyncDisposable
|
||||
{
|
||||
public readonly SqliteConnection Connection = new("Data Source=:memory:");
|
||||
|
||||
@@ -28,6 +28,72 @@ public sealed class CvBuilderTests
|
||||
|
||||
// ---- Resolver (the lens over the master profile) ----
|
||||
|
||||
[Fact]
|
||||
public void Explicit_blank_roundtrips_without_inheriting_profile_content_or_header()
|
||||
{
|
||||
var settings = CvVariantSettingsJson.Deserialize("{\"sectionsExplicit\":true,\"includeProfileContact\":false,\"sections\":[],\"showPhoto\":true}");
|
||||
var json = CvVariantSettingsJson.Serialize(settings);
|
||||
Assert.Contains("\"sectionsExplicit\":true", json);
|
||||
Assert.Contains("\"includeProfileContact\":false", json);
|
||||
var model = CvVariantResolver.Build(Rich(), CvVariantSettingsJson.Deserialize(json), "Fallback", "data:image/png;base64,AA==");
|
||||
Assert.Empty(model.Sections);
|
||||
Assert.Empty(model.FullName);
|
||||
Assert.Null(model.Headline);
|
||||
Assert.Empty(model.Contact);
|
||||
Assert.Null(model.PhotoDataUrl);
|
||||
foreach (var theme in CvThemeCatalog.Themes)
|
||||
{
|
||||
var html = new ThemedCvRenderer().Render(model, theme, settings).Html;
|
||||
Assert.DoesNotContain("<header", html);
|
||||
Assert.DoesNotContain("Ada Lovelace", html);
|
||||
Assert.DoesNotContain("Fallback", html);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Explicit_sections_include_only_selected_content_and_allow_authored_headline()
|
||||
{
|
||||
var settings = CvVariantSettingsJson.Deserialize("{\"sectionsExplicit\":true,\"includeProfileContact\":false,\"headline\":\"Authored headline\",\"sections\":[{\"key\":\"skills\"}]}");
|
||||
var model = CvVariantResolver.Build(Rich(), settings, "Fallback", null);
|
||||
Assert.Equal("skills", Assert.Single(model.Sections).Key);
|
||||
Assert.Equal("Authored headline", model.Headline);
|
||||
Assert.Empty(model.FullName);
|
||||
Assert.Empty(model.Contact);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Legacy_inclusion_flags_remain_absent_and_preserve_profile_inheritance()
|
||||
{
|
||||
var settings = CvVariantSettingsJson.Deserialize("{\"sections\":[]}");
|
||||
var json = CvVariantSettingsJson.Serialize(settings);
|
||||
Assert.DoesNotContain("sectionsExplicit", json);
|
||||
Assert.DoesNotContain("includeProfileContact", json);
|
||||
var model = CvVariantResolver.Build(Rich(), settings, "Fallback", null);
|
||||
Assert.Equal("Ada Lovelace", model.FullName);
|
||||
Assert.NotEmpty(model.Sections);
|
||||
Assert.NotEmpty(model.Contact);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Explicit_blank_survives_save_duplicate_restore_and_design_change()
|
||||
{
|
||||
var (db, service) = NewService("user-1");
|
||||
await using var dispose = db;
|
||||
var settings = CvVariantSettingsJson.Deserialize("{\"sectionsExplicit\":true,\"includeProfileContact\":false,\"sections\":[]}");
|
||||
var created = await service.CreateAsync("user-1", "Blank", null, settings, default);
|
||||
settings.ThemeId = "clean";
|
||||
await service.SaveAsync("user-1", created.Id, null, settings, "manual", 1, default);
|
||||
var copy = await service.DuplicateAsync("user-1", created.Id, null, default);
|
||||
var restored = await service.RestoreVersionAsync("user-1", created.Id, 1, 2, default);
|
||||
foreach (var document in new[] { copy!, restored! })
|
||||
{
|
||||
var stored = CvVariantSettingsJson.Deserialize(document.SettingsJson);
|
||||
Assert.Contains("\"sectionsExplicit\":true", document.SettingsJson);
|
||||
Assert.Contains("\"includeProfileContact\":false", document.SettingsJson);
|
||||
Assert.Empty(CvVariantResolver.Build(Rich(), stored, "Fallback", null).Sections);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Resolver_defaults_include_every_populated_section()
|
||||
{
|
||||
|
||||
@@ -16,6 +16,27 @@ namespace JobTrackerApi.Tests;
|
||||
|
||||
public sealed class CvExportRevisionTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task Explicit_blank_export_is_rejected_before_pdf_allocation()
|
||||
{
|
||||
var current = new Mock<ICurrentUserService>();
|
||||
current.SetupGet(x => x.UserId).Returns("owner");
|
||||
await using var db = new JobTrackerContext(new DbContextOptionsBuilder<JobTrackerContext>()
|
||||
.UseInMemoryDatabase(Guid.NewGuid().ToString()).Options, current.Object);
|
||||
var variants = new CvVariantService(db, new CareerProfileService(db), new ThemedCvRenderer());
|
||||
var settings = CvVariantSettingsJson.Deserialize("{\"sectionsExplicit\":true,\"includeProfileContact\":false,\"sections\":[]}");
|
||||
var cv = await variants.CreateAsync("owner", "Blank", null, settings, default);
|
||||
var user = new ApplicationUser { Id = "owner", FirstName = "Inherited name" };
|
||||
var users = TestHostFactory.CreateUserManager(user);
|
||||
users.Setup(x => x.GetUserAsync(It.IsAny<ClaimsPrincipal>())).ReturnsAsync(user);
|
||||
var pdf = new Mock<ICvPdfExporter>(MockBehavior.Strict);
|
||||
var controller = new CvVariantController(users.Object, variants, pdf.Object, Mock.Of<ISummarizerService>())
|
||||
{ ControllerContext = new() { HttpContext = new DefaultHttpContext() } };
|
||||
var result = Assert.IsType<BadRequestObjectResult>(await controller.ExportPdf(cv.Id, default, 1));
|
||||
Assert.Equal("cv_empty_content", JsonSerializer.SerializeToElement(result.Value).GetProperty("code").GetString());
|
||||
pdf.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(1, "owner", 409)]
|
||||
[InlineData(0, "owner", 409)]
|
||||
|
||||
Reference in New Issue
Block a user