test(application): add comprehensive unit tests for DEX job pipeline
- Add TriggeringDEXJobBatchCommandTests - Test batch command execution with multiple profiles - Verify batch ID generation and propagation - Mock ISQLExecutor and IRecClient dependencies - Add MainQueryExecutionBehaviorTests - Test main query execution success path - Test SQL exception handling (JobSqlException) - Verify MainQueryResults population - Add CheckQueryExecutionBehaviorTests - Test check query validation (ErrorAction.SkipInsert) - Test skip behavior for zero results - Test SQL exception handling - All tests pass on both net480 and net8.0 frameworks - Update ECM.JobRunner.sln with test project reference - Upgrade Microsoft.Extensions.DependencyInjection to 8.0.1 (ReC.Client requirement)
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
using ECMJobRunner.Application.Common.Dtos;
|
||||
using ECMJobRunner.Domain.Interfaces;
|
||||
using ECMJobRunner.Application.Common.Options;
|
||||
using ECMJobRunner.Application.DEXJob;
|
||||
using ECMJobRunner.Domain.Entities;
|
||||
using FluentAssertions;
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using ReC.Client;
|
||||
using ReC.Client.Api;
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace ECMJobRunner.Tests.Application
|
||||
{
|
||||
public class TriggeringDEXJobBatchCommandTests
|
||||
{
|
||||
private readonly Mock<IProfileSqlJobRepository> _mockJobRepo;
|
||||
private readonly Mock<ISender> _mockSender;
|
||||
private readonly TriggeringDEXJobBatchCommandHandler _handler;
|
||||
|
||||
public TriggeringDEXJobBatchCommandTests()
|
||||
{
|
||||
_mockJobRepo = new Mock<IProfileSqlJobRepository>();
|
||||
_mockSender = new Mock<ISender>();
|
||||
_handler = new TriggeringDEXJobBatchCommandHandler(_mockJobRepo.Object, _mockSender.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_WithValidProfileId_ExecutesAllJobs()
|
||||
{
|
||||
// Arrange
|
||||
var profileId = 123;
|
||||
var jobs = new[]
|
||||
{
|
||||
new ProfileSqlJob { Id = 1, ProfileId = profileId, SqlMainQuery = "SELECT 1", SqlCheckQuery = "SELECT 2" },
|
||||
new ProfileSqlJob { Id = 2, ProfileId = profileId, SqlMainQuery = "SELECT 3", SqlCheckQuery = "SELECT 4" }
|
||||
};
|
||||
|
||||
_mockJobRepo
|
||||
.Setup(r => r.FindAsync(It.IsAny<Expression<Func<ProfileSqlJob, bool>>>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(jobs);
|
||||
|
||||
var command = new TriggeringDEXJobBatchCommand { ProfileId = profileId };
|
||||
|
||||
// Act
|
||||
var result = await _handler.Handle(command, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.Should().Be(Unit.Value);
|
||||
_mockSender.Verify(
|
||||
s => s.Send(It.IsAny<TriggeringDEXJobCommand>(), It.IsAny<CancellationToken>()),
|
||||
Times.Exactly(2),
|
||||
"Should send command for each job");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_WithNoJobs_CompletesWithoutError()
|
||||
{
|
||||
// Arrange
|
||||
var profileId = 999;
|
||||
_mockJobRepo
|
||||
.Setup(r => r.FindAsync(It.IsAny<Expression<Func<ProfileSqlJob, bool>>>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(Array.Empty<ProfileSqlJob>());
|
||||
|
||||
var command = new TriggeringDEXJobBatchCommand { ProfileId = profileId };
|
||||
|
||||
// Act
|
||||
var result = await _handler.Handle(command, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.Should().Be(Unit.Value);
|
||||
_mockSender.Verify(
|
||||
s => s.Send(It.IsAny<TriggeringDEXJobCommand>(), It.IsAny<CancellationToken>()),
|
||||
Times.Never,
|
||||
"Should not send any commands when no jobs found");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateBatchId_ReturnsValidFormat()
|
||||
{
|
||||
// Act
|
||||
var batchId = TriggeringDEXJobBatchCommandHandler.CreateBatchId();
|
||||
|
||||
// Assert
|
||||
batchId.Should().NotBeNullOrEmpty();
|
||||
batchId.Should().HaveLength(20, "BatchId should be exactly 20 characters");
|
||||
batchId.Should().MatchRegex(@"^\d{20}$", "BatchId should contain only digits");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateBatchId_GeneratesUniqueBatchIds()
|
||||
{
|
||||
// Act
|
||||
var batchId1 = TriggeringDEXJobBatchCommandHandler.CreateBatchId();
|
||||
var batchId2 = TriggeringDEXJobBatchCommandHandler.CreateBatchId();
|
||||
|
||||
// Assert
|
||||
batchId1.Should().NotBe(batchId2, "Consecutive batch IDs should be different");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_PassesBatchIdToCommands()
|
||||
{
|
||||
// Arrange
|
||||
var profileId = 123;
|
||||
var job = new ProfileSqlJob { Id = 1, ProfileId = profileId, SqlMainQuery = "SELECT 1" };
|
||||
|
||||
_mockJobRepo
|
||||
.Setup(r => r.FindAsync(It.IsAny<Expression<Func<ProfileSqlJob, bool>>>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new[] { job });
|
||||
|
||||
TriggeringDEXJobCommand? capturedCommand = null;
|
||||
_mockSender
|
||||
.Setup(s => s.Send(It.IsAny<TriggeringDEXJobCommand>(), It.IsAny<CancellationToken>()))
|
||||
.Callback<IRequest<Unit>, CancellationToken>((cmd, _) => capturedCommand = cmd as TriggeringDEXJobCommand)
|
||||
.ReturnsAsync(Unit.Value);
|
||||
|
||||
var command = new TriggeringDEXJobBatchCommand { ProfileId = profileId };
|
||||
|
||||
// Act
|
||||
await _handler.Handle(command, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
capturedCommand.Should().NotBeNull();
|
||||
capturedCommand!.BatchId.Should().NotBeNullOrEmpty();
|
||||
capturedCommand.BatchId.Should().HaveLength(20);
|
||||
capturedCommand.Job.Should().Be(job);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user