WorkCamera/server/Zr.Admin.NET/ZR.Tests/MigrationUrlUpdatePropertyTests.cs
2026-01-05 21:20:55 +08:00

315 lines
12 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 Xunit;
using ZR.Model.Business.Dto;
namespace ZR.Tests
{
/// <summary>
/// 迁移URL更新完整性属性测试
/// **Feature: work-camera-2.0, Property 7: Migration URL Update Integrity**
/// **Validates: Requirements 10.3, 10.4**
/// </summary>
public class MigrationUrlUpdatePropertyTests
{
/// <summary>
/// COS域名URL
/// </summary>
private const string CosDomainUrl = "https://miaoyu-1308826010.cos.ap-shanghai.myqcloud.com";
/// <summary>
/// COS路径前缀
/// </summary>
private const string CosPrefixes = "workfiles";
/// <summary>
/// 旧服务器URL前缀
/// </summary>
private const string OldServerUrl = "http://old-server.com";
/// <summary>
/// Property 7: Migration URL Update Integrity - Valid COS URL Validation
///
/// *For any* migration update request,
/// the system SHALL validate all new URLs are valid COS URLs.
///
/// **Validates: Requirements 10.3, 10.4**
/// </summary>
[Property(MaxTest = 100)]
public Property ValidCosUrl_ShouldBeAccepted()
{
// Generate valid COS URL components
var urlGen = from year in Gen.Choose(2020, 2025)
from month in Gen.Choose(1, 12)
from day in Gen.Choose(1, 28)
from timestamp in Gen.Choose(100000000, 999999999)
from random in Gen.Choose(1000, 9999)
select new { year, month, day, timestamp, random };
return Prop.ForAll(
urlGen.ToArbitrary(),
u =>
{
var dateFolder = $"{u.year}{u.month:D2}";
var dayFolder = $"{u.year}{u.month:D2}{u.day:D2}";
var fileName = $"{u.timestamp}_{u.random}.jpg";
var validCosUrl = $"{CosDomainUrl}/{CosPrefixes}/{dateFolder}/{dayFolder}/当日照片/{fileName}";
var isValid = IsValidCosUrl(validCosUrl);
if (!isValid)
return false.Label($"Valid COS URL '{validCosUrl}' should be accepted");
return true.Label("Valid COS URL accepted");
});
}
/// <summary>
/// Property 7: Migration URL Update Integrity - Invalid URL Rejection
///
/// *For any* URL that doesn't match the COS domain pattern,
/// the system SHALL reject it.
///
/// **Validates: Requirements 10.3, 10.4**
/// </summary>
[Property(MaxTest = 100)]
public Property InvalidUrl_ShouldBeRejected()
{
// Generate various invalid URLs
var invalidUrlGen = Gen.OneOf(
// Old server URLs
Gen.Constant($"{OldServerUrl}/images/test.jpg"),
// Wrong domain
Gen.Constant("https://other-bucket.cos.ap-shanghai.myqcloud.com/workfiles/202501/20250101/test.jpg"),
// Missing prefix
Gen.Constant($"{CosDomainUrl}/other/202501/20250101/test.jpg"),
// HTTP instead of HTTPS
Gen.Constant("http://miaoyu-1308826010.cos.ap-shanghai.myqcloud.com/workfiles/202501/20250101/test.jpg"),
// Empty URL
Gen.Constant(""),
// Null-like
Gen.Constant(" ")
);
return Prop.ForAll(
invalidUrlGen.ToArbitrary(),
invalidUrl =>
{
var isValid = IsValidCosUrl(invalidUrl);
if (isValid)
return false.Label($"Invalid URL '{invalidUrl}' should be rejected");
return true.Label("Invalid URL rejected");
});
}
/// <summary>
/// Property 7: Migration URL Update Integrity - URL Pair Mapping Preservation
///
/// *For any* migration update request with N URL pairs,
/// the system SHALL preserve the mapping between old and new URLs.
///
/// **Validates: Requirements 10.3, 10.4**
/// </summary>
[Property(MaxTest = 100)]
public Property UrlPairMapping_ShouldBePreserved()
{
// Generate URL pair count
var pairCountGen = Gen.Choose(1, 5);
return Prop.ForAll(
pairCountGen.ToArbitrary(),
pairCount =>
{
var dto = new MigrationUpdateDto
{
RecordId = 1,
ImageUrls = new List<MigrationUrlPair>()
};
// Generate URL pairs
for (int i = 0; i < pairCount; i++)
{
var timestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds() + i;
var random = new System.Random().Next(1000, 9999);
dto.ImageUrls.Add(new MigrationUrlPair
{
OldUrl = $"{OldServerUrl}/images/image{i}.jpg",
NewUrl = $"{CosDomainUrl}/{CosPrefixes}/202501/20250101/当日照片/{timestamp}_{random}.jpg"
});
}
// Verify: All pairs should have distinct old URLs
var distinctOldUrls = dto.ImageUrls.Select(p => p.OldUrl).Distinct().Count();
if (distinctOldUrls != pairCount)
return false.Label($"Expected {pairCount} distinct old URLs, got {distinctOldUrls}");
// Verify: All pairs should have distinct new URLs
var distinctNewUrls = dto.ImageUrls.Select(p => p.NewUrl).Distinct().Count();
if (distinctNewUrls != pairCount)
return false.Label($"Expected {pairCount} distinct new URLs, got {distinctNewUrls}");
// Verify: Each pair maintains its mapping
for (int i = 0; i < pairCount; i++)
{
var pair = dto.ImageUrls[i];
if (!pair.OldUrl.Contains($"image{i}"))
return false.Label($"Pair {i} old URL mapping broken");
}
return true.Label($"URL pair mapping preserved for {pairCount} pairs");
});
}
/// <summary>
/// Property 7: Migration URL Update Integrity - All URLs Must Be Valid
///
/// *For any* migration update request,
/// if ANY new URL is invalid, the entire update SHALL fail.
///
/// **Validates: Requirements 10.3, 10.4**
/// </summary>
[Property(MaxTest = 100)]
public Property AllUrlsMustBeValid_OrUpdateFails()
{
// Generate URL pair count and position of invalid URL
var testGen = from pairCount in Gen.Choose(2, 5)
from invalidPosition in Gen.Choose(0, pairCount - 1)
select new { pairCount, invalidPosition };
return Prop.ForAll(
testGen.ToArbitrary(),
t =>
{
var dto = new MigrationUpdateDto
{
RecordId = 1,
ImageUrls = new List<MigrationUrlPair>()
};
// Generate URL pairs with one invalid
for (int i = 0; i < t.pairCount; i++)
{
var timestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds() + i;
var random = new System.Random().Next(1000, 9999);
var newUrl = i == t.invalidPosition
? $"{OldServerUrl}/images/invalid{i}.jpg" // Invalid URL
: $"{CosDomainUrl}/{CosPrefixes}/202501/20250101/当日照片/{timestamp}_{random}.jpg"; // Valid URL
dto.ImageUrls.Add(new MigrationUrlPair
{
OldUrl = $"{OldServerUrl}/images/image{i}.jpg",
NewUrl = newUrl
});
}
// Validate all URLs
var allValid = dto.ImageUrls.All(p => IsValidCosUrl(p.NewUrl));
if (allValid)
return false.Label($"Should have detected invalid URL at position {t.invalidPosition}");
// Verify: The invalid URL is at the expected position
var invalidUrl = dto.ImageUrls[t.invalidPosition].NewUrl;
if (IsValidCosUrl(invalidUrl))
return false.Label($"URL at position {t.invalidPosition} should be invalid");
return true.Label($"Correctly detected invalid URL at position {t.invalidPosition}");
});
}
/// <summary>
/// Property 7: Migration URL Update Integrity - Empty Request Handling
///
/// *For any* migration update request with empty or null ImageUrls,
/// the system SHALL reject the request.
///
/// **Validates: Requirements 10.3, 10.4**
/// </summary>
[Fact]
public void EmptyImageUrls_ShouldBeRejected()
{
var dto1 = new MigrationUpdateDto
{
RecordId = 1,
ImageUrls = null
};
var dto2 = new MigrationUpdateDto
{
RecordId = 1,
ImageUrls = new List<MigrationUrlPair>()
};
// Verify: Both should be considered invalid
var isValid1 = ValidateUpdateRequest(dto1);
var isValid2 = ValidateUpdateRequest(dto2);
Assert.False(isValid1, "Null ImageUrls should be rejected");
Assert.False(isValid2, "Empty ImageUrls should be rejected");
}
/// <summary>
/// Property 7: Migration URL Update Integrity - Invalid RecordId Handling
///
/// *For any* migration update request with invalid RecordId,
/// the system SHALL reject the request.
///
/// **Validates: Requirements 10.3, 10.4**
/// </summary>
[Theory]
[InlineData(0)]
[InlineData(-1)]
[InlineData(-100)]
public void InvalidRecordId_ShouldBeRejected(int recordId)
{
var dto = new MigrationUpdateDto
{
RecordId = recordId,
ImageUrls = new List<MigrationUrlPair>
{
new MigrationUrlPair
{
OldUrl = $"{OldServerUrl}/images/test.jpg",
NewUrl = $"{CosDomainUrl}/{CosPrefixes}/202501/20250101/当日照片/test.jpg"
}
}
};
var isValid = ValidateUpdateRequest(dto);
Assert.False(isValid, $"RecordId {recordId} should be rejected");
}
/// <summary>
/// 验证是否为有效的COS URL与CosService.IsValidCosUrl逻辑一致
/// </summary>
private bool IsValidCosUrl(string url)
{
if (string.IsNullOrWhiteSpace(url))
return false;
return url.StartsWith($"{CosDomainUrl}/{CosPrefixes}/");
}
/// <summary>
/// 验证更新请求是否有效
/// </summary>
private bool ValidateUpdateRequest(MigrationUpdateDto dto)
{
if (dto == null)
return false;
if (dto.RecordId <= 0)
return false;
if (dto.ImageUrls == null || dto.ImageUrls.Count == 0)
return false;
return true;
}
}
}