Add comprehensive unit test suite
Test project with 143 tests covering:
- TimelineAnalyserService (27 tests): gap/overlap detection, edge cases
- CVParserService (35 tests): file parsing, extension handling, API calls
- CompanyVerifierService (23 tests): verification, caching, fuzzy matching
- CVCheckService (24 tests): CRUD operations, file upload, job queuing
- ProcessCVCheckJob (34 tests): full workflow, scoring algorithm, flags
Uses xUnit, Moq, FluentAssertions, EF Core InMemory
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 19:45:07 +01:00
|
|
|
using System.Text.Json;
|
|
|
|
|
using FluentAssertions;
|
|
|
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
|
using Moq;
|
2026-01-22 20:47:55 +00:00
|
|
|
using RealCV.Application.Interfaces;
|
|
|
|
|
using RealCV.Application.Models;
|
|
|
|
|
using RealCV.Domain.Entities;
|
|
|
|
|
using RealCV.Domain.Enums;
|
|
|
|
|
using RealCV.Infrastructure.Data;
|
|
|
|
|
using RealCV.Infrastructure.Jobs;
|
Add comprehensive unit test suite
Test project with 143 tests covering:
- TimelineAnalyserService (27 tests): gap/overlap detection, edge cases
- CVParserService (35 tests): file parsing, extension handling, API calls
- CompanyVerifierService (23 tests): verification, caching, fuzzy matching
- CVCheckService (24 tests): CRUD operations, file upload, job queuing
- ProcessCVCheckJob (34 tests): full workflow, scoring algorithm, flags
Uses xUnit, Moq, FluentAssertions, EF Core InMemory
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 19:45:07 +01:00
|
|
|
|
2026-01-22 20:47:55 +00:00
|
|
|
namespace RealCV.Tests.Jobs;
|
Add comprehensive unit test suite
Test project with 143 tests covering:
- TimelineAnalyserService (27 tests): gap/overlap detection, edge cases
- CVParserService (35 tests): file parsing, extension handling, API calls
- CompanyVerifierService (23 tests): verification, caching, fuzzy matching
- CVCheckService (24 tests): CRUD operations, file upload, job queuing
- ProcessCVCheckJob (34 tests): full workflow, scoring algorithm, flags
Uses xUnit, Moq, FluentAssertions, EF Core InMemory
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 19:45:07 +01:00
|
|
|
|
|
|
|
|
public sealed class ProcessCVCheckJobTests : IDisposable
|
|
|
|
|
{
|
|
|
|
|
private readonly ApplicationDbContext _dbContext;
|
|
|
|
|
private readonly Mock<IFileStorageService> _fileStorageServiceMock;
|
|
|
|
|
private readonly Mock<ICVParserService> _cvParserServiceMock;
|
|
|
|
|
private readonly Mock<ICompanyVerifierService> _companyVerifierServiceMock;
|
2026-01-20 16:45:43 +01:00
|
|
|
private readonly Mock<IEducationVerifierService> _educationVerifierServiceMock;
|
Add comprehensive unit test suite
Test project with 143 tests covering:
- TimelineAnalyserService (27 tests): gap/overlap detection, edge cases
- CVParserService (35 tests): file parsing, extension handling, API calls
- CompanyVerifierService (23 tests): verification, caching, fuzzy matching
- CVCheckService (24 tests): CRUD operations, file upload, job queuing
- ProcessCVCheckJob (34 tests): full workflow, scoring algorithm, flags
Uses xUnit, Moq, FluentAssertions, EF Core InMemory
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 19:45:07 +01:00
|
|
|
private readonly Mock<ITimelineAnalyserService> _timelineAnalyserServiceMock;
|
2026-01-20 20:58:12 +01:00
|
|
|
private readonly Mock<IAuditService> _auditServiceMock;
|
Add comprehensive unit test suite
Test project with 143 tests covering:
- TimelineAnalyserService (27 tests): gap/overlap detection, edge cases
- CVParserService (35 tests): file parsing, extension handling, API calls
- CompanyVerifierService (23 tests): verification, caching, fuzzy matching
- CVCheckService (24 tests): CRUD operations, file upload, job queuing
- ProcessCVCheckJob (34 tests): full workflow, scoring algorithm, flags
Uses xUnit, Moq, FluentAssertions, EF Core InMemory
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 19:45:07 +01:00
|
|
|
private readonly Mock<ILogger<ProcessCVCheckJob>> _loggerMock;
|
|
|
|
|
private readonly ProcessCVCheckJob _sut;
|
|
|
|
|
|
|
|
|
|
private static readonly JsonSerializerOptions JsonOptions = new()
|
|
|
|
|
{
|
|
|
|
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
public ProcessCVCheckJobTests()
|
|
|
|
|
{
|
|
|
|
|
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
|
|
|
|
|
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
|
|
|
|
|
.Options;
|
|
|
|
|
|
|
|
|
|
_dbContext = new ApplicationDbContext(options);
|
|
|
|
|
_fileStorageServiceMock = new Mock<IFileStorageService>();
|
|
|
|
|
_cvParserServiceMock = new Mock<ICVParserService>();
|
|
|
|
|
_companyVerifierServiceMock = new Mock<ICompanyVerifierService>();
|
2026-01-20 16:45:43 +01:00
|
|
|
_educationVerifierServiceMock = new Mock<IEducationVerifierService>();
|
Add comprehensive unit test suite
Test project with 143 tests covering:
- TimelineAnalyserService (27 tests): gap/overlap detection, edge cases
- CVParserService (35 tests): file parsing, extension handling, API calls
- CompanyVerifierService (23 tests): verification, caching, fuzzy matching
- CVCheckService (24 tests): CRUD operations, file upload, job queuing
- ProcessCVCheckJob (34 tests): full workflow, scoring algorithm, flags
Uses xUnit, Moq, FluentAssertions, EF Core InMemory
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 19:45:07 +01:00
|
|
|
_timelineAnalyserServiceMock = new Mock<ITimelineAnalyserService>();
|
2026-01-20 20:58:12 +01:00
|
|
|
_auditServiceMock = new Mock<IAuditService>();
|
Add comprehensive unit test suite
Test project with 143 tests covering:
- TimelineAnalyserService (27 tests): gap/overlap detection, edge cases
- CVParserService (35 tests): file parsing, extension handling, API calls
- CompanyVerifierService (23 tests): verification, caching, fuzzy matching
- CVCheckService (24 tests): CRUD operations, file upload, job queuing
- ProcessCVCheckJob (34 tests): full workflow, scoring algorithm, flags
Uses xUnit, Moq, FluentAssertions, EF Core InMemory
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 19:45:07 +01:00
|
|
|
_loggerMock = new Mock<ILogger<ProcessCVCheckJob>>();
|
|
|
|
|
|
|
|
|
|
_sut = new ProcessCVCheckJob(
|
|
|
|
|
_dbContext,
|
|
|
|
|
_fileStorageServiceMock.Object,
|
|
|
|
|
_cvParserServiceMock.Object,
|
|
|
|
|
_companyVerifierServiceMock.Object,
|
2026-01-20 16:45:43 +01:00
|
|
|
_educationVerifierServiceMock.Object,
|
Add comprehensive unit test suite
Test project with 143 tests covering:
- TimelineAnalyserService (27 tests): gap/overlap detection, edge cases
- CVParserService (35 tests): file parsing, extension handling, API calls
- CompanyVerifierService (23 tests): verification, caching, fuzzy matching
- CVCheckService (24 tests): CRUD operations, file upload, job queuing
- ProcessCVCheckJob (34 tests): full workflow, scoring algorithm, flags
Uses xUnit, Moq, FluentAssertions, EF Core InMemory
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 19:45:07 +01:00
|
|
|
_timelineAnalyserServiceMock.Object,
|
2026-01-20 20:58:12 +01:00
|
|
|
_auditServiceMock.Object,
|
Add comprehensive unit test suite
Test project with 143 tests covering:
- TimelineAnalyserService (27 tests): gap/overlap detection, edge cases
- CVParserService (35 tests): file parsing, extension handling, API calls
- CompanyVerifierService (23 tests): verification, caching, fuzzy matching
- CVCheckService (24 tests): CRUD operations, file upload, job queuing
- ProcessCVCheckJob (34 tests): full workflow, scoring algorithm, flags
Uses xUnit, Moq, FluentAssertions, EF Core InMemory
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 19:45:07 +01:00
|
|
|
_loggerMock.Object);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Dispose()
|
|
|
|
|
{
|
|
|
|
|
_dbContext.Dispose();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#region Status Updates
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task ExecuteAsync_UpdatesStatusToProcessingAtStart()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
|
|
|
|
|
SetupDefaultMocks();
|
|
|
|
|
|
|
|
|
|
// Create a flag to capture status change before completion
|
|
|
|
|
CheckStatus? capturedStatusDuringProcessing = null;
|
|
|
|
|
_fileStorageServiceMock
|
|
|
|
|
.Setup(x => x.DownloadAsync(It.IsAny<string>()))
|
|
|
|
|
.ReturnsAsync(() =>
|
|
|
|
|
{
|
|
|
|
|
// Capture the status when download is called (after status update to Processing)
|
|
|
|
|
var check = _dbContext.CVChecks.AsNoTracking().First(c => c.Id == cvCheck.Id);
|
|
|
|
|
capturedStatusDuringProcessing = check.Status;
|
|
|
|
|
return new MemoryStream();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
capturedStatusDuringProcessing.Should().Be(CheckStatus.Processing);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task ExecuteAsync_UpdatesStatusToCompletedWhenDone()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
|
|
|
|
|
SetupDefaultMocks();
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
_dbContext.ChangeTracker.Clear();
|
|
|
|
|
var updatedCheck = await _dbContext.CVChecks.FirstAsync(c => c.Id == cvCheck.Id);
|
|
|
|
|
updatedCheck.Status.Should().Be(CheckStatus.Completed);
|
|
|
|
|
updatedCheck.CompletedAt.Should().NotBeNull();
|
|
|
|
|
updatedCheck.CompletedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task ExecuteAsync_UpdatesStatusToFailedOnError()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
|
|
|
|
|
_fileStorageServiceMock
|
|
|
|
|
.Setup(x => x.DownloadAsync(It.IsAny<string>()))
|
|
|
|
|
.ThrowsAsync(new InvalidOperationException("Storage error"));
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var act = () => _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
await act.Should().ThrowAsync<InvalidOperationException>();
|
|
|
|
|
|
|
|
|
|
_dbContext.ChangeTracker.Clear();
|
|
|
|
|
var updatedCheck = await _dbContext.CVChecks.FirstAsync(c => c.Id == cvCheck.Id);
|
|
|
|
|
updatedCheck.Status.Should().Be(CheckStatus.Failed);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region File Operations
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task ExecuteAsync_DownloadsFileFromBlobStorage()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var expectedBlobUrl = "https://storage.blob.core.windows.net/cvs/test-cv.pdf";
|
|
|
|
|
var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending, blobUrl: expectedBlobUrl);
|
|
|
|
|
SetupDefaultMocks();
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
_fileStorageServiceMock.Verify(
|
|
|
|
|
x => x.DownloadAsync(expectedBlobUrl),
|
|
|
|
|
Times.Once);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region CV Parsing
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task ExecuteAsync_ParsesCVAndSavesExtractedData()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending, fileName: "resume.pdf");
|
|
|
|
|
var expectedCVData = CreateTestCVData();
|
|
|
|
|
|
|
|
|
|
SetupDefaultMocks(cvData: expectedCVData);
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
_cvParserServiceMock.Verify(
|
2026-01-20 16:45:43 +01:00
|
|
|
x => x.ParseAsync(It.IsAny<Stream>(), "resume.pdf", It.IsAny<CancellationToken>()),
|
Add comprehensive unit test suite
Test project with 143 tests covering:
- TimelineAnalyserService (27 tests): gap/overlap detection, edge cases
- CVParserService (35 tests): file parsing, extension handling, API calls
- CompanyVerifierService (23 tests): verification, caching, fuzzy matching
- CVCheckService (24 tests): CRUD operations, file upload, job queuing
- ProcessCVCheckJob (34 tests): full workflow, scoring algorithm, flags
Uses xUnit, Moq, FluentAssertions, EF Core InMemory
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 19:45:07 +01:00
|
|
|
Times.Once);
|
|
|
|
|
|
|
|
|
|
_dbContext.ChangeTracker.Clear();
|
|
|
|
|
var updatedCheck = await _dbContext.CVChecks.FirstAsync(c => c.Id == cvCheck.Id);
|
|
|
|
|
updatedCheck.ExtractedDataJson.Should().NotBeNullOrEmpty();
|
|
|
|
|
|
|
|
|
|
var deserializedData = JsonSerializer.Deserialize<CVData>(updatedCheck.ExtractedDataJson!, JsonOptions);
|
|
|
|
|
deserializedData.Should().NotBeNull();
|
|
|
|
|
deserializedData!.FullName.Should().Be(expectedCVData.FullName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Employment Verification
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task ExecuteAsync_VerifiesEachEmploymentEntry()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
|
|
|
|
|
var cvData = CreateTestCVData(employmentCount: 3);
|
|
|
|
|
SetupDefaultMocks(cvData: cvData);
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
_companyVerifierServiceMock.Verify(
|
|
|
|
|
x => x.VerifyCompanyAsync(
|
|
|
|
|
It.IsAny<string>(),
|
|
|
|
|
It.IsAny<DateOnly?>(),
|
2026-01-20 20:00:24 +01:00
|
|
|
It.IsAny<DateOnly?>(),
|
|
|
|
|
It.IsAny<string?>()),
|
Add comprehensive unit test suite
Test project with 143 tests covering:
- TimelineAnalyserService (27 tests): gap/overlap detection, edge cases
- CVParserService (35 tests): file parsing, extension handling, API calls
- CompanyVerifierService (23 tests): verification, caching, fuzzy matching
- CVCheckService (24 tests): CRUD operations, file upload, job queuing
- ProcessCVCheckJob (34 tests): full workflow, scoring algorithm, flags
Uses xUnit, Moq, FluentAssertions, EF Core InMemory
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 19:45:07 +01:00
|
|
|
Times.Exactly(3));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Timeline Analysis
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task ExecuteAsync_AnalysesTimeline()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
|
|
|
|
|
var cvData = CreateTestCVData();
|
|
|
|
|
SetupDefaultMocks(cvData: cvData);
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
_timelineAnalyserServiceMock.Verify(
|
|
|
|
|
x => x.Analyse(It.Is<List<EmploymentEntry>>(list => list.Count == cvData.Employment.Count)),
|
|
|
|
|
Times.Once);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Score Calculation - All Verified Companies
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task ExecuteAsync_CalculatesCorrectScore_WithAllVerifiedCompanies()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
|
|
|
|
|
var cvData = CreateTestCVData(employmentCount: 2);
|
|
|
|
|
|
|
|
|
|
var verificationResults = cvData.Employment.Select(e => new CompanyVerificationResult
|
|
|
|
|
{
|
|
|
|
|
ClaimedCompany = e.CompanyName,
|
|
|
|
|
IsVerified = true,
|
|
|
|
|
MatchScore = 100,
|
|
|
|
|
MatchedCompanyName = e.CompanyName
|
|
|
|
|
}).ToList();
|
|
|
|
|
|
|
|
|
|
SetupDefaultMocks(
|
|
|
|
|
cvData: cvData,
|
|
|
|
|
verificationResults: verificationResults,
|
|
|
|
|
timelineResult: CreateEmptyTimelineResult());
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
_dbContext.ChangeTracker.Clear();
|
|
|
|
|
var updatedCheck = await _dbContext.CVChecks.FirstAsync(c => c.Id == cvCheck.Id);
|
|
|
|
|
updatedCheck.VeracityScore.Should().Be(100);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Score Calculation - Unverified Companies
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task ExecuteAsync_CalculatesCorrectScore_WithUnverifiedCompanies()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
|
|
|
|
|
var cvData = CreateTestCVData(employmentCount: 2);
|
|
|
|
|
|
|
|
|
|
var verificationResults = new List<CompanyVerificationResult>
|
|
|
|
|
{
|
|
|
|
|
new()
|
|
|
|
|
{
|
|
|
|
|
ClaimedCompany = cvData.Employment[0].CompanyName,
|
|
|
|
|
IsVerified = true,
|
|
|
|
|
MatchScore = 100,
|
|
|
|
|
MatchedCompanyName = cvData.Employment[0].CompanyName
|
|
|
|
|
},
|
|
|
|
|
new()
|
|
|
|
|
{
|
|
|
|
|
ClaimedCompany = cvData.Employment[1].CompanyName,
|
|
|
|
|
IsVerified = false,
|
|
|
|
|
MatchScore = 0,
|
|
|
|
|
VerificationNotes = "Company not found"
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
SetupDefaultMocks(
|
|
|
|
|
cvData: cvData,
|
|
|
|
|
verificationResults: verificationResults,
|
|
|
|
|
timelineResult: CreateEmptyTimelineResult());
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);
|
|
|
|
|
|
|
|
|
|
// Assert - Base 100 - 10 for unverified = 90
|
|
|
|
|
_dbContext.ChangeTracker.Clear();
|
|
|
|
|
var updatedCheck = await _dbContext.CVChecks.FirstAsync(c => c.Id == cvCheck.Id);
|
|
|
|
|
updatedCheck.VeracityScore.Should().Be(90);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task ExecuteAsync_CalculatesCorrectScore_WithMultipleUnverifiedCompanies()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
|
|
|
|
|
var cvData = CreateTestCVData(employmentCount: 3);
|
|
|
|
|
|
|
|
|
|
var verificationResults = cvData.Employment.Select(e => new CompanyVerificationResult
|
|
|
|
|
{
|
|
|
|
|
ClaimedCompany = e.CompanyName,
|
|
|
|
|
IsVerified = false,
|
|
|
|
|
MatchScore = 0,
|
|
|
|
|
VerificationNotes = "Company not found"
|
|
|
|
|
}).ToList();
|
|
|
|
|
|
|
|
|
|
SetupDefaultMocks(
|
|
|
|
|
cvData: cvData,
|
|
|
|
|
verificationResults: verificationResults,
|
|
|
|
|
timelineResult: CreateEmptyTimelineResult());
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);
|
|
|
|
|
|
|
|
|
|
// Assert - Base 100 - (3 * 10) = 70
|
|
|
|
|
_dbContext.ChangeTracker.Clear();
|
|
|
|
|
var updatedCheck = await _dbContext.CVChecks.FirstAsync(c => c.Id == cvCheck.Id);
|
|
|
|
|
updatedCheck.VeracityScore.Should().Be(70);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Score Calculation - Gaps
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task ExecuteAsync_CalculatesCorrectScore_WithGaps()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
|
|
|
|
|
var cvData = CreateTestCVData(employmentCount: 1);
|
|
|
|
|
|
|
|
|
|
var timelineResult = new TimelineAnalysisResult
|
|
|
|
|
{
|
|
|
|
|
TotalGapMonths = 5,
|
|
|
|
|
TotalOverlapMonths = 0,
|
|
|
|
|
Gaps =
|
|
|
|
|
[
|
|
|
|
|
new TimelineGap
|
|
|
|
|
{
|
|
|
|
|
StartDate = new DateOnly(2023, 1, 1),
|
|
|
|
|
EndDate = new DateOnly(2023, 6, 1),
|
|
|
|
|
Months = 5
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
Overlaps = []
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
SetupDefaultMocks(
|
|
|
|
|
cvData: cvData,
|
|
|
|
|
timelineResult: timelineResult);
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);
|
|
|
|
|
|
|
|
|
|
// Assert - Base 100 - (5 * 1) = 95 (5 month gap at 1 per month)
|
|
|
|
|
_dbContext.ChangeTracker.Clear();
|
|
|
|
|
var updatedCheck = await _dbContext.CVChecks.FirstAsync(c => c.Id == cvCheck.Id);
|
|
|
|
|
updatedCheck.VeracityScore.Should().Be(95);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task ExecuteAsync_CalculatesCorrectScore_WithGaps_MaxPenaltyOf10()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
|
|
|
|
|
var cvData = CreateTestCVData(employmentCount: 1);
|
|
|
|
|
|
|
|
|
|
var timelineResult = new TimelineAnalysisResult
|
|
|
|
|
{
|
|
|
|
|
TotalGapMonths = 15,
|
|
|
|
|
TotalOverlapMonths = 0,
|
|
|
|
|
Gaps =
|
|
|
|
|
[
|
|
|
|
|
new TimelineGap
|
|
|
|
|
{
|
|
|
|
|
StartDate = new DateOnly(2022, 1, 1),
|
|
|
|
|
EndDate = new DateOnly(2023, 4, 1),
|
|
|
|
|
Months = 15 // 15 months gap, but max penalty is 10
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
Overlaps = []
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
SetupDefaultMocks(
|
|
|
|
|
cvData: cvData,
|
|
|
|
|
timelineResult: timelineResult);
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);
|
|
|
|
|
|
|
|
|
|
// Assert - Base 100 - min(15, 10) = 90 (max gap penalty is 10)
|
|
|
|
|
_dbContext.ChangeTracker.Clear();
|
|
|
|
|
var updatedCheck = await _dbContext.CVChecks.FirstAsync(c => c.Id == cvCheck.Id);
|
|
|
|
|
updatedCheck.VeracityScore.Should().Be(90);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task ExecuteAsync_CalculatesCorrectScore_WithMultipleGaps()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
|
|
|
|
|
var cvData = CreateTestCVData(employmentCount: 1);
|
|
|
|
|
|
|
|
|
|
var timelineResult = new TimelineAnalysisResult
|
|
|
|
|
{
|
|
|
|
|
TotalGapMonths = 8,
|
|
|
|
|
TotalOverlapMonths = 0,
|
|
|
|
|
Gaps =
|
|
|
|
|
[
|
|
|
|
|
new TimelineGap
|
|
|
|
|
{
|
|
|
|
|
StartDate = new DateOnly(2022, 1, 1),
|
|
|
|
|
EndDate = new DateOnly(2022, 4, 1),
|
|
|
|
|
Months = 3
|
|
|
|
|
},
|
|
|
|
|
new TimelineGap
|
|
|
|
|
{
|
|
|
|
|
StartDate = new DateOnly(2023, 1, 1),
|
|
|
|
|
EndDate = new DateOnly(2023, 6, 1),
|
|
|
|
|
Months = 5
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
Overlaps = []
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
SetupDefaultMocks(
|
|
|
|
|
cvData: cvData,
|
|
|
|
|
timelineResult: timelineResult);
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);
|
|
|
|
|
|
|
|
|
|
// Assert - Base 100 - min(3,10) - min(5,10) = 100 - 3 - 5 = 92
|
|
|
|
|
_dbContext.ChangeTracker.Clear();
|
|
|
|
|
var updatedCheck = await _dbContext.CVChecks.FirstAsync(c => c.Id == cvCheck.Id);
|
|
|
|
|
updatedCheck.VeracityScore.Should().Be(92);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Score Calculation - Overlaps
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task ExecuteAsync_CalculatesCorrectScore_WithOverlaps_NoDeductionIfLessThan2Months()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
|
|
|
|
|
var cvData = CreateTestCVData(employmentCount: 2);
|
|
|
|
|
|
|
|
|
|
var timelineResult = new TimelineAnalysisResult
|
|
|
|
|
{
|
|
|
|
|
TotalGapMonths = 0,
|
|
|
|
|
TotalOverlapMonths = 2,
|
|
|
|
|
Gaps = [],
|
|
|
|
|
Overlaps =
|
|
|
|
|
[
|
|
|
|
|
new TimelineOverlap
|
|
|
|
|
{
|
|
|
|
|
Company1 = "Company A",
|
|
|
|
|
Company2 = "Company B",
|
|
|
|
|
OverlapStart = new DateOnly(2023, 1, 1),
|
|
|
|
|
OverlapEnd = new DateOnly(2023, 3, 1),
|
|
|
|
|
Months = 2 // Exactly 2 months - no penalty (transition period)
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
SetupDefaultMocks(
|
|
|
|
|
cvData: cvData,
|
|
|
|
|
timelineResult: timelineResult);
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);
|
|
|
|
|
|
|
|
|
|
// Assert - Base 100 - (2 - 2) * 2 = 100 (2 month transition allowed)
|
|
|
|
|
_dbContext.ChangeTracker.Clear();
|
|
|
|
|
var updatedCheck = await _dbContext.CVChecks.FirstAsync(c => c.Id == cvCheck.Id);
|
|
|
|
|
updatedCheck.VeracityScore.Should().Be(100);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
2026-01-21 01:36:25 +01:00
|
|
|
public async Task ExecuteAsync_CalculatesCorrectScore_WithOverlaps_NoDeduction()
|
Add comprehensive unit test suite
Test project with 143 tests covering:
- TimelineAnalyserService (27 tests): gap/overlap detection, edge cases
- CVParserService (35 tests): file parsing, extension handling, API calls
- CompanyVerifierService (23 tests): verification, caching, fuzzy matching
- CVCheckService (24 tests): CRUD operations, file upload, job queuing
- ProcessCVCheckJob (34 tests): full workflow, scoring algorithm, flags
Uses xUnit, Moq, FluentAssertions, EF Core InMemory
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 19:45:07 +01:00
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
|
|
|
|
|
var cvData = CreateTestCVData(employmentCount: 2);
|
|
|
|
|
|
|
|
|
|
var timelineResult = new TimelineAnalysisResult
|
|
|
|
|
{
|
|
|
|
|
TotalGapMonths = 0,
|
|
|
|
|
TotalOverlapMonths = 5,
|
|
|
|
|
Gaps = [],
|
|
|
|
|
Overlaps =
|
|
|
|
|
[
|
|
|
|
|
new TimelineOverlap
|
|
|
|
|
{
|
|
|
|
|
Company1 = "Company A",
|
|
|
|
|
Company2 = "Company B",
|
|
|
|
|
OverlapStart = new DateOnly(2023, 1, 1),
|
|
|
|
|
OverlapEnd = new DateOnly(2023, 6, 1),
|
|
|
|
|
Months = 5
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
SetupDefaultMocks(
|
|
|
|
|
cvData: cvData,
|
|
|
|
|
timelineResult: timelineResult);
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);
|
|
|
|
|
|
2026-01-21 01:36:25 +01:00
|
|
|
// Assert - Overlaps are now informational only, no penalty
|
Add comprehensive unit test suite
Test project with 143 tests covering:
- TimelineAnalyserService (27 tests): gap/overlap detection, edge cases
- CVParserService (35 tests): file parsing, extension handling, API calls
- CompanyVerifierService (23 tests): verification, caching, fuzzy matching
- CVCheckService (24 tests): CRUD operations, file upload, job queuing
- ProcessCVCheckJob (34 tests): full workflow, scoring algorithm, flags
Uses xUnit, Moq, FluentAssertions, EF Core InMemory
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 19:45:07 +01:00
|
|
|
_dbContext.ChangeTracker.Clear();
|
|
|
|
|
var updatedCheck = await _dbContext.CVChecks.FirstAsync(c => c.Id == cvCheck.Id);
|
2026-01-21 01:36:25 +01:00
|
|
|
updatedCheck.VeracityScore.Should().Be(100);
|
Add comprehensive unit test suite
Test project with 143 tests covering:
- TimelineAnalyserService (27 tests): gap/overlap detection, edge cases
- CVParserService (35 tests): file parsing, extension handling, API calls
- CompanyVerifierService (23 tests): verification, caching, fuzzy matching
- CVCheckService (24 tests): CRUD operations, file upload, job queuing
- ProcessCVCheckJob (34 tests): full workflow, scoring algorithm, flags
Uses xUnit, Moq, FluentAssertions, EF Core InMemory
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 19:45:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
2026-01-21 01:36:25 +01:00
|
|
|
public async Task ExecuteAsync_CalculatesCorrectScore_WithMultipleOverlaps_NoDeduction()
|
Add comprehensive unit test suite
Test project with 143 tests covering:
- TimelineAnalyserService (27 tests): gap/overlap detection, edge cases
- CVParserService (35 tests): file parsing, extension handling, API calls
- CompanyVerifierService (23 tests): verification, caching, fuzzy matching
- CVCheckService (24 tests): CRUD operations, file upload, job queuing
- ProcessCVCheckJob (34 tests): full workflow, scoring algorithm, flags
Uses xUnit, Moq, FluentAssertions, EF Core InMemory
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 19:45:07 +01:00
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
|
|
|
|
|
var cvData = CreateTestCVData(employmentCount: 3);
|
|
|
|
|
|
|
|
|
|
var timelineResult = new TimelineAnalysisResult
|
|
|
|
|
{
|
|
|
|
|
TotalGapMonths = 0,
|
|
|
|
|
TotalOverlapMonths = 10,
|
|
|
|
|
Gaps = [],
|
|
|
|
|
Overlaps =
|
|
|
|
|
[
|
|
|
|
|
new TimelineOverlap
|
|
|
|
|
{
|
|
|
|
|
Company1 = "Company A",
|
|
|
|
|
Company2 = "Company B",
|
|
|
|
|
OverlapStart = new DateOnly(2023, 1, 1),
|
|
|
|
|
OverlapEnd = new DateOnly(2023, 6, 1),
|
2026-01-21 01:36:25 +01:00
|
|
|
Months = 5
|
Add comprehensive unit test suite
Test project with 143 tests covering:
- TimelineAnalyserService (27 tests): gap/overlap detection, edge cases
- CVParserService (35 tests): file parsing, extension handling, API calls
- CompanyVerifierService (23 tests): verification, caching, fuzzy matching
- CVCheckService (24 tests): CRUD operations, file upload, job queuing
- ProcessCVCheckJob (34 tests): full workflow, scoring algorithm, flags
Uses xUnit, Moq, FluentAssertions, EF Core InMemory
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 19:45:07 +01:00
|
|
|
},
|
|
|
|
|
new TimelineOverlap
|
|
|
|
|
{
|
|
|
|
|
Company1 = "Company B",
|
|
|
|
|
Company2 = "Company C",
|
|
|
|
|
OverlapStart = new DateOnly(2024, 1, 1),
|
|
|
|
|
OverlapEnd = new DateOnly(2024, 6, 1),
|
2026-01-21 01:36:25 +01:00
|
|
|
Months = 5
|
Add comprehensive unit test suite
Test project with 143 tests covering:
- TimelineAnalyserService (27 tests): gap/overlap detection, edge cases
- CVParserService (35 tests): file parsing, extension handling, API calls
- CompanyVerifierService (23 tests): verification, caching, fuzzy matching
- CVCheckService (24 tests): CRUD operations, file upload, job queuing
- ProcessCVCheckJob (34 tests): full workflow, scoring algorithm, flags
Uses xUnit, Moq, FluentAssertions, EF Core InMemory
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 19:45:07 +01:00
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
SetupDefaultMocks(
|
|
|
|
|
cvData: cvData,
|
|
|
|
|
timelineResult: timelineResult);
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);
|
|
|
|
|
|
2026-01-21 01:36:25 +01:00
|
|
|
// Assert - Overlaps are informational only, no penalty
|
Add comprehensive unit test suite
Test project with 143 tests covering:
- TimelineAnalyserService (27 tests): gap/overlap detection, edge cases
- CVParserService (35 tests): file parsing, extension handling, API calls
- CompanyVerifierService (23 tests): verification, caching, fuzzy matching
- CVCheckService (24 tests): CRUD operations, file upload, job queuing
- ProcessCVCheckJob (34 tests): full workflow, scoring algorithm, flags
Uses xUnit, Moq, FluentAssertions, EF Core InMemory
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 19:45:07 +01:00
|
|
|
_dbContext.ChangeTracker.Clear();
|
|
|
|
|
var updatedCheck = await _dbContext.CVChecks.FirstAsync(c => c.Id == cvCheck.Id);
|
2026-01-21 01:36:25 +01:00
|
|
|
updatedCheck.VeracityScore.Should().Be(100);
|
Add comprehensive unit test suite
Test project with 143 tests covering:
- TimelineAnalyserService (27 tests): gap/overlap detection, edge cases
- CVParserService (35 tests): file parsing, extension handling, API calls
- CompanyVerifierService (23 tests): verification, caching, fuzzy matching
- CVCheckService (24 tests): CRUD operations, file upload, job queuing
- ProcessCVCheckJob (34 tests): full workflow, scoring algorithm, flags
Uses xUnit, Moq, FluentAssertions, EF Core InMemory
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 19:45:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region CVFlag Records
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task ExecuteAsync_CreatesCVFlagRecordsForUnverifiedCompanies()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
|
|
|
|
|
var cvData = CreateTestCVData(employmentCount: 1);
|
|
|
|
|
|
|
|
|
|
var verificationResults = new List<CompanyVerificationResult>
|
|
|
|
|
{
|
|
|
|
|
new()
|
|
|
|
|
{
|
|
|
|
|
ClaimedCompany = "Fake Corp",
|
|
|
|
|
IsVerified = false,
|
|
|
|
|
MatchScore = 0,
|
|
|
|
|
VerificationNotes = "Company not found in registry"
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
SetupDefaultMocks(
|
|
|
|
|
cvData: cvData,
|
|
|
|
|
verificationResults: verificationResults,
|
|
|
|
|
timelineResult: CreateEmptyTimelineResult());
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
_dbContext.ChangeTracker.Clear();
|
|
|
|
|
var flags = await _dbContext.CVFlags.Where(f => f.CVCheckId == cvCheck.Id).ToListAsync();
|
|
|
|
|
|
2026-01-21 01:36:25 +01:00
|
|
|
// Filter for specific flag type (job now creates multiple informational flags)
|
|
|
|
|
var unverifiedFlags = flags.Where(f => f.Title == "Unverified Company").ToList();
|
|
|
|
|
unverifiedFlags.Should().HaveCount(1);
|
|
|
|
|
unverifiedFlags[0].Category.Should().Be(FlagCategory.Employment);
|
|
|
|
|
unverifiedFlags[0].Severity.Should().Be(FlagSeverity.Warning);
|
|
|
|
|
unverifiedFlags[0].ScoreImpact.Should().Be(-10);
|
Add comprehensive unit test suite
Test project with 143 tests covering:
- TimelineAnalyserService (27 tests): gap/overlap detection, edge cases
- CVParserService (35 tests): file parsing, extension handling, API calls
- CompanyVerifierService (23 tests): verification, caching, fuzzy matching
- CVCheckService (24 tests): CRUD operations, file upload, job queuing
- ProcessCVCheckJob (34 tests): full workflow, scoring algorithm, flags
Uses xUnit, Moq, FluentAssertions, EF Core InMemory
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 19:45:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task ExecuteAsync_CreatesCVFlagRecordsForGaps()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
|
|
|
|
|
var cvData = CreateTestCVData(employmentCount: 1);
|
|
|
|
|
|
|
|
|
|
var timelineResult = new TimelineAnalysisResult
|
|
|
|
|
{
|
|
|
|
|
TotalGapMonths = 3,
|
|
|
|
|
TotalOverlapMonths = 0,
|
|
|
|
|
Gaps =
|
|
|
|
|
[
|
|
|
|
|
new TimelineGap
|
|
|
|
|
{
|
|
|
|
|
StartDate = new DateOnly(2023, 1, 1),
|
|
|
|
|
EndDate = new DateOnly(2023, 4, 1),
|
|
|
|
|
Months = 3
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
Overlaps = []
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
SetupDefaultMocks(
|
|
|
|
|
cvData: cvData,
|
|
|
|
|
timelineResult: timelineResult);
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
_dbContext.ChangeTracker.Clear();
|
|
|
|
|
var flags = await _dbContext.CVFlags.Where(f => f.CVCheckId == cvCheck.Id).ToListAsync();
|
|
|
|
|
|
2026-01-21 01:36:25 +01:00
|
|
|
// Filter for specific flag type (job now creates multiple informational flags)
|
|
|
|
|
var gapFlags = flags.Where(f => f.Title == "Employment Gap").ToList();
|
|
|
|
|
gapFlags.Should().HaveCount(1);
|
|
|
|
|
gapFlags[0].Category.Should().Be(FlagCategory.Timeline);
|
|
|
|
|
gapFlags[0].Severity.Should().Be(FlagSeverity.Info); // Less than 6 months
|
|
|
|
|
gapFlags[0].ScoreImpact.Should().Be(-3);
|
Add comprehensive unit test suite
Test project with 143 tests covering:
- TimelineAnalyserService (27 tests): gap/overlap detection, edge cases
- CVParserService (35 tests): file parsing, extension handling, API calls
- CompanyVerifierService (23 tests): verification, caching, fuzzy matching
- CVCheckService (24 tests): CRUD operations, file upload, job queuing
- ProcessCVCheckJob (34 tests): full workflow, scoring algorithm, flags
Uses xUnit, Moq, FluentAssertions, EF Core InMemory
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 19:45:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task ExecuteAsync_CreatesCVFlagRecordsForGaps_WarningSeverityFor6PlusMonths()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
|
|
|
|
|
var cvData = CreateTestCVData(employmentCount: 1);
|
|
|
|
|
|
|
|
|
|
var timelineResult = new TimelineAnalysisResult
|
|
|
|
|
{
|
|
|
|
|
TotalGapMonths = 8,
|
|
|
|
|
TotalOverlapMonths = 0,
|
|
|
|
|
Gaps =
|
|
|
|
|
[
|
|
|
|
|
new TimelineGap
|
|
|
|
|
{
|
|
|
|
|
StartDate = new DateOnly(2023, 1, 1),
|
|
|
|
|
EndDate = new DateOnly(2023, 9, 1),
|
|
|
|
|
Months = 8
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
Overlaps = []
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
SetupDefaultMocks(
|
|
|
|
|
cvData: cvData,
|
|
|
|
|
timelineResult: timelineResult);
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
_dbContext.ChangeTracker.Clear();
|
|
|
|
|
var flags = await _dbContext.CVFlags.Where(f => f.CVCheckId == cvCheck.Id).ToListAsync();
|
|
|
|
|
|
2026-01-21 01:36:25 +01:00
|
|
|
// Filter for specific flag type (job now creates multiple informational flags)
|
|
|
|
|
var gapFlags = flags.Where(f => f.Title == "Employment Gap").ToList();
|
|
|
|
|
gapFlags.Should().HaveCount(1);
|
|
|
|
|
gapFlags[0].Severity.Should().Be(FlagSeverity.Warning); // 6+ months
|
Add comprehensive unit test suite
Test project with 143 tests covering:
- TimelineAnalyserService (27 tests): gap/overlap detection, edge cases
- CVParserService (35 tests): file parsing, extension handling, API calls
- CompanyVerifierService (23 tests): verification, caching, fuzzy matching
- CVCheckService (24 tests): CRUD operations, file upload, job queuing
- ProcessCVCheckJob (34 tests): full workflow, scoring algorithm, flags
Uses xUnit, Moq, FluentAssertions, EF Core InMemory
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 19:45:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task ExecuteAsync_CreatesCVFlagRecordsForOverlaps()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
|
|
|
|
|
var cvData = CreateTestCVData(employmentCount: 2);
|
|
|
|
|
|
|
|
|
|
var timelineResult = new TimelineAnalysisResult
|
|
|
|
|
{
|
|
|
|
|
TotalGapMonths = 0,
|
|
|
|
|
TotalOverlapMonths = 4,
|
|
|
|
|
Gaps = [],
|
|
|
|
|
Overlaps =
|
|
|
|
|
[
|
|
|
|
|
new TimelineOverlap
|
|
|
|
|
{
|
|
|
|
|
Company1 = "Company A",
|
|
|
|
|
Company2 = "Company B",
|
|
|
|
|
OverlapStart = new DateOnly(2023, 1, 1),
|
|
|
|
|
OverlapEnd = new DateOnly(2023, 5, 1),
|
|
|
|
|
Months = 4
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
SetupDefaultMocks(
|
|
|
|
|
cvData: cvData,
|
|
|
|
|
timelineResult: timelineResult);
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
_dbContext.ChangeTracker.Clear();
|
|
|
|
|
var flags = await _dbContext.CVFlags.Where(f => f.CVCheckId == cvCheck.Id).ToListAsync();
|
|
|
|
|
|
2026-01-21 01:36:25 +01:00
|
|
|
// Overlaps are now informational only (legitimate for part-time, consulting, transitions)
|
|
|
|
|
var overlapFlags = flags.Where(f => f.Title == "Concurrent Employment").ToList();
|
|
|
|
|
overlapFlags.Should().HaveCount(1);
|
|
|
|
|
overlapFlags[0].Category.Should().Be(FlagCategory.Timeline);
|
|
|
|
|
overlapFlags[0].Severity.Should().Be(FlagSeverity.Info); // Informational only
|
|
|
|
|
overlapFlags[0].ScoreImpact.Should().Be(0); // No penalty
|
Add comprehensive unit test suite
Test project with 143 tests covering:
- TimelineAnalyserService (27 tests): gap/overlap detection, edge cases
- CVParserService (35 tests): file parsing, extension handling, API calls
- CompanyVerifierService (23 tests): verification, caching, fuzzy matching
- CVCheckService (24 tests): CRUD operations, file upload, job queuing
- ProcessCVCheckJob (34 tests): full workflow, scoring algorithm, flags
Uses xUnit, Moq, FluentAssertions, EF Core InMemory
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 19:45:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
2026-01-21 01:36:25 +01:00
|
|
|
public async Task ExecuteAsync_CreatesCVFlagRecordsForOverlaps_InfoSeverityEvenFor6PlusMonths()
|
Add comprehensive unit test suite
Test project with 143 tests covering:
- TimelineAnalyserService (27 tests): gap/overlap detection, edge cases
- CVParserService (35 tests): file parsing, extension handling, API calls
- CompanyVerifierService (23 tests): verification, caching, fuzzy matching
- CVCheckService (24 tests): CRUD operations, file upload, job queuing
- ProcessCVCheckJob (34 tests): full workflow, scoring algorithm, flags
Uses xUnit, Moq, FluentAssertions, EF Core InMemory
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 19:45:07 +01:00
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
|
|
|
|
|
var cvData = CreateTestCVData(employmentCount: 2);
|
|
|
|
|
|
|
|
|
|
var timelineResult = new TimelineAnalysisResult
|
|
|
|
|
{
|
|
|
|
|
TotalGapMonths = 0,
|
|
|
|
|
TotalOverlapMonths = 8,
|
|
|
|
|
Gaps = [],
|
|
|
|
|
Overlaps =
|
|
|
|
|
[
|
|
|
|
|
new TimelineOverlap
|
|
|
|
|
{
|
|
|
|
|
Company1 = "Company A",
|
|
|
|
|
Company2 = "Company B",
|
|
|
|
|
OverlapStart = new DateOnly(2023, 1, 1),
|
|
|
|
|
OverlapEnd = new DateOnly(2023, 9, 1),
|
|
|
|
|
Months = 8
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
SetupDefaultMocks(
|
|
|
|
|
cvData: cvData,
|
|
|
|
|
timelineResult: timelineResult);
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
_dbContext.ChangeTracker.Clear();
|
|
|
|
|
var flags = await _dbContext.CVFlags.Where(f => f.CVCheckId == cvCheck.Id).ToListAsync();
|
|
|
|
|
|
2026-01-21 01:36:25 +01:00
|
|
|
// Overlaps are informational regardless of duration (common for part-time, consulting)
|
|
|
|
|
var overlapFlags = flags.Where(f => f.Title == "Concurrent Employment").ToList();
|
|
|
|
|
overlapFlags.Should().HaveCount(1);
|
|
|
|
|
overlapFlags[0].Severity.Should().Be(FlagSeverity.Info); // Always info now
|
Add comprehensive unit test suite
Test project with 143 tests covering:
- TimelineAnalyserService (27 tests): gap/overlap detection, edge cases
- CVParserService (35 tests): file parsing, extension handling, API calls
- CompanyVerifierService (23 tests): verification, caching, fuzzy matching
- CVCheckService (24 tests): CRUD operations, file upload, job queuing
- ProcessCVCheckJob (34 tests): full workflow, scoring algorithm, flags
Uses xUnit, Moq, FluentAssertions, EF Core InMemory
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 19:45:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task ExecuteAsync_CreatesAppropriateCVFlagRecords_MultipleIssues()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
|
|
|
|
|
var cvData = CreateTestCVData(employmentCount: 2);
|
|
|
|
|
|
|
|
|
|
var verificationResults = new List<CompanyVerificationResult>
|
|
|
|
|
{
|
|
|
|
|
new()
|
|
|
|
|
{
|
|
|
|
|
ClaimedCompany = "Verified Corp",
|
|
|
|
|
IsVerified = true,
|
|
|
|
|
MatchScore = 100
|
|
|
|
|
},
|
|
|
|
|
new()
|
|
|
|
|
{
|
|
|
|
|
ClaimedCompany = "Unverified Inc",
|
|
|
|
|
IsVerified = false,
|
|
|
|
|
MatchScore = 0,
|
|
|
|
|
VerificationNotes = "Not found"
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var timelineResult = new TimelineAnalysisResult
|
|
|
|
|
{
|
|
|
|
|
TotalGapMonths = 3,
|
|
|
|
|
TotalOverlapMonths = 0,
|
|
|
|
|
Gaps =
|
|
|
|
|
[
|
|
|
|
|
new TimelineGap
|
|
|
|
|
{
|
|
|
|
|
StartDate = new DateOnly(2023, 1, 1),
|
|
|
|
|
EndDate = new DateOnly(2023, 4, 1),
|
|
|
|
|
Months = 3
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
Overlaps = []
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
SetupDefaultMocks(
|
|
|
|
|
cvData: cvData,
|
|
|
|
|
verificationResults: verificationResults,
|
|
|
|
|
timelineResult: timelineResult);
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
_dbContext.ChangeTracker.Clear();
|
|
|
|
|
var flags = await _dbContext.CVFlags.Where(f => f.CVCheckId == cvCheck.Id).ToListAsync();
|
|
|
|
|
|
2026-01-21 01:36:25 +01:00
|
|
|
// Check for specific penalty flags (job also creates informational flags)
|
Add comprehensive unit test suite
Test project with 143 tests covering:
- TimelineAnalyserService (27 tests): gap/overlap detection, edge cases
- CVParserService (35 tests): file parsing, extension handling, API calls
- CompanyVerifierService (23 tests): verification, caching, fuzzy matching
- CVCheckService (24 tests): CRUD operations, file upload, job queuing
- ProcessCVCheckJob (34 tests): full workflow, scoring algorithm, flags
Uses xUnit, Moq, FluentAssertions, EF Core InMemory
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 19:45:07 +01:00
|
|
|
flags.Should().Contain(f => f.Title == "Unverified Company");
|
|
|
|
|
flags.Should().Contain(f => f.Title == "Employment Gap");
|
2026-01-21 01:36:25 +01:00
|
|
|
|
|
|
|
|
// Verify the specific penalty flags have correct values
|
|
|
|
|
var unverifiedFlag = flags.First(f => f.Title == "Unverified Company");
|
|
|
|
|
unverifiedFlag.ScoreImpact.Should().Be(-10);
|
|
|
|
|
|
|
|
|
|
var gapFlag = flags.First(f => f.Title == "Employment Gap");
|
|
|
|
|
gapFlag.ScoreImpact.Should().Be(-3);
|
Add comprehensive unit test suite
Test project with 143 tests covering:
- TimelineAnalyserService (27 tests): gap/overlap detection, edge cases
- CVParserService (35 tests): file parsing, extension handling, API calls
- CompanyVerifierService (23 tests): verification, caching, fuzzy matching
- CVCheckService (24 tests): CRUD operations, file upload, job queuing
- ProcessCVCheckJob (34 tests): full workflow, scoring algorithm, flags
Uses xUnit, Moq, FluentAssertions, EF Core InMemory
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 19:45:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Non-Existent Check
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task ExecuteAsync_WithNonExistentCheckId_HandlesGracefully()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var nonExistentId = Guid.NewGuid();
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
await _sut.ExecuteAsync(nonExistentId, CancellationToken.None);
|
|
|
|
|
|
|
|
|
|
// Assert - should complete without exception
|
|
|
|
|
_fileStorageServiceMock.Verify(
|
|
|
|
|
x => x.DownloadAsync(It.IsAny<string>()),
|
|
|
|
|
Times.Never);
|
|
|
|
|
_cvParserServiceMock.Verify(
|
2026-01-20 16:45:43 +01:00
|
|
|
x => x.ParseAsync(It.IsAny<Stream>(), It.IsAny<string>(), It.IsAny<CancellationToken>()),
|
Add comprehensive unit test suite
Test project with 143 tests covering:
- TimelineAnalyserService (27 tests): gap/overlap detection, edge cases
- CVParserService (35 tests): file parsing, extension handling, API calls
- CompanyVerifierService (23 tests): verification, caching, fuzzy matching
- CVCheckService (24 tests): CRUD operations, file upload, job queuing
- ProcessCVCheckJob (34 tests): full workflow, scoring algorithm, flags
Uses xUnit, Moq, FluentAssertions, EF Core InMemory
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 19:45:07 +01:00
|
|
|
Times.Never);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Score Floor
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task ExecuteAsync_ScoreNeverGoesBelowZero()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
|
|
|
|
|
var cvData = CreateTestCVData(employmentCount: 15); // 15 unverified = -150 points
|
|
|
|
|
|
|
|
|
|
var verificationResults = cvData.Employment.Select(e => new CompanyVerificationResult
|
|
|
|
|
{
|
|
|
|
|
ClaimedCompany = e.CompanyName,
|
|
|
|
|
IsVerified = false,
|
|
|
|
|
MatchScore = 0,
|
|
|
|
|
VerificationNotes = "Not found"
|
|
|
|
|
}).ToList();
|
|
|
|
|
|
|
|
|
|
var timelineResult = new TimelineAnalysisResult
|
|
|
|
|
{
|
|
|
|
|
TotalGapMonths = 50, // Large gaps
|
|
|
|
|
TotalOverlapMonths = 20,
|
|
|
|
|
Gaps =
|
|
|
|
|
[
|
|
|
|
|
new TimelineGap
|
|
|
|
|
{
|
|
|
|
|
StartDate = new DateOnly(2020, 1, 1),
|
|
|
|
|
EndDate = new DateOnly(2024, 3, 1),
|
|
|
|
|
Months = 50
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
Overlaps =
|
|
|
|
|
[
|
|
|
|
|
new TimelineOverlap
|
|
|
|
|
{
|
|
|
|
|
Company1 = "A",
|
|
|
|
|
Company2 = "B",
|
|
|
|
|
OverlapStart = new DateOnly(2023, 1, 1),
|
|
|
|
|
OverlapEnd = new DateOnly(2024, 9, 1),
|
|
|
|
|
Months = 20
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
SetupDefaultMocks(
|
|
|
|
|
cvData: cvData,
|
|
|
|
|
verificationResults: verificationResults,
|
|
|
|
|
timelineResult: timelineResult);
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);
|
|
|
|
|
|
|
|
|
|
// Assert - Score should be 0, not negative
|
|
|
|
|
// Penalties: 15 * 10 = 150 (unverified) + min(50,10) = 10 (gap) + (20-2)*2 = 36 (overlap) = 196
|
|
|
|
|
// 100 - 196 = -96, but should be clamped to 0
|
|
|
|
|
_dbContext.ChangeTracker.Clear();
|
|
|
|
|
var updatedCheck = await _dbContext.CVChecks.FirstAsync(c => c.Id == cvCheck.Id);
|
|
|
|
|
updatedCheck.VeracityScore.Should().Be(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Veracity Report
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task ExecuteAsync_GeneratesVeracityReportJson()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
|
|
|
|
|
var cvData = CreateTestCVData(employmentCount: 1);
|
|
|
|
|
SetupDefaultMocks(cvData: cvData);
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
_dbContext.ChangeTracker.Clear();
|
|
|
|
|
var updatedCheck = await _dbContext.CVChecks.FirstAsync(c => c.Id == cvCheck.Id);
|
|
|
|
|
updatedCheck.ReportJson.Should().NotBeNullOrEmpty();
|
|
|
|
|
|
|
|
|
|
var report = JsonSerializer.Deserialize<VeracityReport>(updatedCheck.ReportJson!, JsonOptions);
|
|
|
|
|
report.Should().NotBeNull();
|
|
|
|
|
report!.OverallScore.Should().Be(updatedCheck.VeracityScore);
|
|
|
|
|
report.GeneratedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromMinutes(1));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Theory]
|
|
|
|
|
[InlineData(0, "Excellent", 100)] // 0 unverified = 100 >= 90 = Excellent
|
|
|
|
|
[InlineData(1, "Excellent", 90)] // 1 unverified = 90 >= 90 = Excellent
|
|
|
|
|
[InlineData(2, "Good", 80)] // 2 unverified = 80 >= 75 = Good
|
|
|
|
|
[InlineData(3, "Fair", 70)] // 3 unverified = 70 >= 60 = Fair (70 < 75)
|
|
|
|
|
[InlineData(4, "Fair", 60)] // 4 unverified = 60 >= 60 = Fair
|
|
|
|
|
[InlineData(5, "Poor", 50)] // 5 unverified = 50 >= 40 = Poor
|
|
|
|
|
[InlineData(6, "Poor", 40)] // 6 unverified = 40 >= 40 = Poor
|
|
|
|
|
[InlineData(7, "Very Poor", 30)] // 7 unverified = 30 < 40 = Very Poor
|
|
|
|
|
[InlineData(10, "Very Poor", 0)] // 10 unverified = 0 < 40 = Very Poor
|
|
|
|
|
public async Task ExecuteAsync_GeneratesCorrectScoreLabel(int unverifiedCount, string expectedLabel, int expectedScore)
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
|
|
|
|
|
|
|
|
|
|
// Use exact number of unverified companies to get predictable score
|
|
|
|
|
var employmentCount = Math.Max(1, unverifiedCount);
|
|
|
|
|
var cvData = CreateTestCVData(employmentCount: employmentCount);
|
|
|
|
|
|
|
|
|
|
var verificationResults = cvData.Employment.Select((e, i) => new CompanyVerificationResult
|
|
|
|
|
{
|
|
|
|
|
ClaimedCompany = e.CompanyName,
|
|
|
|
|
IsVerified = i >= unverifiedCount, // First N are unverified
|
|
|
|
|
MatchScore = i >= unverifiedCount ? 100 : 0,
|
|
|
|
|
VerificationNotes = i >= unverifiedCount ? null : "Not found"
|
|
|
|
|
}).ToList();
|
|
|
|
|
|
|
|
|
|
SetupDefaultMocks(
|
|
|
|
|
cvData: cvData,
|
|
|
|
|
verificationResults: verificationResults,
|
|
|
|
|
timelineResult: CreateEmptyTimelineResult());
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
_dbContext.ChangeTracker.Clear();
|
|
|
|
|
var updatedCheck = await _dbContext.CVChecks.FirstAsync(c => c.Id == cvCheck.Id);
|
|
|
|
|
|
|
|
|
|
updatedCheck.VeracityScore.Should().Be(expectedScore);
|
|
|
|
|
|
|
|
|
|
var report = JsonSerializer.Deserialize<VeracityReport>(updatedCheck.ReportJson!, JsonOptions);
|
|
|
|
|
report!.ScoreLabel.Should().Be(expectedLabel);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Helper Methods
|
|
|
|
|
|
|
|
|
|
private async Task<CVCheck> CreateCVCheckInDatabase(
|
|
|
|
|
CheckStatus status = CheckStatus.Pending,
|
|
|
|
|
string blobUrl = "https://storage.blob.core.windows.net/cvs/test.pdf",
|
|
|
|
|
string fileName = "test.pdf")
|
|
|
|
|
{
|
|
|
|
|
var cvCheck = new CVCheck
|
|
|
|
|
{
|
|
|
|
|
Id = Guid.NewGuid(),
|
|
|
|
|
UserId = Guid.NewGuid(),
|
|
|
|
|
OriginalFileName = fileName,
|
|
|
|
|
BlobUrl = blobUrl,
|
|
|
|
|
Status = status
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
_dbContext.CVChecks.Add(cvCheck);
|
|
|
|
|
await _dbContext.SaveChangesAsync();
|
|
|
|
|
_dbContext.ChangeTracker.Clear();
|
|
|
|
|
|
|
|
|
|
return cvCheck;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void SetupDefaultMocks(
|
|
|
|
|
CVData? cvData = null,
|
|
|
|
|
List<CompanyVerificationResult>? verificationResults = null,
|
2026-01-20 16:45:43 +01:00
|
|
|
List<EducationVerificationResult>? educationResults = null,
|
Add comprehensive unit test suite
Test project with 143 tests covering:
- TimelineAnalyserService (27 tests): gap/overlap detection, edge cases
- CVParserService (35 tests): file parsing, extension handling, API calls
- CompanyVerifierService (23 tests): verification, caching, fuzzy matching
- CVCheckService (24 tests): CRUD operations, file upload, job queuing
- ProcessCVCheckJob (34 tests): full workflow, scoring algorithm, flags
Uses xUnit, Moq, FluentAssertions, EF Core InMemory
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 19:45:07 +01:00
|
|
|
TimelineAnalysisResult? timelineResult = null)
|
|
|
|
|
{
|
|
|
|
|
cvData ??= CreateTestCVData();
|
|
|
|
|
timelineResult ??= CreateEmptyTimelineResult();
|
|
|
|
|
|
|
|
|
|
_fileStorageServiceMock
|
|
|
|
|
.Setup(x => x.DownloadAsync(It.IsAny<string>()))
|
|
|
|
|
.ReturnsAsync(new MemoryStream());
|
|
|
|
|
|
|
|
|
|
_cvParserServiceMock
|
2026-01-20 16:45:43 +01:00
|
|
|
.Setup(x => x.ParseAsync(It.IsAny<Stream>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
Add comprehensive unit test suite
Test project with 143 tests covering:
- TimelineAnalyserService (27 tests): gap/overlap detection, edge cases
- CVParserService (35 tests): file parsing, extension handling, API calls
- CompanyVerifierService (23 tests): verification, caching, fuzzy matching
- CVCheckService (24 tests): CRUD operations, file upload, job queuing
- ProcessCVCheckJob (34 tests): full workflow, scoring algorithm, flags
Uses xUnit, Moq, FluentAssertions, EF Core InMemory
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 19:45:07 +01:00
|
|
|
.ReturnsAsync(cvData);
|
|
|
|
|
|
|
|
|
|
if (verificationResults != null)
|
|
|
|
|
{
|
|
|
|
|
var queue = new Queue<CompanyVerificationResult>(verificationResults);
|
|
|
|
|
_companyVerifierServiceMock
|
|
|
|
|
.Setup(x => x.VerifyCompanyAsync(
|
|
|
|
|
It.IsAny<string>(),
|
|
|
|
|
It.IsAny<DateOnly?>(),
|
2026-01-20 20:00:24 +01:00
|
|
|
It.IsAny<DateOnly?>(),
|
|
|
|
|
It.IsAny<string?>()))
|
Add comprehensive unit test suite
Test project with 143 tests covering:
- TimelineAnalyserService (27 tests): gap/overlap detection, edge cases
- CVParserService (35 tests): file parsing, extension handling, API calls
- CompanyVerifierService (23 tests): verification, caching, fuzzy matching
- CVCheckService (24 tests): CRUD operations, file upload, job queuing
- ProcessCVCheckJob (34 tests): full workflow, scoring algorithm, flags
Uses xUnit, Moq, FluentAssertions, EF Core InMemory
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 19:45:07 +01:00
|
|
|
.ReturnsAsync(() => queue.Count > 0 ? queue.Dequeue() : CreateDefaultVerificationResult());
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
_companyVerifierServiceMock
|
|
|
|
|
.Setup(x => x.VerifyCompanyAsync(
|
|
|
|
|
It.IsAny<string>(),
|
|
|
|
|
It.IsAny<DateOnly?>(),
|
2026-01-20 20:00:24 +01:00
|
|
|
It.IsAny<DateOnly?>(),
|
|
|
|
|
It.IsAny<string?>()))
|
Add comprehensive unit test suite
Test project with 143 tests covering:
- TimelineAnalyserService (27 tests): gap/overlap detection, edge cases
- CVParserService (35 tests): file parsing, extension handling, API calls
- CompanyVerifierService (23 tests): verification, caching, fuzzy matching
- CVCheckService (24 tests): CRUD operations, file upload, job queuing
- ProcessCVCheckJob (34 tests): full workflow, scoring algorithm, flags
Uses xUnit, Moq, FluentAssertions, EF Core InMemory
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 19:45:07 +01:00
|
|
|
.ReturnsAsync(CreateDefaultVerificationResult());
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-20 16:45:43 +01:00
|
|
|
_educationVerifierServiceMock
|
|
|
|
|
.Setup(x => x.VerifyAll(
|
|
|
|
|
It.IsAny<List<EducationEntry>>(),
|
|
|
|
|
It.IsAny<List<EmploymentEntry>?>()))
|
|
|
|
|
.Returns(educationResults ?? []);
|
|
|
|
|
|
Add comprehensive unit test suite
Test project with 143 tests covering:
- TimelineAnalyserService (27 tests): gap/overlap detection, edge cases
- CVParserService (35 tests): file parsing, extension handling, API calls
- CompanyVerifierService (23 tests): verification, caching, fuzzy matching
- CVCheckService (24 tests): CRUD operations, file upload, job queuing
- ProcessCVCheckJob (34 tests): full workflow, scoring algorithm, flags
Uses xUnit, Moq, FluentAssertions, EF Core InMemory
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 19:45:07 +01:00
|
|
|
_timelineAnalyserServiceMock
|
|
|
|
|
.Setup(x => x.Analyse(It.IsAny<List<EmploymentEntry>>()))
|
|
|
|
|
.Returns(timelineResult);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static CVData CreateTestCVData(int employmentCount = 1)
|
|
|
|
|
{
|
|
|
|
|
var employment = Enumerable.Range(0, employmentCount)
|
|
|
|
|
.Select(i => new EmploymentEntry
|
|
|
|
|
{
|
|
|
|
|
CompanyName = $"Company {i + 1}",
|
|
|
|
|
JobTitle = $"Developer {i + 1}",
|
|
|
|
|
StartDate = new DateOnly(2020 + i, 1, 1),
|
|
|
|
|
EndDate = new DateOnly(2021 + i, 1, 1)
|
|
|
|
|
})
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
return new CVData
|
|
|
|
|
{
|
|
|
|
|
FullName = "John Doe",
|
|
|
|
|
Email = "john.doe@example.com",
|
|
|
|
|
Employment = employment
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static TimelineAnalysisResult CreateEmptyTimelineResult()
|
|
|
|
|
{
|
|
|
|
|
return new TimelineAnalysisResult
|
|
|
|
|
{
|
|
|
|
|
TotalGapMonths = 0,
|
|
|
|
|
TotalOverlapMonths = 0,
|
|
|
|
|
Gaps = [],
|
|
|
|
|
Overlaps = []
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static CompanyVerificationResult CreateDefaultVerificationResult()
|
|
|
|
|
{
|
|
|
|
|
return new CompanyVerificationResult
|
|
|
|
|
{
|
|
|
|
|
ClaimedCompany = "Test Company",
|
|
|
|
|
IsVerified = true,
|
|
|
|
|
MatchScore = 100,
|
|
|
|
|
MatchedCompanyName = "Test Company Ltd"
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
}
|