- 从文件模块的多部分上传处理器中移除未使用的实体引用 - 从即时通讯模块的群组成员处理器中移除未使用的实体引用 - 从RAG模块的AI模型配置中移除未使用的实体引用 - 从监控模块的告警规则配置中移除未使用的实体引用 - 从平台模块的角色权限处理器中移除未使用的实体引用 - 从脚本模块的执行日志实体引用中进行清理 - 从多个基础设施配置类中移除未使用的依赖项 - 优化测试文件中的导入声明,移除不必要的引用
139 lines
5.5 KiB
C#
139 lines
5.5 KiB
C#
using BuildFlow.Shared.Domain.Common;
|
||
using BuildFlow.Shared.Infrastructure;
|
||
using File.Application.Features.Files.Handlers;
|
||
using File.Application.Features.Files.Queries;
|
||
using File.Domain.Entities.FileMeta;
|
||
using File.Infrastructure.Persistence;
|
||
using File.Infrastructure.Storage;
|
||
using FluentAssertions;
|
||
using Microsoft.EntityFrameworkCore;
|
||
using Moq;
|
||
using Xunit;
|
||
|
||
namespace BuildFlow.File.Tests;
|
||
|
||
/// <summary>
|
||
/// 秒传预检测试。验证查重三维度(同用户/跨用户/防碰撞)+ S3 存在性校验(孤儿清理)。
|
||
/// </summary>
|
||
public class CheckFileQueryHandlerTests
|
||
{
|
||
private const string OwnerId = "user-001";
|
||
private const string Sha = "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824";
|
||
|
||
private static (FileDbContext db, Mock<IS3StorageService> s3, Mock<ICurrentUserContext> user) CreateDeps(string dbName)
|
||
{
|
||
var user = new Mock<ICurrentUserContext>();
|
||
user.SetupGet(u => u.UserId).Returns(OwnerId);
|
||
user.SetupGet(u => u.IPAddress).Returns("127.0.0.1");
|
||
var s3 = new Mock<IS3StorageService>();
|
||
// 默认对象存在;个别测试覆盖 false
|
||
s3.Setup(x => x.ObjectExistsAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||
.ReturnsAsync(true);
|
||
|
||
var options = new DbContextOptionsBuilder<FileDbContext>()
|
||
.UseInMemoryDatabase(dbName)
|
||
.AddInterceptors(new AuditInterceptor(user.Object))
|
||
.Options;
|
||
return (new FileDbContext(options), s3, user);
|
||
}
|
||
|
||
private static FileMetaEntity NewFile(string owner, string hash, long size, string name = "a.txt")
|
||
=> new()
|
||
{
|
||
Name = name,
|
||
S3Key = Guid.NewGuid().ToString(),
|
||
S3Bucket = "files",
|
||
Size = size,
|
||
ContentType = "text/plain",
|
||
ContentHash = hash,
|
||
OwnerId = owner,
|
||
};
|
||
|
||
[Fact]
|
||
public async Task Check_同用户同哈希同大小_S3对象存在_命中秒传()
|
||
{
|
||
var (db, s3, user) = CreateDeps(nameof(Check_同用户同哈希同大小_S3对象存在_命中秒传));
|
||
db.Files.Add(NewFile(OwnerId, Sha, 100));
|
||
await db.SaveChangesAsync();
|
||
|
||
var handler = new CheckFileQueryHandler(db, s3.Object, user.Object);
|
||
|
||
var result = await handler.Handle(new CheckFileQuery(Sha, 100), CancellationToken.None);
|
||
|
||
result.Exists.Should().BeTrue();
|
||
result.File.Should().NotBeNull();
|
||
result.File!.Size.Should().Be(100);
|
||
result.File.OwnerId.Should().Be(OwnerId);
|
||
}
|
||
|
||
[Fact]
|
||
public async Task Check_无匹配_返回未命中()
|
||
{
|
||
var (db, s3, user) = CreateDeps(nameof(Check_无匹配_返回未命中));
|
||
var handler = new CheckFileQueryHandler(db, s3.Object, user.Object);
|
||
|
||
var result = await handler.Handle(new CheckFileQuery(Sha, 100), CancellationToken.None);
|
||
|
||
result.Exists.Should().BeFalse();
|
||
result.File.Should().BeNull();
|
||
}
|
||
|
||
[Fact]
|
||
public async Task Check_他人文件不命中_所有权隔离()
|
||
{
|
||
var (db, s3, user) = CreateDeps(nameof(Check_他人文件不命中_所有权隔离));
|
||
db.Files.Add(NewFile("another-user", Sha, 100));
|
||
await db.SaveChangesAsync();
|
||
var handler = new CheckFileQueryHandler(db, s3.Object, user.Object);
|
||
|
||
var result = await handler.Handle(new CheckFileQuery(Sha, 100), CancellationToken.None);
|
||
|
||
result.Exists.Should().BeFalse("他人文件不应被秒传命中(所有权隔离)");
|
||
}
|
||
|
||
[Fact]
|
||
public async Task Check_大小不匹配不命中_防哈希碰撞()
|
||
{
|
||
var (db, s3, user) = CreateDeps(nameof(Check_大小不匹配不命中_防哈希碰撞));
|
||
db.Files.Add(NewFile(OwnerId, Sha, 100));
|
||
await db.SaveChangesAsync();
|
||
var handler = new CheckFileQueryHandler(db, s3.Object, user.Object);
|
||
|
||
var result = await handler.Handle(new CheckFileQuery(Sha, 999), CancellationToken.None);
|
||
|
||
result.Exists.Should().BeFalse("大小不同不应命中(防哈希碰撞)");
|
||
}
|
||
|
||
[Fact]
|
||
public async Task Check_文件无哈希不命中()
|
||
{
|
||
var (db, s3, user) = CreateDeps(nameof(Check_文件无哈希不命中));
|
||
db.Files.Add(NewFile(OwnerId, hash: null!, 100));
|
||
await db.SaveChangesAsync();
|
||
var handler = new CheckFileQueryHandler(db, s3.Object, user.Object);
|
||
|
||
var result = await handler.Handle(new CheckFileQuery(Sha, 100), CancellationToken.None);
|
||
|
||
result.Exists.Should().BeFalse("无哈希的老文件不参与秒传查重");
|
||
}
|
||
|
||
[Fact]
|
||
public async Task Check_S3对象已丢失_清理孤儿FileMeta_返回未命中()
|
||
{
|
||
// 缺陷E回归:FileMetaEntity 存在但 S3 对象被外部删除,应清理孤儿并返回未命中(而非误报秒传成功)
|
||
var (db, s3, user) = CreateDeps(nameof(Check_S3对象已丢失_清理孤儿FileMeta_返回未命中));
|
||
db.Files.Add(NewFile(OwnerId, Sha, 100));
|
||
await db.SaveChangesAsync();
|
||
s3.Setup(x => x.ObjectExistsAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||
.ReturnsAsync(false); // S3 对象不存在
|
||
var handler = new CheckFileQueryHandler(db, s3.Object, user.Object);
|
||
|
||
var result = await handler.Handle(new CheckFileQuery(Sha, 100), CancellationToken.None);
|
||
|
||
result.Exists.Should().BeFalse("S3 对象丢失不应误报秒传成功");
|
||
// 孤儿 FileMetaEntity 应被清理
|
||
var remaining = await db.Files.ToListAsync();
|
||
remaining.Should().BeEmpty("孤儿 FileMetaEntity 应被清理");
|
||
}
|
||
}
|