using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Dpz.Core.Entity.Base;
using Dpz.Core.Entity.Base.Image;
using Dpz.Core.Infrastructure;
using Dpz.Core.Service.ObjectStorage;
using Dpz.Core.Shard.Service;
using Microsoft.Extensions.Logging.Abstractions;
using NUnit.Framework;
namespace Dpz.Core.Server.ObjectStorage.Test;
[TestFixture]
public class UpyunUploadMetadataTest
{
[Test]
public async Task UploadAsync_ImageHeaders_ReturnsImageMetadata()
{
using var httpClient = CreateHttpClient(() =>
{
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new ByteArrayContent([]),
};
response.Headers.Add("x-upyun-file-type", "png");
response.Headers.Add("x-upyun-width", "640");
response.Headers.Add("x-upyun-height", "480");
response.Headers.Add("x-upyun-frames", "1");
return response;
});
var upload = new UpyunUpload(httpClient, NullLogger<UpyunUpload>.Instance);
var result = await upload.UploadAsync(
["images", "demo.png"],
() => new ByteArrayContent([1, 2, 3]),
CreateOperator(),
size: 3,
hash: "local-md5"
);
Assert.That(result, Is.TypeOf<ImageMetadata>());
var image = (ImageMetadata)result;
Assert.Multiple(() =>
{
Assert.That(image.Url, Is.EqualTo("https://cdn.example.com/images/demo.png"));
Assert.That(image.Width, Is.EqualTo(640));
Assert.That(image.Height, Is.EqualTo(480));
Assert.That(image.Frames, Is.EqualTo(1));
Assert.That(image.Hash, Is.EqualTo("local-md5"));
Assert.That(image.Size, Is.EqualTo(3));
Assert.That(image.Format.Kind, Is.EqualTo(ImageFormatKind.Png));
});
}
[Test]
public async Task UploadAsync_NonImageHeaders_ReturnsFileMetadata()
{
using var httpClient = CreateHttpClient(() =>
new HttpResponseMessage(HttpStatusCode.OK) { Content = new ByteArrayContent([]) }
);
var upload = new UpyunUpload(httpClient, NullLogger<UpyunUpload>.Instance);
var result = await upload.UploadAsync(
["files", "readme.txt"],
() => new StringContent("hello"),
CreateOperator(),
size: 5,
hash: "local-md5"
);
Assert.That(result, Is.TypeOf<FileMetadata>());
var file = (FileMetadata)result;
Assert.Multiple(() =>
{
Assert.That(file.Url, Is.EqualTo("https://cdn.example.com/files/readme.txt"));
Assert.That(file.Size, Is.EqualTo(5));
Assert.That(file.Hash, Is.EqualTo("local-md5"));
});
}
[Test]
public async Task UploadFileAsync_SmallFile_UsesMetadataUrl()
{
using var httpClient = CreateHttpClient(() => throw new InvalidOperationException());
var uploader = new ParallelChunkBreakpointUpload<UpyunOperator>(
NullLogger<ParallelChunkBreakpointUpload<UpyunOperator>>.Instance,
httpClient,
CreateOperator()
);
await using var stream = new MemoryStream([1, 2, 3]);
var cloudFile = new CloudFile(stream, ["files", "small.txt"]);
var result = await uploader.UploadFileAsync(
cloudFile,
(_, _, size, hash, _, _) =>
Task.FromResult<IFileMetadata>(
new FileMetadata("https://cdn.example.com/files/small.txt", size, hash)
)
);
Assert.That(result, Is.Not.Null);
Assert.Multiple(() =>
{
Assert.That(result!.Value.Url, Is.EqualTo("https://cdn.example.com/files/small.txt"));
Assert.That(result.Value.Md5, Is.Not.Null.And.Not.Empty);
});
}
private static UpyunOperator CreateOperator() =>
new()
{
Bucket = "bucket",
Host = "https://cdn.example.com",
Operator = "operator",
Password = "password",
};
private static HttpClient CreateHttpClient(Func<HttpResponseMessage> responseFactory) =>
new(new StubHttpMessageHandler(responseFactory))
{
BaseAddress = new Uri("https://v0.api.upyun.com"),
};
private sealed class StubHttpMessageHandler(Func<HttpResponseMessage> responseFactory)
: HttpMessageHandler
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken
)
{
return Task.FromResult(responseFactory());
}
}
}
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
代码解释
这是一个针对又拍云(Upyun)对象存储服务上传功能的单元测试类,使用 NUnit 测试框架编写。主要测试上传文件时元数据的处理逻辑。
主要组成部分
1. 测试类结构
[TestFixture]
public class UpyunUploadMetadataTest
- 使用
[TestFixture]标记为 NUnit 测试类 - 专门测试
UpyunUpload类的元数据处理功能
2. 三个核心测试方法
Test 1: UploadAsync_ImageHeaders_ReturnsImageMetadata
测试目的:验证上传图片文件时,能正确解析又拍云返回的图片元数据
测试流程:
- 创建模拟 HTTP 客户端,返回包含图片信息的响应头:
x-upyun-file-type: pngx-upyun-width: 640x-upyun-height: 480x-upyun-frames: 1
- 调用上传方法
- 断言返回结果是
ImageMetadata类型 - 验证图片元数据的各个字段(URL、宽度、高度、帧数、哈希值、大小、格式等)
Test 2: UploadAsync_NonImageHeaders_ReturnsFileMetadata
测试目的:验证上传非图片文件时,返回基础文件元数据
测试流程:
- 创建模拟 HTTP 客户端,返回不包含图片特定头的响应
- 上传文本文件
- 断言返回结果是
FileMetadata类型 - 验证文件元数据(URL、大小、哈希值)
Test 3: UploadFileAsync_SmallFile_UsesMetadataUrl
测试目的:验证小文件上传时使用元数据中的 URL
测试流程:
- 创建会抛出异常的模拟客户端(确保不会真正发送 HTTP 请求)
- 使用
ParallelChunkBreakpointUpload上传小文件 - 验证返回的 URL 和 MD5 值
3. 辅助方法
CreateOperator()
创建又拍云操作配置对象,包含:
Bucket: 存储空间名称Host: CDN 域名Operator: 操作员账号Password: 操作员密码
CreateHttpClient()
创建带有自定义响应的 HTTP 客户端,用于模拟又拍云 API 的响应
StubHttpMessageHandler
核心测试辅助类,继承 HttpMessageHandler:
- 通过工厂函数
responseFactory自定义 HTTP 响应 - 避免真实的网络请求
- 实现可控的测试环境
技术要点
- 依赖注入模拟:使用
NullLogger避免测试中的日志依赖 - HTTP 客户端模拟:通过自定义
HttpMessageHandler实现完全可控的 HTTP 响应 - 资源管理:使用
using和await using确保资源正确释放 - 断言组织:使用
Assert.Multiple批量验证多个条件 - 类型检查:验证返回的元数据类型是否符合预期(
ImageMetadatavsFileMetadata)
测试价值
- ✅ 验证图片和普通文件的区分逻辑
- ✅ 确保元数据正确解析
- ✅ 测试小文件上传的特殊处理
- ✅ 无需真实网络环境即可完成测试
AI 正在分析代码…
评论加载中...