using Application.Dto.Periodic; using Service.Transaction; namespace WebApi.Test.Application; /// /// TransactionPeriodicApplication 单元测试 /// public class TransactionPeriodicApplicationTest : BaseApplicationTest { private readonly ITransactionPeriodicRepository _periodicRepository; private readonly ITransactionPeriodicService _periodicService; private readonly ILogger _logger; private readonly TransactionPeriodicApplication _application; public TransactionPeriodicApplicationTest() { _periodicRepository = Substitute.For(); _periodicService = Substitute.For(); _logger = CreateMockLogger(); _application = new TransactionPeriodicApplication(_periodicRepository, _periodicService, _logger); } #region GetListAsync Tests [Fact] public async Task GetListAsync_有数据_应返回分页结果() { // Arrange var pageIndex = 1; var pageSize = 10; var periodics = new List { new() { Id = 1, Amount = 100, Type = TransactionType.Expense, Classify = "房租", IsEnabled = true }, new() { Id = 2, Amount = 5000, Type = TransactionType.Income, Classify = "工资", IsEnabled = true } }; _periodicRepository.GetPagedListAsync(pageIndex, pageSize, null).Returns(periodics); _periodicRepository.GetTotalCountAsync(null).Returns(2L); // Act var result = await _application.GetListAsync(pageIndex, pageSize); // Assert result.Data.Should().HaveCount(2); result.Total.Should().Be(2); result.Data[0].Amount.Should().Be(100); } [Fact] public async Task GetListAsync_带搜索关键词_应过滤结果() { // Arrange var keyword = "房租"; var periodics = new List { new() { Id = 1, Amount = 1000, Type = TransactionType.Expense, Classify = "房租", IsEnabled = true } }; _periodicRepository.GetPagedListAsync(1, 20, keyword).Returns(periodics); _periodicRepository.GetTotalCountAsync(keyword).Returns(1L); // Act var result = await _application.GetListAsync(1, 20, keyword); // Assert result.Data.Should().HaveCount(1); result.Total.Should().Be(1); } #endregion #region GetByIdAsync Tests [Fact] public async Task GetByIdAsync_存在的记录_应返回详情() { // Arrange var periodic = new TransactionPeriodic { Id = 1, Amount = 1000, Type = TransactionType.Expense, Classify = "房租", Reason = "每月房租", IsEnabled = true }; _periodicRepository.GetByIdAsync(1).Returns(periodic); // Act var result = await _application.GetByIdAsync(1); // Assert result.Should().NotBeNull(); result.Id.Should().Be(1); result.Amount.Should().Be(1000); result.Classify.Should().Be("房租"); } [Fact] public async Task GetByIdAsync_不存在的记录_应抛出NotFoundException() { // Arrange _periodicRepository.GetByIdAsync(999).Returns((TransactionPeriodic?)null); // Act & Assert await Assert.ThrowsAsync(() => _application.GetByIdAsync(999)); } #endregion #region CreateAsync Tests [Fact] public async Task CreateAsync_有效请求_应成功创建() { // Arrange var request = new CreatePeriodicRequest { PeriodicType = PeriodicType.Monthly, PeriodicConfig = "1", Amount = 1000, Type = TransactionType.Expense, Classify = "房租", Reason = "每月房租" }; var nextExecuteTime = DateTime.Now.AddDays(1); _periodicService.CalculateNextExecuteTime(Arg.Any(), Arg.Any()) .Returns(nextExecuteTime); _periodicRepository.AddAsync(Arg.Any()).Returns(true); // Act var result = await _application.CreateAsync(request); // Assert result.Should().NotBeNull(); result.Amount.Should().Be(1000); result.IsEnabled.Should().BeTrue(); await _periodicRepository.Received(1).AddAsync(Arg.Is( p => p.Amount == 1000 && p.Classify == "房租" )); } [Fact] public async Task CreateAsync_Repository添加失败_应抛出BusinessException() { // Arrange var request = new CreatePeriodicRequest { PeriodicType = PeriodicType.Monthly, Amount = 1000, Type = TransactionType.Expense }; _periodicService.CalculateNextExecuteTime(Arg.Any(), Arg.Any()) .Returns(DateTime.Now.AddDays(1)); _periodicRepository.AddAsync(Arg.Any()).Returns(false); // Act & Assert await Assert.ThrowsAsync(() => _application.CreateAsync(request)); } #endregion #region UpdateAsync Tests [Fact] public async Task UpdateAsync_有效请求_应成功更新() { // Arrange var existingPeriodic = new TransactionPeriodic { Id = 1, Amount = 1000, Type = TransactionType.Expense, Classify = "房租", IsEnabled = true }; var request = new UpdatePeriodicRequest { Id = 1, PeriodicType = PeriodicType.Monthly, PeriodicConfig = "1", Amount = 1200, Type = TransactionType.Expense, Classify = "房租", Reason = "房租涨价", IsEnabled = true }; _periodicRepository.GetByIdAsync(1).Returns(existingPeriodic); _periodicService.CalculateNextExecuteTime(Arg.Any(), Arg.Any()) .Returns(DateTime.Now.AddMonths(1)); _periodicRepository.UpdateAsync(Arg.Any()).Returns(true); // Act await _application.UpdateAsync(request); // Assert await _periodicRepository.Received(1).UpdateAsync(Arg.Is( p => p.Id == 1 && p.Amount == 1200 && p.Reason == "房租涨价" )); } [Fact] public async Task UpdateAsync_记录不存在_应抛出NotFoundException() { // Arrange var request = new UpdatePeriodicRequest { Id = 999, PeriodicType = PeriodicType.Monthly, Amount = 1000, Type = TransactionType.Expense, IsEnabled = true }; _periodicRepository.GetByIdAsync(999).Returns((TransactionPeriodic?)null); // Act & Assert await Assert.ThrowsAsync(() => _application.UpdateAsync(request)); } [Fact] public async Task UpdateAsync_Repository更新失败_应抛出BusinessException() { // Arrange var existingPeriodic = new TransactionPeriodic { Id = 1, Amount = 1000, Type = TransactionType.Expense }; var request = new UpdatePeriodicRequest { Id = 1, PeriodicType = PeriodicType.Monthly, Amount = 1200, Type = TransactionType.Expense, IsEnabled = true }; _periodicRepository.GetByIdAsync(1).Returns(existingPeriodic); _periodicService.CalculateNextExecuteTime(Arg.Any(), Arg.Any()) .Returns(DateTime.Now.AddMonths(1)); _periodicRepository.UpdateAsync(Arg.Any()).Returns(false); // Act & Assert await Assert.ThrowsAsync(() => _application.UpdateAsync(request)); } #endregion #region DeleteByIdAsync Tests [Fact] public async Task DeleteByIdAsync_成功删除_不应抛出异常() { // Arrange _periodicRepository.DeleteAsync(1).Returns(true); // Act await _application.DeleteByIdAsync(1); // Assert await _periodicRepository.Received(1).DeleteAsync(1); } [Fact] public async Task DeleteByIdAsync_删除失败_应抛出BusinessException() { // Arrange _periodicRepository.DeleteAsync(999).Returns(false); // Act & Assert await Assert.ThrowsAsync(() => _application.DeleteByIdAsync(999)); } #endregion #region ToggleEnabledAsync Tests [Fact] public async Task ToggleEnabledAsync_禁用账单_应成功更新状态() { // Arrange var periodic = new TransactionPeriodic { Id = 1, Amount = 1000, Type = TransactionType.Expense, IsEnabled = true }; _periodicRepository.GetByIdAsync(1).Returns(periodic); _periodicRepository.UpdateAsync(Arg.Any()).Returns(true); // Act await _application.ToggleEnabledAsync(1, false); // Assert await _periodicRepository.Received(1).UpdateAsync(Arg.Is( p => p.Id == 1 && p.IsEnabled == false )); } [Fact] public async Task ToggleEnabledAsync_启用账单_应成功更新状态() { // Arrange var periodic = new TransactionPeriodic { Id = 1, Amount = 1000, Type = TransactionType.Expense, IsEnabled = false }; _periodicRepository.GetByIdAsync(1).Returns(periodic); _periodicRepository.UpdateAsync(Arg.Any()).Returns(true); // Act await _application.ToggleEnabledAsync(1, true); // Assert await _periodicRepository.Received(1).UpdateAsync(Arg.Is( p => p.Id == 1 && p.IsEnabled == true )); } [Fact] public async Task ToggleEnabledAsync_记录不存在_应抛出NotFoundException() { // Arrange _periodicRepository.GetByIdAsync(999).Returns((TransactionPeriodic?)null); // Act & Assert await Assert.ThrowsAsync(() => _application.ToggleEnabledAsync(999, false)); } [Fact] public async Task ToggleEnabledAsync_更新失败_应抛出BusinessException() { // Arrange var periodic = new TransactionPeriodic { Id = 1, Amount = 1000, Type = TransactionType.Expense, IsEnabled = true }; _periodicRepository.GetByIdAsync(1).Returns(periodic); _periodicRepository.UpdateAsync(Arg.Any()).Returns(false); // Act & Assert await Assert.ThrowsAsync(() => _application.ToggleEnabledAsync(1, false)); } #endregion }