using JobTrackerApi.Controllers; using JobTrackerApi.Data; using JobTrackerApi.Models; using JobTrackerApi.Services; using JobTrackerApi.Tests.TestSupport; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Moq; using Xunit; namespace JobTrackerApi.Tests; public sealed class CorrespondenceControllerTests { [Fact] public async Task Create_tags_manually_entered_correspondence_with_manual_provider() { await using var db = TestHostFactory.CreateInMemoryDb(); 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 controller = new CorrespondenceController(db); var request = new CorrespondenceController.CreateCorrespondenceRequestV2( job.Id, "Me", "Called to follow up.", "Follow-up call", "Call", null, "outbound", null, null, null, null, null, null); var result = await controller.Create(request, CancellationToken.None); Assert.IsType(((CreatedAtActionResult)result.Result!).Value); var stored = await db.Correspondences.SingleAsync(); Assert.Equal("manual", stored.Provider); } [Fact] public async Task Message_detail_is_owner_scoped_and_tolerates_bad_metadata() { var databaseName = Guid.NewGuid().ToString(); var options = new DbContextOptionsBuilder().UseInMemoryDatabase(databaseName).Options; var owner = new Mock(); owner.SetupGet(service => service.UserId).Returns("user-1"); int messageId; await using (var ownerDb = new JobTrackerContext(options, owner.Object)) { var company = new Company { Name = "Acme", OwnerUserId = "user-1" }; ownerDb.Companies.Add(company); await ownerDb.SaveChangesAsync(); var job = new JobApplication { JobTitle = "Backend Developer", CompanyId = company.Id, OwnerUserId = "user-1" }; ownerDb.JobApplications.Add(job); await ownerDb.SaveChangesAsync(); var message = new Correspondence { JobApplicationId = job.Id, From = "Company", Subject = "Interview", Content = "Safe saved copy", ExternalLabelsJson = "not-json", AttachmentMetadataJson = "also-not-json" }; ownerDb.Correspondences.Add(message); await ownerDb.SaveChangesAsync(); messageId = message.Id; var ownerResult = await new CorrespondenceController(ownerDb).GetMessage(messageId, CancellationToken.None); var ok = Assert.IsType(ownerResult.Result); var detail = Assert.IsType(ok.Value); Assert.Equal("Safe saved copy", detail.BodyText); Assert.Empty(detail.Labels); Assert.Empty(detail.Attachments); var inboxResult = await new CorrespondenceController(ownerDb).GetInbox(null, null, null, CancellationToken.None); var inbox = Assert.IsType(inboxResult.Result); var item = Assert.Single(Assert.IsType>(inbox.Value)); Assert.Equal(0, item.LabelCount); Assert.Equal(0, item.AttachmentCount); } var other = new Mock(); other.SetupGet(service => service.UserId).Returns("user-2"); await using var otherDb = new JobTrackerContext(options, other.Object); var otherResult = await new CorrespondenceController(otherDb).GetMessage(messageId, CancellationToken.None); Assert.IsType(otherResult.Result); } [Fact] public async Task Paged_inbox_returns_every_owned_message_without_the_legacy_cap() { await using var db = TestHostFactory.CreateInMemoryDb(); var company = new Company { Name = "Scale fixture", OwnerUserId = "user-1" }; var otherCompany = new Company { Name = "Other tenant", OwnerUserId = "user-2" }; db.Companies.AddRange(company, otherCompany); await db.SaveChangesAsync(); var job = new JobApplication { JobTitle = "Paged role", CompanyId = company.Id, OwnerUserId = "user-1" }; var otherJob = new JobApplication { JobTitle = "Private role", CompanyId = otherCompany.Id, OwnerUserId = "user-2" }; db.JobApplications.AddRange(job, otherJob); await db.SaveChangesAsync(); db.Correspondences.AddRange(Enumerable.Range(1, 205).Select(index => new Correspondence { JobApplicationId = job.Id, From = "Recruiter", Subject = $"Message {index}", Content = $"Content {index}", Date = new DateTime(2026, 1, 1).AddMinutes(index) })); db.Correspondences.Add(new Correspondence { JobApplicationId = otherJob.Id, From = "Other recruiter", Subject = "Private message", Content = "Must stay tenant isolated", Date = new DateTime(2026, 2, 1) }); await db.SaveChangesAsync(); var result = await new CorrespondenceController(db).GetInboxPage(null, null, null, 3, 100, CancellationToken.None); var page = Assert.IsType(Assert.IsType(result.Result).Value); Assert.Equal(205, page.Total); Assert.Equal(3, page.TotalPages); Assert.Equal(3, page.Page); Assert.Equal(5, page.Items.Count); Assert.Equal("Message 5", page.Items[0].Subject); Assert.Equal("Message 1", page.Items[^1].Subject); } }