Files
Hua.Todo/test/Hua.Todo.Host.Tests/Meeting/MeetingAiBreakdownTests.cs
T
ShaoHua 4fe0b5a963 feat: 完成云同步、语音控制与多平台扩展基础架构搭建
本次提交完成了项目核心基础架构升级:
1. 新增动态API中间件与权限控制系统,支持匿名/鉴权接口分离
2. 搭建云同步服务体系,包含认证、任务同步、安全策略等核心模块
3. 实现语音控制全链路,从STT/意图解析到命令执行
4. 新增任务类型、附件实体与相关仓储接口
5. 重构前端配置与代理规则,统一后端端口为5057
6. 新增多平台测试项目与CI脚本优化
7. 完善项目文档与代码注释规范

移除了旧版迁移文件与冗余代理配置,调整项目结构适配跨平台部署需求。
2026-06-21 03:26:04 +08:00

433 lines
12 KiB
C#

using System.Reflection;
using Hua.Todo.Application.Services.Meeting;
using Hua.Todo.Application.Services.Meeting.Models;
using Hua.Todo.Core.Entities;
using Xunit;
namespace Hua.Todo.Host.Tests.Meeting;
/// <summary>
/// 工单 03-03 AI 任务拆分服务测试。
/// </summary>
public class MeetingAiBreakdownTests
{
[Fact]
public void MeetingTaskSuggestion_DefaultProperties()
{
var suggestion = new MeetingTaskSuggestion
{
Title = "整理需求文档",
Priority = TaskPriority.High,
Reason = "会议中提及需要在周五前完成"
};
Assert.Equal("整理需求文档", suggestion.Title);
Assert.Equal(TaskPriority.High, suggestion.Priority);
Assert.Equal("会议中提及需要在周五前完成", suggestion.Reason);
}
[Fact]
public void MeetingTaskSuggestion_DefaultValues()
{
var suggestion = new MeetingTaskSuggestion();
Assert.Equal(string.Empty, suggestion.Title);
Assert.Equal(TaskPriority.Medium, suggestion.Priority);
Assert.Equal(string.Empty, suggestion.Reason);
}
[Fact]
public void BreakdownRequest_CanBeCreated()
{
var taskId = Guid.NewGuid();
var request = new BreakdownRequest
{
TaskId = taskId,
Notes = "会议讨论了三个议题"
};
Assert.Equal(taskId, request.TaskId);
Assert.Equal("会议讨论了三个议题", request.Notes);
}
[Fact]
public void BreakdownRequest_NotesCanBeNull()
{
var taskId = Guid.NewGuid();
var request = new BreakdownRequest
{
TaskId = taskId,
Notes = null
};
Assert.Equal(taskId, request.TaskId);
Assert.Null(request.Notes);
}
[Fact]
public void BreakdownResponse_ContainsCorrectData()
{
var suggestions = new List<MeetingTaskSuggestion>
{
new() { Title = "任务A", Priority = TaskPriority.High, Reason = "原因A" },
new() { Title = "任务B", Priority = TaskPriority.Low, Reason = "原因B" }
};
var taskId = Guid.NewGuid();
var response = new BreakdownResponse
{
TaskId = taskId,
Suggestions = suggestions
};
Assert.Equal(taskId, response.TaskId);
Assert.Equal(2, response.Suggestions.Count);
Assert.Equal("任务A", response.Suggestions[0].Title);
}
[Fact]
public void BreakdownResponse_DefaultSuggestionsIsEmpty()
{
var response = new BreakdownResponse();
Assert.NotNull(response.Suggestions);
Assert.Empty(response.Suggestions);
}
[Fact]
public void SubTaskCreateItem_CanBeCreated()
{
var item = new SubTaskCreateItem
{
Title = "子任务标题",
Priority = TaskPriority.High
};
Assert.Equal("子任务标题", item.Title);
Assert.Equal(TaskPriority.High, item.Priority);
}
[Fact]
public void SubTaskCreateItem_DefaultPriorityIsMedium()
{
var item = new SubTaskCreateItem();
Assert.Equal(TaskPriority.Medium, item.Priority);
}
[Fact]
public void ConfirmBreakdownRequest_CanBeCreated()
{
var taskId = Guid.NewGuid();
var request = new ConfirmBreakdownRequest
{
TaskId = taskId,
SubTasks = new List<SubTaskCreateItem>
{
new() { Title = "任务1" },
new() { Title = "任务2", Priority = TaskPriority.High }
}
};
Assert.Equal(taskId, request.TaskId);
Assert.Equal(2, request.SubTasks.Count);
Assert.Equal("任务1", request.SubTasks[0].Title);
Assert.Equal(TaskPriority.High, request.SubTasks[1].Priority);
}
[Fact]
public void ConfirmBreakdownRequest_DefaultSubTasksIsEmpty()
{
var request = new ConfirmBreakdownRequest();
Assert.NotNull(request.SubTasks);
Assert.Empty(request.SubTasks);
}
[Fact]
public void BatchCreateResult_CanBeCreated()
{
var result = new BatchCreateResult
{
CreatedCount = 3,
SubTasks = new List<Application.Models.TaskDto>
{
new() { Id = Guid.NewGuid(), Title = "任务1" },
new() { Id = Guid.NewGuid(), Title = "任务2" },
new() { Id = Guid.NewGuid(), Title = "任务3" }
}
};
Assert.Equal(3, result.CreatedCount);
Assert.Equal(3, result.SubTasks.Count);
Assert.Equal("任务1", result.SubTasks[0].Title);
}
[Fact]
public void BatchCreateResult_DefaultSubTasksIsEmpty()
{
var result = new BatchCreateResult();
Assert.Equal(0, result.CreatedCount);
Assert.NotNull(result.SubTasks);
Assert.Empty(result.SubTasks);
}
/// <summary>
/// 测试 ParseBreakdownJson 解析有效的 LLM JSON 响应。
/// </summary>
[Fact]
public void ParseBreakdownJson_ValidJson_ReturnsSuggestions()
{
string json = """
{
"suggestions": [
{
"title": "整理需求文档",
"priority": 1,
"reason": "会议中决定需要整理"
},
{
"title": "安排评审会议",
"priority": 2,
"reason": "下周前需要完成评审"
}
]
}
""";
var result = MeetingAiBreakdownService.ParseBreakdownJson(json);
Assert.Equal(2, result.Count);
Assert.Equal("整理需求文档", result[0].Title);
Assert.Equal(TaskPriority.Medium, result[0].Priority);
Assert.Equal("会议中决定需要整理", result[0].Reason);
Assert.Equal("安排评审会议", result[1].Title);
Assert.Equal(TaskPriority.High, result[1].Priority);
Assert.Equal("下周前需要完成评审", result[1].Reason);
}
/// <summary>
/// 测试 ParseBreakdownJson 解析被 markdown 代码块包裹的 LLM 响应。
/// </summary>
[Fact]
public void ParseBreakdownJson_MarkdownWrapped_StripsMarkdown()
{
string json = """
```json
{
"suggestions": [
{
"title": "更新项目计划",
"priority": 1,
"reason": "里程碑已调整"
}
]
}
```
""";
var result = MeetingAiBreakdownService.ParseBreakdownJson(json);
Assert.Single(result);
Assert.Equal("更新项目计划", result[0].Title);
}
/// <summary>
/// 测试 ParseBreakdownJson 处理空建议列表。
/// </summary>
[Fact]
public void ParseBreakdownJson_EmptySuggestions_ReturnsEmptyList()
{
string json = """{"suggestions":[]}""";
var result = MeetingAiBreakdownService.ParseBreakdownJson(json);
Assert.Empty(result);
}
/// <summary>
/// 测试 ParseBreakdownJson 过滤标题为空的条目。
/// </summary>
[Fact]
public void ParseBreakdownJson_FiltersEmptyTitle()
{
string json = """
{
"suggestions": [
{
"title": "",
"priority": 1,
"reason": "空标题"
},
{
"title": "有效任务",
"priority": 0,
"reason": "有标题"
}
]
}
""";
var result = MeetingAiBreakdownService.ParseBreakdownJson(json);
Assert.Single(result);
Assert.Equal("有效任务", result[0].Title);
}
/// <summary>
/// 测试 ParseBreakdownJson 过滤空白标题的条目。
/// </summary>
[Fact]
public void ParseBreakdownJson_FiltersWhitespaceTitle()
{
string json = """
{
"suggestions": [
{
"title": " ",
"priority": 1,
"reason": "空白标题"
},
{
"title": "有效任务",
"priority": 0,
"reason": "有标题"
}
]
}
""";
var result = MeetingAiBreakdownService.ParseBreakdownJson(json);
Assert.Single(result);
Assert.Equal("有效任务", result[0].Title);
}
/// <summary>
/// 测试 ParseBreakdownJson 处理无效 JSON。
/// </summary>
[Fact]
public void ParseBreakdownJson_InvalidJson_ReturnsEmptyList()
{
string json = "这不是有效的 JSON";
var result = MeetingAiBreakdownService.ParseBreakdownJson(json);
Assert.Empty(result);
}
/// <summary>
/// 测试 ParseBreakdownJson 处理空字符串。
/// </summary>
[Fact]
public void ParseBreakdownJson_EmptyString_ReturnsEmptyList()
{
var result = MeetingAiBreakdownService.ParseBreakdownJson("");
Assert.Empty(result);
}
/// <summary>
/// 测试 ParseBreakdownJson 返回的条目标题已 Trim。
/// </summary>
[Fact]
public void ParseBreakdownJson_TrimsTitleWhitespace()
{
string json = """
{
"suggestions": [
{
"title": " 前后空格 ",
"priority": 1,
"reason": "测试"
}
]
}
""";
var result = MeetingAiBreakdownService.ParseBreakdownJson(json);
Assert.Single(result);
Assert.Equal("前后空格", result[0].Title);
}
/// <summary>
/// 测试 ParseBreakdownJson 处理 Reason 为 null 的条目。
/// </summary>
[Fact]
public void ParseBreakdownJson_NullReason_ReturnsEmptyReason()
{
string json = """
{
"suggestions": [
{
"title": "测试任务",
"priority": 1
}
]
}
""";
var result = MeetingAiBreakdownService.ParseBreakdownJson(json);
Assert.Single(result);
Assert.Equal("测试任务", result[0].Title);
Assert.Equal(string.Empty, result[0].Reason);
}
/// <summary>
/// 测试 IMeetingService 接口包含 RequestBreakdownAsync 方法。
/// </summary>
[Fact]
public void IMeetingService_HasRequestBreakdownAsyncMethod()
{
var method = typeof(IMeetingService).GetMethod("RequestBreakdownAsync");
Assert.NotNull(method);
Assert.Equal(typeof(Task<BreakdownResponse>), method!.ReturnType);
}
/// <summary>
/// 测试 IMeetingService 接口包含 ConfirmBreakdownAsync 方法。
/// </summary>
[Fact]
public void IMeetingService_HasConfirmBreakdownAsyncMethod()
{
var method = typeof(IMeetingService).GetMethod("ConfirmBreakdownAsync");
Assert.NotNull(method);
Assert.Equal(typeof(Task<BatchCreateResult>), method!.ReturnType);
}
/// <summary>
/// 测试 MeetingAiBreakdownService 类型存在并具有所需方法。
/// </summary>
[Fact]
public void MeetingAiBreakdownService_HasAnalyzeAsyncMethod()
{
var method = typeof(MeetingAiBreakdownService).GetMethod("AnalyzeAsync");
Assert.NotNull(method);
}
/// <summary>
/// 测试 MeetingAiBreakdownService 类型存在并具有 ConfirmAndCreateAsync 方法。
/// </summary>
[Fact]
public void MeetingAiBreakdownService_HasConfirmAndCreateAsyncMethod()
{
var method = typeof(MeetingAiBreakdownService).GetMethod("ConfirmAndCreateAsync");
Assert.NotNull(method);
}
/// <summary>
/// 测试 MeetingAiBreakdownService 类型存在并具有 ParseBreakdownJson 公开静态方法。
/// </summary>
[Fact]
public void MeetingAiBreakdownService_HasParseBreakdownJsonMethod()
{
var method = typeof(MeetingAiBreakdownService).GetMethod("ParseBreakdownJson",
BindingFlags.Public | BindingFlags.Static);
Assert.NotNull(method);
Assert.True(method!.IsStatic);
}
}