feat(ops): automate backups and private CV benchmarks
CI and Deploy / test (push) Successful in 6m47s
CI and Deploy / deploy (push) Failing after 25s

This commit is contained in:
cesnimda
2026-09-02 00:49:06 +02:00
parent bd7fa05b8c
commit de85492373
10 changed files with 301 additions and 46 deletions
+46 -18
View File
@@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Caching.Memory;
using Moq;
using Xunit;
@@ -52,24 +53,18 @@ public sealed class CvCorpusHarnessTests
userManager.Setup(x => x.GetUserAsync(It.IsAny<System.Security.Claims.ClaimsPrincipal>())).ReturnsAsync(user);
userManager.Setup(x => x.UpdateAsync(It.IsAny<ApplicationUser>())).ReturnsAsync(IdentityResult.Success);
var aiService = new Mock<ISummarizerService>();
aiService.Setup(x => x.SummarizeSectionAsync(It.Is<string>(instruction => instruction.Contains("Extract this CV into structured JSON", StringComparison.Ordinal)), It.IsAny<string>(), 3200, 900)).ReturnsAsync(string.Empty);
aiService.Setup(x => x.SummarizeSectionAsync(It.Is<string>(instruction => instruction.Contains("Reconstruct this CV text extracted from a PDF", StringComparison.Ordinal)), It.IsAny<string>(), 2800, 900)).ReturnsAsync((string _, string text, int _, int __) => text);
var aiService = CreateSummarizerFromEnvironment();
var cvAiNormalizer = CreateCvAiNormalizerFromEnvironment();
await using var db = TestHostFactory.CreateInMemoryDb();
var paths = CreatePaths(outputRoot);
var controller = new ProfileCvController(userManager.Object, aiService.Object, db, paths, null, NoOpCvAiClassifier.Instance, cvAiNormalizer)
var controller = new ProfileCvController(userManager.Object, aiService, db, paths, null, NoOpCvAiClassifier.Instance, cvAiNormalizer)
{
ControllerContext = new ControllerContext { HttpContext = new DefaultHttpContext() }
};
var extractMethod = typeof(ProfileCvController).GetMethod("ExtractTextAsync", BindingFlags.NonPublic | BindingFlags.Static);
var reconstructMethod = typeof(ProfileCvController).GetMethod("MaybeReconstructStructuredCvAsync", BindingFlags.NonPublic | BindingFlags.Instance);
var buildMethod = typeof(ProfileCvController).GetMethod("BuildStructuredCvAsync", BindingFlags.NonPublic | BindingFlags.Instance);
var extractMethod = typeof(ProfileCvController).GetMethod("ExtractStructuredCvFromFileAsync", BindingFlags.NonPublic | BindingFlags.Instance);
Assert.NotNull(extractMethod);
Assert.NotNull(reconstructMethod);
Assert.NotNull(buildMethod);
var entries = new List<CvBenchmarkEntry>();
foreach (var path in files)
@@ -83,16 +78,14 @@ public sealed class CvCorpusHarnessTests
};
var extension = Path.GetExtension(path);
var extractTask = (Task<string>)extractMethod!.Invoke(null, new object[] { formFile, extension })!;
var text = await extractTask;
var extractTask = (Task)extractMethod!.Invoke(controller, new object[] { formFile, extension, CancellationToken.None })!;
await extractTask;
var extraction = extractTask.GetType().GetProperty("Result")!.GetValue(extractTask)!;
var resultType = extraction.GetType();
var text = (string)resultType.GetProperty("RawText")!.GetValue(extraction)!;
Assert.False(string.IsNullOrWhiteSpace(text));
var reconstructTask = (Task<string>)reconstructMethod!.Invoke(controller, new object[] { text, CancellationToken.None })!;
var normalizedText = await reconstructTask;
Assert.False(string.IsNullOrWhiteSpace(normalizedText));
var buildTask = (Task<StructuredCvProfile>)buildMethod!.Invoke(controller, new object[] { normalizedText, CancellationToken.None })!;
var structured = StructuredCvProfileJson.Normalize(await buildTask);
var structured = StructuredCvProfileJson.Normalize(
(StructuredCvProfile)resultType.GetProperty("StructuredCv")!.GetValue(extraction)!);
Assert.NotNull(structured);
var slug = Slugify(fileName);
@@ -382,9 +375,44 @@ public sealed class CvCorpusHarnessTests
{
client.BaseAddress = new Uri(baseUrl.Trim());
client.Timeout = TimeSpan.FromSeconds(180);
var token = Environment.GetEnvironmentVariable("CV_AI_SERVICE_TOKEN");
if (!string.IsNullOrWhiteSpace(token)) client.DefaultRequestHeaders.Add("X-Ai-Service-Token", token);
});
var provider = services.BuildServiceProvider();
var factory = provider.GetRequiredService<System.Net.Http.IHttpClientFactory>();
return new CvAiNormalizer(factory);
}
private static ISummarizerService CreateSummarizerFromEnvironment()
{
var baseUrl = Environment.GetEnvironmentVariable("CV_AI_BASE_URL");
if (string.IsNullOrWhiteSpace(baseUrl))
{
var mock = new Mock<ISummarizerService>();
mock.Setup(x => x.ExtractTextAsync(It.IsAny<Stream>(), It.IsAny<string>(), It.IsAny<string?>(), It.IsAny<CancellationToken>()))
.Returns(async (Stream stream, string fileName, string? contentType, CancellationToken cancellationToken) =>
{
using var reader = new StreamReader(stream);
var text = await reader.ReadToEndAsync(cancellationToken);
return new AiTextExtractionResult(text, false, contentType ?? "text/plain", null, text.Length, fileName);
});
mock.Setup(x => x.SummarizeSectionAsync(It.Is<string>(instruction => instruction.Contains("Extract this CV into structured JSON", StringComparison.Ordinal)), It.IsAny<string>(), 3200, 900)).ReturnsAsync(string.Empty);
mock.Setup(x => x.SummarizeSectionAsync(It.Is<string>(instruction => instruction.Contains("Reconstruct this CV text extracted from a PDF", StringComparison.Ordinal)), It.IsAny<string>(), 2800, 900)).ReturnsAsync((string _, string text, int _, int __) => text);
return mock.Object;
}
var services = new Microsoft.Extensions.DependencyInjection.ServiceCollection();
services.AddMemoryCache();
services.AddHttpClient("ai-service", client =>
{
client.BaseAddress = new Uri(baseUrl.Trim());
client.Timeout = TimeSpan.FromMinutes(3);
var token = Environment.GetEnvironmentVariable("CV_AI_SERVICE_TOKEN");
if (!string.IsNullOrWhiteSpace(token)) client.DefaultRequestHeaders.Add("X-Ai-Service-Token", token);
});
var provider = services.BuildServiceProvider();
return new SummarizerService(
provider.GetRequiredService<System.Net.Http.IHttpClientFactory>(),
provider.GetRequiredService<IMemoryCache>());
}
}
@@ -0,0 +1,24 @@
Alex Example
Software Developer
alex.example@example.test | +44 20 7946 0000 | London, United Kingdom
https://example.test/alex
Professional Summary
Software developer building reliable web applications and APIs.
Technical Skills
C#, .NET, TypeScript, SQL, Docker
Professional Experience
Software Developer | Example Systems Ltd | London, United Kingdom
January 2022 - Present
- Built accessible web application features using .NET and TypeScript.
- Improved automated test coverage for critical application workflows.
Education
BSc Computer Science | Example University | London, United Kingdom
September 2018 - June 2021
Languages
English - Native
Norwegian Bokmal - Intermediate