feat(cv-qa): map missing records from reviewed source evidence
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
using JobTrackerApi.Models;
|
||||
using JobTrackerApi.Services;
|
||||
using Xunit;
|
||||
|
||||
namespace JobTrackerApi.Tests;
|
||||
|
||||
public class CvImportMappingTests
|
||||
{
|
||||
private static readonly CvImportDocument Source = new("Engineer Example 2020\nDesigner Other 2022\nPortfolio https://example.test\nSQL", false, "application/pdf", 2, 100, "synthetic.pdf",
|
||||
new[] { new CvImportBlock("b1", "Engineer Example 2020", "paragraph", 1), new CvImportBlock("b2", "Designer Other 2022", "paragraph", 2), new CvImportBlock("b3", "Portfolio https://example.test", "paragraph", 2), new CvImportBlock("b4", "SQL", "paragraph", 2) }, Array.Empty<string>(), "docling");
|
||||
private static CvImportMappedField Field(string name, string value, string block = "b1", bool corrected = false) => new(name, value, block, corrected);
|
||||
private static StructuredCvProfile Apply(StructuredCvProfile current, params CvImportRecordMapping[] mappings) => CvImportMapping.Apply(current, mappings, Source, 21, 7, "abc");
|
||||
|
||||
[Fact]
|
||||
public void Mapping_only_authors_multiple_missing_records_with_real_per_field_origin_and_date_precision()
|
||||
{
|
||||
var result = Apply(new(), new("first", "jobs", null, new[] { Field("title", "Engineer"), Field("company", "Example"), Field("start", "2020") }),
|
||||
new("second", "jobs", null, new[] { Field("title", "Designer", "b2"), Field("company", "Other", "b2"), Field("start", "2022", "b2") }));
|
||||
Assert.Equal(2, result.Jobs.Count);
|
||||
Assert.Equal("2020", result.Jobs[0].Start);
|
||||
Assert.Null(result.Jobs[0].End);
|
||||
Assert.Null(result.Jobs[0].StartDate);
|
||||
var evidence = result.Metadata.Fields["jobs.1.company"];
|
||||
Assert.Equal("b2", evidence.SourceBlockId);
|
||||
Assert.Equal(2, evidence.SourcePage);
|
||||
Assert.Equal(21, evidence.SourceArtifactId);
|
||||
Assert.Equal(7, evidence.SourceExtractionRunId);
|
||||
Assert.Equal("abc", evidence.SourceFileSha256);
|
||||
Assert.Equal("user-source-mapping", evidence.Method);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Explicit_target_updates_only_approved_leaves_and_lists_append_with_destination_evidence()
|
||||
{
|
||||
var current = new StructuredCvProfile { Jobs = { new() { Id = "stable", Title = "Original", Company = "Keep company", Bullets = { "Old" } } }, Skills = { "C#" } };
|
||||
current.Metadata.Fields["jobs.0.bullets.0"] = new() { SourceSnippet = "Old" };
|
||||
var result = Apply(current, new("target", "jobs", 0, new[] { Field("title", "Engineer"), Field("bullets", "Reviewed achievement", corrected: true) }),
|
||||
new("skill", "skills", null, new[] { Field("value", "SQL", "b4") }));
|
||||
var job = Assert.Single(result.Jobs);
|
||||
Assert.Equal("stable", job.Id);
|
||||
Assert.Equal("Keep company", job.Company);
|
||||
Assert.Equal("Engineer", job.Title);
|
||||
Assert.Equal(new[] { "Old", "Reviewed achievement" }, job.Bullets);
|
||||
Assert.Equal("Old", result.Metadata.Fields["jobs.0.bullets.0"].SourceSnippet);
|
||||
Assert.Equal("user-correction", result.Metadata.Fields["jobs.0.bullets.1"].Method);
|
||||
Assert.Equal(new[] { "C#", "SQL" }, result.Skills);
|
||||
Assert.Equal("Original", current.Jobs[0].Title);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Contact_mapping_authors_labelled_url_without_replacing_saved_links()
|
||||
{
|
||||
var result = Apply(new() { Contact = new() { GitHub = "https://github.com/saved", Links = { new() { Label = "Blog", Url = "https://blog.test" } } } },
|
||||
new CvImportRecordMapping("link", "contact", null, new[] { Field("linkLabel", "Portfolio", "b3"), Field("linkUrl", "https://example.test", "b3") }));
|
||||
Assert.Equal("https://github.com/saved", result.Contact.GitHub);
|
||||
Assert.Equal(2, result.Contact.Links.Count);
|
||||
Assert.Equal("Portfolio", result.Contact.Links[1].Label);
|
||||
Assert.Equal("b3", result.Metadata.Fields["contact.links.1.url"].SourceBlockId);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("metadata", "value", "Engineer", "b1", null)]
|
||||
[InlineData("jobs", "id", "Engineer", "b1", null)]
|
||||
[InlineData("jobs", "startDate", "2020", "b1", null)]
|
||||
[InlineData("jobs", "title.extra", "Engineer", "b1", null)]
|
||||
[InlineData("skills", "value.sourceBlockId", "SQL", "b4", null)]
|
||||
[InlineData("skills", "sourceBlockId", "SQL", "b4", null)]
|
||||
[InlineData("jobs", "title", "Engineer", "foreign-block", null)]
|
||||
[InlineData("jobs", "title", "Not in source", "b1", null)]
|
||||
[InlineData("jobs", "isCurrent", "yes", "b1", null)]
|
||||
[InlineData("jobs", "title", "Engineer", "b1", 0)]
|
||||
[InlineData("jobs", "title", "Engineer", "b1", -1)]
|
||||
[InlineData("skills", "value", "SQL", "b4", 0)]
|
||||
public void Invalid_or_unowned_mappings_fail_closed(string category, string name, string value, string block, int? index)
|
||||
=> Assert.Throws<ArgumentException>(() => Apply(new(), new CvImportRecordMapping("invalid", category, index, new[] { Field(name, value, block) })));
|
||||
|
||||
[Fact]
|
||||
public void Empty_identity_duplicate_ids_fields_and_oversized_requests_are_rejected()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => Apply(new(), new CvImportRecordMapping("empty", "jobs", null, Array.Empty<CvImportMappedField>())));
|
||||
Assert.Throws<ArgumentException>(() => Apply(new(), new CvImportRecordMapping("identity", "jobs", null, new[] { Field("start", "2020") })));
|
||||
var mapping = new CvImportRecordMapping("same", "jobs", null, new[] { Field("title", "Engineer") });
|
||||
Assert.Throws<ArgumentException>(() => Apply(new(), mapping, mapping));
|
||||
Assert.Throws<ArgumentException>(() => Apply(new(), mapping with { Fields = new[] { Field("title", "Engineer"), Field("title", "Engineer") } }));
|
||||
Assert.Throws<ArgumentException>(() => Apply(new(), mapping with { Fields = new[] { Field("title", new string('x', 8001), corrected: true) } }));
|
||||
Assert.Throws<ArgumentException>(() => Apply(new(), Enumerable.Range(0, 26).Select(i => mapping with { ClientId = i.ToString() }).ToArray()));
|
||||
Assert.Throws<ArgumentException>(() => Apply(new(), mapping with { Fields = Enumerable.Repeat(Field("bullets", "Engineer"), 201).ToArray() }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Grounding_respects_valid_selected_occurrence_and_rejects_invalid_selected_evidence()
|
||||
{
|
||||
var document = Source with { Blocks = new[] { new CvImportBlock("first", "Engineer", "paragraph", 1), new CvImportBlock("second", "Engineer Example", "paragraph", 2) } };
|
||||
var candidate = new StructuredCvProfile { Jobs = { new() { Title = "Engineer", Company = "Example" } } };
|
||||
candidate.Metadata.Fields["jobs.0.title"] = new() { SourceBlockId = "second", SourceSnippet = "Engineer", SourcePage = 2 };
|
||||
candidate.Metadata.Fields["jobs.0.company"] = new() { SourceBlockId = "first", SourceSnippet = "Example" };
|
||||
var result = CvDocumentReview.Ground(candidate, document);
|
||||
Assert.Equal("second", result.Metadata.Fields["jobs.0.title"].SourceBlockId);
|
||||
Assert.Null(result.Jobs[0].Company);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Selected_evidence_survives_scalar_list_deduplication_without_changing_occurrence()
|
||||
{
|
||||
var source = Source with { Blocks = new[] { new CvImportBlock("first", "SQL", "paragraph", 1), new CvImportBlock("second", "SQL Engineer", "paragraph", 2) } };
|
||||
var candidate = new StructuredCvProfile { Skills = { "SQL", "SQL", "Engineer" } };
|
||||
candidate.Metadata.Fields["skills.2"] = new() { SourceBlockId = "second", SourceSnippet = "Engineer" };
|
||||
var result = CvDocumentReview.Ground(candidate, source);
|
||||
Assert.Equal(new[] { "SQL", "Engineer" }, result.Skills);
|
||||
Assert.Equal("second", result.Metadata.Fields["skills.1"].SourceBlockId);
|
||||
Assert.DoesNotContain("skills.2", result.Metadata.Fields.Keys);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Mapping_comparison_catalog_exposes_only_allowlisted_current_values_for_the_selected_target()
|
||||
{
|
||||
var current = new StructuredCvProfile
|
||||
{
|
||||
Contact = new() { FullName = "Saved name", GitHub = "https://github.com/saved" }, Skills = { "C#", "SQL" },
|
||||
Jobs = { new() { Id = "private-id", Title = "Saved title", Company = "Saved company", StartDate = "2020-01", Bullets = { "First", "Second" } }, new() { Title = "Different title" } },
|
||||
};
|
||||
current.Metadata.Fields["jobs.0.title"] = new() { SourceSnippet = "private evidence" };
|
||||
var targets = CvImportMapping.Targets(current);
|
||||
Assert.DoesNotContain(targets, t => t.Category == "contact");
|
||||
var target = Assert.Single(targets, t => t.Category == "jobs" && t.Index == 0);
|
||||
Assert.Equal("Saved title", target.Values!["title"]);
|
||||
Assert.Equal("First\nSecond", target.Values["bullets"]);
|
||||
Assert.DoesNotContain("id", target.Values.Keys);
|
||||
Assert.DoesNotContain("metadata", target.Values.Keys);
|
||||
Assert.DoesNotContain("startDate", target.Values.Keys);
|
||||
Assert.Equal("Different title", targets.Single(t => t.Category == "jobs" && t.Index == 1).Values!["title"]);
|
||||
var catalog = CvImportMapping.Catalog(current);
|
||||
Assert.Equal("Saved name", catalog.Single(c => c.Category == "contact").Fields.Single(f => f.Name == "fullName").CurrentValue);
|
||||
Assert.Equal("C#\nSQL", catalog.Single(c => c.Category == "skills").Fields.Single(f => f.Name == "value").CurrentValue);
|
||||
Assert.All(catalog.Single(c => c.Category == "jobs").Fields, f => Assert.Null(f.CurrentValue));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Py", "Python", false)]
|
||||
[InlineData("2020", "12020", false)]
|
||||
[InlineData("SQL", "NoSQL", false)]
|
||||
[InlineData("C#", "Used C# and SQL.", true)]
|
||||
[InlineData(".NET", "Built .NET services.", true)]
|
||||
[InlineData("2020", "2020 - 2021", true)]
|
||||
[InlineData("Engineer", "Engineer.", true)]
|
||||
public void Source_mode_mapping_requires_honest_token_boundaries(string value, string source, bool supported)
|
||||
{
|
||||
var document = Source with { Blocks = new[] { new CvImportBlock("token", source, "paragraph", 1) } };
|
||||
var mapping = new CvImportRecordMapping("token", "skills", null, new[] { Field("value", value, "token") });
|
||||
if (supported) Assert.Equal(value, Assert.Single(CvImportMapping.Apply(new(), new[] { mapping }, document, 1, 1, "hash").Skills));
|
||||
else Assert.Throws<ArgumentException>(() => CvImportMapping.Apply(new(), new[] { mapping }, document, 1, 1, "hash"));
|
||||
var correction = mapping with { Fields = new[] { Field("value", value, "token", corrected: true) } };
|
||||
Assert.Equal(value, Assert.Single(CvImportMapping.Apply(new(), new[] { correction }, document, 1, 1, "hash").Skills));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user