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", }; }