feat(ui): instant match-score panel on the Candidate Fit tab
Adds a MatchScoreCard at the top of the Candidate Fit tab that loads the deterministic /match-score endpoint independently of the slow AI narrative, so users see a reproducible score, matched/missing keyword chips, and per-section coverage immediately. - MatchScore types + cached, attachment-independent load effect - graceful 'not enough signal' state - EN/NB translations - frontend panel test (matched/missing/section + degraded state) - backend integration tests for GetMatchScore (happy path + missing CV) - README endpoint reference Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -56,6 +56,60 @@ public sealed class JobApplicationsEndpointBehaviorTests
|
||||
Assert.Contains("Profile page", badRequest.Value?.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Match_score_scores_job_against_profile_cv()
|
||||
{
|
||||
await using var db = CreateDb();
|
||||
var company = new Company { Name = "Acme", OwnerUserId = "user-1" };
|
||||
db.Companies.Add(company);
|
||||
db.Users.Add(new ApplicationUser
|
||||
{
|
||||
Id = "user-1",
|
||||
UserName = "u",
|
||||
Email = "u@example.com",
|
||||
ProfileCvText = "Backend engineer skilled in C#, .NET, SQL and Docker. Built REST APIs.",
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var job = new JobApplication
|
||||
{
|
||||
JobTitle = "Senior C# Backend Developer",
|
||||
CompanyId = company.Id,
|
||||
OwnerUserId = "user-1",
|
||||
Description = "We need strong C#, .NET, SQL, Docker and REST API experience.",
|
||||
};
|
||||
db.JobApplications.Add(job);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var controller = CreateController(db, "user-1");
|
||||
var result = await controller.GetMatchScore(job.Id, CancellationToken.None);
|
||||
|
||||
var ok = Assert.IsType<OkObjectResult>(result.Result);
|
||||
var dto = Assert.IsType<JobApplicationsController.MatchScoreDto>(ok.Value);
|
||||
Assert.True(dto.HasEnoughSignal);
|
||||
Assert.True(dto.Score >= 75, $"expected strong score, got {dto.Score}");
|
||||
Assert.Contains("C#", dto.MatchedKeywords);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Match_score_requires_profile_cv()
|
||||
{
|
||||
await using var db = CreateDb();
|
||||
var company = new Company { Name = "Acme", OwnerUserId = "user-1" };
|
||||
db.Companies.Add(company);
|
||||
db.Users.Add(new ApplicationUser { Id = "user-1", UserName = "u", Email = "u@example.com" });
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var job = new JobApplication { JobTitle = "Dev", CompanyId = company.Id, OwnerUserId = "user-1", Description = "C# .NET" };
|
||||
db.JobApplications.Add(job);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var controller = CreateController(db, "user-1");
|
||||
var result = await controller.GetMatchScore(job.Id, CancellationToken.None);
|
||||
|
||||
Assert.IsType<BadRequestObjectResult>(result.Result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Create_normalizes_structured_salary()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user