using NSubstitute; using Service.AI; using Service.Transaction; using System.Text.Json; namespace WebApi.Test.Service; /// /// SmartHandleService 单元测试 /// public class SmartHandleServiceTest : BaseTest { private readonly IOpenAiService _openAiService; private readonly ITransactionRecordRepository _transactionRecordRepository; private readonly ITransactionStatisticsService _transactionStatisticsService; private readonly ITextSegmentService _textSegmentService; private readonly ILogger _logger; private readonly ITransactionCategoryRepository _categoryRepository; private readonly IConfigService _configService; private readonly IClassificationIconPromptProvider _iconPromptProvider; private readonly SmartHandleService _service; public SmartHandleServiceTest() { _openAiService = Substitute.For(); _transactionRecordRepository = Substitute.For(); _transactionStatisticsService = Substitute.For(); _textSegmentService = Substitute.For(); _logger = Substitute.For>(); _categoryRepository = Substitute.For(); _configService = Substitute.For(); _iconPromptProvider = Substitute.For(); _service = new SmartHandleService( _transactionRecordRepository, _transactionStatisticsService, _textSegmentService, _logger, _categoryRepository, _openAiService, _configService, _iconPromptProvider ); } #region GenerateSingleCategoryIconAsync Tests [Fact] public async Task GenerateSingleCategoryIconAsync_AI返回带Markdown标记的JSON_应成功解析() { // Arrange const string categoryName = "餐饮"; const TransactionType categoryType = TransactionType.Expense; const string aiResponseWithMarkdown = """ ```json [""] ``` """; _iconPromptProvider.GetSingleIconPrompt(categoryName, categoryType).Returns("system prompt"); _openAiService.ChatAsync(Arg.Any(), Arg.Any(), Arg.Any()) .Returns(aiResponseWithMarkdown); // Act var result = await _service.GenerateSingleCategoryIconAsync(categoryName, categoryType); // Assert result.Should().NotBeNull(); result.Should().Contain(""] """; _iconPromptProvider.GetSingleIconPrompt(categoryName, categoryType).Returns("system prompt"); _openAiService.ChatAsync(Arg.Any(), Arg.Any(), Arg.Any()) .Returns(aiResponsePureJson); // Act var result = await _service.GenerateSingleCategoryIconAsync(categoryName, categoryType); // Assert result.Should().NotBeNull(); result.Should().Contain("(), Arg.Any(), Arg.Any()) .Returns((string?)null); // Act var result = await _service.GenerateSingleCategoryIconAsync(categoryName, categoryType); // Assert result.Should().BeNull(); } [Fact] public async Task GenerateSingleCategoryIconAsync_AI返回无效JSON_应返回null() { // Arrange const string categoryName = "娱乐"; const TransactionType categoryType = TransactionType.Expense; const string invalidJsonResponse = "这是一个文本响应,不是 JSON"; _iconPromptProvider.GetSingleIconPrompt(categoryName, categoryType).Returns("system prompt"); _openAiService.ChatAsync(Arg.Any(), Arg.Any(), Arg.Any()) .Returns(invalidJsonResponse); // Act var result = await _service.GenerateSingleCategoryIconAsync(categoryName, categoryType); // Assert result.Should().BeNull(); } #endregion #region GenerateCategoryIconsAsync Tests [Fact] public async Task GenerateCategoryIconsAsync_AI返回带Markdown标记的JSON_应成功解析() { // Arrange const string categoryName = "餐饮"; const TransactionType categoryType = TransactionType.Expense; const int iconCount = 5; const string aiResponseWithMarkdown = """ ```json [ "", "", "", "", "" ] ``` """; _iconPromptProvider.GetPrompt(categoryName, categoryType).Returns("system prompt"); _openAiService.ChatAsync(Arg.Any(), Arg.Any(), Arg.Any()) .Returns(aiResponseWithMarkdown); // Act var result = await _service.GenerateCategoryIconsAsync(categoryName, categoryType, iconCount); // Assert result.Should().NotBeNull(); result.Should().HaveCount(iconCount); result.Should().AllSatisfy(icon => icon.Should().Contain("", "", "", "", "" ] """; _iconPromptProvider.GetPrompt(categoryName, categoryType).Returns("system prompt"); _openAiService.ChatAsync(Arg.Any(), Arg.Any(), Arg.Any()) .Returns(aiResponsePureJson); // Act var result = await _service.GenerateCategoryIconsAsync(categoryName, categoryType, iconCount); // Assert result.Should().NotBeNull(); result.Should().HaveCount(iconCount); } [Fact] public async Task GenerateCategoryIconsAsync_AI返回空响应_应返回null() { // Arrange const string categoryName = "购物"; const TransactionType categoryType = TransactionType.Expense; const int iconCount = 5; _iconPromptProvider.GetPrompt(categoryName, categoryType).Returns("system prompt"); _openAiService.ChatAsync(Arg.Any(), Arg.Any(), Arg.Any()) .Returns((string?)null); // Act var result = await _service.GenerateCategoryIconsAsync(categoryName, categoryType, iconCount); // Assert result.Should().BeNull(); } [Fact] public async Task GenerateCategoryIconsAsync_AI返回图标数量不正确_应返回null() { // Arrange const string categoryName = "娱乐"; const TransactionType categoryType = TransactionType.Expense; const int iconCount = 5; const string aiResponseWith3Icons = """ [ "", "", "" ] """; _iconPromptProvider.GetPrompt(categoryName, categoryType).Returns("system prompt"); _openAiService.ChatAsync(Arg.Any(), Arg.Any(), Arg.Any()) .Returns(aiResponseWith3Icons); // Act var result = await _service.GenerateCategoryIconsAsync(categoryName, categoryType, iconCount); // Assert result.Should().BeNull(); } [Fact] public async Task GenerateCategoryIconsAsync_AI返回无效JSON_应返回null() { // Arrange const string categoryName = "其他"; const TransactionType categoryType = TransactionType.Expense; const int iconCount = 5; const string invalidJsonResponse = "这是一个文本响应,不是 JSON"; _iconPromptProvider.GetPrompt(categoryName, categoryType).Returns("system prompt"); _openAiService.ChatAsync(Arg.Any(), Arg.Any(), Arg.Any()) .Returns(invalidJsonResponse); // Act var result = await _service.GenerateCategoryIconsAsync(categoryName, categoryType, iconCount); // Assert result.Should().BeNull(); } #endregion }