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.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging.Abstractions; using Moq; using Xunit; namespace JobTrackerApi.Tests; public sealed class JobApplicationsFollowUpDraftTests { [Fact] public async Task Follow_up_draft_uses_imported_thread_and_saved_package_context() { await using var db = CreateDb(); var company = new Company { Name = "Acme", RecruiterName = "Maria Recruiter", RecruiterEmail = "maria@acme.test", OwnerUserId = "user-1" }; db.Companies.Add(company); db.Users.Add(new ApplicationUser { Id = "user-1", UserName = "user@example.test", Email = "user@example.test", DisplayName = "Casey Candidate" }); await db.SaveChangesAsync(); var job = new JobApplication { JobTitle = "Backend Developer", CompanyId = company.Id, OwnerUserId = "user-1", Status = "Waiting", DateApplied = new DateTime(2026, 3, 1, 0, 0, 0, DateTimeKind.Utc), Description = "Need .NET APIs and clear stakeholder communication.", ShortSummary = "Strong overlap in backend API delivery.", TailoredCvText = "Saved tailored CV", CoverLetterText = "Saved cover letter", RecruiterMessageDraft = "Saved recruiter message", Notes = "Original notes\n\n<<>>\nSaved application answer\n<<>>" }; db.JobApplications.Add(job); await db.SaveChangesAsync(); db.Correspondences.Add(new Correspondence { JobApplicationId = job.Id, From = "Company", Subject = "Backend Developer application update", ExternalThreadId = "thread-42", ExternalFrom = "Maria Recruiter ", ExternalTo = "user@example.test", Content = "We're aligning interview times and want someone who can own the API layer.", Date = new DateTime(2026, 3, 10, 0, 0, 0, DateTimeKind.Utc) }); await db.SaveChangesAsync(); var summarizer = new Mock(); summarizer .Setup(service => service.SummarizeSectionAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync((string instruction, string context, int _, int __) => { if (instruction.Contains("List up to 4 concrete application-package signals", StringComparison.OrdinalIgnoreCase)) { return "Keep the thread moving on interview timing.\nReuse the API-ownership language from the recruiter."; } if (instruction.Contains("follow-up email", StringComparison.OrdinalIgnoreCase)) { return context.Contains("Saved cover letter", StringComparison.OrdinalIgnoreCase) && context.Contains("Backend Developer application update", StringComparison.OrdinalIgnoreCase) && context.Contains("Maria Recruiter", StringComparison.OrdinalIgnoreCase) ? "Hi Maria,\n\nI wanted to follow up on the Backend Developer thread and reiterate my fit for owning the API layer.\n\nThanks,\nCasey" : "Generic follow-up"; } return "Generated text"; }); var controller = CreateController(db, summarizer.Object, "user-1"); var result = await controller.GetFollowUpDraft(job.Id, "waiting-update", null, CancellationToken.None); var ok = Assert.IsType(result.Result); var payload = Assert.IsType(ok.Value); Assert.Equal("Re: Backend Developer application update", payload.Subject); Assert.Contains("Maria", payload.Body); Assert.Contains("Saved application package material is available", payload.ContextSummary); Assert.Contains(payload.ContextSignals, item => item.Contains("Saved cover letter available", StringComparison.OrdinalIgnoreCase)); Assert.Contains(payload.ContextSignals, item => item.Contains("thread participants", StringComparison.OrdinalIgnoreCase)); Assert.Equal("Backend Developer application update", payload.ThreadSubject); Assert.Contains("Maria Recruiter", payload.LastCorrespondenceFrom ?? string.Empty); } private static JobApplicationsController CreateController(JobTrackerContext db, ISummarizerService summarizer, string userId) { var controller = new JobApplicationsController(db, summarizer, Mock.Of(), CreateUserManager().Object, NullLogger.Instance); controller.ControllerContext = new ControllerContext { HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.NameIdentifier, userId) }, "test")) } }; return controller; } private static JobTrackerContext CreateDb() { return TestHostFactory.CreateInMemoryDb(); } private static Mock> CreateUserManager() { return TestHostFactory.CreateUserManager(); } }