using BuildFlow.Shared.Domain.Common; using BuildFlow.Shared.Infrastructure; using File.Application.Common; using File.Application.Features.Files.Commands; using File.Application.Features.Files.Handlers; using File.Application.Features.Folders.Commands; using File.Application.Features.Folders.Handlers; using File.Application.Features.Multipart.Commands; using File.Application.Features.Multipart.DTOs; using File.Application.Features.Multipart.Handlers; using File.Domain.Entities.FileMeta; using File.Domain.Entities.MultipartUploadSession; using File.Domain.Values; using File.Infrastructure.Persistence; using File.Infrastructure.Storage; using FluentAssertions; using MediatR; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; using Moq; using Xunit; namespace BuildFlow.File.Tests; /// 文件大小确认状态在上传路径与 EF 模型中的持久化契约测试。 public class FileSizeVerifiedTests { private const string OwnerId = "user-001"; private const string Bucket = "files"; [Fact] public async Task DirectZeroByteUploadMarksSizeVerified() { var (db, s3, user) = CreateDependencies(); var handler = new UploadFileCommandHandler( s3.Object, db, user.Object, CreateBucketResolver(), new Mock().Object); await handler.Handle( new UploadFileCommand("empty.txt", Stream.Null, "text/plain", 0), CancellationToken.None); var file = await db.Files.SingleAsync(); file.Size.Should().Be(0); file.SizeVerified.Should().BeTrue(); } [Fact] public async Task FolderUploadMarksSizeVerified() { var (db, s3, user) = CreateDependencies(); var handler = new UploadToFolderCommandHandler( db, s3.Object, user.Object, CreateBucketResolver()); await handler.Handle( new UploadToFolderCommand( Guid.NewGuid(), "report.pdf", new MemoryStream([1, 2, 3]), "application/pdf", 3), CancellationToken.None); (await db.Files.SingleAsync()).SizeVerified.Should().BeTrue(); } [Fact] public async Task CompletedMultipartUploadMarksSizeVerified() { var (db, s3, user) = CreateDependencies(); db.UploadSessions.Add(new MultipartUploadSessionEntity { BucketName = Bucket, ObjectKey = "multipart-key", UploadId = "upload-1", OwnerId = OwnerId, Status = UploadSessionStatus.Active, }); await db.SaveChangesAsync(); var handler = new CompleteMultipartCommandHandler( s3.Object, db, user.Object, CreateBucketResolver()); await handler.Handle( new CompleteMultipartCommand( "multipart-key", "upload-1", [new CompletedPartDto(1, "etag-1")], "archive.zip", 1024), CancellationToken.None); (await db.Files.SingleAsync()).SizeVerified.Should().BeTrue(); } [Fact] public void ModelHasPendingSizeVerificationIndex() { var (db, _, _) = CreateDependencies(); var entity = db.Model.FindEntityType(typeof(FileMetaEntity)); entity.Should().NotBeNull(); var sizeVerified = entity!.FindProperty(nameof(FileMetaEntity.SizeVerified)); sizeVerified.Should().NotBeNull(); sizeVerified!.GetColumnName(StoreObjectIdentifier.Table("b_files", null)) .Should().Be("size_verified"); entity!.GetIndexes().Should().Contain(index => index.Properties.Select(property => property.Name) .SequenceEqual(new[] { nameof(FileMetaEntity.SizeVerified), nameof(FileMetaEntity.CreatedAt) })); } private static (FileDbContext Db, Mock S3, Mock User) CreateDependencies() { var user = new Mock(); user.SetupGet(context => context.UserId).Returns(OwnerId); user.SetupGet(context => context.IPAddress).Returns("127.0.0.1"); var options = new DbContextOptionsBuilder() .UseInMemoryDatabase($"file-size-verified-{Guid.NewGuid():N}") .AddInterceptors(new AuditInterceptor(user.Object)) .Options; return (new FileDbContext(options), new Mock(), user); } private static IBucketResolver CreateBucketResolver() { var resolver = new Mock(); resolver.Setup(x => x.GetDefaultBucketAsync(It.IsAny())) .ReturnsAsync(Bucket); return resolver.Object; } }