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

93 lines
3.8 KiB
C#

using System.Text;
using File.Infrastructure.Storage;
using FluentAssertions;
using Microsoft.Extensions.Options;
using Moq;
using Xunit;
using Xunit.Abstractions;
namespace BuildFlow.File.Tests;
/// <summary>真实 S3 兼容存储往返测试,供隔离 MinIO SOP 阶段执行。</summary>
public sealed class S3StorageServiceIntegrationTests(ITestOutputHelper output)
{
[Fact]
[Trait("Category", "External")]
public async Task UploadDownloadAndZeroByteObjectsRoundTrip()
{
var endpoint = GetRequiredVariable("BUILDFLOW_FILE_S3_TEST_ENDPOINT");
var accessKey = GetRequiredVariable("BUILDFLOW_FILE_S3_TEST_ACCESS_KEY");
var secretKey = GetRequiredVariable("BUILDFLOW_FILE_S3_TEST_SECRET_KEY");
var options = new Mock<IOptionsMonitor<S3Options>>();
options.SetupGet(x => x.CurrentValue).Returns(new S3Options
{
Endpoint = endpoint,
AccessKey = accessKey,
SecretKey = secretKey,
Region = "us-east-1",
});
var storage = new S3StorageService(options.Object);
var bucket = $"buildflow-sop-{Guid.NewGuid():N}";
var payloadKey = "payload.bin";
var zeroByteKey = "empty.bin";
var bucketCreated = false;
try
{
await storage.CreateBucketAsync(bucket, CancellationToken.None);
bucketCreated = true;
var payload = Encoding.UTF8.GetBytes("BuildFlow isolated MinIO round trip");
await using (var payloadStream = new MemoryStream(payload, writable: false))
{
await storage.UploadFileAsync(
bucket,
payloadKey,
payloadStream,
"application/octet-stream",
CancellationToken.None);
}
var download = await storage.DownloadFileAsync(bucket, payloadKey, CancellationToken.None);
await using (download.Stream)
await using (var downloaded = new MemoryStream())
{
await download.Stream.CopyToAsync(downloaded, CancellationToken.None);
downloaded.ToArray().Should().Equal(payload);
}
download.ContentType.Should().Be("application/octet-stream");
await using (var emptyStream = new MemoryStream(Array.Empty<byte>(), writable: false))
{
await storage.UploadFileAsync(
bucket,
zeroByteKey,
emptyStream,
"application/octet-stream",
CancellationToken.None);
}
(await storage.ObjectExistsAsync(bucket, zeroByteKey, CancellationToken.None)).Should().BeTrue();
(await storage.GetObjectSizeAsync(bucket, zeroByteKey, CancellationToken.None)).Should().Be(0);
var listed = await storage.ListObjectsAsync(bucket, null, 100, null, CancellationToken.None);
listed.Files.Select(x => x.Key).Should().BeEquivalentTo(payloadKey, zeroByteKey);
output.WriteLine($"SOP_METRIC s3_object_count={listed.Files.Count}");
output.WriteLine($"SOP_METRIC s3_object_bytes={payload.Length}");
}
finally
{
if (bucketCreated)
{
await storage.DeleteFileAsync(bucket, payloadKey, CancellationToken.None);
await storage.DeleteFileAsync(bucket, zeroByteKey, CancellationToken.None);
await storage.DeleteBucketAsync(bucket, CancellationToken.None);
}
}
}
private static string GetRequiredVariable(string name)
=> Environment.GetEnvironmentVariable(name)
?? throw new InvalidOperationException($"必须设置环境变量 {name} 才能运行真实 S3 集成测试。");
}