using FsCheck; using FsCheck.Xunit; using WorkCameraExport.Forms; using Xunit; using SystemRandom = System.Random; namespace WorkCameraExport.Tests { /// /// 图片上传数量属性测试 /// Feature: work-camera-2.0.1, Property 3: 图片上传数量限制 /// Feature: work-camera-2.0.1, Property 4: 图片删除后列表更新 /// Validates: Requirements 4.5, 4.6 /// public class ImageUploadPropertyTests { private const int MaxImageCount = 15; #region Property 3: 图片上传数量限制 /// /// Property 3: 图片上传数量限制 /// For any 上传操作,图片列表长度应始终不超过 15。 /// 当尝试添加图片使总数超过 15 时,应拒绝添加并提示用户。 /// [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(); // 添加初始图片(模拟已有图片) 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})"); }); } /// /// Property 3 扩展: 当已达到最大数量时,不应添加任何图片 /// [Property(MaxTest = 100)] public Property ImageUpload_WhenAtMax_ShouldNotAddMore() { return Prop.ForAll( Gen.Choose(1, 10).ToArbitrary(), // 尝试添加的图片数量 (addCount) => { // 模拟已满的图片列表 var imageList = new List(); 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}"); }); } /// /// Property 3 扩展: 添加的图片数量应等于 min(请求数量, 剩余槽位) /// [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}"); }); } /// /// Property 3 扩展: 边界值测试 - 恰好 15 张图片 /// [Fact] public void ImageUpload_ExactlyMax_ShouldNotAllowMore() { var imageList = new List(); 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); } /// /// Property 3 扩展: 边界值测试 - 14 张图片,添加 1 张 /// [Fact] public void ImageUpload_OneBelowMax_ShouldAllowOneMore() { var imageList = new List(); 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: 图片删除后列表更新 /// /// Property 4: 图片删除后列表更新 /// For any 图片删除操作,删除后图片列表长度应减少 1, /// 且被删除的图片不应存在于列表中。 /// [Property(MaxTest = 100)] public Property ImageDelete_ShouldReduceCountByOne() { return Prop.ForAll( Gen.Choose(1, MaxImageCount).ToArbitrary(), // 初始图片数量 1-15 (initialCount) => { // 创建图片列表 var imageList = new List(); 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}"); }); } /// /// Property 4 扩展: 删除后排序应保持连续 /// [Property(MaxTest = 100)] public Property ImageDelete_ShouldMaintainSortOrder() { return Prop.ForAll( Gen.Choose(2, MaxImageCount).ToArbitrary(), // 初始图片数量 2-15(至少2张才能测试删除后排序) (initialCount) => { // 创建图片列表 var imageList = new List(); 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}"); }); } /// /// Property 4 扩展: 删除所有图片后列表应为空 /// [Property(MaxTest = 100)] public Property ImageDelete_AllImages_ShouldResultInEmptyList() { return Prop.ForAll( Gen.Choose(1, MaxImageCount).ToArbitrary(), // 初始图片数量 (initialCount) => { // 创建图片列表 var imageList = new List(); 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}"); }); } /// /// Property 4 扩展: 删除无效索引应不改变列表 /// [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(); 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}"); }); } /// /// Property 4 扩展: 删除后可以继续添加图片 /// [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(); 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})"); }); } /// /// Property 4 扩展: 边界值测试 - 删除唯一的图片 /// [Fact] public void ImageDelete_SingleImage_ShouldResultInEmptyList() { var imageList = new List { new ImageItem { Id = 1, SortOrder = 0 } }; imageList.RemoveAt(0); Assert.Empty(imageList); } /// /// Property 4 扩展: 边界值测试 - 删除第一张图片 /// [Fact] public void ImageDelete_FirstImage_ShouldShiftOthers() { var imageList = new List { 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); } /// /// Property 4 扩展: 边界值测试 - 删除最后一张图片 /// [Fact] public void ImageDelete_LastImage_ShouldNotAffectOthers() { var imageList = new List { 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 } }