- 移除IWorkflowTriggerService接口及其实现,改用Mediator模式直接发送命令 - 更新AiBotHandler移除workflowTrigger依赖,使用新命令模式触发工作流 - 修改ApproveTaskEndpoint和RejectTaskEndpoint返回bool类型并使用Send.OkAsync - 更新appsettings.json中AI助手的默认指令配置 - 在BuildFlowHostModule.cs中移除旧的工作流触发服务注册 - 添加SSE流式响应路径前缀配置以支持RAG聊天流 - 重构ChatAgentService使用HTTP客户端工厂和运行时配置解析 - 实现AiChatOutputSanitizer用于清洗AI输出中的模板内容 - 更新EmbeddingService使用运行时配置解析器动态创建客户端 - 调整RAG查询处理器中的系统提示指令内容 - 添加构建流按钮操作的单元测试验证发布和禁用功能 - 扩展即时消息集成测试覆盖文件消息元数据处理场景 - 更新平台权限种子数据添加按钮发布和禁用权限 - 实现AI模型运行时配置解析器支持动态模型切换
116 lines
4.2 KiB
C#
116 lines
4.2 KiB
C#
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("本用例不应下载文件服务对象");
|
||
}
|
||
}
|