Files
ECMJobRunner/ECMJobRunner.Tests/Application/TriggeringDEXJobBatchCommandTests.cs
TekH 52c0614975 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)
2026-07-11 16:44:53 +02:00

136 lines
4.9 KiB
C#

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);
}
}
}