using System.Security.Claims; using JobTrackerApi.Controllers; using JobTrackerApi.Data; using JobTrackerApi.Models; using JobTrackerApi.Services; using JobTrackerApi.Tests.TestSupport; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging.Abstractions; using Moq; using Xunit; namespace JobTrackerApi.Tests; // Career Workspace foundation, Phase F2 (career-workspace-implementation-roadmap.md). Every // TailoredCvDraft save should dual-write a CvVariant + CvVersion + TailoredApplication without // changing TailoredCvDraft's own behavior -- these tests lock in that seam. public sealed class CvVariantSyncTests { [Fact] public async Task SaveTailoredCvDraft_creates_a_variant_version_and_application_link() { await using var db = TestHostFactory.CreateInMemoryDb(); var job = await SeedJobAsync(db); var controller = CreateController(db, "user-1"); var result = await controller.SaveTailoredCvDraft(job.Id, new JobApplicationsController.SaveTailoredCvDraftRequest( "ats-minimal", "Backend Engineer", new List { "Built things." }, new List { "C#" }, new List(), new List(), new List(), null, "edited"), CancellationToken.None); Assert.IsType(result); var variant = Assert.Single(db.CvVariants.IgnoreQueryFilters().Where(x => x.OwnerUserId == "user-1")); Assert.Equal(1, variant.Version); Assert.Contains("Backend Developer", variant.Name); var link = Assert.Single(db.TailoredApplications.IgnoreQueryFilters().Where(x => x.JobApplicationId == job.Id)); Assert.Equal(variant.Id, link.CvVariantId); var version = Assert.Single(db.CvVersions.IgnoreQueryFilters().Where(x => x.CvVariantId == variant.Id)); Assert.Equal(1, version.Version); } [Fact] public async Task SaveTailoredCvDraft_reuses_the_same_variant_on_subsequent_saves() { await using var db = TestHostFactory.CreateInMemoryDb(); var job = await SeedJobAsync(db); var controller = CreateController(db, "user-1"); var request = new JobApplicationsController.SaveTailoredCvDraftRequest( "ats-minimal", "Backend Engineer", new List { "Built things." }, new List { "C#" }, new List(), new List(), new List(), null, "edited"); await controller.SaveTailoredCvDraft(job.Id, request, CancellationToken.None); await controller.SaveTailoredCvDraft(job.Id, request with { Headline = "Updated headline" }, CancellationToken.None); var variant = Assert.Single(db.CvVariants.IgnoreQueryFilters().Where(x => x.OwnerUserId == "user-1")); Assert.Equal(2, variant.Version); var links = db.TailoredApplications.IgnoreQueryFilters().Where(x => x.JobApplicationId == job.Id).ToList(); Assert.Single(links); var versions = db.CvVersions.IgnoreQueryFilters().Where(x => x.CvVariantId == variant.Id).ToList(); Assert.Equal(2, versions.Count); } private static async Task SeedJobAsync(JobTrackerContext db) { var company = new Company { Name = "Acme", OwnerUserId = "user-1" }; db.Companies.Add(company); db.Users.Add(new ApplicationUser { Id = "user-1", UserName = "user@example.test", Email = "user@example.test" }); await db.SaveChangesAsync(); var job = new JobApplication { JobTitle = "Backend Developer", CompanyId = company.Id, OwnerUserId = "user-1" }; db.JobApplications.Add(job); await db.SaveChangesAsync(); return job; } private static JobApplicationsController CreateController(JobTrackerContext db, string userId) { var user = db.Users.AsNoTracking().FirstOrDefault(x => x.Id == userId); var controller = new JobApplicationsController( db, Mock.Of(), Mock.Of(), TestHostFactory.CreateUserManager(user).Object, NullLogger.Instance, Mock.Of(), Mock.Of()); controller.ControllerContext = new ControllerContext { HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.NameIdentifier, userId) }, "test")) } }; return controller; } }