using JobTrackerApi.Data; using JobTrackerApi.Models; using JobTrackerApi.Services; using Microsoft.EntityFrameworkCore; using Moq; using Xunit; namespace JobTrackerApi.Tests; public sealed class CareerProfileServiceTests { private static JobTrackerContext NewContext(string userId) { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(Guid.NewGuid().ToString()) .Options; var currentUser = new Mock(); currentUser.SetupGet(service => service.UserId).Returns(userId); return new JobTrackerContext(options, currentUser.Object); } [Fact] public async Task SaveVersionAsync_assigns_stable_ids_to_items_missing_one() { await using var db = NewContext("user-1"); var service = new CareerProfileService(db); var profile = new StructuredCvProfile { Jobs = { new StructuredCvJob { Title = "Engineer", Company = "Acme" } }, }; var saved = await service.SaveVersionAsync("user-1", profile, "upload", default); Assert.False(string.IsNullOrWhiteSpace(saved.Jobs[0].Id)); } [Fact] public async Task SaveVersionAsync_preserves_existing_ids_across_saves() { await using var db = NewContext("user-1"); var service = new CareerProfileService(db); var profile = new StructuredCvProfile { Jobs = { new StructuredCvJob { Title = "Engineer", Company = "Acme" } }, }; await service.SaveVersionAsync("user-1", profile, "upload", default); var firstId = profile.Jobs[0].Id; await service.SaveVersionAsync("user-1", profile, "rebuild", default); Assert.Equal(firstId, profile.Jobs[0].Id); } [Theory] [InlineData("January 2020", "2020-01")] [InlineData("Mar 2019", "2019-03")] [InlineData("03/2019", "2019-03")] [InlineData("2019-03", "2019-03")] [InlineData("Present", null)] [InlineData("2020", null)] [InlineData(null, null)] public void CvDateNormalizer_parses_common_formats_without_guessing(string? input, string? expected) { Assert.Equal(expected, CvDateNormalizer.TryParseYearMonth(input)); } [Fact] public async Task SaveVersionAsync_persists_current_snapshot_and_append_only_history() { await using var db = NewContext("user-1"); var service = new CareerProfileService(db); var profile = new StructuredCvProfile { Summary = { "First version" } }; await service.SaveVersionAsync("user-1", profile, "upload", default); await service.SaveVersionAsync("user-1", profile, "improve", default); var snapshots = await db.CareerProfiles.IgnoreQueryFilters().Where(x => x.OwnerUserId == "user-1").ToListAsync(); var history = await db.CareerProfileVersions.IgnoreQueryFilters().Where(x => x.OwnerUserId == "user-1").ToListAsync(); Assert.Single(snapshots); Assert.Equal(2, snapshots[0].Version); Assert.Equal(2, history.Count); Assert.Equal(new[] { "upload", "improve" }, history.OrderBy(x => x.Version).Select(x => x.Source)); } [Fact] public async Task SaveVersionAsync_does_not_normalize_end_date_when_job_is_current() { await using var db = NewContext("user-1"); var service = new CareerProfileService(db); var profile = new StructuredCvProfile { Jobs = { new StructuredCvJob { Title = "Engineer", Start = "Jan 2020", End = "Present", IsCurrent = true } }, }; var saved = await service.SaveVersionAsync("user-1", profile, "upload", default); Assert.Equal("2020-01", saved.Jobs[0].StartDate); Assert.Null(saved.Jobs[0].EndDate); } }