fix
This commit is contained in:
447
WebApi.Test/Application/TransactionCategoryApplicationTest.cs
Normal file
447
WebApi.Test/Application/TransactionCategoryApplicationTest.cs
Normal file
@@ -0,0 +1,447 @@
|
||||
using Application.Dto.Category;
|
||||
using Service.AI;
|
||||
|
||||
namespace WebApi.Test.Application;
|
||||
|
||||
/// <summary>
|
||||
/// TransactionCategoryApplication 单元测试
|
||||
/// </summary>
|
||||
public class TransactionCategoryApplicationTest : BaseApplicationTest
|
||||
{
|
||||
private readonly ITransactionCategoryRepository _categoryRepository;
|
||||
private readonly ITransactionRecordRepository _transactionRepository;
|
||||
private readonly IBudgetRepository _budgetRepository;
|
||||
private readonly ISmartHandleService _smartHandleService;
|
||||
private readonly ILogger<TransactionCategoryApplication> _logger;
|
||||
private readonly TransactionCategoryApplication _application;
|
||||
|
||||
public TransactionCategoryApplicationTest()
|
||||
{
|
||||
_categoryRepository = Substitute.For<ITransactionCategoryRepository>();
|
||||
_transactionRepository = Substitute.For<ITransactionRecordRepository>();
|
||||
_budgetRepository = Substitute.For<IBudgetRepository>();
|
||||
_smartHandleService = Substitute.For<ISmartHandleService>();
|
||||
_logger = CreateMockLogger<TransactionCategoryApplication>();
|
||||
_application = new TransactionCategoryApplication(
|
||||
_categoryRepository,
|
||||
_transactionRepository,
|
||||
_budgetRepository,
|
||||
_smartHandleService,
|
||||
_logger);
|
||||
}
|
||||
|
||||
#region GetListAsync Tests
|
||||
|
||||
[Fact]
|
||||
public async Task GetListAsync_无类型筛选_应返回所有分类()
|
||||
{
|
||||
// Arrange
|
||||
var categories = new List<TransactionCategory>
|
||||
{
|
||||
new() { Id = 1, Name = "餐饮", Type = TransactionType.Expense },
|
||||
new() { Id = 2, Name = "工资", Type = TransactionType.Income }
|
||||
};
|
||||
_categoryRepository.GetAllAsync().Returns(categories);
|
||||
|
||||
// Act
|
||||
var result = await _application.GetListAsync();
|
||||
|
||||
// Assert
|
||||
result.Should().HaveCount(2);
|
||||
result.Should().Contain(c => c.Name == "餐饮");
|
||||
result.Should().Contain(c => c.Name == "工资");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetListAsync_指定类型_应返回该类型分类()
|
||||
{
|
||||
// Arrange
|
||||
var expenseCategories = new List<TransactionCategory>
|
||||
{
|
||||
new() { Id = 1, Name = "餐饮", Type = TransactionType.Expense },
|
||||
new() { Id = 2, Name = "交通", Type = TransactionType.Expense }
|
||||
};
|
||||
_categoryRepository.GetCategoriesByTypeAsync(TransactionType.Expense).Returns(expenseCategories);
|
||||
|
||||
// Act
|
||||
var result = await _application.GetListAsync(TransactionType.Expense);
|
||||
|
||||
// Assert
|
||||
result.Should().HaveCount(2);
|
||||
result.Should().AllSatisfy(c => c.Type.Should().Be(TransactionType.Expense));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region GetByIdAsync Tests
|
||||
|
||||
[Fact]
|
||||
public async Task GetByIdAsync_存在的分类_应返回分类详情()
|
||||
{
|
||||
// Arrange
|
||||
var category = new TransactionCategory
|
||||
{
|
||||
Id = 1,
|
||||
Name = "餐饮",
|
||||
Type = TransactionType.Expense
|
||||
};
|
||||
_categoryRepository.GetByIdAsync(1).Returns(category);
|
||||
|
||||
// Act
|
||||
var result = await _application.GetByIdAsync(1);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
result.Id.Should().Be(1);
|
||||
result.Name.Should().Be("餐饮");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetByIdAsync_不存在的分类_应抛出NotFoundException()
|
||||
{
|
||||
// Arrange
|
||||
_categoryRepository.GetByIdAsync(999).Returns((TransactionCategory?)null);
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => _application.GetByIdAsync(999));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region CreateAsync Tests
|
||||
|
||||
[Fact]
|
||||
public async Task CreateAsync_有效请求_应成功创建()
|
||||
{
|
||||
// Arrange
|
||||
var request = new CreateCategoryRequest
|
||||
{
|
||||
Name = "新分类",
|
||||
Type = TransactionType.Expense
|
||||
};
|
||||
_categoryRepository.GetByNameAndTypeAsync("新分类", TransactionType.Expense).Returns((TransactionCategory?)null);
|
||||
_categoryRepository.AddAsync(Arg.Any<TransactionCategory>()).Returns(true);
|
||||
|
||||
// Act
|
||||
var result = await _application.CreateAsync(request);
|
||||
|
||||
// Assert
|
||||
result.Should().BeGreaterThan(0);
|
||||
await _categoryRepository.Received(1).AddAsync(Arg.Is<TransactionCategory>(
|
||||
c => c.Name == "新分类" && c.Type == TransactionType.Expense
|
||||
));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateAsync_同名分类已存在_应抛出ValidationException()
|
||||
{
|
||||
// Arrange
|
||||
var existingCategory = new TransactionCategory
|
||||
{
|
||||
Id = 1,
|
||||
Name = "餐饮",
|
||||
Type = TransactionType.Expense
|
||||
};
|
||||
var request = new CreateCategoryRequest
|
||||
{
|
||||
Name = "餐饮",
|
||||
Type = TransactionType.Expense
|
||||
};
|
||||
_categoryRepository.GetByNameAndTypeAsync("餐饮", TransactionType.Expense).Returns(existingCategory);
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<ValidationException>(() => _application.CreateAsync(request));
|
||||
exception.Message.Should().Contain("已存在相同名称的分类");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateAsync_Repository添加失败_应抛出BusinessException()
|
||||
{
|
||||
// Arrange
|
||||
var request = new CreateCategoryRequest
|
||||
{
|
||||
Name = "测试分类",
|
||||
Type = TransactionType.Expense
|
||||
};
|
||||
_categoryRepository.GetByNameAndTypeAsync("测试分类", TransactionType.Expense).Returns((TransactionCategory?)null);
|
||||
_categoryRepository.AddAsync(Arg.Any<TransactionCategory>()).Returns(false);
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<BusinessException>(() => _application.CreateAsync(request));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region UpdateAsync Tests
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateAsync_有效请求_应成功更新()
|
||||
{
|
||||
// Arrange
|
||||
var existingCategory = new TransactionCategory
|
||||
{
|
||||
Id = 1,
|
||||
Name = "旧名称",
|
||||
Type = TransactionType.Expense
|
||||
};
|
||||
var request = new UpdateCategoryRequest
|
||||
{
|
||||
Id = 1,
|
||||
Name = "新名称"
|
||||
};
|
||||
_categoryRepository.GetByIdAsync(1).Returns(existingCategory);
|
||||
_categoryRepository.GetByNameAndTypeAsync("新名称", TransactionType.Expense).Returns((TransactionCategory?)null);
|
||||
_categoryRepository.UpdateAsync(Arg.Any<TransactionCategory>()).Returns(true);
|
||||
|
||||
// Act
|
||||
await _application.UpdateAsync(request);
|
||||
|
||||
// Assert
|
||||
await _categoryRepository.Received(1).UpdateAsync(Arg.Is<TransactionCategory>(
|
||||
c => c.Id == 1 && c.Name == "新名称"
|
||||
));
|
||||
await _transactionRepository.Received(1).UpdateCategoryNameAsync("旧名称", "新名称", TransactionType.Expense);
|
||||
await _budgetRepository.Received(1).UpdateBudgetCategoryNameAsync("旧名称", "新名称", TransactionType.Expense);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateAsync_分类不存在_应抛出NotFoundException()
|
||||
{
|
||||
// Arrange
|
||||
var request = new UpdateCategoryRequest
|
||||
{
|
||||
Id = 999,
|
||||
Name = "新名称"
|
||||
};
|
||||
_categoryRepository.GetByIdAsync(999).Returns((TransactionCategory?)null);
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => _application.UpdateAsync(request));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateAsync_新名称已存在_应抛出ValidationException()
|
||||
{
|
||||
// Arrange
|
||||
var existingCategory = new TransactionCategory
|
||||
{
|
||||
Id = 1,
|
||||
Name = "旧名称",
|
||||
Type = TransactionType.Expense
|
||||
};
|
||||
var conflictingCategory = new TransactionCategory
|
||||
{
|
||||
Id = 2,
|
||||
Name = "冲突名称",
|
||||
Type = TransactionType.Expense
|
||||
};
|
||||
var request = new UpdateCategoryRequest
|
||||
{
|
||||
Id = 1,
|
||||
Name = "冲突名称"
|
||||
};
|
||||
_categoryRepository.GetByIdAsync(1).Returns(existingCategory);
|
||||
_categoryRepository.GetByNameAndTypeAsync("冲突名称", TransactionType.Expense).Returns(conflictingCategory);
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<ValidationException>(() => _application.UpdateAsync(request));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateAsync_名称未改变_不应同步更新关联数据()
|
||||
{
|
||||
// Arrange
|
||||
var existingCategory = new TransactionCategory
|
||||
{
|
||||
Id = 1,
|
||||
Name = "相同名称",
|
||||
Type = TransactionType.Expense
|
||||
};
|
||||
var request = new UpdateCategoryRequest
|
||||
{
|
||||
Id = 1,
|
||||
Name = "相同名称"
|
||||
};
|
||||
_categoryRepository.GetByIdAsync(1).Returns(existingCategory);
|
||||
_categoryRepository.UpdateAsync(Arg.Any<TransactionCategory>()).Returns(true);
|
||||
|
||||
// Act
|
||||
await _application.UpdateAsync(request);
|
||||
|
||||
// Assert
|
||||
await _transactionRepository.DidNotReceive().UpdateCategoryNameAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<TransactionType>());
|
||||
await _budgetRepository.DidNotReceive().UpdateBudgetCategoryNameAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<TransactionType>());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DeleteAsync Tests
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteAsync_未被使用的分类_应成功删除()
|
||||
{
|
||||
// Arrange
|
||||
_categoryRepository.IsCategoryInUseAsync(1).Returns(false);
|
||||
_categoryRepository.DeleteAsync(1).Returns(true);
|
||||
|
||||
// Act
|
||||
await _application.DeleteAsync(1);
|
||||
|
||||
// Assert
|
||||
await _categoryRepository.Received(1).DeleteAsync(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteAsync_已被使用的分类_应抛出ValidationException()
|
||||
{
|
||||
// Arrange
|
||||
_categoryRepository.IsCategoryInUseAsync(1).Returns(true);
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<ValidationException>(() => _application.DeleteAsync(1));
|
||||
exception.Message.Should().Contain("已被使用");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteAsync_删除失败_应抛出BusinessException()
|
||||
{
|
||||
// Arrange
|
||||
_categoryRepository.IsCategoryInUseAsync(1).Returns(false);
|
||||
_categoryRepository.DeleteAsync(1).Returns(false);
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<BusinessException>(() => _application.DeleteAsync(1));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region BatchCreateAsync Tests
|
||||
|
||||
[Fact]
|
||||
public async Task BatchCreateAsync_有效请求列表_应返回创建数量()
|
||||
{
|
||||
// Arrange
|
||||
var requests = new List<CreateCategoryRequest>
|
||||
{
|
||||
new() { Name = "分类1", Type = TransactionType.Expense },
|
||||
new() { Name = "分类2", Type = TransactionType.Expense }
|
||||
};
|
||||
_categoryRepository.AddRangeAsync(Arg.Any<List<TransactionCategory>>()).Returns(true);
|
||||
|
||||
// Act
|
||||
var result = await _application.BatchCreateAsync(requests);
|
||||
|
||||
// Assert
|
||||
result.Should().Be(2);
|
||||
await _categoryRepository.Received(1).AddRangeAsync(Arg.Is<List<TransactionCategory>>(
|
||||
list => list.Count == 2
|
||||
));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BatchCreateAsync_Repository添加失败_应抛出BusinessException()
|
||||
{
|
||||
// Arrange
|
||||
var requests = new List<CreateCategoryRequest>
|
||||
{
|
||||
new() { Name = "分类1", Type = TransactionType.Expense }
|
||||
};
|
||||
_categoryRepository.AddRangeAsync(Arg.Any<List<TransactionCategory>>()).Returns(false);
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<BusinessException>(() => _application.BatchCreateAsync(requests));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region UpdateSelectedIconAsync Tests
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateSelectedIconAsync_有效索引_应更新选中图标()
|
||||
{
|
||||
// Arrange
|
||||
var category = new TransactionCategory
|
||||
{
|
||||
Id = 1,
|
||||
Name = "测试",
|
||||
Type = TransactionType.Expense,
|
||||
Icon = """["<svg>icon1</svg>","<svg>icon2</svg>","<svg>icon3</svg>"]"""
|
||||
};
|
||||
var request = new UpdateSelectedIconRequest
|
||||
{
|
||||
CategoryId = 1,
|
||||
SelectedIndex = 2
|
||||
};
|
||||
_categoryRepository.GetByIdAsync(1).Returns(category);
|
||||
_categoryRepository.UpdateAsync(Arg.Any<TransactionCategory>()).Returns(true);
|
||||
|
||||
// Act
|
||||
await _application.UpdateSelectedIconAsync(request);
|
||||
|
||||
// Assert
|
||||
await _categoryRepository.Received(1).UpdateAsync(Arg.Is<TransactionCategory>(
|
||||
c => c.Id == 1 && c.Icon != null && c.Icon.Contains("icon3")
|
||||
));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateSelectedIconAsync_分类不存在_应抛出NotFoundException()
|
||||
{
|
||||
// Arrange
|
||||
var request = new UpdateSelectedIconRequest
|
||||
{
|
||||
CategoryId = 999,
|
||||
SelectedIndex = 0
|
||||
};
|
||||
_categoryRepository.GetByIdAsync(999).Returns((TransactionCategory?)null);
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => _application.UpdateSelectedIconAsync(request));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateSelectedIconAsync_分类无图标_应抛出ValidationException()
|
||||
{
|
||||
// Arrange
|
||||
var category = new TransactionCategory
|
||||
{
|
||||
Id = 1,
|
||||
Name = "测试",
|
||||
Type = TransactionType.Expense,
|
||||
Icon = null
|
||||
};
|
||||
var request = new UpdateSelectedIconRequest
|
||||
{
|
||||
CategoryId = 1,
|
||||
SelectedIndex = 0
|
||||
};
|
||||
_categoryRepository.GetByIdAsync(1).Returns(category);
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<ValidationException>(() => _application.UpdateSelectedIconAsync(request));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateSelectedIconAsync_索引超出范围_应抛出ValidationException()
|
||||
{
|
||||
// Arrange
|
||||
var category = new TransactionCategory
|
||||
{
|
||||
Id = 1,
|
||||
Name = "测试",
|
||||
Type = TransactionType.Expense,
|
||||
Icon = """["<svg>icon1</svg>"]"""
|
||||
};
|
||||
var request = new UpdateSelectedIconRequest
|
||||
{
|
||||
CategoryId = 1,
|
||||
SelectedIndex = 5
|
||||
};
|
||||
_categoryRepository.GetByIdAsync(1).Returns(category);
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<ValidationException>(() => _application.UpdateSelectedIconAsync(request));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user