feat(ops): automate backups and private CV benchmarks
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user