HaniBlindBox/server/HoneyBox/tests/HoneyBox.Tests/Services/TencentCosProviderTests.cs
2026-01-19 15:05:52 +08:00

306 lines
10 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 FsCheck;
using FsCheck.Xunit;
using HoneyBox.Admin.Business.Models.Config;
using HoneyBox.Admin.Business.Services.Storage;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;
namespace HoneyBox.Tests.Services;
/// <summary>
/// TencentCosProvider 单元测试
/// 注意: 由于COS SDK依赖UploadAsync和DeleteAsync方法的测试需要在集成测试中进行
/// 这里只测试静态辅助方法和URL生成逻辑
/// </summary>
public class TencentCosProviderTests
{
#region URL Generation Tests
[Theory]
[InlineData("https://cdn.example.com", "uploads/2026/01/19/test.jpg", "https://cdn.example.com/uploads/2026/01/19/test.jpg")]
[InlineData("https://cdn.example.com/", "uploads/2026/01/19/test.jpg", "https://cdn.example.com/uploads/2026/01/19/test.jpg")]
[InlineData("cdn.example.com", "uploads/2026/01/19/test.jpg", "https://cdn.example.com/uploads/2026/01/19/test.jpg")]
[InlineData("http://cdn.example.com", "uploads/2026/01/19/test.jpg", "http://cdn.example.com/uploads/2026/01/19/test.jpg")]
[InlineData("https://cdn.example.com", "/uploads/2026/01/19/test.jpg", "https://cdn.example.com/uploads/2026/01/19/test.jpg")]
public void GenerateAccessUrl_ShouldFormatCorrectly(string domain, string objectKey, string expectedUrl)
{
// Act
var result = TencentCosProvider.GenerateAccessUrl(domain, objectKey);
// Assert
Assert.Equal(expectedUrl, result);
}
[Fact]
public void GenerateUniqueFileName_ShouldPreserveExtension()
{
// Arrange
var originalFileName = "test.jpg";
// Act
var result = TencentCosProvider.GenerateUniqueFileName(originalFileName);
// Assert
Assert.EndsWith(".jpg", result);
}
[Theory]
[InlineData("test.JPG", ".jpg")]
[InlineData("test.JPEG", ".jpeg")]
[InlineData("test.PNG", ".png")]
[InlineData("test.GIF", ".gif")]
[InlineData("test.WebP", ".webp")]
public void GenerateUniqueFileName_ShouldNormalizeExtensionToLowercase(string fileName, string expectedExtension)
{
// Act
var result = TencentCosProvider.GenerateUniqueFileName(fileName);
// Assert
Assert.EndsWith(expectedExtension, result);
}
[Fact]
public void GenerateUniqueFileName_ShouldGenerateDifferentNamesForSameFile()
{
// Arrange
var fileName = "same_file.jpg";
// Act
var result1 = TencentCosProvider.GenerateUniqueFileName(fileName);
Thread.Sleep(1); // 确保时间戳不同
var result2 = TencentCosProvider.GenerateUniqueFileName(fileName);
// Assert
Assert.NotEqual(result1, result2);
}
[Fact]
public void GenerateUniqueFileName_ShouldContainTimestamp()
{
// Arrange
var fileName = "test.jpg";
var now = DateTime.Now;
// Act
var result = TencentCosProvider.GenerateUniqueFileName(fileName);
// Assert
// 文件名格式: {timestamp}_{guid}.jpg
// timestamp格式: yyyyMMddHHmmssfff
Assert.Contains(now.ToString("yyyyMMdd"), result);
}
[Fact]
public void GenerateUniqueFileName_ShouldContainGuidPart()
{
// Arrange
var fileName = "test.jpg";
// Act
var result = TencentCosProvider.GenerateUniqueFileName(fileName);
// Assert
// 文件名格式: {timestamp}_{guid}.jpg
// 应该包含下划线分隔符
Assert.Contains("_", result);
// 下划线后应该有8个字符GUID前8位+ 扩展名
var parts = result.Split('_');
Assert.Equal(2, parts.Length);
}
[Fact]
public void GenerateAccessUrl_ShouldHandleDomainWithoutProtocol()
{
// Arrange
var domain = "cdn.example.com";
var objectKey = "uploads/test.jpg";
// Act
var result = TencentCosProvider.GenerateAccessUrl(domain, objectKey);
// Assert
Assert.StartsWith("https://", result);
}
[Fact]
public void GenerateAccessUrl_ShouldPreserveHttpProtocol()
{
// Arrange
var domain = "http://cdn.example.com";
var objectKey = "uploads/test.jpg";
// Act
var result = TencentCosProvider.GenerateAccessUrl(domain, objectKey);
// Assert
Assert.StartsWith("http://", result);
Assert.DoesNotContain("https://", result);
}
[Fact]
public void GenerateAccessUrl_ShouldRemoveTrailingSlashFromDomain()
{
// Arrange
var domain = "https://cdn.example.com/";
var objectKey = "uploads/test.jpg";
// Act
var result = TencentCosProvider.GenerateAccessUrl(domain, objectKey);
// Assert
Assert.DoesNotContain("//uploads", result);
}
[Fact]
public void GenerateAccessUrl_ShouldHandleObjectKeyWithLeadingSlash()
{
// Arrange
var domain = "https://cdn.example.com";
var objectKey = "/uploads/test.jpg";
// Act
var result = TencentCosProvider.GenerateAccessUrl(domain, objectKey);
// Assert
Assert.Equal("https://cdn.example.com/uploads/test.jpg", result);
}
#endregion
}
/// <summary>
/// TencentCosProvider 属性测试
/// **Property 4: 存储策略一致性 - COS URL格式**
/// **Validates: Requirements 3.3, 3.4**
/// </summary>
public class TencentCosProviderPropertyTests
{
/// <summary>
/// **Feature: image-upload-feature, Property 4: 存储策略一致性 - COS URL格式**
/// *For any* 有效的Domain和objectKey生成的URL SHALL 以配置的Domain开头
/// **Validates: Requirements 3.3**
/// </summary>
[Property(MaxTest = 100)]
public bool CosStorage_UrlFormat_ShouldStartWithDomain(PositiveInt seed)
{
// 生成测试数据
var domains = new[]
{
"https://cdn.example.com",
"https://bucket.cos.ap-guangzhou.myqcloud.com",
"cdn.test.com",
"http://cdn.local.com"
};
var domain = domains[seed.Get % domains.Length];
var objectKey = $"uploads/2026/01/{(seed.Get % 28) + 1:D2}/test_{seed.Get}.jpg";
// Act
var url = TencentCosProvider.GenerateAccessUrl(domain, objectKey);
// 验证URL以正确的协议和域名开头
var normalizedDomain = domain.TrimEnd('/');
if (!normalizedDomain.StartsWith("http://", StringComparison.OrdinalIgnoreCase) &&
!normalizedDomain.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
{
normalizedDomain = $"https://{normalizedDomain}";
}
return url.StartsWith(normalizedDomain);
}
/// <summary>
/// **Feature: image-upload-feature, Property 4: 存储策略一致性 - COS URL格式**
/// *For any* 有效的Domain和objectKey生成的URL SHALL 包含完整的对象路径
/// **Validates: Requirements 3.3**
/// </summary>
[Property(MaxTest = 100)]
public bool CosStorage_UrlFormat_ShouldContainObjectKey(PositiveInt seed)
{
var domain = "https://cdn.example.com";
var objectKey = $"uploads/2026/01/{(seed.Get % 28) + 1:D2}/file_{seed.Get}.jpg";
// Act
var url = TencentCosProvider.GenerateAccessUrl(domain, objectKey);
// 验证URL包含对象路径
var normalizedKey = objectKey.StartsWith('/') ? objectKey : $"/{objectKey}";
return url.EndsWith(normalizedKey) || url.Contains(objectKey);
}
/// <summary>
/// **Feature: image-upload-feature, Property 3: 文件名唯一性**
/// *For any* 两次调用GenerateUniqueFileName即使使用相同的原始文件名生成的文件名 SHALL 不同
/// **Validates: Requirements 1.5**
/// </summary>
[Property(MaxTest = 100)]
public bool CosStorage_FileNames_ShouldBeUnique(PositiveInt seed)
{
var extensions = new[] { ".jpg", ".jpeg", ".png", ".gif", ".webp" };
var extension = extensions[seed.Get % extensions.Length];
var fileName = $"test{extension}";
// 生成两个文件名
var name1 = TencentCosProvider.GenerateUniqueFileName(fileName);
Thread.Sleep(1); // 确保时间戳不同
var name2 = TencentCosProvider.GenerateUniqueFileName(fileName);
// 验证两个文件名不同
return name1 != name2;
}
/// <summary>
/// **Feature: image-upload-feature, Property 4: 存储策略一致性 - COS URL格式**
/// *For any* 生成的文件名SHALL 保留原始文件的扩展名(小写)
/// **Validates: Requirements 3.3**
/// </summary>
[Property(MaxTest = 100)]
public bool CosStorage_FileName_ShouldPreserveExtension(PositiveInt seed)
{
var extensions = new[] { ".jpg", ".jpeg", ".png", ".gif", ".webp", ".JPG", ".PNG", ".WEBP" };
var extension = extensions[seed.Get % extensions.Length];
var fileName = $"original_{seed.Get}{extension}";
// Act
var uniqueName = TencentCosProvider.GenerateUniqueFileName(fileName);
// 验证扩展名被保留(小写)
var expectedExtension = extension.ToLowerInvariant();
return uniqueName.EndsWith(expectedExtension);
}
/// <summary>
/// **Feature: image-upload-feature, Property 4: 存储策略一致性 - COS URL格式**
/// *For any* 生成的URLSHALL 不包含双斜杠(除了协议部分)
/// **Validates: Requirements 3.3**
/// </summary>
[Property(MaxTest = 100)]
public bool CosStorage_UrlFormat_ShouldNotContainDoubleSlashes(PositiveInt seed)
{
var domains = new[]
{
"https://cdn.example.com",
"https://cdn.example.com/",
"cdn.test.com",
"cdn.test.com/"
};
var domain = domains[seed.Get % domains.Length];
var objectKeys = new[]
{
"uploads/2026/01/19/test.jpg",
"/uploads/2026/01/19/test.jpg"
};
var objectKey = objectKeys[seed.Get % objectKeys.Length];
// Act
var url = TencentCosProvider.GenerateAccessUrl(domain, objectKey);
// 移除协议部分后检查是否有双斜杠
var urlWithoutProtocol = url.Replace("https://", "").Replace("http://", "");
return !urlWithoutProtocol.Contains("//");
}
}