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

143 lines
5.9 KiB
C#

using BuildFlow.Application.Document.Queries;
using BuildFlow.Application.Document.Handlers;
using BuildFlow.Application.Embedding.Commands;
using BuildFlow.Application.Embedding.Handlers;
using BuildFlow.Application.Chat.Commands;
using BuildFlow.Application.Chat.Handlers;
using BuildFlow.Application.Chat.Queries;
using BuildFlow.Domain.Entities;
using BuildFlow.Domain.Enums;
using BuildFlow.Domain.Interfaces;
using BuildFlow.Infrastructure.Persistence;
using BuildFlow.Rag.Tests.Stub;
using BuildFlow.Shared.Domain.Exceptions;
using BuildFlow.Shared.Infrastructure;
using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Xunit;
namespace BuildFlow.Rag.Tests;
/// <summary>
/// [SOP-RAG] Embedding Command + Document/Chat Query Handler 补测。
/// 覆盖:EmbedText/EmbedBatch / GetDocuments / GetConversationDetail / GetConversations / DeleteConversation。
/// </summary>
public class EmbeddingAndQueryTests
{
private static RagDbContext CreateDb([System.Runtime.CompilerServices.CallerMemberName] string name = "")
{
var userContext = new TestCurrentUserContext();
var options = new DbContextOptionsBuilder<RagDbContext>()
.UseInMemoryDatabase($"rag-misc-{name}-{Guid.NewGuid():N}")
.AddInterceptors(new AuditInterceptor(userContext))
.Options;
return new RagDbContext(options);
}
// ===== Embedding =====
[Fact]
public async Task EmbedText_ReturnsVectorAndDimensions()
{
var embedding = new StubEmbeddingService();
var handler = new EmbedTextCommandHandler(embedding);
var result = await handler.Handle(new EmbedTextCommand("测试文本"), CancellationToken.None);
result.Vector.Should().HaveCount(StubEmbeddingService.Dimensions);
result.Dimensions.Should().Be(StubEmbeddingService.Dimensions);
}
[Fact]
public async Task EmbedBatch_ReturnsVectorsInOrder()
{
var embedding = new StubEmbeddingService();
var handler = new EmbedBatchCommandHandler(embedding);
var result = await handler.Handle(
new EmbedBatchCommand(["文本A", "文本B", "文本C"]), CancellationToken.None);
result.Vectors.Should().HaveCount(3);
result.Vectors[0].Should().HaveCount(StubEmbeddingService.Dimensions);
result.Dimensions.Should().Be(StubEmbeddingService.Dimensions);
}
// ===== Document Queries =====
[Fact]
public async Task GetDocuments_ByKnowledgeBase_ReturnsOnlyMatching()
{
var db = CreateDb();
var kbId = Guid.NewGuid();
var otherKbId = Guid.NewGuid();
db.Documents.Add(new Document { KnowledgeBaseId = kbId, Title = "doc1", FileName = "a.md", FilePath = "a", ContentType = "text", Status = DocumentStatus.Completed });
db.Documents.Add(new Document { KnowledgeBaseId = kbId, Title = "doc2", FileName = "b.md", FilePath = "b", ContentType = "text", Status = DocumentStatus.Pending });
db.Documents.Add(new Document { KnowledgeBaseId = otherKbId, Title = "other", FileName = "c.md", FilePath = "c", ContentType = "text", Status = DocumentStatus.Completed });
await db.SaveChangesAsync();
var handler = new GetDocumentsQueryHandler(db);
var result = await handler.Handle(new GetDocumentsQuery(kbId), CancellationToken.None);
result.Should().HaveCount(2);
result.Should().OnlyContain(d => d.KnowledgeBaseId == kbId);
}
// ===== Chat Queries =====
[Fact]
public async Task GetConversations_ReturnsCurrentUserOnly()
{
var db = CreateDb();
var userContext = new TestCurrentUserContext();
var myId = Guid.Parse(userContext.UserId);
db.Conversations.Add(new Conversation { Title = "我的", UserId = myId });
db.Conversations.Add(new Conversation { Title = "他人的", UserId = Guid.NewGuid() });
await db.SaveChangesAsync();
var handler = new GetConversationsQueryHandler(db, userContext);
var result = await handler.Handle(new GetConversationsQuery(), CancellationToken.None);
result.Should().HaveCount(1);
result[0].Title.Should().Be("我的");
}
[Fact]
public async Task GetConversationDetail_Existing_ReturnsWithMessages()
{
var db = CreateDb();
var convId = Guid.NewGuid();
var userId = Guid.Parse(new TestCurrentUserContext().UserId);
db.Conversations.Add(new Conversation { Id = convId, Title = "详情", UserId = userId });
db.ChatMessages.Add(new ChatMessage { ConversationId = convId, Role = ChatRole.User, Content = "你好" });
db.ChatMessages.Add(new ChatMessage { ConversationId = convId, Role = ChatRole.Assistant, Content = "回复" });
await db.SaveChangesAsync();
var cache = new StubChatMessageCache();
var handler = new GetConversationDetailQueryHandler(db, cache);
var result = await handler.Handle(new GetConversationDetailQuery(convId), CancellationToken.None);
result.Title.Should().Be("详情");
result.Messages.Should().HaveCount(2);
}
[Fact]
public async Task DeleteConversation_RemovesFromDbAndCache()
{
var db = CreateDb();
var convId = Guid.NewGuid();
var userId = Guid.Parse(new TestCurrentUserContext().UserId);
db.Conversations.Add(new Conversation { Id = convId, Title = "待删", UserId = userId });
await db.SaveChangesAsync();
var cache = new StubChatMessageCache();
await cache.SetMessagesAsync(convId, [new CachedChatMessage(Guid.NewGuid(), "user", "hi", null, DateTime.UtcNow)]);
var handler = new DeleteConversationCommandHandler(db, cache);
await handler.Handle(new DeleteConversationCommand(convId), CancellationToken.None);
(await db.Conversations.AnyAsync(c => c.Id == convId)).Should().BeFalse();
cache.Count(convId).Should().Be(0, "cache 也应清除");
}
}