Implement S03 follow-up draft context loop
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
using System.Security.Claims;
|
||||
using JobTrackerApi.Controllers;
|
||||
using JobTrackerApi.Data;
|
||||
using JobTrackerApi.Models;
|
||||
using JobTrackerApi.Services;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
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<<<APPLICATION_ANSWER_DRAFT>>>\nSaved application answer\n<<<END_APPLICATION_ANSWER_DRAFT>>>"
|
||||
};
|
||||
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 <maria@acme.test>",
|
||||
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<ISummarizerService>();
|
||||
summarizer
|
||||
.Setup(service => service.SummarizeSectionAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()))
|
||||
.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<OkObjectResult>(result.Result);
|
||||
var payload = Assert.IsType<JobApplicationsController.FollowUpDraftDto>(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<IAppEmailSender>(), CreateUserManager().Object);
|
||||
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()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<JobTrackerContext>()
|
||||
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
||||
.Options;
|
||||
var currentUser = new Mock<ICurrentUserService>();
|
||||
currentUser.SetupGet(service => service.UserId).Returns("user-1");
|
||||
return new JobTrackerContext(options, currentUser.Object);
|
||||
}
|
||||
|
||||
private static Mock<UserManager<ApplicationUser>> CreateUserManager()
|
||||
{
|
||||
var store = new Mock<IUserStore<ApplicationUser>>();
|
||||
return new Mock<UserManager<ApplicationUser>>(
|
||||
store.Object,
|
||||
Options.Create(new IdentityOptions()),
|
||||
new PasswordHasher<ApplicationUser>(),
|
||||
Array.Empty<IUserValidator<ApplicationUser>>(),
|
||||
Array.Empty<IPasswordValidator<ApplicationUser>>(),
|
||||
new UpperInvariantLookupNormalizer(),
|
||||
new IdentityErrorDescriber(),
|
||||
null!,
|
||||
new NullLogger<UserManager<ApplicationUser>>()
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user