feat(cv): qualify templates and reliable local import processing

This commit is contained in:
cesnimda
2026-10-28 14:36:12 +01:00
parent 73ea888375
commit 3f4db8ca81
69 changed files with 2597 additions and 180 deletions
@@ -19,6 +19,60 @@ namespace JobTrackerApi.Tests;
public sealed class CvProcessingOperationTests
{
[Fact]
public async Task Document_worker_processes_previously_queued_imports_while_generative_jobs_remain_disabled()
{
await using var fixture = await Fixture.CreateAsync();
await fixture.SeedUserAsync();
await using (var scope = fixture.Provider.CreateAsyncScope())
{
using var owner = scope.ServiceProvider.GetRequiredService<CurrentUserService>().UseBackgroundUser("user-1");
Assert.IsType<AcceptedResult>(await CreateController(scope.ServiceProvider).Upload(File()));
var bytes = Encoding.UTF8.GetBytes("%PDF-1.4\nSynthetic document transport");
Assert.IsType<AcceptedResult>(await CreateController(scope.ServiceProvider).Import(new FormFile(new MemoryStream(bytes), 0, bytes.Length, "file", "synthetic.pdf") { Headers = new HeaderDictionary(), ContentType = "application/pdf" }));
}
var configuration = fixture.Provider.GetRequiredService<IConfiguration>();
configuration["Workers:AiOperationsEnabled"] = "false";
configuration["Workers:CvDocumentImportsEnabled"] = "true";
using var hosted = new AiOperationHostedService(fixture.Provider.GetRequiredService<AiOperationWorker>(), configuration);
await hosted.StartAsync(default);
try
{
var ready = false;
for (var attempt = 0; attempt < 50 && !ready; attempt++)
{
await Task.Delay(100);
await using var scope = fixture.Provider.CreateAsyncScope();
var processingDb = scope.ServiceProvider.GetRequiredService<JobTrackerContext>();
ready = await processingDb.CvExtractionRuns.IgnoreQueryFilters()
.AnyAsync(run => run.Trigger == "document_import" && run.Status == "pending_review") &&
await processingDb.UserOperations.IgnoreQueryFilters().AnyAsync(operation => operation.Status == OperationStatuses.Succeeded);
}
Assert.True(ready, "Document-only processing must run even while generative AI workers are disabled.");
}
finally { await hosted.StopAsync(default); }
await using var verify = fixture.Provider.CreateAsyncScope();
var db = verify.ServiceProvider.GetRequiredService<JobTrackerContext>();
Assert.Equal("queued", (await db.CvExtractionRuns.IgnoreQueryFilters().SingleAsync(run => run.Trigger != "document_import")).Status);
Assert.Equal(1, await db.UserOperations.IgnoreQueryFilters().CountAsync(operation => operation.Status == OperationStatuses.Succeeded));
Assert.Equal(1, await db.UserOperations.IgnoreQueryFilters().CountAsync(operation => operation.Status == OperationStatuses.Queued));
}
[Fact]
public async Task Disabled_document_and_AI_workers_reject_import_with_an_actionable_service_error()
{
await using var fixture = await Fixture.CreateAsync();
await fixture.SeedUserAsync();
fixture.Provider.GetRequiredService<IConfiguration>()["Workers:AiOperationsEnabled"] = "false";
await using var scope = fixture.Provider.CreateAsyncScope();
using var owner = scope.ServiceProvider.GetRequiredService<CurrentUserService>().UseBackgroundUser("user-1");
var bytes = Encoding.UTF8.GetBytes("%PDF-1.4\nSynthetic document transport");
var response = await CreateController(scope.ServiceProvider).Import(new FormFile(new MemoryStream(bytes), 0, bytes.Length, "file", "synthetic.pdf") { Headers = new HeaderDictionary(), ContentType = "application/pdf" });
Assert.Equal(503, Assert.IsType<ObjectResult>(response).StatusCode);
var db = scope.ServiceProvider.GetRequiredService<JobTrackerContext>();
Assert.Empty(await db.UserOperations.ToListAsync());
Assert.Equal("failed", (await db.CvExtractionRuns.SingleAsync()).Status);
}
[Fact]
public async Task Worker_that_lost_its_lease_cannot_overwrite_a_recovered_successful_review()
{
@@ -317,6 +371,8 @@ public sealed class CvProcessingOperationTests
["Data:CvArtifactsRoot"] = Path.Combine(tempRoot, "CvArtifacts"),
["AiQueue:HeartbeatSeconds"] = "5",
["Ai:ExternalProcessingEnabled"] = "false",
["Workers:AiOperationsEnabled"] = "true",
["Workers:CvDocumentImportsEnabled"] = "false",
}).Build();
var environment = new Mock<IHostEnvironment>();
environment.SetupGet(item => item.ContentRootPath).Returns(tempRoot);