build-flow.api/tests/BuildFlow.File.Tests/CheckFileQueryHandlerTests.cs
xiangning 6c110b579a refactor(dependencies): 移除未使用的域实体引用
- 从文件模块的多部分上传处理器中移除未使用的实体引用
- 从即时通讯模块的群组成员处理器中移除未使用的实体引用
- 从RAG模块的AI模型配置中移除未使用的实体引用
- 从监控模块的告警规则配置中移除未使用的实体引用
- 从平台模块的角色权限处理器中移除未使用的实体引用
- 从脚本模块的执行日志实体引用中进行清理
- 从多个基础设施配置类中移除未使用的依赖项
- 优化测试文件中的导入声明,移除不必要的引用
2026-07-19 14:09:47 +08:00

139 lines
5.5 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 应被清理");
}
}