fix
This commit is contained in:
467
WebApi.Test/Service/ClassificationIconPromptProviderTest.cs
Normal file
467
WebApi.Test/Service/ClassificationIconPromptProviderTest.cs
Normal file
@@ -0,0 +1,467 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
using Service.AI;
|
||||
using Service.AppSettingModel;
|
||||
|
||||
namespace WebApi.Test.Service;
|
||||
|
||||
/// <summary>
|
||||
/// ClassificationIconPromptProvider 单元测试
|
||||
/// </summary>
|
||||
public class ClassificationIconPromptProviderTest : BaseTest
|
||||
{
|
||||
private readonly ClassificationIconPromptProvider _provider;
|
||||
private readonly ILogger<ClassificationIconPromptProvider> _logger;
|
||||
private readonly IOptions<IconPromptSettings> _config;
|
||||
|
||||
public ClassificationIconPromptProviderTest()
|
||||
{
|
||||
_logger = Substitute.For<ILogger<ClassificationIconPromptProvider>>();
|
||||
_config = Options.Create(new IconPromptSettings
|
||||
{
|
||||
EnableNewPrompt = true,
|
||||
GrayScaleRatio = 1.0,
|
||||
StyleStrength = 0.7,
|
||||
ColorScheme = "single-color"
|
||||
});
|
||||
_provider = new ClassificationIconPromptProvider(_logger, _config);
|
||||
}
|
||||
|
||||
#region GetPrompt Tests
|
||||
|
||||
[Fact]
|
||||
public void GetPrompt_支出分类_应包含正确的上下文信息()
|
||||
{
|
||||
// Arrange
|
||||
const string categoryName = "餐饮";
|
||||
const TransactionType categoryType = TransactionType.Expense;
|
||||
|
||||
// Act
|
||||
var prompt = _provider.GetPrompt(categoryName, categoryType);
|
||||
|
||||
// Assert
|
||||
prompt.Should().Contain(categoryName);
|
||||
prompt.Should().Contain("支出");
|
||||
prompt.Should().Contain("分类名称");
|
||||
prompt.Should().Contain("分类类型");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetPrompt_收入分类_应包含正确的上下文信息()
|
||||
{
|
||||
// Arrange
|
||||
const string categoryName = "工资";
|
||||
const TransactionType categoryType = TransactionType.Income;
|
||||
|
||||
// Act
|
||||
var prompt = _provider.GetPrompt(categoryName, categoryType);
|
||||
|
||||
// Assert
|
||||
prompt.Should().Contain(categoryName);
|
||||
prompt.Should().Contain("收入");
|
||||
prompt.Should().Contain("分类名称");
|
||||
prompt.Should().Contain("分类类型");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetPrompt_不计入收支分类_应包含正确的上下文信息()
|
||||
{
|
||||
// Arrange
|
||||
const string categoryName = "转账";
|
||||
const TransactionType categoryType = TransactionType.None;
|
||||
|
||||
// Act
|
||||
var prompt = _provider.GetPrompt(categoryName, categoryType);
|
||||
|
||||
// Assert
|
||||
prompt.Should().Contain(categoryName);
|
||||
prompt.Should().Contain("不计入收支");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetPrompt_应包含设计要求()
|
||||
{
|
||||
// Arrange
|
||||
const string categoryName = "交通";
|
||||
const TransactionType categoryType = TransactionType.Expense;
|
||||
|
||||
// Act
|
||||
var prompt = _provider.GetPrompt(categoryName, categoryType);
|
||||
|
||||
// Assert
|
||||
prompt.Should().Contain("24x24");
|
||||
prompt.Should().Contain("viewBox=\"0 0 24 24\"");
|
||||
prompt.Should().Contain("扁平化、单色、极致简约");
|
||||
prompt.Should().Contain("避免渐变和阴影");
|
||||
prompt.Should().Contain("保持线条简洁");
|
||||
prompt.Should().Contain("高对比度");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetPrompt_应包含SVG格式要求()
|
||||
{
|
||||
// Arrange
|
||||
const string categoryName = "购物";
|
||||
const TransactionType categoryType = TransactionType.Expense;
|
||||
|
||||
// Act
|
||||
var prompt = _provider.GetPrompt(categoryName, categoryType);
|
||||
|
||||
// Assert
|
||||
prompt.Should().Contain("JSON 数组格式");
|
||||
prompt.Should().Contain("5 个完整的 SVG 字符串");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetPrompt_应包含返回格式说明()
|
||||
{
|
||||
// Arrange
|
||||
const string categoryName = "娱乐";
|
||||
const TransactionType categoryType = TransactionType.Expense;
|
||||
|
||||
// Act
|
||||
var prompt = _provider.GetPrompt(categoryName, categoryType);
|
||||
|
||||
// Assert
|
||||
prompt.Should().Contain("[\"<svg>...</svg>\", \"<svg>...</svg>\", \"<svg>...</svg>\", \"<svg>...</svg>\", \"<svg>...</svg>\"]");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetPrompt_多次调用_应生成相同的提示词结构()
|
||||
{
|
||||
// Arrange
|
||||
const string categoryName = "餐饮";
|
||||
const TransactionType categoryType = TransactionType.Expense;
|
||||
|
||||
// Act
|
||||
var prompt1 = _provider.GetPrompt(categoryName, categoryType);
|
||||
var prompt2 = _provider.GetPrompt(categoryName, categoryType);
|
||||
|
||||
// Assert
|
||||
prompt1.Should().Be(prompt2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetPrompt_不同分类_应生成不同的提示词()
|
||||
{
|
||||
// Arrange
|
||||
const string categoryName1 = "餐饮";
|
||||
const string categoryName2 = "交通";
|
||||
const TransactionType categoryType = TransactionType.Expense;
|
||||
|
||||
// Act
|
||||
var prompt1 = _provider.GetPrompt(categoryName1, categoryType);
|
||||
var prompt2 = _provider.GetPrompt(categoryName2, categoryType);
|
||||
|
||||
// Assert
|
||||
prompt1.Should().NotBe(prompt2);
|
||||
prompt1.Should().Contain(categoryName1);
|
||||
prompt2.Should().Contain(categoryName2);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region GetSingleIconPrompt Tests
|
||||
|
||||
[Fact]
|
||||
public void GetSingleIconPrompt_支出分类_应包含正确的上下文信息()
|
||||
{
|
||||
// Arrange
|
||||
const string categoryName = "餐饮";
|
||||
const TransactionType categoryType = TransactionType.Expense;
|
||||
|
||||
// Act
|
||||
var prompt = _provider.GetSingleIconPrompt(categoryName, categoryType);
|
||||
|
||||
// Assert
|
||||
prompt.Should().Contain(categoryName);
|
||||
prompt.Should().Contain("支出");
|
||||
prompt.Should().Contain("分类名称");
|
||||
prompt.Should().Contain("分类类型");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetSingleIconPrompt_应包含单个图标要求()
|
||||
{
|
||||
// Arrange
|
||||
const string categoryName = "交通";
|
||||
const TransactionType categoryType = TransactionType.Expense;
|
||||
|
||||
// Act
|
||||
var prompt = _provider.GetSingleIconPrompt(categoryName, categoryType);
|
||||
|
||||
// Assert
|
||||
prompt.Should().Contain("生成 1 个");
|
||||
prompt.Should().Contain("24x24");
|
||||
prompt.Should().Contain("viewBox=\"0 0 24 24\"");
|
||||
prompt.Should().Contain("扁平化、单色、极致简约");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetSingleIconPrompt_应包含返回格式说明()
|
||||
{
|
||||
// Arrange
|
||||
const string categoryName = "购物";
|
||||
const TransactionType categoryType = TransactionType.Expense;
|
||||
|
||||
// Act
|
||||
var prompt = _provider.GetSingleIconPrompt(categoryName, categoryType);
|
||||
|
||||
// Assert
|
||||
prompt.Should().Contain("JSON 数组格式");
|
||||
prompt.Should().Contain("1 个完整的 SVG 字符串");
|
||||
prompt.Should().Contain("[\"<svg>...</svg>\"]");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetSingleIconPrompt_应与GetPrompt生成不同的提示词()
|
||||
{
|
||||
// Arrange
|
||||
const string categoryName = "餐饮";
|
||||
const TransactionType categoryType = TransactionType.Expense;
|
||||
|
||||
// Act
|
||||
var singlePrompt = _provider.GetSingleIconPrompt(categoryName, categoryType);
|
||||
var multiPrompt = _provider.GetPrompt(categoryName, categoryType);
|
||||
|
||||
// Assert
|
||||
singlePrompt.Should().NotBe(multiPrompt);
|
||||
singlePrompt.Should().Contain("生成 1 个");
|
||||
multiPrompt.Should().Contain("生成 5 个");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region PromptTemplateEngine Tests (3.1)
|
||||
|
||||
[Fact]
|
||||
public void PromptTemplateEngine_ReplacePlaceholders_应正确替换单个占位符()
|
||||
{
|
||||
// Arrange
|
||||
var template = "分类名称:{{category_name}},类型:{{category_type}}";
|
||||
var placeholders = new Dictionary<string, string>
|
||||
{
|
||||
["category_name"] = "餐饮",
|
||||
["category_type"] = "支出"
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = PromptTemplateEngine.ReplacePlaceholders(template, placeholders);
|
||||
|
||||
// Assert
|
||||
result.Should().Be("分类名称:餐饮,类型:支出");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PromptTemplateEngine_ReplacePlaceholders_应正确替换多个占位符()
|
||||
{
|
||||
// Arrange
|
||||
var template = "颜色方案:{{color_scheme}},简约度:{{style_strength}}";
|
||||
var placeholders = new Dictionary<string, string>
|
||||
{
|
||||
["color_scheme"] = "single-color",
|
||||
["style_strength"] = "0.8 - 高度简约"
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = PromptTemplateEngine.ReplacePlaceholders(template, placeholders);
|
||||
|
||||
// Assert
|
||||
result.Should().Be("颜色方案:single-color,简约度:0.8 - 高度简约");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PromptTemplateEngine_ReplacePlaceholders_空模板应返回原字符串()
|
||||
{
|
||||
// Arrange
|
||||
var template = string.Empty;
|
||||
var placeholders = new Dictionary<string, string>();
|
||||
|
||||
// Act
|
||||
var result = PromptTemplateEngine.ReplacePlaceholders(template, placeholders);
|
||||
|
||||
// Assert
|
||||
result.Should().Be(string.Empty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PromptTemplateEngine_ReplacePlaceholders_空占位符字典应返回原字符串()
|
||||
{
|
||||
// Arrange
|
||||
var template = "测试模板字符串";
|
||||
var placeholders = new Dictionary<string, string>();
|
||||
|
||||
// Act
|
||||
var result = PromptTemplateEngine.ReplacePlaceholders(template, placeholders);
|
||||
|
||||
// Assert
|
||||
result.Should().Be("测试模板字符串");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 新版提示词生成逻辑测试 (3.2)
|
||||
|
||||
[Fact]
|
||||
public void GetPrompt_新版提示词_应包含简约风格要求()
|
||||
{
|
||||
// Arrange
|
||||
const string categoryName = "餐饮";
|
||||
const TransactionType categoryType = TransactionType.Expense;
|
||||
|
||||
// Act
|
||||
var prompt = _provider.GetPrompt(categoryName, categoryType);
|
||||
|
||||
// Assert
|
||||
prompt.Should().Contain("扁平化、单色、极致简约");
|
||||
prompt.Should().Contain("避免渐变和阴影");
|
||||
prompt.Should().Contain("保持线条简洁");
|
||||
prompt.Should().Contain("高对比度");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetPrompt_新版提示词_应包含颜色方案占位符替换()
|
||||
{
|
||||
// Arrange
|
||||
const string categoryName = "交通";
|
||||
const TransactionType categoryType = TransactionType.Expense;
|
||||
|
||||
// Act
|
||||
var prompt = _provider.GetPrompt(categoryName, categoryType);
|
||||
|
||||
// Assert
|
||||
prompt.Should().Contain("颜色方案:single-color");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetPrompt_新版提示词_应包含风格强度描述()
|
||||
{
|
||||
// Arrange
|
||||
const string categoryName = "娱乐";
|
||||
const TransactionType categoryType = TransactionType.Expense;
|
||||
|
||||
// Act
|
||||
var prompt = _provider.GetPrompt(categoryName, categoryType);
|
||||
|
||||
// Assert
|
||||
prompt.Should().Contain("简约度:0.7");
|
||||
prompt.Should().Contain("高度简约(去除所有装饰)");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 抽象分类特殊处理测试 (3.3)
|
||||
|
||||
[Fact]
|
||||
public void IconPromptSettings_初始化_应包含默认抽象分类配置()
|
||||
{
|
||||
// Arrange & Act
|
||||
var config = new IconPromptSettings();
|
||||
|
||||
// Assert
|
||||
config.AbstractCategories.Should().ContainKey("其他");
|
||||
config.AbstractCategories.Should().ContainKey("通用");
|
||||
config.AbstractCategories.Should().ContainKey("未知");
|
||||
config.AbstractCategories["其他"].GeometryShape.Should().Be("circle");
|
||||
config.AbstractCategories["其他"].ColorCode.Should().Be("#9E9E9E");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IconPromptSettings_抽象分类配置_可以自定义()
|
||||
{
|
||||
// Arrange
|
||||
var config = new IconPromptSettings();
|
||||
|
||||
// Act
|
||||
config.AbstractCategories["自定义分类"] = new AbstractCategoryConfig
|
||||
{
|
||||
GeometryShape = "hexagon",
|
||||
ColorCode = "#FF5722"
|
||||
};
|
||||
|
||||
// Assert
|
||||
config.AbstractCategories["自定义分类"].GeometryShape.Should().Be("hexagon");
|
||||
config.AbstractCategories["自定义分类"].ColorCode.Should().Be("#FF5722");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 风格强度参数注入测试 (3.4)
|
||||
|
||||
[Fact]
|
||||
public void PromptTemplateEngine_ReplaceForIconGeneration_风格强度09_应显示极度简约()
|
||||
{
|
||||
// Arrange
|
||||
var template = "简约度:{{style_strength}}";
|
||||
|
||||
// Act
|
||||
var result = PromptTemplateEngine.ReplaceForIconGeneration(
|
||||
template,
|
||||
"餐饮",
|
||||
"支出",
|
||||
"single-color",
|
||||
0.95);
|
||||
|
||||
// Assert
|
||||
result.Should().Contain("0.9");
|
||||
result.Should().Contain("极度简约(仅保留最核心元素)");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PromptTemplateEngine_ReplaceForIconGeneration_风格强度07_应显示高度简约()
|
||||
{
|
||||
// Arrange
|
||||
var template = "简约度:{{style_strength}}";
|
||||
|
||||
// Act
|
||||
var result = PromptTemplateEngine.ReplaceForIconGeneration(
|
||||
template,
|
||||
"交通",
|
||||
"支出",
|
||||
"single-color",
|
||||
0.7);
|
||||
|
||||
// Assert
|
||||
result.Should().Contain("0.7");
|
||||
result.Should().Contain("高度简约(去除所有装饰)");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PromptTemplateEngine_ReplaceForIconGeneration_风格强度05_应显示简约()
|
||||
{
|
||||
// Arrange
|
||||
var template = "简约度:{{style_strength}}";
|
||||
|
||||
// Act
|
||||
var result = PromptTemplateEngine.ReplaceForIconGeneration(
|
||||
template,
|
||||
"购物",
|
||||
"支出",
|
||||
"single-color",
|
||||
0.5);
|
||||
|
||||
// Assert
|
||||
result.Should().Contain("0.5");
|
||||
result.Should().Contain("简约(保留必要细节)");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PromptTemplateEngine_ReplaceForIconGeneration_风格强度03_应显示适中()
|
||||
{
|
||||
// Arrange
|
||||
var template = "简约度:{{style_strength}}";
|
||||
|
||||
// Act
|
||||
var result = PromptTemplateEngine.ReplaceForIconGeneration(
|
||||
template,
|
||||
"娱乐",
|
||||
"支出",
|
||||
"single-color",
|
||||
0.3);
|
||||
|
||||
// Assert
|
||||
result.Should().Contain("0.3");
|
||||
result.Should().Contain("适中");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
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