using BuildFlow.Application.Document.Commands;
using BuildFlow.Application.Document.Handlers;
using BuildFlow.Domain.Entities.KnowledgeBase;
using BuildFlow.Domain.Enums;
using BuildFlow.Infrastructure.Persistence;
using BuildFlow.Shared.Domain.Common;
using File.Application.Features.Files.Commands;
using File.Application.Features.Files.DTOs;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Moq;
using Xunit;
namespace BuildFlow.Rag.Tests;
///
/// 文档上传处理器测试。文件内容必须通过 File Application 命令上传。
///
public class UploadDocumentCommandHandlerTests
{
[Fact]
public async Task Handle_ShouldSendUploadFileCommandAndPersistDocument()
{
await using var db = CreateDb();
var knowledgeBaseId = Guid.NewGuid();
db.KnowledgeBases.Add(BuildKnowledgeBase(knowledgeBaseId));
await db.SaveChangesAsync();
await using var fileStream = new MemoryStream([1, 2, 3]);
var mediator = new Mock();
mediator.Setup(m => m.Send(It.IsAny(), It.IsAny()))
.ReturnsAsync((UploadFileCommand command, CancellationToken _) =>
new UploadFileResponse("uploaded", command.ObjectKey));
var config = new ConfigurationManager
{
["FileService:Bucket"] = "rag-documents"
};
var handler = new UploadDocumentCommandHandler(db, mediator.Object, config);
var result = await handler.Handle(new UploadDocumentCommand(
knowledgeBaseId,
"Architecture",
"architecture.md",
fileStream,
fileStream.Length,
"text/markdown"), CancellationToken.None);
var document = await db.Documents.SingleAsync(doc => doc.Id == result.Id);
Assert.StartsWith("rag-documents/docs/", document.FilePath);
mediator.Verify(m => m.Send(
It.Is(command =>
command.ObjectKey.StartsWith($"docs/{knowledgeBaseId}/", StringComparison.Ordinal)
&& command.ObjectKey.EndsWith("/architecture.md", StringComparison.Ordinal)
&& ReferenceEquals(command.Data, fileStream)
&& command.ContentType == "text/markdown"
&& command.Size == fileStream.Length),
It.IsAny()), Times.Once);
}
private static RagDbContext CreateDb()
{
var db = new RagDbContext(
new DbContextOptionsBuilder()
.UseInMemoryDatabase($"rag-upload-{Guid.NewGuid():N}")
.Options);
db.SavingChanges += (_, _) =>
{
foreach (var entry in db.ChangeTracker.Entries())
{
if (entry.Entity is IAuditable auditable)
{
auditable.CreatedBy ??= "test";
auditable.UpdatedBy ??= "test";
if (auditable.CreatedAt == default) auditable.CreatedAt = DateTime.UtcNow;
if (auditable.UpdatedAt == default) auditable.UpdatedAt = DateTime.UtcNow;
}
if (entry.Entity is IHasOperatorIP operatorEntity)
{
operatorEntity.OperatorIP ??= "127.0.0.1";
}
}
};
return db;
}
private static KnowledgeBaseEntity BuildKnowledgeBase(Guid id) => new()
{
Id = id,
Name = "Architecture",
Status = KnowledgeBaseStatusEnum.Active,
CreatedBy = "test",
UpdatedBy = "test",
OperatorIP = "127.0.0.1",
};
}