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>
718 lines
21 KiB
C#
718 lines
21 KiB
C#
using System.Linq.Expressions;
|
|
using System.Text.Json;
|
|
using FluentAssertions;
|
|
using Hangfire;
|
|
using Hangfire.Common;
|
|
using Hangfire.States;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Logging;
|
|
using Moq;
|
|
using TrueCV.Application.DTOs;
|
|
using TrueCV.Application.Interfaces;
|
|
using TrueCV.Application.Models;
|
|
using TrueCV.Domain.Entities;
|
|
using TrueCV.Domain.Enums;
|
|
using TrueCV.Infrastructure.Data;
|
|
using TrueCV.Infrastructure.Jobs;
|
|
using TrueCV.Infrastructure.Services;
|
|
|
|
namespace TrueCV.Tests.Services;
|
|
|
|
public sealed class CVCheckServiceTests : IDisposable
|
|
{
|
|
private readonly ApplicationDbContext _dbContext;
|
|
private readonly Mock<IFileStorageService> _fileStorageServiceMock;
|
|
private readonly Mock<IBackgroundJobClient> _backgroundJobClientMock;
|
|
private readonly Mock<ILogger<CVCheckService>> _loggerMock;
|
|
private readonly CVCheckService _sut;
|
|
|
|
public CVCheckServiceTests()
|
|
{
|
|
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
|
|
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
|
|
.Options;
|
|
|
|
_dbContext = new ApplicationDbContext(options);
|
|
_fileStorageServiceMock = new Mock<IFileStorageService>();
|
|
_backgroundJobClientMock = new Mock<IBackgroundJobClient>();
|
|
_loggerMock = new Mock<ILogger<CVCheckService>>();
|
|
|
|
_sut = new CVCheckService(
|
|
_dbContext,
|
|
_fileStorageServiceMock.Object,
|
|
_backgroundJobClientMock.Object,
|
|
_loggerMock.Object);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_dbContext.Dispose();
|
|
}
|
|
|
|
#region CreateCheckAsync Tests
|
|
|
|
[Fact]
|
|
public async Task CreateCheckAsync_CreatesRecordWithCorrectData()
|
|
{
|
|
// Arrange
|
|
var userId = Guid.NewGuid();
|
|
var fileName = "test-cv.pdf";
|
|
var expectedBlobUrl = "https://storage.blob.core.windows.net/cvs/test-cv.pdf";
|
|
|
|
using var stream = new MemoryStream();
|
|
_fileStorageServiceMock
|
|
.Setup(x => x.UploadAsync(It.IsAny<Stream>(), fileName))
|
|
.ReturnsAsync(expectedBlobUrl);
|
|
|
|
// Act
|
|
var checkId = await _sut.CreateCheckAsync(userId, stream, fileName);
|
|
|
|
// Assert
|
|
var savedCheck = await _dbContext.CVChecks.FirstOrDefaultAsync(c => c.Id == checkId);
|
|
|
|
savedCheck.Should().NotBeNull();
|
|
savedCheck!.UserId.Should().Be(userId);
|
|
savedCheck.OriginalFileName.Should().Be(fileName);
|
|
savedCheck.BlobUrl.Should().Be(expectedBlobUrl);
|
|
savedCheck.Status.Should().Be(CheckStatus.Pending);
|
|
savedCheck.CreatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CreateCheckAsync_UploadsFileToBlobStorage()
|
|
{
|
|
// Arrange
|
|
var userId = Guid.NewGuid();
|
|
var fileName = "resume.docx";
|
|
var expectedBlobUrl = "https://storage.blob.core.windows.net/cvs/resume.docx";
|
|
|
|
using var stream = new MemoryStream(new byte[] { 0x01, 0x02, 0x03 });
|
|
_fileStorageServiceMock
|
|
.Setup(x => x.UploadAsync(stream, fileName))
|
|
.ReturnsAsync(expectedBlobUrl);
|
|
|
|
// Act
|
|
await _sut.CreateCheckAsync(userId, stream, fileName);
|
|
|
|
// Assert
|
|
_fileStorageServiceMock.Verify(
|
|
x => x.UploadAsync(stream, fileName),
|
|
Times.Once);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CreateCheckAsync_QueuesHangfireBackgroundJob()
|
|
{
|
|
// Arrange
|
|
var userId = Guid.NewGuid();
|
|
var fileName = "cv.pdf";
|
|
|
|
using var stream = new MemoryStream();
|
|
_fileStorageServiceMock
|
|
.Setup(x => x.UploadAsync(It.IsAny<Stream>(), It.IsAny<string>()))
|
|
.ReturnsAsync("https://blob.url/cv.pdf");
|
|
|
|
// Act
|
|
var checkId = await _sut.CreateCheckAsync(userId, stream, fileName);
|
|
|
|
// Assert
|
|
_backgroundJobClientMock.Verify(
|
|
x => x.Create(
|
|
It.Is<Job>(job =>
|
|
job.Type == typeof(ProcessCVCheckJob) &&
|
|
job.Method.Name == nameof(ProcessCVCheckJob.ExecuteAsync)),
|
|
It.IsAny<EnqueuedState>()),
|
|
Times.Once);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CreateCheckAsync_ReturnsNewCheckId()
|
|
{
|
|
// Arrange
|
|
var userId = Guid.NewGuid();
|
|
var fileName = "my-cv.pdf";
|
|
|
|
using var stream = new MemoryStream();
|
|
_fileStorageServiceMock
|
|
.Setup(x => x.UploadAsync(It.IsAny<Stream>(), It.IsAny<string>()))
|
|
.ReturnsAsync("https://blob.url/my-cv.pdf");
|
|
|
|
// Act
|
|
var checkId = await _sut.CreateCheckAsync(userId, stream, fileName);
|
|
|
|
// Assert
|
|
checkId.Should().NotBe(Guid.Empty);
|
|
|
|
var savedCheck = await _dbContext.CVChecks.FirstOrDefaultAsync(c => c.Id == checkId);
|
|
savedCheck.Should().NotBeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CreateCheckAsync_ThrowsArgumentNullException_WhenFileIsNull()
|
|
{
|
|
// Arrange
|
|
var userId = Guid.NewGuid();
|
|
var fileName = "test.pdf";
|
|
|
|
// Act
|
|
var act = () => _sut.CreateCheckAsync(userId, null!, fileName);
|
|
|
|
// Assert
|
|
await act.Should().ThrowAsync<ArgumentNullException>()
|
|
.WithParameterName("file");
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(null)]
|
|
[InlineData("")]
|
|
[InlineData(" ")]
|
|
public async Task CreateCheckAsync_ThrowsArgumentException_WhenFileNameIsNullOrWhitespace(string? fileName)
|
|
{
|
|
// Arrange
|
|
var userId = Guid.NewGuid();
|
|
using var stream = new MemoryStream();
|
|
|
|
// Act
|
|
var act = () => _sut.CreateCheckAsync(userId, stream, fileName!);
|
|
|
|
// Assert
|
|
await act.Should().ThrowAsync<ArgumentException>()
|
|
.WithParameterName("fileName");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region GetCheckAsync Tests
|
|
|
|
[Fact]
|
|
public async Task GetCheckAsync_ReturnsNull_WhenCheckDoesNotExist()
|
|
{
|
|
// Arrange
|
|
var nonExistentId = Guid.NewGuid();
|
|
|
|
// Act
|
|
var result = await _sut.GetCheckAsync(nonExistentId);
|
|
|
|
// Assert
|
|
result.Should().BeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetCheckAsync_ReturnsCorrectDto_WhenCheckExists()
|
|
{
|
|
// Arrange
|
|
var checkId = Guid.NewGuid();
|
|
var userId = Guid.NewGuid();
|
|
var createdAt = DateTime.UtcNow.AddHours(-1);
|
|
var completedAt = DateTime.UtcNow.AddMinutes(-30);
|
|
|
|
var cvCheck = new CVCheck
|
|
{
|
|
Id = checkId,
|
|
UserId = userId,
|
|
OriginalFileName = "professional-cv.pdf",
|
|
BlobUrl = "https://blob.url/professional-cv.pdf",
|
|
Status = CheckStatus.Completed,
|
|
VeracityScore = 85,
|
|
CreatedAt = createdAt,
|
|
CompletedAt = completedAt
|
|
};
|
|
|
|
_dbContext.CVChecks.Add(cvCheck);
|
|
await _dbContext.SaveChangesAsync();
|
|
|
|
// Act
|
|
var result = await _sut.GetCheckAsync(checkId);
|
|
|
|
// Assert
|
|
result.Should().NotBeNull();
|
|
result!.Id.Should().Be(checkId);
|
|
result.OriginalFileName.Should().Be("professional-cv.pdf");
|
|
result.Status.Should().Be("Completed");
|
|
result.VeracityScore.Should().Be(85);
|
|
result.CompletedAt.Should().Be(completedAt);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetCheckAsync_ReturnsPendingStatus_ForNewCheck()
|
|
{
|
|
// Arrange
|
|
var checkId = Guid.NewGuid();
|
|
|
|
var cvCheck = new CVCheck
|
|
{
|
|
Id = checkId,
|
|
UserId = Guid.NewGuid(),
|
|
OriginalFileName = "new-cv.pdf",
|
|
BlobUrl = "https://blob.url/new-cv.pdf",
|
|
Status = CheckStatus.Pending,
|
|
CreatedAt = DateTime.UtcNow
|
|
};
|
|
|
|
_dbContext.CVChecks.Add(cvCheck);
|
|
await _dbContext.SaveChangesAsync();
|
|
|
|
// Act
|
|
var result = await _sut.GetCheckAsync(checkId);
|
|
|
|
// Assert
|
|
result.Should().NotBeNull();
|
|
result!.Status.Should().Be("Pending");
|
|
result.VeracityScore.Should().BeNull();
|
|
result.CompletedAt.Should().BeNull();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region GetUserChecksAsync Tests
|
|
|
|
[Fact]
|
|
public async Task GetUserChecksAsync_ReturnsEmptyList_WhenUserHasNoChecks()
|
|
{
|
|
// Arrange
|
|
var userId = Guid.NewGuid();
|
|
|
|
// Act
|
|
var result = await _sut.GetUserChecksAsync(userId);
|
|
|
|
// Assert
|
|
result.Should().NotBeNull();
|
|
result.Should().BeEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetUserChecksAsync_ReturnsAllChecksForUser()
|
|
{
|
|
// Arrange
|
|
var userId = Guid.NewGuid();
|
|
|
|
var check1 = new CVCheck
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
UserId = userId,
|
|
OriginalFileName = "cv1.pdf",
|
|
BlobUrl = "https://blob.url/cv1.pdf",
|
|
Status = CheckStatus.Completed,
|
|
VeracityScore = 90,
|
|
CreatedAt = DateTime.UtcNow.AddDays(-2)
|
|
};
|
|
|
|
var check2 = new CVCheck
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
UserId = userId,
|
|
OriginalFileName = "cv2.pdf",
|
|
BlobUrl = "https://blob.url/cv2.pdf",
|
|
Status = CheckStatus.Pending,
|
|
CreatedAt = DateTime.UtcNow.AddDays(-1)
|
|
};
|
|
|
|
var check3 = new CVCheck
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
UserId = userId,
|
|
OriginalFileName = "cv3.pdf",
|
|
BlobUrl = "https://blob.url/cv3.pdf",
|
|
Status = CheckStatus.Processing,
|
|
CreatedAt = DateTime.UtcNow
|
|
};
|
|
|
|
_dbContext.CVChecks.AddRange(check1, check2, check3);
|
|
await _dbContext.SaveChangesAsync();
|
|
|
|
// Act
|
|
var result = await _sut.GetUserChecksAsync(userId);
|
|
|
|
// Assert
|
|
result.Should().HaveCount(3);
|
|
result.Select(c => c.OriginalFileName)
|
|
.Should().Contain(new[] { "cv1.pdf", "cv2.pdf", "cv3.pdf" });
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetUserChecksAsync_DoesNotReturnOtherUsersChecks()
|
|
{
|
|
// Arrange
|
|
var userId1 = Guid.NewGuid();
|
|
var userId2 = Guid.NewGuid();
|
|
|
|
var user1Check = new CVCheck
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
UserId = userId1,
|
|
OriginalFileName = "user1-cv.pdf",
|
|
BlobUrl = "https://blob.url/user1-cv.pdf",
|
|
Status = CheckStatus.Completed,
|
|
CreatedAt = DateTime.UtcNow.AddHours(-1)
|
|
};
|
|
|
|
var user2Check1 = new CVCheck
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
UserId = userId2,
|
|
OriginalFileName = "user2-cv-a.pdf",
|
|
BlobUrl = "https://blob.url/user2-cv-a.pdf",
|
|
Status = CheckStatus.Pending,
|
|
CreatedAt = DateTime.UtcNow.AddHours(-2)
|
|
};
|
|
|
|
var user2Check2 = new CVCheck
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
UserId = userId2,
|
|
OriginalFileName = "user2-cv-b.pdf",
|
|
BlobUrl = "https://blob.url/user2-cv-b.pdf",
|
|
Status = CheckStatus.Completed,
|
|
CreatedAt = DateTime.UtcNow
|
|
};
|
|
|
|
_dbContext.CVChecks.AddRange(user1Check, user2Check1, user2Check2);
|
|
await _dbContext.SaveChangesAsync();
|
|
|
|
// Act
|
|
var result = await _sut.GetUserChecksAsync(userId1);
|
|
|
|
// Assert
|
|
result.Should().HaveCount(1);
|
|
result.Single().OriginalFileName.Should().Be("user1-cv.pdf");
|
|
result.Should().NotContain(c => c.OriginalFileName.StartsWith("user2"));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetUserChecksAsync_ReturnsChecksOrderedByCreatedAtDescending()
|
|
{
|
|
// Arrange
|
|
var userId = Guid.NewGuid();
|
|
|
|
// Create checks with specific IDs so we can update them after save
|
|
var oldestId = Guid.NewGuid();
|
|
var middleId = Guid.NewGuid();
|
|
var newestId = Guid.NewGuid();
|
|
|
|
var oldestCheck = new CVCheck
|
|
{
|
|
Id = oldestId,
|
|
UserId = userId,
|
|
OriginalFileName = "oldest.pdf",
|
|
BlobUrl = "https://blob.url/oldest.pdf",
|
|
Status = CheckStatus.Completed
|
|
};
|
|
|
|
var middleCheck = new CVCheck
|
|
{
|
|
Id = middleId,
|
|
UserId = userId,
|
|
OriginalFileName = "middle.pdf",
|
|
BlobUrl = "https://blob.url/middle.pdf",
|
|
Status = CheckStatus.Completed
|
|
};
|
|
|
|
var newestCheck = new CVCheck
|
|
{
|
|
Id = newestId,
|
|
UserId = userId,
|
|
OriginalFileName = "newest.pdf",
|
|
BlobUrl = "https://blob.url/newest.pdf",
|
|
Status = CheckStatus.Pending
|
|
};
|
|
|
|
// Add in random order - SaveChangesAsync will set CreatedAt to DateTime.UtcNow
|
|
_dbContext.CVChecks.AddRange(middleCheck, newestCheck, oldestCheck);
|
|
await _dbContext.SaveChangesAsync();
|
|
|
|
// Detach all entities to allow direct update
|
|
_dbContext.ChangeTracker.Clear();
|
|
|
|
// Now update CreatedAt timestamps using raw SQL to bypass SaveChangesAsync override
|
|
// Since InMemory database doesn't support raw SQL, we modify directly
|
|
var oldest = await _dbContext.CVChecks.FindAsync(oldestId);
|
|
var middle = await _dbContext.CVChecks.FindAsync(middleId);
|
|
var newest = await _dbContext.CVChecks.FindAsync(newestId);
|
|
|
|
oldest!.CreatedAt = DateTime.UtcNow.AddDays(-7);
|
|
middle!.CreatedAt = DateTime.UtcNow.AddDays(-3);
|
|
newest!.CreatedAt = DateTime.UtcNow;
|
|
|
|
// Mark as modified (not added) to bypass the CreatedAt override
|
|
_dbContext.Entry(oldest).State = EntityState.Modified;
|
|
_dbContext.Entry(middle).State = EntityState.Modified;
|
|
_dbContext.Entry(newest).State = EntityState.Modified;
|
|
|
|
await _dbContext.SaveChangesAsync();
|
|
_dbContext.ChangeTracker.Clear();
|
|
|
|
// Act
|
|
var result = await _sut.GetUserChecksAsync(userId);
|
|
|
|
// Assert
|
|
result.Should().HaveCount(3);
|
|
result[0].OriginalFileName.Should().Be("newest.pdf");
|
|
result[1].OriginalFileName.Should().Be("middle.pdf");
|
|
result[2].OriginalFileName.Should().Be("oldest.pdf");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region GetCheckForUserAsync Tests
|
|
|
|
[Fact]
|
|
public async Task GetCheckForUserAsync_ReturnsNull_WhenCheckDoesNotExist()
|
|
{
|
|
// Arrange
|
|
var checkId = Guid.NewGuid();
|
|
var userId = Guid.NewGuid();
|
|
|
|
// Act
|
|
var result = await _sut.GetCheckForUserAsync(checkId, userId);
|
|
|
|
// Assert
|
|
result.Should().BeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetCheckForUserAsync_ReturnsNull_WhenCheckBelongsToDifferentUser()
|
|
{
|
|
// Arrange
|
|
var checkId = Guid.NewGuid();
|
|
var ownerId = Guid.NewGuid();
|
|
var requestingUserId = Guid.NewGuid();
|
|
|
|
var cvCheck = new CVCheck
|
|
{
|
|
Id = checkId,
|
|
UserId = ownerId,
|
|
OriginalFileName = "owner-cv.pdf",
|
|
BlobUrl = "https://blob.url/owner-cv.pdf",
|
|
Status = CheckStatus.Completed,
|
|
CreatedAt = DateTime.UtcNow
|
|
};
|
|
|
|
_dbContext.CVChecks.Add(cvCheck);
|
|
await _dbContext.SaveChangesAsync();
|
|
|
|
// Act
|
|
var result = await _sut.GetCheckForUserAsync(checkId, requestingUserId);
|
|
|
|
// Assert
|
|
result.Should().BeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetCheckForUserAsync_ReturnsCheck_WhenCheckBelongsToUser()
|
|
{
|
|
// Arrange
|
|
var checkId = Guid.NewGuid();
|
|
var userId = Guid.NewGuid();
|
|
|
|
var cvCheck = new CVCheck
|
|
{
|
|
Id = checkId,
|
|
UserId = userId,
|
|
OriginalFileName = "my-cv.pdf",
|
|
BlobUrl = "https://blob.url/my-cv.pdf",
|
|
Status = CheckStatus.Completed,
|
|
VeracityScore = 88,
|
|
CreatedAt = DateTime.UtcNow
|
|
};
|
|
|
|
_dbContext.CVChecks.Add(cvCheck);
|
|
await _dbContext.SaveChangesAsync();
|
|
|
|
// Act
|
|
var result = await _sut.GetCheckForUserAsync(checkId, userId);
|
|
|
|
// Assert
|
|
result.Should().NotBeNull();
|
|
result!.Id.Should().Be(checkId);
|
|
result.OriginalFileName.Should().Be("my-cv.pdf");
|
|
result.VeracityScore.Should().Be(88);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region GetReportAsync Tests
|
|
|
|
[Fact]
|
|
public async Task GetReportAsync_ReturnsNull_WhenCheckDoesNotExist()
|
|
{
|
|
// Arrange
|
|
var checkId = Guid.NewGuid();
|
|
var userId = Guid.NewGuid();
|
|
|
|
// Act
|
|
var result = await _sut.GetReportAsync(checkId, userId);
|
|
|
|
// Assert
|
|
result.Should().BeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetReportAsync_ReturnsNull_WhenCheckBelongsToDifferentUser()
|
|
{
|
|
// Arrange
|
|
var checkId = Guid.NewGuid();
|
|
var ownerId = Guid.NewGuid();
|
|
var requestingUserId = Guid.NewGuid();
|
|
|
|
var report = CreateTestReport();
|
|
var cvCheck = new CVCheck
|
|
{
|
|
Id = checkId,
|
|
UserId = ownerId,
|
|
OriginalFileName = "cv.pdf",
|
|
BlobUrl = "https://blob.url/cv.pdf",
|
|
Status = CheckStatus.Completed,
|
|
ReportJson = JsonSerializer.Serialize(report),
|
|
CreatedAt = DateTime.UtcNow
|
|
};
|
|
|
|
_dbContext.CVChecks.Add(cvCheck);
|
|
await _dbContext.SaveChangesAsync();
|
|
|
|
// Act
|
|
var result = await _sut.GetReportAsync(checkId, requestingUserId);
|
|
|
|
// Assert
|
|
result.Should().BeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetReportAsync_ReturnsNull_WhenCheckIsNotCompleted()
|
|
{
|
|
// Arrange
|
|
var checkId = Guid.NewGuid();
|
|
var userId = Guid.NewGuid();
|
|
|
|
var cvCheck = new CVCheck
|
|
{
|
|
Id = checkId,
|
|
UserId = userId,
|
|
OriginalFileName = "cv.pdf",
|
|
BlobUrl = "https://blob.url/cv.pdf",
|
|
Status = CheckStatus.Processing,
|
|
CreatedAt = DateTime.UtcNow
|
|
};
|
|
|
|
_dbContext.CVChecks.Add(cvCheck);
|
|
await _dbContext.SaveChangesAsync();
|
|
|
|
// Act
|
|
var result = await _sut.GetReportAsync(checkId, userId);
|
|
|
|
// Assert
|
|
result.Should().BeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetReportAsync_ReturnsNull_WhenReportJsonIsEmpty()
|
|
{
|
|
// Arrange
|
|
var checkId = Guid.NewGuid();
|
|
var userId = Guid.NewGuid();
|
|
|
|
var cvCheck = new CVCheck
|
|
{
|
|
Id = checkId,
|
|
UserId = userId,
|
|
OriginalFileName = "cv.pdf",
|
|
BlobUrl = "https://blob.url/cv.pdf",
|
|
Status = CheckStatus.Completed,
|
|
ReportJson = string.Empty,
|
|
CreatedAt = DateTime.UtcNow
|
|
};
|
|
|
|
_dbContext.CVChecks.Add(cvCheck);
|
|
await _dbContext.SaveChangesAsync();
|
|
|
|
// Act
|
|
var result = await _sut.GetReportAsync(checkId, userId);
|
|
|
|
// Assert
|
|
result.Should().BeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetReportAsync_ReturnsReport_WhenCheckIsCompletedWithValidReport()
|
|
{
|
|
// Arrange
|
|
var checkId = Guid.NewGuid();
|
|
var userId = Guid.NewGuid();
|
|
|
|
var report = CreateTestReport();
|
|
var cvCheck = new CVCheck
|
|
{
|
|
Id = checkId,
|
|
UserId = userId,
|
|
OriginalFileName = "cv.pdf",
|
|
BlobUrl = "https://blob.url/cv.pdf",
|
|
Status = CheckStatus.Completed,
|
|
ReportJson = JsonSerializer.Serialize(report),
|
|
CreatedAt = DateTime.UtcNow
|
|
};
|
|
|
|
_dbContext.CVChecks.Add(cvCheck);
|
|
await _dbContext.SaveChangesAsync();
|
|
|
|
// Act
|
|
var result = await _sut.GetReportAsync(checkId, userId);
|
|
|
|
// Assert
|
|
result.Should().NotBeNull();
|
|
result!.OverallScore.Should().Be(85);
|
|
result.ScoreLabel.Should().Be("Good");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetReportAsync_ReturnsNull_WhenReportJsonIsInvalid()
|
|
{
|
|
// Arrange
|
|
var checkId = Guid.NewGuid();
|
|
var userId = Guid.NewGuid();
|
|
|
|
var cvCheck = new CVCheck
|
|
{
|
|
Id = checkId,
|
|
UserId = userId,
|
|
OriginalFileName = "cv.pdf",
|
|
BlobUrl = "https://blob.url/cv.pdf",
|
|
Status = CheckStatus.Completed,
|
|
ReportJson = "{ invalid json }",
|
|
CreatedAt = DateTime.UtcNow
|
|
};
|
|
|
|
_dbContext.CVChecks.Add(cvCheck);
|
|
await _dbContext.SaveChangesAsync();
|
|
|
|
// Act
|
|
var result = await _sut.GetReportAsync(checkId, userId);
|
|
|
|
// Assert
|
|
result.Should().BeNull();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Helper Methods
|
|
|
|
private static VeracityReport CreateTestReport()
|
|
{
|
|
return new VeracityReport
|
|
{
|
|
OverallScore = 85,
|
|
ScoreLabel = "Good",
|
|
EmploymentVerifications = [],
|
|
TimelineAnalysis = new TimelineAnalysisResult
|
|
{
|
|
Gaps = [],
|
|
Overlaps = [],
|
|
TotalGapMonths = 0,
|
|
TotalOverlapMonths = 0
|
|
},
|
|
Flags = [],
|
|
GeneratedAt = DateTime.UtcNow
|
|
};
|
|
}
|
|
|
|
#endregion
|
|
}
|