using JobTrackerApi.Models; using Xunit; namespace JobTrackerApi.Tests; // Phase 2.1-b — CV extraction coverage. The structured model and StructuredCvProfileJson.FromSections // already map Projects, Certifications and Languages headings into the profile; the gap was upstream // (the AI /cv/normalize prompt never emitted those headings, so the sections were dropped). These // tests lock the C# side so that once the normalized markdown carries # Projects / # Certifications / // # Languages, they reach the structured profile — and so a future change can't silently regress it. // // Content shapes mirror what the (fixed) normalizer produces for the benchmark CV // (Connor Babbington): a Projects section, and languages stated as "Name: Level". public sealed class CvExtractionCoverageTests { private static StructuredCvSection Section(string name, string content) => new() { Name = name, Content = content }; [Fact] public void FromSections_maps_a_Projects_heading_into_structured_projects() { var profile = StructuredCvProfileJson.FromSections(new[] { Section("Projects", "JobTrack\nFull-stack job-application tracker (React, ASP.NET Core, SQLite, Docker).\n\n" + "InboxIntel\nGmail analytics and safe bulk-cleanup tool in .NET 8 with PostgreSQL."), }); Assert.Equal(2, profile.Projects.Count); Assert.Contains(profile.Projects, p => p.Name == "JobTrack"); Assert.Contains(profile.Projects, p => p.Name == "InboxIntel"); } [Fact] public void FromSections_maps_a_Certifications_heading_into_structured_certifications() { var profile = StructuredCvProfileJson.FromSections(new[] { Section("Certifications", "Extended Diploma NVQ Level 3 in ICT\n\nAZ-900 Azure Fundamentals"), }); Assert.NotEmpty(profile.Certifications); Assert.Contains(profile.Certifications, c => (c.Name ?? "").Contains("NVQ", System.StringComparison.OrdinalIgnoreCase)); } [Fact] public void FromSections_maps_a_Languages_heading_with_levels() { var profile = StructuredCvProfileJson.FromSections(new[] { Section("Languages", "English: Native\nNorwegian: B1"), }); Assert.Equal(2, profile.Languages.Count); Assert.Contains(profile.Languages, l => l.Name == "English" && (l.Level ?? "").Contains("Native")); Assert.Contains(profile.Languages, l => l.Name == "Norwegian" && (l.Level ?? "").Contains("B1")); } // The benchmark CV has all four rich sections; confirm they coexist without one clobbering another. [Fact] public void FromSections_populates_projects_certifications_and_languages_together() { var profile = StructuredCvProfileJson.FromSections(new[] { Section("Skills", "C#\n.NET\nDocker"), Section("Projects", "JobTrack\nJob-application tracker."), Section("Certifications", "NVQ Level 3 in ICT"), Section("Languages", "English: Native\nNorwegian: B1"), }); Assert.NotEmpty(profile.Skills); Assert.Single(profile.Projects); Assert.NotEmpty(profile.Certifications); Assert.Equal(2, profile.Languages.Count); } }