using ECMJobRunner.Application.Behaviors; using ECMJobRunner.Application.Common.Constants; using ECMJobRunner.Application.Common.Dtos; using ECMJobRunner.Application.Common.Exceptions; using ECMJobRunner.Application.Common.Interfaces; using ECMJobRunner.Application.Common.Options; using ECMJobRunner.Application.DEXJob.Commands; using ECMJobRunner.Domain.Entities; using FluentAssertions; using MediatR; using Microsoft.Extensions.Options; using Moq; using System; using System.Threading; using System.Threading.Tasks; using Xunit; namespace ECMJobRunner.Tests.Application { public class CheckQueryExecutionBehaviorTests { private readonly Mock _mockExecutor; private readonly Mock> _mockOptions; private readonly CheckQueryExecutionBehavior _behavior; private readonly Mock> _mockNext; public CheckQueryExecutionBehaviorTests() { _mockExecutor = new Mock(); _mockOptions = new Mock>(); _mockOptions.Setup(o => o.Value).Returns(new DexJobOptions()); _behavior = new CheckQueryExecutionBehavior(_mockExecutor.Object, _mockOptions.Object); _mockNext = new Mock>(); _mockNext.Setup(n => n()).ReturnsAsync(Unit.Value); } [Fact] public async Task Handle_WithValidCheckQuery_ExecutesSuccessfully() { // Arrange var command = new TriggeringDEXJobCommand { Job = new ProfileSqlJob { SqlCheckQuery = "SELECT COUNT(*) AS [Return Value] FROM Table" }, BatchId = "20260711143025123456" }; _mockExecutor .Setup(e => e.ExecuteQueryAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(new CheckQueryResult { ReturnValue = 5 }); // Act #if NET48 var result = await _behavior.Handle(command, CancellationToken.None, _mockNext.Object); #else var result = await _behavior.Handle(command, _mockNext.Object, CancellationToken.None); #endif // Assert result.Should().Be(Unit.Value); _mockExecutor.Verify(e => e.ExecuteQueryAsync(It.IsAny(), It.IsAny()), Times.Once); _mockNext.Verify(n => n(), Times.Once); } [Theory] [InlineData(1)] [InlineData(10)] [InlineData(100)] public async Task Handle_WithPositiveReturnValue_DoesNotThrow(int returnValue) { // Arrange var command = new TriggeringDEXJobCommand { Job = new ProfileSqlJob { SqlCheckQuery = "SELECT COUNT(*) AS [Return Value] FROM Table" }, BatchId = "20260711143025123456" }; _mockExecutor .Setup(e => e.ExecuteQueryAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(new CheckQueryResult { ReturnValue = returnValue }); // Act #if NET48 Func act = async () => await _behavior.Handle(command, CancellationToken.None, _mockNext.Object); #else Func act = async () => await _behavior.Handle(command, _mockNext.Object, CancellationToken.None); #endif // Assert await act.Should().NotThrowAsync(); } [Theory] [InlineData(0)] [InlineData(-1)] [InlineData(-10)] public async Task Handle_WithZeroOrNegativeReturnValue_ThrowsDEXJobException(int returnValue) { // Arrange var command = new TriggeringDEXJobCommand { Job = new ProfileSqlJob { SqlCheckQuery = "SELECT COUNT(*) AS [Return Value] FROM Table" }, BatchId = "20260711143025123456" }; _mockExecutor .Setup(e => e.ExecuteQueryAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(new CheckQueryResult { ReturnValue = returnValue }); // Act #if NET48 Func act = async () => await _behavior.Handle(command, CancellationToken.None, _mockNext.Object); #else Func act = async () => await _behavior.Handle(command, _mockNext.Object, CancellationToken.None); #endif // Assert await act.Should().ThrowAsync() .WithMessage($"*unexpectedly returned the value {returnValue}*"); } [Fact] public async Task Handle_WithNullCheckQuery_IgnoresByDefault() { // Arrange var command = new TriggeringDEXJobCommand { Job = new ProfileSqlJob { SqlCheckQuery = null }, BatchId = "20260711143025123456" }; // Act #if NET48 var result = await _behavior.Handle(command, CancellationToken.None, _mockNext.Object); #else var result = await _behavior.Handle(command, _mockNext.Object, CancellationToken.None); #endif // Assert result.Should().Be(Unit.Value); _mockExecutor.Verify(e => e.ExecuteQueryAsync(It.IsAny(), It.IsAny()), Times.Never); _mockNext.Verify(n => n(), Times.Once); } [Fact] public async Task Handle_WithNullCheckQueryAndStopOption_ThrowsDEXJobException() { // Arrange var options = new DexJobOptions { Error = new DexJobOptions.DexJobErrorHandlingOptions { CheckQuery = new DexJobOptions.DexJobErrorHandlingOptions.SqlQueryErrorHandlingOptions { IfNullOrWhiteSpace = ErrorAction.Stop } } }; _mockOptions.Setup(o => o.Value).Returns(options); var behavior = new CheckQueryExecutionBehavior(_mockExecutor.Object, _mockOptions.Object); var command = new TriggeringDEXJobCommand { Job = new ProfileSqlJob { SqlCheckQuery = null }, BatchId = "20260711143025123456" }; // Act #if NET48 Func act = async () => await behavior.Handle(command, CancellationToken.None, _mockNext.Object); #else Func act = async () => await behavior.Handle(command, _mockNext.Object, CancellationToken.None); #endif // Assert await act.Should().ThrowAsync() .WithMessage("*SQL Check Query is null or empty*"); } [Fact] public async Task Handle_WithBatchIdPlaceholder_ReplacesCorrectly() { // Arrange var batchId = "20260711143025123456"; var command = new TriggeringDEXJobCommand { Job = new ProfileSqlJob { SqlCheckQuery = "SELECT COUNT(*) AS [Return Value] FROM Table WHERE BatchId = '#INT#BATCH_ID'" }, BatchId = batchId }; string? capturedSql = null; _mockExecutor .Setup(e => e.ExecuteQueryAsync(It.IsAny(), It.IsAny())) .Callback((sql, _) => capturedSql = sql) .ReturnsAsync(new CheckQueryResult { ReturnValue = 1 }); // Act #if NET48 await _behavior.Handle(command, CancellationToken.None, _mockNext.Object); #else await _behavior.Handle(command, _mockNext.Object, CancellationToken.None); #endif // Assert capturedSql.Should().Contain(batchId); capturedSql.Should().NotContain("#INT#BATCH_ID"); } [Fact] public async Task Handle_WithNullResult_ThrowsDEXJobException() { // Arrange var command = new TriggeringDEXJobCommand { Job = new ProfileSqlJob { SqlCheckQuery = "SELECT COUNT(*) AS [Return Value] FROM Table" }, BatchId = "20260711143025123456" }; _mockExecutor .Setup(e => e.ExecuteQueryAsync(It.IsAny(), It.IsAny())) .ReturnsAsync((CheckQueryResult?)null); // Act #if NET48 Func act = async () => await _behavior.Handle(command, CancellationToken.None, _mockNext.Object); #else Func act = async () => await _behavior.Handle(command, _mockNext.Object, CancellationToken.None); #endif // Assert await act.Should().ThrowAsync() .WithMessage("*Check Query returned nothing*"); } } }