Files
jobtrackingapp/JobTrackerApi.Tests/InterviewPrepPersistenceTests.cs
T
cesnimda 8fe39031ea
CI and Deploy / test (pull_request) Failing after 55s
CI and Deploy / deploy (pull_request) Has been skipped
fix(email): retire legacy SMTP follow-up
Keep follow-up draft generation but remove the direct application SMTP delivery boundary. Route users to the provider-aware Job email flow and return 410 for legacy API callers.
2026-08-10 00:43:52 +02:00

133 lines
5.6 KiB
C#

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.EntityFrameworkCore;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Xunit;
namespace JobTrackerApi.Tests;
// Interview prep persistence (career-workspace-implementation-roadmap.md Phase F5): the AI call
// used to re-run on every tab open. These tests lock in that a note is generated once, reused on
// subsequent reads, and only regenerated when the attachment context changes or a refresh is
// explicitly requested.
public sealed class InterviewPrepPersistenceTests
{
[Fact]
public async Task GetInterviewPrep_persists_and_reuses_the_generated_note()
{
await using var db = TestHostFactory.CreateInMemoryDb();
var job = await SeedJobAsync(db);
var summarizer = new Mock<ISummarizerService>();
summarizer.SetupSequence(x => x.SummarizeSectionAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()))
.ReturnsAsync("First generated brief.")
.ReturnsAsync("Second generated brief.");
var controller = CreateController(db, summarizer.Object, "user-1");
var first = await controller.GetInterviewPrep(job.Id, null, false, CancellationToken.None);
Assert.Equal("First generated brief.", GetDto(first).Summary);
var second = await controller.GetInterviewPrep(job.Id, null, false, CancellationToken.None);
Assert.Equal("First generated brief.", GetDto(second).Summary);
summarizer.Verify(x => x.SummarizeSectionAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()), Times.Once);
var stored = Assert.Single(db.InterviewPrepNotes.IgnoreQueryFilters().Where(x => x.JobApplicationId == job.Id));
Assert.Equal("First generated brief.", stored.Summary);
}
[Fact]
public async Task GetInterviewPrep_regenerates_when_refresh_is_requested()
{
await using var db = TestHostFactory.CreateInMemoryDb();
var job = await SeedJobAsync(db);
var summarizer = new Mock<ISummarizerService>();
summarizer.SetupSequence(x => x.SummarizeSectionAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()))
.ReturnsAsync("First generated brief.")
.ReturnsAsync("Refreshed brief.");
var controller = CreateController(db, summarizer.Object, "user-1");
await controller.GetInterviewPrep(job.Id, null, false, CancellationToken.None);
var refreshed = await controller.GetInterviewPrep(job.Id, null, true, CancellationToken.None);
Assert.Equal("Refreshed brief.", GetDto(refreshed).Summary);
summarizer.Verify(x => x.SummarizeSectionAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()), Times.Exactly(2));
var stored = Assert.Single(db.InterviewPrepNotes.IgnoreQueryFilters().Where(x => x.JobApplicationId == job.Id));
Assert.Equal("Refreshed brief.", stored.Summary);
}
[Fact]
public async Task GetInterviewPrep_regenerates_when_attachment_selection_changes()
{
await using var db = TestHostFactory.CreateInMemoryDb();
var job = await SeedJobAsync(db);
var summarizer = new Mock<ISummarizerService>();
summarizer.SetupSequence(x => x.SummarizeSectionAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()))
.ReturnsAsync("No-attachment brief.")
.ReturnsAsync("With-attachment brief.");
var controller = CreateController(db, summarizer.Object, "user-1");
await controller.GetInterviewPrep(job.Id, null, false, CancellationToken.None);
var withAttachment = await controller.GetInterviewPrep(job.Id, "7", false, CancellationToken.None);
Assert.Equal("With-attachment brief.", GetDto(withAttachment).Summary);
summarizer.Verify(x => x.SummarizeSectionAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()), Times.Exactly(2));
}
private static InterviewPrepDto GetDto(ActionResult<InterviewPrepDto> result)
=> (InterviewPrepDto)Assert.IsType<OkObjectResult>(result.Result).Value!;
private static async Task<JobApplication> SeedJobAsync(JobTrackerContext db)
{
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",
Description = "Needs .NET and SQL experience.",
};
db.JobApplications.Add(job);
await db.SaveChangesAsync();
return job;
}
private static JobApplicationsController CreateController(JobTrackerContext db, ISummarizerService summarizer, string userId)
{
var controller = new JobApplicationsController(
db,
summarizer,
TestHostFactory.CreateUserManager(null).Object,
Mock.Of<ICvTemplateRenderer>(),
Mock.Of<ICvPdfExporter>());
controller.ControllerContext = new ControllerContext
{
HttpContext = new DefaultHttpContext
{
User = new ClaimsPrincipal(new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.NameIdentifier, userId)
}, "test"))
}
};
return controller;
}
}