using JobTrackerApi.Controllers; using JobTrackerApi.Data; using JobTrackerApi.Models; using JobTrackerApi.Services; using JobTrackerApi.Tests.TestSupport; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Moq; using Xunit; namespace JobTrackerApi.Tests; // JobApplication.HasResume/HasCoverLetter/HasPortfolio/HasOtherAttachment are derived from // Attachment rows (backlog Wave 3), not manually settable. These tests exercise the single // place they're written: AttachmentsController's Purpose-change and Delete paths. public sealed class AttachmentFlagsRecomputeTests { [Fact] public async Task Changing_purpose_to_resume_sets_HasResume() { await using var db = TestHostFactory.CreateInMemoryDb(); var (job, attachment) = await SeedJobWithAttachmentAsync(db, purpose: "other"); var controller = CreateController(db); var result = await controller.Rename(attachment.Id, new AttachmentsController.UpdateAttachmentRequest(null, "resume", null), CancellationToken.None); Assert.IsType(result); var updated = await db.JobApplications.SingleAsync(j => j.Id == job.Id); Assert.True(updated.HasResume); Assert.True(updated.HasOtherAttachment == false); } [Fact] public async Task Deleting_the_only_resume_attachment_clears_HasResume() { await using var db = TestHostFactory.CreateInMemoryDb(); var (job, attachment) = await SeedJobWithAttachmentAsync(db, purpose: "resume"); var controller = CreateController(db); var result = await controller.Delete(attachment.Id, CancellationToken.None); Assert.IsType(result); var updated = await db.JobApplications.SingleAsync(j => j.Id == job.Id); Assert.False(updated.HasResume); } [Fact] public async Task Attachment_with_case_study_purpose_counts_as_other() { await using var db = TestHostFactory.CreateInMemoryDb(); var (job, attachment) = await SeedJobWithAttachmentAsync(db, purpose: "resume"); var controller = CreateController(db); await controller.Rename(attachment.Id, new AttachmentsController.UpdateAttachmentRequest(null, "case-study", null), CancellationToken.None); var updated = await db.JobApplications.SingleAsync(j => j.Id == job.Id); Assert.False(updated.HasResume); Assert.True(updated.HasOtherAttachment); } private static async Task<(JobApplication Job, Attachment Attachment)> SeedJobWithAttachmentAsync(JobTrackerContext db, string purpose) { var company = new Company { Name = "Acme", OwnerUserId = "user-1" }; db.Companies.Add(company); await db.SaveChangesAsync(); var job = new JobApplication { JobTitle = "Backend Developer", CompanyId = company.Id, OwnerUserId = "user-1" }; db.JobApplications.Add(job); await db.SaveChangesAsync(); var attachment = new Attachment { JobApplicationId = job.Id, FileName = "file.pdf", FilePath = Path.Combine(Path.GetTempPath(), $"jobtracker-attachment-test-{Guid.NewGuid():N}.pdf"), FileType = "application/pdf", FileSize = 100, Purpose = purpose, }; db.Attachments.Add(attachment); await db.SaveChangesAsync(); job.HasResume = purpose == "resume"; job.HasOtherAttachment = purpose is not ("resume" or "cover-letter" or "portfolio"); await db.SaveChangesAsync(); return (job, attachment); } private static AttachmentsController CreateController(JobTrackerContext db) { var tempRoot = Path.Combine(Path.GetTempPath(), $"jobtracker-attachments-tests-{Guid.NewGuid():N}"); Directory.CreateDirectory(tempRoot); var config = new ConfigurationBuilder() .AddInMemoryCollection(new Dictionary { ["Data:Root"] = tempRoot }) .Build(); var env = new Mock(); env.SetupGet(x => x.ContentRootPath).Returns(tempRoot); var paths = new AppPaths(config, env.Object); return new AttachmentsController(paths, db) { ControllerContext = new ControllerContext { HttpContext = new DefaultHttpContext() } }; } }