build-flow.api/tests/BuildFlow.File.Tests/CheckFileQueryHandlerTests.cs

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;
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 FileMeta 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回归:FileMeta 存在但 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 对象丢失不应误报秒传成功");
// 孤儿 FileMeta 应被清理
var remaining = await db.Files.ToListAsync();
remaining.Should().BeEmpty("孤儿 FileMeta 应被清理");
}
}