using ECMJobRunner.Application.Common.Dtos; using ECMJobRunner.Application.Common.Interfaces; using ECMJobRunner.Application.Common.Options; using ECMJobRunner.Application.DEXJob.Commands; using ECMJobRunner.Domain.Entities; using ECMJobRunner.Domain.Interfaces; 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 _mockJobRepo; private readonly Mock _mockSender; private readonly TriggeringDEXJobBatchCommandHandler _handler; public TriggeringDEXJobBatchCommandTests() { _mockJobRepo = new Mock(); _mockSender = new Mock(); _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>>(), It.IsAny())) .ReturnsAsync(jobs); var command = new TriggeringProfileJobBatchCommand { ProfileId = profileId }; // Act var result = await _handler.Handle(command, CancellationToken.None); // Assert result.Should().Be(Unit.Value); _mockSender.Verify( s => s.Send(It.IsAny(), It.IsAny()), 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>>(), It.IsAny())) .ReturnsAsync(Array.Empty()); var command = new TriggeringProfileJobBatchCommand { ProfileId = profileId }; // Act var result = await _handler.Handle(command, CancellationToken.None); // Assert result.Should().Be(Unit.Value); _mockSender.Verify( s => s.Send(It.IsAny(), It.IsAny()), 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>>(), It.IsAny())) .ReturnsAsync(new[] { job }); TriggeringProfileJobCommand? capturedCommand = null; _mockSender .Setup(s => s.Send(It.IsAny(), It.IsAny())) .Callback, CancellationToken>((cmd, _) => capturedCommand = cmd as TriggeringProfileJobCommand) .ReturnsAsync(Unit.Value); var command = new TriggeringProfileJobBatchCommand { 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); } } }