fix
This commit is contained in:
263
WebApi.Test/Service/SmartHandleServiceTest.cs
Normal file
263
WebApi.Test/Service/SmartHandleServiceTest.cs
Normal file
@@ -0,0 +1,263 @@
|
||||
using NSubstitute;
|
||||
using Service.AI;
|
||||
using Service.Transaction;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace WebApi.Test.Service;
|
||||
|
||||
/// <summary>
|
||||
/// SmartHandleService 单元测试
|
||||
/// </summary>
|
||||
public class SmartHandleServiceTest : BaseTest
|
||||
{
|
||||
private readonly IOpenAiService _openAiService;
|
||||
private readonly ITransactionRecordRepository _transactionRecordRepository;
|
||||
private readonly ITransactionStatisticsService _transactionStatisticsService;
|
||||
private readonly ITextSegmentService _textSegmentService;
|
||||
private readonly ILogger<SmartHandleService> _logger;
|
||||
private readonly ITransactionCategoryRepository _categoryRepository;
|
||||
private readonly IConfigService _configService;
|
||||
private readonly IClassificationIconPromptProvider _iconPromptProvider;
|
||||
private readonly SmartHandleService _service;
|
||||
|
||||
public SmartHandleServiceTest()
|
||||
{
|
||||
_openAiService = Substitute.For<IOpenAiService>();
|
||||
_transactionRecordRepository = Substitute.For<ITransactionRecordRepository>();
|
||||
_transactionStatisticsService = Substitute.For<ITransactionStatisticsService>();
|
||||
_textSegmentService = Substitute.For<ITextSegmentService>();
|
||||
_logger = Substitute.For<ILogger<SmartHandleService>>();
|
||||
_categoryRepository = Substitute.For<ITransactionCategoryRepository>();
|
||||
_configService = Substitute.For<IConfigService>();
|
||||
_iconPromptProvider = Substitute.For<IClassificationIconPromptProvider>();
|
||||
|
||||
_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
|
||||
["<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\"><circle cx=\"12\" cy=\"12\" r=\"10\" fill=\"#ff6b6b\"/></svg>"]
|
||||
```
|
||||
""";
|
||||
|
||||
_iconPromptProvider.GetSingleIconPrompt(categoryName, categoryType).Returns("system prompt");
|
||||
_openAiService.ChatAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<int>())
|
||||
.Returns(aiResponseWithMarkdown);
|
||||
|
||||
// Act
|
||||
var result = await _service.GenerateSingleCategoryIconAsync(categoryName, categoryType);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
result.Should().Contain("<svg");
|
||||
result.Should().Contain("viewBox=\"0 0 24 24\"");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GenerateSingleCategoryIconAsync_AI返回纯JSON_应成功解析()
|
||||
{
|
||||
// Arrange
|
||||
const string categoryName = "交通";
|
||||
const TransactionType categoryType = TransactionType.Expense;
|
||||
const string aiResponsePureJson = """
|
||||
["<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><rect x=\"5\" y=\"5\" width=\"14\" height=\"14\" fill=\"#4a90e2\"/></svg>"]
|
||||
""";
|
||||
|
||||
_iconPromptProvider.GetSingleIconPrompt(categoryName, categoryType).Returns("system prompt");
|
||||
_openAiService.ChatAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<int>())
|
||||
.Returns(aiResponsePureJson);
|
||||
|
||||
// Act
|
||||
var result = await _service.GenerateSingleCategoryIconAsync(categoryName, categoryType);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
result.Should().Contain("<svg");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GenerateSingleCategoryIconAsync_AI返回空响应_应返回null()
|
||||
{
|
||||
// Arrange
|
||||
const string categoryName = "购物";
|
||||
const TransactionType categoryType = TransactionType.Expense;
|
||||
|
||||
_iconPromptProvider.GetSingleIconPrompt(categoryName, categoryType).Returns("system prompt");
|
||||
_openAiService.ChatAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<int>())
|
||||
.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<string>(), Arg.Any<string>(), Arg.Any<int>())
|
||||
.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
|
||||
[
|
||||
"<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><circle cx=\"12\" cy=\"12\" r=\"10\" fill=\"#ff6b6b\"/></svg>",
|
||||
"<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><rect x=\"6\" y=\"6\" width=\"12\" height=\"12\" fill=\"#4ecdc4\"/></svg>",
|
||||
"<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path d=\"M12 2L2 22h20L12 2z\" fill=\"#f5a623\"/></svg>",
|
||||
"<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><ellipse cx=\"12\" cy=\"12\" rx=\"10\" ry=\"6\" fill=\"#6c5ce7\"/></svg>",
|
||||
"<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><polygon points=\"12,2 22,8 22,16 12,22 2,16 2,8\" fill=\"#a29bfe\"/></svg>"
|
||||
]
|
||||
```
|
||||
""";
|
||||
|
||||
_iconPromptProvider.GetPrompt(categoryName, categoryType).Returns("system prompt");
|
||||
_openAiService.ChatAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<int>())
|
||||
.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("<svg"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GenerateCategoryIconsAsync_AI返回纯JSON_应成功解析()
|
||||
{
|
||||
// Arrange
|
||||
const string categoryName = "交通";
|
||||
const TransactionType categoryType = TransactionType.Expense;
|
||||
const int iconCount = 5;
|
||||
const string aiResponsePureJson = """
|
||||
[
|
||||
"<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><circle cx=\"12\" cy=\"12\" r=\"10\" fill=\"#ff6b6b\"/></svg>",
|
||||
"<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><rect x=\"6\" y=\"6\" width=\"12\" height=\"12\" fill=\"#4ecdc4\"/></svg>",
|
||||
"<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path d=\"M12 2L2 22h20L12 2z\" fill=\"#f5a623\"/></svg>",
|
||||
"<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><ellipse cx=\"12\" cy=\"12\" rx=\"10\" ry=\"6\" fill=\"#6c5ce7\"/></svg>",
|
||||
"<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><polygon points=\"12,2 22,8 22,16 12,22 2,16 2,8\" fill=\"#a29bfe\"/></svg>"
|
||||
]
|
||||
""";
|
||||
|
||||
_iconPromptProvider.GetPrompt(categoryName, categoryType).Returns("system prompt");
|
||||
_openAiService.ChatAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<int>())
|
||||
.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<string>(), Arg.Any<string>(), Arg.Any<int>())
|
||||
.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 = """
|
||||
[
|
||||
"<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><circle cx=\"12\" cy=\"12\" r=\"10\" fill=\"#ff6b6b\"/></svg>",
|
||||
"<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><rect x=\"6\" y=\"6\" width=\"12\" height=\"12\" fill=\"#4ecdc4\"/></svg>",
|
||||
"<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path d=\"M12 2L2 22h20L12 2z\" fill=\"#f5a623\"/></svg>"
|
||||
]
|
||||
""";
|
||||
|
||||
_iconPromptProvider.GetPrompt(categoryName, categoryType).Returns("system prompt");
|
||||
_openAiService.ChatAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<int>())
|
||||
.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<string>(), Arg.Any<string>(), Arg.Any<int>())
|
||||
.Returns(invalidJsonResponse);
|
||||
|
||||
// Act
|
||||
var result = await _service.GenerateCategoryIconsAsync(categoryName, categoryType, iconCount);
|
||||
|
||||
// Assert
|
||||
result.Should().BeNull();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user