- Add AutoMapper profile for CfgProfile and ProfileSqlJob entity-to-DTO mappings - Consolidate GetProfileByIdQuery and GetAllActiveProfilesQuery into unified GetProfileQuery - Implement flexible filtering with nullable query options (Id, Active, TypeId, ProfileName) - Add IncludeSqlJobs option for optimized SQL job loading - Move CfgProfileDto to Common/Dtos for better architecture alignment - Add comprehensive unit tests for GetProfileQuery with multiple filter scenarios - Move ISQLExecutor interface from Domain to Application layer - Add GetByIdWithSqlJobsAsync and GetAllActiveWithSqlJobsAsync to repository
235 lines
8.8 KiB
C#
235 lines
8.8 KiB
C#
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<ISQLExecutor> _mockExecutor;
|
|
private readonly Mock<IOptions<DexJobOptions>> _mockOptions;
|
|
private readonly CheckQueryExecutionBehavior<TriggeringDEXJobCommand, Unit> _behavior;
|
|
private readonly Mock<RequestHandlerDelegate<Unit>> _mockNext;
|
|
|
|
public CheckQueryExecutionBehaviorTests()
|
|
{
|
|
_mockExecutor = new Mock<ISQLExecutor>();
|
|
_mockOptions = new Mock<IOptions<DexJobOptions>>();
|
|
_mockOptions.Setup(o => o.Value).Returns(new DexJobOptions());
|
|
_behavior = new CheckQueryExecutionBehavior<TriggeringDEXJobCommand, Unit>(_mockExecutor.Object, _mockOptions.Object);
|
|
_mockNext = new Mock<RequestHandlerDelegate<Unit>>();
|
|
_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<CheckQueryResult>(It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
|
.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<CheckQueryResult>(It.IsAny<string>(), It.IsAny<CancellationToken>()), 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<CheckQueryResult>(It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(new CheckQueryResult { ReturnValue = returnValue });
|
|
|
|
// Act
|
|
#if NET48
|
|
Func<Task> act = async () => await _behavior.Handle(command, CancellationToken.None, _mockNext.Object);
|
|
#else
|
|
Func<Task> 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<CheckQueryResult>(It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(new CheckQueryResult { ReturnValue = returnValue });
|
|
|
|
// Act
|
|
#if NET48
|
|
Func<Task> act = async () => await _behavior.Handle(command, CancellationToken.None, _mockNext.Object);
|
|
#else
|
|
Func<Task> act = async () => await _behavior.Handle(command, _mockNext.Object, CancellationToken.None);
|
|
#endif
|
|
|
|
// Assert
|
|
await act.Should().ThrowAsync<JobException>()
|
|
.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<CheckQueryResult>(It.IsAny<string>(), It.IsAny<CancellationToken>()), 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<TriggeringDEXJobCommand, Unit>(_mockExecutor.Object, _mockOptions.Object);
|
|
var command = new TriggeringDEXJobCommand
|
|
{
|
|
Job = new ProfileSqlJob { SqlCheckQuery = null },
|
|
BatchId = "20260711143025123456"
|
|
};
|
|
|
|
// Act
|
|
#if NET48
|
|
Func<Task> act = async () => await behavior.Handle(command, CancellationToken.None, _mockNext.Object);
|
|
#else
|
|
Func<Task> act = async () => await behavior.Handle(command, _mockNext.Object, CancellationToken.None);
|
|
#endif
|
|
|
|
// Assert
|
|
await act.Should().ThrowAsync<JobException>()
|
|
.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<CheckQueryResult>(It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
|
.Callback<string, CancellationToken>((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<CheckQueryResult>(It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync((CheckQueryResult?)null);
|
|
|
|
// Act
|
|
#if NET48
|
|
Func<Task> act = async () => await _behavior.Handle(command, CancellationToken.None, _mockNext.Object);
|
|
#else
|
|
Func<Task> act = async () => await _behavior.Handle(command, _mockNext.Object, CancellationToken.None);
|
|
#endif
|
|
|
|
// Assert
|
|
await act.Should().ThrowAsync<JobException>()
|
|
.WithMessage("*Check Query returned nothing*");
|
|
}
|
|
}
|
|
}
|