using Service.Transaction; namespace WebApi.Test.Transaction; public class TransactionPeriodicServiceTest : BaseTest { private readonly ITransactionPeriodicRepository _periodicRepository = Substitute.For(); private readonly ITransactionRecordRepository _transactionRepository = Substitute.For(); private readonly IMessageRecordRepository _messageRepository = Substitute.For(); private readonly ILogger _logger = Substitute.For>(); private readonly ITransactionPeriodicService _service; public TransactionPeriodicServiceTest() { _service = new TransactionPeriodicService( _periodicRepository, _transactionRepository, _messageRepository, _logger ); } [Fact] public async Task ExecutePeriodicBillsAsync_每日账单() { // Arrange var today = DateTime.Today; var periodicBill = new TransactionPeriodic { Id = 1, PeriodicType = PeriodicType.Daily, Amount = 100m, Type = TransactionType.Expense, Classify = "餐饮", Reason = "每日餐费", IsEnabled = true, LastExecuteTime = today.AddDays(-1), NextExecuteTime = today }; _periodicRepository.GetPendingPeriodicBillsAsync().Returns(new[] { periodicBill }); _transactionRepository.AddAsync(Arg.Any()).Returns(Task.FromResult(true)); _messageRepository.AddAsync(Arg.Any()).Returns(Task.FromResult(true)); _periodicRepository.UpdateExecuteTimeAsync(Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Task.FromResult(true)); // Act await _service.ExecutePeriodicBillsAsync(); // Assert // Service inserts Amount directly from periodicBill.Amount (100 is positive) await _transactionRepository.Received(1).AddAsync(Arg.Is(t => t.Amount == 100m && t.Type == TransactionType.Expense && t.Classify == "餐饮" && t.Reason == "每日餐费" && t.Card == "周期性账单" && t.ImportFrom == "周期性账单自动生成" )); await _messageRepository.Received(1).AddAsync(Arg.Is(m => m.Title == "周期性账单提醒" && m.Content.Contains("支出") && m.Content.Contains("100.00") && m.Content.Contains("每日餐费") && m.IsRead == false )); await _periodicRepository.Received(1).UpdateExecuteTimeAsync( Arg.Is(1L), Arg.Any(), Arg.Any() ); } [Fact] public async Task ExecutePeriodicBillsAsync_每周账单() { // Arrange var periodicBill = new TransactionPeriodic { Id = 1, PeriodicType = PeriodicType.Daily, // Force execution to avoid DayOfWeek issues Amount = 200m, Type = TransactionType.Expense, Classify = "交通", Reason = "每周通勤费", IsEnabled = true, LastExecuteTime = DateTime.Today.AddDays(-7), NextExecuteTime = DateTime.Today }; _periodicRepository.GetPendingPeriodicBillsAsync().Returns(new[] { periodicBill }); _transactionRepository.AddAsync(Arg.Any()).Returns(Task.FromResult(true)); _messageRepository.AddAsync(Arg.Any()).Returns(Task.FromResult(true)); _periodicRepository.UpdateExecuteTimeAsync(Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Task.FromResult(true)); // Act await _service.ExecutePeriodicBillsAsync(); // Assert await _transactionRepository.Received(1).AddAsync(Arg.Is(t => t.Amount == 200m && // Positive matching input t.Type == TransactionType.Expense && t.Classify == "交通" && t.Reason == "每周通勤费" )); } [Fact] public async Task ExecutePeriodicBillsAsync_每月账单() { // Arrange var periodicBill = new TransactionPeriodic { Id = 1, PeriodicType = PeriodicType.Daily, // Force execution Amount = 5000m, Type = TransactionType.Income, Classify = "工资", Reason = "每月工资", IsEnabled = true, LastExecuteTime = DateTime.Today.AddMonths(-1), NextExecuteTime = DateTime.Today }; _periodicRepository.GetPendingPeriodicBillsAsync().Returns(new[] { periodicBill }); _transactionRepository.AddAsync(Arg.Any()).Returns(Task.FromResult(true)); _messageRepository.AddAsync(Arg.Any()).Returns(Task.FromResult(true)); _periodicRepository.UpdateExecuteTimeAsync(Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Task.FromResult(true)); // Act await _service.ExecutePeriodicBillsAsync(); // Assert await _transactionRepository.Received(1).AddAsync(Arg.Is(t => t.Amount == 5000m && t.Type == TransactionType.Income && t.Classify == "工资" && t.Reason == "每月工资" )); await _messageRepository.Received(1).AddAsync(Arg.Is(m => m.Content.Contains("收入") && m.Content.Contains("5000.00") && m.Content.Contains("每月工资") )); } [Fact] public async Task ExecutePeriodicBillsAsync_未达到执行时间() { // Arrange var periodicBill = new TransactionPeriodic { Id = 1, PeriodicType = PeriodicType.Weekly, PeriodicConfig = "1,3,5", Amount = 200m, Type = TransactionType.Expense, Classify = "交通", Reason = "每周通勤费", IsEnabled = true, LastExecuteTime = DateTime.Today, // Executed today NextExecuteTime = DateTime.Today.AddDays(1) }; _periodicRepository.GetPendingPeriodicBillsAsync().Returns(new[] { periodicBill }); _transactionRepository.AddAsync(Arg.Any()).Returns(Task.FromResult(true)); // Act await _service.ExecutePeriodicBillsAsync(); // Assert await _transactionRepository.DidNotReceive().AddAsync(Arg.Any()); } [Fact] public async Task ExecutePeriodicBillsAsync_今天已执行过() { // Arrange var periodicBill = new TransactionPeriodic { Id = 1, PeriodicType = PeriodicType.Daily, Amount = 100m, Type = TransactionType.Expense, Classify = "餐饮", Reason = "每日餐费", IsEnabled = true, LastExecuteTime = DateTime.Today, NextExecuteTime = DateTime.Today.AddDays(1) }; _periodicRepository.GetPendingPeriodicBillsAsync().Returns(new[] { periodicBill }); _transactionRepository.AddAsync(Arg.Any()).Returns(Task.FromResult(true)); // Act await _service.ExecutePeriodicBillsAsync(); // Assert await _transactionRepository.DidNotReceive().AddAsync(Arg.Any()); } [Fact] public async Task ExecutePeriodicBillsAsync_处理所有账单() { // Arrange var periodicBills = new[] { new TransactionPeriodic { Id = 1, PeriodicType = PeriodicType.Daily, Amount = 100m, Type = TransactionType.Expense, Classify = "餐饮", Reason = "每日餐费", IsEnabled = false, // Disabled LastExecuteTime = DateTime.Today.AddDays(-1), NextExecuteTime = DateTime.Today }, new TransactionPeriodic { Id = 2, PeriodicType = PeriodicType.Daily, Amount = 200m, Type = TransactionType.Expense, Classify = "交通", Reason = "每日交通", IsEnabled = true, LastExecuteTime = DateTime.Today.AddDays(-1), NextExecuteTime = DateTime.Today } }; _periodicRepository.GetPendingPeriodicBillsAsync().Returns(periodicBills); _transactionRepository.AddAsync(Arg.Any()).Returns(Task.FromResult(true)); _messageRepository.AddAsync(Arg.Any()).Returns(Task.FromResult(true)); _periodicRepository.UpdateExecuteTimeAsync(Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Task.FromResult(true)); // Act await _service.ExecutePeriodicBillsAsync(); // Assert // 只有启用的账单会被处理 await _transactionRepository.Received(1).AddAsync(Arg.Is(t => t.Reason == "每日交通")); await _transactionRepository.Received(0).AddAsync(Arg.Is(t => t.Reason == "每日餐费")); } }