feat(ai): queue strategy snapshots

This commit is contained in:
cesnimda
2026-08-09 12:51:46 +02:00
parent 5eb9b3cb96
commit a62122640c
12 changed files with 790 additions and 158 deletions
@@ -13,9 +13,9 @@ using Xunit;
namespace JobTrackerApi.Tests;
// Candidate fit and focus plan share AiWorkspaceNote persistence with the same rules as
// InterviewPrepNote: reuse across calls, regenerate on refresh, regenerate when the attachment
// selection changes (career-workspace-implementation-roadmap.md Phase F5).
// Candidate fit and Strategy Snapshot share AiWorkspaceNote persistence. Strategy generation is
// durable now, so this file verifies its service-level result cache rather than invoking a model
// from the GET endpoint.
public sealed class AiWorkspaceNotePersistenceTests
{
[Fact]
@@ -66,24 +66,25 @@ public sealed class AiWorkspaceNotePersistenceTests
}
[Fact]
public async Task GetFocusPlan_persists_and_reuses_the_generated_note()
public async Task StrategySnapshot_generation_persists_a_stable_cached_result()
{
await using var db = TestHostFactory.CreateInMemoryDb();
var job = await SeedJobWithCvAsync(db);
var summarizer = new Mock<ISummarizerService>();
var callCount = 0;
summarizer.Setup(x => x.SummarizeSectionAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()))
.ReturnsAsync(() => { callCount++; return $"Text {callCount}"; });
summarizer.Setup(x => x.GenerateSectionWithMetadataAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new AiGenerationResult("""{"strategicSummary":"Lead with backend delivery.","cvBulletIdeas":["Built APIs"],"proofPointsToLeadWith":["Led delivery"],"coverLetterAngles":["Relevant platform work"]}""", "ollama", "test-model"));
var controller = CreateController(db, summarizer.Object, "user-1");
var service = new StrategySnapshotService(db, summarizer.Object);
var generated = await service.GenerateAsync(job.Id, [], CancellationToken.None);
var cached = await service.GetCachedAsync(job.Id, string.Empty, CancellationToken.None);
await controller.GetFocusPlan(job.Id, null, false, CancellationToken.None);
var callsAfterFirst = callCount;
await controller.GetFocusPlan(job.Id, null, false, CancellationToken.None);
Assert.Equal(callsAfterFirst, callCount);
Assert.NotNull(cached);
Assert.Equal(generated.Result.StrategicSummary, cached.StrategicSummary);
Assert.Equal(generated.Result.CvBulletIdeas, cached.CvBulletIdeas);
Assert.Equal(generated.Result.ProofPointsToLeadWith, cached.ProofPointsToLeadWith);
Assert.Equal(generated.Result.CoverLetterAngles, cached.CoverLetterAngles);
Assert.Equal("ollama", generated.Provider);
var stored = Assert.Single(db.AiWorkspaceNotes.IgnoreQueryFilters().Where(x => x.JobApplicationId == job.Id && x.NoteType == "focus-plan"));
Assert.NotEmpty(stored.ResultJson);