build-flow.api/tests/BuildFlow.Rag.Tests/PreviewDocumentChunksQueryHandlerTests.cs
xiangning 54dc3fc26e refactor(rag): 重构AI对话和工作流集成实现
- 移除IWorkflowTriggerService接口及其实现,改用Mediator模式直接发送命令
- 更新AiBotHandler移除workflowTrigger依赖,使用新命令模式触发工作流
- 修改ApproveTaskEndpoint和RejectTaskEndpoint返回bool类型并使用Send.OkAsync
- 更新appsettings.json中AI助手的默认指令配置
- 在BuildFlowHostModule.cs中移除旧的工作流触发服务注册
- 添加SSE流式响应路径前缀配置以支持RAG聊天流
- 重构ChatAgentService使用HTTP客户端工厂和运行时配置解析
- 实现AiChatOutputSanitizer用于清洗AI输出中的模板内容
- 更新EmbeddingService使用运行时配置解析器动态创建客户端
- 调整RAG查询处理器中的系统提示指令内容
- 添加构建流按钮操作的单元测试验证发布和禁用功能
- 扩展即时消息集成测试覆盖文件消息元数据处理场景
- 更新平台权限种子数据添加按钮发布和禁用权限
- 实现AI模型运行时配置解析器支持动态模型切换
2026-07-04 21:29:37 +08:00

116 lines
4.2 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using BuildFlow.Application.Document.Handlers;
using BuildFlow.Application.Document.Queries;
using BuildFlow.Domain.Entities;
using BuildFlow.Domain.Enums;
using BuildFlow.Domain.Interfaces;
using BuildFlow.Infrastructure.AI;
using BuildFlow.Infrastructure.Persistence;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Xunit;
namespace BuildFlow.Rag.Tests;
/// <summary>
/// 文档切片预览测试。预览只提取并分块,不生成 embedding,也不写入正式分块表。
/// </summary>
public class PreviewDocumentChunksQueryHandlerTests
{
[Fact]
public async Task HandleShouldReturnDryRunChunksWithoutPersistingDocumentChunks()
{
var options = new DbContextOptionsBuilder<RagDbContext>()
.UseInMemoryDatabase($"rag-preview-{Guid.NewGuid():N}")
.Options;
await using var db = new RagDbContext(options);
var kbId = Guid.NewGuid();
var docId = Guid.NewGuid();
var filePath = Path.Combine(Path.GetTempPath(), $"preview-{Guid.NewGuid():N}.md");
await File.WriteAllTextAsync(filePath, "# 第一章\n\nBuildFlow 文档切片预览。\n\n# 第二章\n\n上传后先看切片,再手动处理向量。");
try
{
await db.KnowledgeBases.AddAsync(new KnowledgeBase
{
Id = kbId,
Name = "预览知识库",
ChunkSize = 40,
ChunkOverlap = 5,
ChunkingMode = ChunkingMode.Heading,
EmbeddingModel = "text-embedding-embeddinggemma-300m",
Status = KnowledgeBaseStatus.Active,
CreatedBy = "test",
UpdatedBy = "test",
OperatorIP = "127.0.0.1",
});
await db.Documents.AddAsync(new Document
{
Id = docId,
KnowledgeBaseId = kbId,
Title = "preview.md",
FileName = "preview.md",
FilePath = filePath,
FileSize = 64,
ContentType = "text/markdown",
Status = DocumentStatus.Pending,
CreatedBy = "test",
UpdatedBy = "test",
OperatorIP = "127.0.0.1",
});
await db.SaveChangesAsync();
var handler = new PreviewDocumentChunksQueryHandler(
db,
new StubDocumentExtractorFactory(),
new TextChunker(),
new StubFileStorageClient(),
new HttpContextAccessor());
var result = await handler.Handle(new PreviewDocumentChunksQuery(docId), CancellationToken.None);
Assert.Equal(2, result.Count);
Assert.All(result, chunk => Assert.False(chunk.HasEmbedding));
Assert.Contains("第一章", result[0].Content);
Assert.Contains("第二章", result[1].Content);
Assert.Equal(0, await db.DocumentChunks.CountAsync());
}
finally
{
File.Delete(filePath);
}
}
private sealed class StubDocumentExtractorFactory : IDocumentExtractorFactory
{
public IDocumentExtractor GetExtractor(string fileNameOrExtension) => new StubDocumentExtractor();
}
private sealed class StubDocumentExtractor : IDocumentExtractor
{
public IReadOnlyCollection<string> SupportedExtensions { get; init; } = ["md"];
public async Task<ExtractedDocument> ExtractAsync(string filePath, CancellationToken ct = default)
{
return new ExtractedDocument
{
Content = await File.ReadAllTextAsync(filePath, ct),
Title = Path.GetFileName(filePath),
};
}
}
private sealed class StubFileStorageClient : IFileStorageClient
{
public Task UploadAsync(
string bucket,
string objectKey,
Stream content,
string contentType,
string? authToken,
CancellationToken ct = default) => Task.CompletedTask;
public Task<Stream> DownloadAsync(string bucket, string objectKey, string? authToken, CancellationToken ct = default)
=> throw new InvalidOperationException("本用例不应下载文件服务对象");
}
}