WorkCamera/client/WorkCameraExport.Tests/ImageUploadPropertyTests.cs
2026-01-05 23:58:56 +08:00

422 lines
16 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 WorkCameraExport.Forms;
using Xunit;
using SystemRandom = System.Random;
namespace WorkCameraExport.Tests
{
/// <summary>
/// 图片上传数量属性测试
/// Feature: work-camera-2.0.1, Property 3: 图片上传数量限制
/// Feature: work-camera-2.0.1, Property 4: 图片删除后列表更新
/// Validates: Requirements 4.5, 4.6
/// </summary>
public class ImageUploadPropertyTests
{
private const int MaxImageCount = 15;
#region Property 3:
/// <summary>
/// Property 3: 图片上传数量限制
/// For any 上传操作,图片列表长度应始终不超过 15。
/// 当尝试添加图片使总数超过 15 时,应拒绝添加并提示用户。
/// </summary>
[Property(MaxTest = 100)]
public Property ImageUpload_ShouldNeverExceedMaxCount()
{
return Prop.ForAll(
Gen.Choose(0, 20).ToArbitrary(), // 初始图片数量 0-20
Gen.Choose(1, 10).ToArbitrary(), // 尝试添加的图片数量 1-10
(initialCount, addCount) =>
{
// 模拟图片列表
var imageList = new List<ImageItem>();
// 添加初始图片(模拟已有图片)
var actualInitial = Math.Min(initialCount, MaxImageCount);
for (int i = 0; i < actualInitial; i++)
{
imageList.Add(new ImageItem { SortOrder = i, IsExisting = true });
}
// 计算可添加的数量
var remainingSlots = MaxImageCount - imageList.Count;
var canAdd = Math.Min(addCount, remainingSlots);
// 模拟添加图片
for (int i = 0; i < canAdd; i++)
{
imageList.Add(new ImageItem { SortOrder = imageList.Count, IsExisting = false });
}
// 验证:图片数量不超过最大值
var countWithinLimit = imageList.Count <= MaxImageCount;
return countWithinLimit
.Label($"Initial: {actualInitial}, Tried to add: {addCount}, " +
$"Actually added: {canAdd}, Final count: {imageList.Count} (max: {MaxImageCount})");
});
}
/// <summary>
/// Property 3 扩展: 当已达到最大数量时,不应添加任何图片
/// </summary>
[Property(MaxTest = 100)]
public Property ImageUpload_WhenAtMax_ShouldNotAddMore()
{
return Prop.ForAll(
Gen.Choose(1, 10).ToArbitrary(), // 尝试添加的图片数量
(addCount) =>
{
// 模拟已满的图片列表
var imageList = new List<ImageItem>();
for (int i = 0; i < MaxImageCount; i++)
{
imageList.Add(new ImageItem { SortOrder = i, IsExisting = true });
}
var initialCount = imageList.Count;
// 计算可添加的数量应该是0
var remainingSlots = MaxImageCount - imageList.Count;
var canAdd = Math.Min(addCount, remainingSlots);
// 验证:不应添加任何图片
var noAddition = canAdd == 0;
var countUnchanged = imageList.Count == initialCount;
return (noAddition && countUnchanged)
.Label($"Tried to add: {addCount}, Can add: {canAdd}, " +
$"Count unchanged: {countUnchanged}");
});
}
/// <summary>
/// Property 3 扩展: 添加的图片数量应等于 min(请求数量, 剩余槽位)
/// </summary>
[Property(MaxTest = 100)]
public Property ImageUpload_AddedCount_ShouldEqualMinOfRequestedAndRemaining()
{
return Prop.ForAll(
Gen.Choose(0, MaxImageCount).ToArbitrary(), // 初始图片数量
Gen.Choose(1, 20).ToArbitrary(), // 尝试添加的图片数量
(initialCount, addCount) =>
{
var remainingSlots = MaxImageCount - initialCount;
var expectedAdded = Math.Min(addCount, Math.Max(0, remainingSlots));
var expectedFinal = initialCount + expectedAdded;
// 验证计算逻辑
var addedCorrect = expectedAdded == Math.Min(addCount, Math.Max(0, remainingSlots));
var finalCorrect = expectedFinal <= MaxImageCount;
return (addedCorrect && finalCorrect)
.Label($"Initial: {initialCount}, Requested: {addCount}, " +
$"Expected added: {expectedAdded}, Expected final: {expectedFinal}");
});
}
/// <summary>
/// Property 3 扩展: 边界值测试 - 恰好 15 张图片
/// </summary>
[Fact]
public void ImageUpload_ExactlyMax_ShouldNotAllowMore()
{
var imageList = new List<ImageItem>();
for (int i = 0; i < MaxImageCount; i++)
{
imageList.Add(new ImageItem { SortOrder = i });
}
var remainingSlots = MaxImageCount - imageList.Count;
Assert.Equal(MaxImageCount, imageList.Count);
Assert.Equal(0, remainingSlots);
}
/// <summary>
/// Property 3 扩展: 边界值测试 - 14 张图片,添加 1 张
/// </summary>
[Fact]
public void ImageUpload_OneBelowMax_ShouldAllowOneMore()
{
var imageList = new List<ImageItem>();
for (int i = 0; i < MaxImageCount - 1; i++)
{
imageList.Add(new ImageItem { SortOrder = i });
}
var remainingSlots = MaxImageCount - imageList.Count;
Assert.Equal(MaxImageCount - 1, imageList.Count);
Assert.Equal(1, remainingSlots);
}
#endregion
#region Property 4:
/// <summary>
/// Property 4: 图片删除后列表更新
/// For any 图片删除操作,删除后图片列表长度应减少 1
/// 且被删除的图片不应存在于列表中。
/// </summary>
[Property(MaxTest = 100)]
public Property ImageDelete_ShouldReduceCountByOne()
{
return Prop.ForAll(
Gen.Choose(1, MaxImageCount).ToArbitrary(), // 初始图片数量 1-15
(initialCount) =>
{
// 创建图片列表
var imageList = new List<ImageItem>();
for (int i = 0; i < initialCount; i++)
{
imageList.Add(new ImageItem
{
Id = i,
SortOrder = i,
Url = $"http://example.com/image{i}.jpg"
});
}
// 随机选择一个索引删除
var deleteIndex = new SystemRandom().Next(0, imageList.Count);
var deletedItem = imageList[deleteIndex];
// 执行删除
imageList.RemoveAt(deleteIndex);
// 验证:数量减少 1
var countReduced = imageList.Count == initialCount - 1;
// 验证:被删除的图片不在列表中
var itemRemoved = !imageList.Any(i => i.Id == deletedItem.Id);
return (countReduced && itemRemoved)
.Label($"Initial: {initialCount}, After delete: {imageList.Count}, " +
$"Deleted item removed: {itemRemoved}");
});
}
/// <summary>
/// Property 4 扩展: 删除后排序应保持连续
/// </summary>
[Property(MaxTest = 100)]
public Property ImageDelete_ShouldMaintainSortOrder()
{
return Prop.ForAll(
Gen.Choose(2, MaxImageCount).ToArbitrary(), // 初始图片数量 2-15至少2张才能测试删除后排序
(initialCount) =>
{
// 创建图片列表
var imageList = new List<ImageItem>();
for (int i = 0; i < initialCount; i++)
{
imageList.Add(new ImageItem { Id = i, SortOrder = i });
}
// 随机选择一个索引删除
var deleteIndex = new SystemRandom().Next(0, imageList.Count);
imageList.RemoveAt(deleteIndex);
// 重新排序
for (int i = 0; i < imageList.Count; i++)
{
imageList[i].SortOrder = i;
}
// 验证:排序是连续的 0, 1, 2, ...
var sortOrderCorrect = true;
for (int i = 0; i < imageList.Count; i++)
{
if (imageList[i].SortOrder != i)
{
sortOrderCorrect = false;
break;
}
}
return sortOrderCorrect
.Label($"Initial: {initialCount}, After delete: {imageList.Count}, " +
$"Sort order correct: {sortOrderCorrect}");
});
}
/// <summary>
/// Property 4 扩展: 删除所有图片后列表应为空
/// </summary>
[Property(MaxTest = 100)]
public Property ImageDelete_AllImages_ShouldResultInEmptyList()
{
return Prop.ForAll(
Gen.Choose(1, MaxImageCount).ToArbitrary(), // 初始图片数量
(initialCount) =>
{
// 创建图片列表
var imageList = new List<ImageItem>();
for (int i = 0; i < initialCount; i++)
{
imageList.Add(new ImageItem { Id = i, SortOrder = i });
}
// 删除所有图片
while (imageList.Count > 0)
{
imageList.RemoveAt(0);
}
// 验证:列表为空
return (imageList.Count == 0)
.Label($"Initial: {initialCount}, After deleting all: {imageList.Count}");
});
}
/// <summary>
/// Property 4 扩展: 删除无效索引应不改变列表
/// </summary>
[Property(MaxTest = 100)]
public Property ImageDelete_InvalidIndex_ShouldNotChangeList()
{
return Prop.ForAll(
Gen.Choose(1, MaxImageCount).ToArbitrary(), // 初始图片数量
Gen.Choose(-10, -1).ToArbitrary(), // 无效的负索引
(initialCount, invalidIndex) =>
{
// 创建图片列表
var imageList = new List<ImageItem>();
for (int i = 0; i < initialCount; i++)
{
imageList.Add(new ImageItem { Id = i, SortOrder = i });
}
// 尝试删除无效索引(模拟边界检查)
var canDelete = invalidIndex >= 0 && invalidIndex < imageList.Count;
// 验证:无效索引不应允许删除
return (!canDelete)
.Label($"Initial: {initialCount}, Invalid index: {invalidIndex}, " +
$"Can delete: {canDelete}");
});
}
/// <summary>
/// Property 4 扩展: 删除后可以继续添加图片
/// </summary>
[Property(MaxTest = 100)]
public Property ImageDelete_ThenAdd_ShouldWork()
{
return Prop.ForAll(
Gen.Choose(1, MaxImageCount).ToArbitrary(), // 初始图片数量
Gen.Choose(1, 5).ToArbitrary(), // 删除数量
Gen.Choose(1, 5).ToArbitrary(), // 添加数量
(initialCount, deleteCount, addCount) =>
{
// 创建图片列表
var imageList = new List<ImageItem>();
for (int i = 0; i < initialCount; i++)
{
imageList.Add(new ImageItem { Id = i, SortOrder = i });
}
// 删除图片
var actualDelete = Math.Min(deleteCount, imageList.Count);
for (int i = 0; i < actualDelete; i++)
{
if (imageList.Count > 0)
{
imageList.RemoveAt(0);
}
}
var afterDelete = imageList.Count;
// 添加图片
var remainingSlots = MaxImageCount - imageList.Count;
var actualAdd = Math.Min(addCount, remainingSlots);
for (int i = 0; i < actualAdd; i++)
{
imageList.Add(new ImageItem { Id = 100 + i, SortOrder = imageList.Count });
}
var finalCount = imageList.Count;
// 验证:最终数量正确
var expectedFinal = afterDelete + actualAdd;
var countCorrect = finalCount == expectedFinal;
var withinLimit = finalCount <= MaxImageCount;
return (countCorrect && withinLimit)
.Label($"Initial: {initialCount}, Deleted: {actualDelete}, " +
$"Added: {actualAdd}, Final: {finalCount} (expected: {expectedFinal})");
});
}
/// <summary>
/// Property 4 扩展: 边界值测试 - 删除唯一的图片
/// </summary>
[Fact]
public void ImageDelete_SingleImage_ShouldResultInEmptyList()
{
var imageList = new List<ImageItem>
{
new ImageItem { Id = 1, SortOrder = 0 }
};
imageList.RemoveAt(0);
Assert.Empty(imageList);
}
/// <summary>
/// Property 4 扩展: 边界值测试 - 删除第一张图片
/// </summary>
[Fact]
public void ImageDelete_FirstImage_ShouldShiftOthers()
{
var imageList = new List<ImageItem>
{
new ImageItem { Id = 1, SortOrder = 0 },
new ImageItem { Id = 2, SortOrder = 1 },
new ImageItem { Id = 3, SortOrder = 2 }
};
imageList.RemoveAt(0);
// 重新排序
for (int i = 0; i < imageList.Count; i++)
{
imageList[i].SortOrder = i;
}
Assert.Equal(2, imageList.Count);
Assert.Equal(2, imageList[0].Id);
Assert.Equal(0, imageList[0].SortOrder);
}
/// <summary>
/// Property 4 扩展: 边界值测试 - 删除最后一张图片
/// </summary>
[Fact]
public void ImageDelete_LastImage_ShouldNotAffectOthers()
{
var imageList = new List<ImageItem>
{
new ImageItem { Id = 1, SortOrder = 0 },
new ImageItem { Id = 2, SortOrder = 1 },
new ImageItem { Id = 3, SortOrder = 2 }
};
imageList.RemoveAt(2);
Assert.Equal(2, imageList.Count);
Assert.Equal(1, imageList[0].Id);
Assert.Equal(2, imageList[1].Id);
}
#endregion
}
}