using FluentAssertions; using Fake.Application; using Fake.EventBus.Distributed; using Fake.ObjectStorage; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Moq; using Oms.Application.Dtos.Orders; using Oms.Application.Messaging; using Oms.Application.Services.Orders.Batch; using Oms.Domain.ChannelsAggregate.Repositories; using Oms.Domain.OrdersAggregate.Consts; using Oms.Domain.OrdersAggregate.Entities; using Oms.Domain.OrdersAggregate.Repositories; using Oms.Domain.SystemAggregate.Repositories; namespace Oms.Tests; public class OrderBatchJobCoordinatorGovernanceTests { private readonly Mock _batchJobRepository = new(MockBehavior.Strict); private readonly Mock _orderRepository = new(MockBehavior.Loose); private readonly Mock _countryRepository = new(MockBehavior.Loose); private readonly Mock _serviceChannelRepository = new(MockBehavior.Loose); private readonly Mock _fileStorage = new(MockBehavior.Loose); private readonly Mock _eventBus = new(MockBehavior.Strict); private readonly OrderBatchJobCoordinator _coordinator; public OrderBatchJobCoordinatorGovernanceTests() { var batchOptions = Options.Create(new OrderBatchRabbitMqOptions { Enabled = true }); var actionExecutor = new Mock(MockBehavior.Loose); var processor = new OrderBatchJobProcessor( _batchJobRepository.Object, _orderRepository.Object, actionExecutor.Object, batchOptions, NullLogger.Instance); _coordinator = new OrderBatchJobCoordinator( _batchJobRepository.Object, _orderRepository.Object, _countryRepository.Object, _serviceChannelRepository.Object, _fileStorage.Object, _eventBus.Object, processor, batchOptions, NullLogger.Instance); } [Fact] public async Task RetryFailedAsync_ShouldResetFailedItemsAndPublish() { var job = CreateJob(); var failedItem = CreateItem(1, OrderBatchJobItemStatusCodes.Failed, "网络错误"); var successItem = CreateItem(2, OrderBatchJobItemStatusCodes.Success); _batchJobRepository .Setup(x => x.GetByIdAsync(job.Id, It.IsAny())) .ReturnsAsync(job); _batchJobRepository .Setup(x => x.GetItemsByJobIdAsync(job.Id, It.IsAny())) .ReturnsAsync([failedItem, successItem]); _batchJobRepository .Setup(x => x.UpdateItemAsync(failedItem, It.IsAny())) .Returns(Task.CompletedTask); _batchJobRepository .Setup(x => x.UpdateJobAsync(job, It.IsAny())) .Returns(Task.CompletedTask); _eventBus .Setup(x => x.PublishAsync(It.IsAny(), It.IsAny())) .Returns(Task.CompletedTask); var output = await _coordinator.RetryFailedAsync(job.Id, null, 99); output.JobId.Should().Be(job.Id); output.RetriedCount.Should().Be(1); failedItem.Status.Should().Be(OrderBatchJobItemStatusCodes.Pending); failedItem.RetryCount.Should().Be(0); failedItem.FailReason.Should().BeNull(); job.Status.Should().Be(OrderBatchJobStatusCodes.Running); _eventBus.Verify( x => x.PublishAsync( It.Is(m => m.JobItemId == failedItem.Id), It.IsAny()), Times.Once); } [Fact] public async Task CancelPendingAsync_ShouldMarkPendingAsCancelled() { var job = CreateJob(); var pendingItem = CreateItem(3, OrderBatchJobItemStatusCodes.Pending); var successItem = CreateItem(4, OrderBatchJobItemStatusCodes.Success); _batchJobRepository .Setup(x => x.GetByIdAsync(job.Id, It.IsAny())) .ReturnsAsync(job); _batchJobRepository .Setup(x => x.GetItemsByJobIdAsync(job.Id, It.IsAny())) .ReturnsAsync([pendingItem, successItem]); _batchJobRepository .Setup(x => x.UpdateItemAsync(pendingItem, It.IsAny())) .Returns(Task.CompletedTask); _batchJobRepository .Setup(x => x.UpdateJobAsync(job, It.IsAny())) .Returns(Task.CompletedTask); var output = await _coordinator.CancelPendingAsync(job.Id, 99); output.JobId.Should().Be(job.Id); output.CancelledCount.Should().Be(1); pendingItem.Status.Should().Be(OrderBatchJobItemStatusCodes.Cancelled); pendingItem.FailReason.Should().Be("任务已取消"); job.Status.Should().Be(OrderBatchJobStatusCodes.Completed); } [Fact] public async Task RetryFailedAsync_WhenNoFailedItems_ShouldThrow() { var job = CreateJob(); _batchJobRepository .Setup(x => x.GetByIdAsync(job.Id, It.IsAny())) .ReturnsAsync(job); _batchJobRepository .Setup(x => x.GetItemsByJobIdAsync(job.Id, It.IsAny())) .ReturnsAsync([CreateItem(5, OrderBatchJobItemStatusCodes.Success)]); var act = () => _coordinator.RetryFailedAsync(job.Id, null, 99); await act.Should().ThrowAsync() .WithMessage("没有可重试的失败子项"); } private static OrderBatchJob CreateJob() => new() { Id = 501, Action = OrderBatchActionCodes.Pay, Status = OrderBatchJobStatusCodes.Completed, Total = 2, SuccessCount = 1, FailCount = 1, }; private static OrderBatchJobItem CreateItem(int id, string status, string? failReason = null) => new() { Id = id, JobId = 501, OrderId = 9000 + id, Action = OrderBatchActionCodes.Pay, Status = status, FailReason = failReason, MessageId = $"501:{id}:pay", }; }