2026-02-10 17:49:19 +08:00
|
|
|
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 TransactionCategoryApplication _application;
|
|
|
|
|
|
|
|
|
|
public TransactionCategoryApplicationTest()
|
|
|
|
|
{
|
|
|
|
|
_categoryRepository = Substitute.For<ITransactionCategoryRepository>();
|
|
|
|
|
_transactionRepository = Substitute.For<ITransactionRecordRepository>();
|
|
|
|
|
_budgetRepository = Substitute.For<IBudgetRepository>();
|
|
|
|
|
_smartHandleService = Substitute.For<ISmartHandleService>();
|
|
|
|
|
_application = new TransactionCategoryApplication(
|
|
|
|
|
_categoryRepository,
|
|
|
|
|
_transactionRepository,
|
|
|
|
|
_budgetRepository,
|
2026-02-11 13:00:01 +08:00
|
|
|
_smartHandleService);
|
2026-02-10 17:49:19 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#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
|
2026-02-15 10:10:28 +08:00
|
|
|
|
|
|
|
|
#region DeleteIconAsync Tests
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task DeleteIconAsync_存在的分类_应成功删除图标()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var category = new TransactionCategory
|
|
|
|
|
{
|
|
|
|
|
Id = 1,
|
|
|
|
|
Name = "测试",
|
|
|
|
|
Type = TransactionType.Expense,
|
|
|
|
|
Icon = """["<svg>icon1</svg>","<svg>icon2</svg>"]"""
|
|
|
|
|
};
|
|
|
|
|
_categoryRepository.GetByIdAsync(1).Returns(category);
|
|
|
|
|
_categoryRepository.UpdateAsync(Arg.Any<TransactionCategory>()).Returns(true);
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
await _application.DeleteIconAsync(1);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
await _categoryRepository.Received(1).UpdateAsync(Arg.Is<TransactionCategory>(
|
|
|
|
|
c => c.Id == 1 && c.Icon == null
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task DeleteIconAsync_分类不存在_应抛出NotFoundException()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
_categoryRepository.GetByIdAsync(999).Returns((TransactionCategory?)null);
|
|
|
|
|
|
|
|
|
|
// Act & Assert
|
|
|
|
|
await Assert.ThrowsAsync<NotFoundException>(() => _application.DeleteIconAsync(999));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task DeleteIconAsync_更新失败_应抛出BusinessException()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var category = new TransactionCategory
|
|
|
|
|
{
|
|
|
|
|
Id = 1,
|
|
|
|
|
Name = "测试",
|
|
|
|
|
Type = TransactionType.Expense,
|
|
|
|
|
Icon = """["<svg>icon1</svg>"]"""
|
|
|
|
|
};
|
|
|
|
|
_categoryRepository.GetByIdAsync(1).Returns(category);
|
|
|
|
|
_categoryRepository.UpdateAsync(Arg.Any<TransactionCategory>()).Returns(false);
|
|
|
|
|
|
|
|
|
|
// Act & Assert
|
|
|
|
|
await Assert.ThrowsAsync<BusinessException>(() => _application.DeleteIconAsync(1));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task DeleteIconAsync_无图标的分类_应成功()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var category = new TransactionCategory
|
|
|
|
|
{
|
|
|
|
|
Id = 1,
|
|
|
|
|
Name = "测试",
|
|
|
|
|
Type = TransactionType.Expense,
|
|
|
|
|
Icon = null
|
|
|
|
|
};
|
|
|
|
|
_categoryRepository.GetByIdAsync(1).Returns(category);
|
|
|
|
|
_categoryRepository.UpdateAsync(Arg.Any<TransactionCategory>()).Returns(true);
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
await _application.DeleteIconAsync(1);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
await _categoryRepository.Received(1).UpdateAsync(Arg.Is<TransactionCategory>(
|
|
|
|
|
c => c.Id == 1 && c.Icon == null
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
2026-02-10 17:49:19 +08:00
|
|
|
}
|