xiangning 7ecd78550f test: 补充 platform/rag/file 模块单元测试
- platform: 通知与权限模块 handler 测试
- rag: Chat 端到端、知识库 CRUD、嵌入与查询测试
- file: 文件分享下载与文件夹/桶设置测试
2026-07-09 12:52:00 +08:00

143 lines
5.3 KiB
C#

using BuildFlow.Application.Chat.Commands;
using BuildFlow.Application.Chat.Handlers;
using BuildFlow.Domain.Entities;
using BuildFlow.Domain.Enums;
using BuildFlow.Infrastructure.Persistence;
using BuildFlow.Rag.Tests.Stub;
using BuildFlow.Shared.Domain.Common;
using BuildFlow.Shared.Domain.Exceptions;
using BuildFlow.Shared.Infrastructure;
using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using Xunit;
namespace BuildFlow.Rag.Tests;
/// <summary>
/// [SOP-RAG-02] 多轮对话端到端测试。
/// 不依赖 pgvector。验证:CreateConversation → SendMessage(两轮)→ 历史拼装 → cache 一致性。
/// 飞书文档: https://ecn1rid3e27z.feishu.cn/wiki/CBrcwbDH6ialzFkF1cgcnvKenHM
/// </summary>
public class ChatEndToEndTests
{
private static RagDbContext CreateDb(string name)
{
// 挂生产 AuditInterceptor,自动填充 CreatedBy/UpdatedBy/OperatorIP 等审计字段
// (InMemory 默认无拦截器,导致必填审计字段为空抛 DbUpdateException)
var userContext = new TestCurrentUserContext();
var options = new DbContextOptionsBuilder<RagDbContext>()
.UseInMemoryDatabase($"rag-chat-{name}-{Guid.NewGuid():N}")
.AddInterceptors(new AuditInterceptor(userContext))
.Options;
return new RagDbContext(options);
}
private static void SeedConversation(RagDbContext db, Guid conversationId, Guid userId)
{
db.Conversations.Add(new Conversation
{
Id = conversationId,
Title = "测试会话",
UserId = userId
});
db.SaveChanges();
}
[Fact]
public async Task CreateConversation_ReturnsConversationDto()
{
// Given
await using var db = CreateDb("create");
var userContext = new TestCurrentUserContext();
var handler = new CreateConversationCommandHandler(db, userContext);
// When
var result = await handler.Handle(
new CreateConversationCommand("我的对话", null),
CancellationToken.None);
// Then
result.Title.Should().Be("我的对话");
result.UserId.Should().Be(Guid.Parse(userContext.UserId));
result.Id.Should().NotBeEmpty();
db.Conversations.Should().ContainSingle();
}
[Fact]
public async Task SendMessage_TwoRounds_HistoryAssembledAndCacheConsistent()
{
// Given: 已创建会话 + 两轮对话
await using var db = CreateDb("multi-turn");
var userId = Guid.Parse(new TestCurrentUserContext().UserId);
var conversationId = Guid.NewGuid();
SeedConversation(db, conversationId, userId);
var cache = new StubChatMessageCache();
var chatAgent = new StubAIChatAgent { ReplyText = "你好!我是 AI 助手。" };
var handler = new SendMessageCommandHandler(db, chatAgent, cache);
// When: 第一轮
var round1 = await handler.Handle(
new SendMessageCommand(conversationId, "你好"),
CancellationToken.None);
// Then: 第一轮返回 messageId + 内容
round1.MessageId.Should().NotBeEmpty();
round1.Content.Should().Be("你好!我是 AI 助手。");
// DB: 1 user + 1 assistant = 2 条消息
db.ChatMessages.Should().HaveCount(2);
db.ChatMessages.Count(m => m.Role == ChatRole.User).Should().Be(1);
db.ChatMessages.Count(m => m.Role == ChatRole.Assistant).Should().Be(1);
// Cache: 同步 2 条
cache.Count(conversationId).Should().Be(2, "cache 与 DB 一致");
// When: 第二轮(验证历史拼装 - Stub chatAgent 不依赖历史但 Handler 应查历史)
var round2 = await handler.Handle(
new SendMessageCommand(conversationId, "我刚才说了什么?"),
CancellationToken.None);
// Then: 第二轮同样返回
round2.MessageId.Should().NotBeEmpty();
round2.Content.Should().NotBeNullOrEmpty();
// DB: 累计 4 条(2 user + 2 assistant)
db.ChatMessages.Should().HaveCount(4);
cache.Count(conversationId).Should().Be(4, "两轮后 cache 4 条");
}
[Fact]
public async Task SendMessage_ConversationNotFound_ThrowsNotFoundException()
{
// Given: 会话不存在
await using var db = CreateDb("not-found");
var cache = new StubChatMessageCache();
var chatAgent = new StubAIChatAgent();
var handler = new SendMessageCommandHandler(db, chatAgent, cache);
// When/Then: 发消息到不存在的会话
var act = async () => await handler.Handle(
new SendMessageCommand(Guid.NewGuid(), "内容"),
CancellationToken.None);
await act.Should().ThrowAsync<NotFoundException>();
}
[Fact]
public async Task CreateConversation_UnauthorizedUser_Throws()
{
// Given: UserId = "system"(未授权)
await using var db = CreateDb("unauth");
var userContext = new TestCurrentUserContext { UserId = "system" };
var handler = new CreateConversationCommandHandler(db, userContext);
// When/Then
var act = async () => await handler.Handle(
new CreateConversationCommand("x", null),
CancellationToken.None);
await act.Should().ThrowAsync<UnauthorizedException>("未授权用户不能创建会话");
}
}