143 lines
5.3 KiB
C#
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>("未授权用户不能创建会话");
|
|
}
|
|
}
|