- 添加HNSW向量索引提升检索性能 - 实现Hybrid检索模式支持向量和关键词双路召回 - 采用RRF算法融合多路召回结果 - 支持ParentChild分块模式的上下文回填 - 优化向量写入采用批量SQL操作提升性能 - 添加RAG PostgreSQL连接字符串配置 - 更新部署脚本增加PGVector集成测试 - 修正表名引用从document_chunks到b_document_chunks - 重构RagRetrievalService实现完整的检索流程 - 增加重排策略支持关键词和LLM重排 - 修改服务器部署地址配置
124 lines
4.5 KiB
C#
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
|
|
}
|