build-flow.api/tests/BuildFlow.File.Tests/FileQueryHandlersTests.cs
xiangning 7479521546 feat(rag): 实现RAG模块的Hybrid检索和向量索引优化
- 添加HNSW向量索引提升检索性能
- 实现Hybrid检索模式支持向量和关键词双路召回
- 采用RRF算法融合多路召回结果
- 支持ParentChild分块模式的上下文回填
- 优化向量写入采用批量SQL操作提升性能
- 添加RAG PostgreSQL连接字符串配置
- 更新部署脚本增加PGVector集成测试
- 修正表名引用从document_chunks到b_document_chunks
- 重构RagRetrievalService实现完整的检索流程
- 增加重排策略支持关键词和LLM重排
- 修改服务器部署地址配置
2026-08-11 10:59:09 +08:00

124 lines
4.5 KiB
C#

using File.Application.Common;
using File.Application.Features.Files.Handlers;
using File.Application.Features.Files.Queries;
using File.Infrastructure.Storage;
using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Moq;
using Xunit;
namespace BuildFlow.File.Tests;
/// <summary>
/// 4 个文件查询 Handler 测试(均为纯委托/组装,无异常分支,仅覆盖成功路径):
/// GetFileContentQueryHandler / GetFilePreviewQueryHandler / GetOfficePreviewQueryHandler / ListFilesQueryHandler。
/// </summary>
public class FileQueryHandlersTests
{
private static Mock<IBucketResolver> Bucket(string name = "files")
{
var bucket = new Mock<IBucketResolver>();
bucket.Setup(x => x.GetDefaultBucketAsync(It.IsAny<CancellationToken>())).ReturnsAsync(name);
return bucket;
}
#region GetFileContentQueryHandler
[Fact]
public async Task GetFileContent_返回S3文本内容()
{
var s3 = new Mock<IS3StorageService>();
s3.Setup(x => x.GetFileContentAsync("files", "k1", It.IsAny<long>(), It.IsAny<CancellationToken>()))
.ReturnsAsync("hello world");
var handler = new GetFileContentQueryHandler(s3.Object, Bucket().Object);
var result = await handler.Handle(new GetFileContentQuery("k1"), CancellationToken.None);
result.Content.Should().Be("hello world");
s3.Verify(x => x.GetFileContentAsync("files", "k1", It.IsAny<long>(), It.IsAny<CancellationToken>()), Times.Once);
}
#endregion
#region GetFilePreviewQueryHandler
[Fact]
public async Task GetFilePreview_返回24h预签名URL()
{
var s3 = new Mock<IS3StorageService>();
s3.Setup(x => x.GeneratePresignedUrlAsync(
"files", "k2", It.IsAny<TimeSpan>(), It.IsAny<CancellationToken>()))
.ReturnsAsync("https://presigned.example.com/k2");
var handler = new GetFilePreviewQueryHandler(s3.Object, Bucket().Object);
var result = await handler.Handle(new GetFilePreviewQuery("k2"), CancellationToken.None);
result.PresignedUrl.Should().Be("https://presigned.example.com/k2");
}
#endregion
#region GetOfficePreviewQueryHandler
[Fact]
public async Task GetOfficePreview_返回KKFileView预览地址()
{
var kk = new Mock<IOptionsMonitor<KkFileViewOptions>>();
kk.Setup(x => x.CurrentValue).Returns(new KkFileViewOptions
{
BaseUrl = "http://kk.example.com",
BackendBaseUrl = "http://backend.example.com",
InternalKey = "secret-key",
});
var handler = new GetOfficePreviewQueryHandler(kk.Object);
var result = await handler.Handle(new GetOfficePreviewQuery("docs/report.pdf"), CancellationToken.None);
result.PreviewUrl.Should().Contain("http://kk.example.com/onlinePreview?url=");
result.PreviewUrl.Should().NotBeNullOrEmpty();
}
#endregion
#region ListFilesQueryHandler
[Fact]
public async Task ListFiles_映射S3对象列表并返回()
{
var s3 = new Mock<IS3StorageService>();
s3.Setup(x => x.ListObjectsAsync(
"files", "prefix/", It.IsAny<int>(), null, It.IsAny<CancellationToken>()))
.ReturnsAsync(new ListFilesResult(
new List<FileInfoDto> { new("prefix/a.txt", 10, new DateTime(2026, 1, 1), "etag-1") },
null));
var handler = new ListFilesQueryHandler(s3.Object, Bucket().Object);
var result = await handler.Handle(
new ListFilesQuery("prefix/", null, null), CancellationToken.None);
result.Files.Should().HaveCount(1);
result.Files[0].Key.Should().Be("prefix/a.txt");
result.Files[0].Size.Should().Be(10);
result.NextContinuationToken.Should().BeNull();
}
[Fact]
public async Task ListFiles_未指定MaxKeys_使用默认分页上限()
{
var s3 = new Mock<IS3StorageService>();
s3.Setup(x => x.ListObjectsAsync(
"files", null, It.IsAny<int>(), null, It.IsAny<CancellationToken>()))
.ReturnsAsync(new ListFilesResult(new List<FileInfoDto>(), null));
var handler = new ListFilesQueryHandler(s3.Object, Bucket().Object);
await handler.Handle(new ListFilesQuery(null, null, null), CancellationToken.None);
// 默认 100 条;断言 S3 收到的是默认上限(<=1000 上限内)
s3.Verify(x => x.ListObjectsAsync(
"files", null, It.Is<int>(v => v > 0 && v <= 1000), null, It.IsAny<CancellationToken>()), Times.Once);
}
#endregion
}