using FsCheck; using FsCheck.Xunit; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using MiAssessment.Core.Services; using MiAssessment.Model.Data; using MiAssessment.Model.Entities; using Moq; using Xunit; namespace MiAssessment.Tests.Services; /// /// PlannerService 属性测试 /// 验证规划师服务的列表查询过滤和排序正确性 /// public class PlannerServicePropertyTests { private readonly Mock> _mockLogger = new(); #region Property 1: 列表查询过滤正确性 /// /// Property 1: 规划师列表只返回启用状态的记录 /// *For any* Planner list query, the returned items SHALL only include records /// with Status=1 and IsDeleted=false. /// /// **Feature: miniapp-api, Property 1: 列表查询过滤正确性** /// **Validates: Requirements 10.1** /// [Property(MaxTest = 100)] public bool PlannerListOnlyReturnsEnabledRecords(PositiveInt seed) { // Arrange: 创建包含不同状态的Planner数据 using var dbContext = CreateDbContext(); // 创建启用的规划师 for (int i = 0; i < 3; i++) { dbContext.Planners.Add(CreatePlanner(seed.Get + i, status: 1, isDeleted: false)); } // 创建禁用的规划师 for (int i = 0; i < 2; i++) { dbContext.Planners.Add(CreatePlanner(seed.Get + 100 + i, status: 0, isDeleted: false)); } // 创建已删除的规划师 for (int i = 0; i < 2; i++) { dbContext.Planners.Add(CreatePlanner(seed.Get + 200 + i, status: 1, isDeleted: true)); } dbContext.SaveChanges(); var service = new PlannerService(dbContext, _mockLogger.Object); // Act: 调用服务方法 var result = service.GetListAsync().GetAwaiter().GetResult(); // Assert: 验证返回结果只包含Status=1且未删除的记录 // 1. 返回的记录数应该等于启用且未删除的记录数 if (result.Count != 3) return false; // 2. 验证数据库中确实存在被过滤掉的记录 var allPlanners = dbContext.Planners.ToList(); var disabledPlanners = allPlanners.Where(p => p.Status == 0 && !p.IsDeleted).ToList(); var deletedPlanners = allPlanners.Where(p => p.IsDeleted).ToList(); if (disabledPlanners.Count != 2 || deletedPlanners.Count != 2) return false; // 3. 验证返回的记录中不包含禁用或已删除的记录 var returnedIds = result.Select(p => p.Id).ToHashSet(); var disabledIds = disabledPlanners.Select(p => p.Id).ToHashSet(); var deletedIds = deletedPlanners.Select(p => p.Id).ToHashSet(); return !returnedIds.Intersect(disabledIds).Any() && !returnedIds.Intersect(deletedIds).Any(); } /// /// Property 1: 规划师列表不返回已删除的记录 /// /// **Feature: miniapp-api, Property 1: 列表查询过滤正确性** /// **Validates: Requirements 10.1** /// [Property(MaxTest = 100)] public bool PlannerListExcludesDeletedRecords(PositiveInt seed) { // Arrange using var dbContext = CreateDbContext(); // 创建一个启用的规划师 var activePlanner = CreatePlanner(seed.Get, status: 1, isDeleted: false); dbContext.Planners.Add(activePlanner); // 创建已删除的规划师(即使Status=1) var deletedPlanner = CreatePlanner(seed.Get + 1, status: 1, isDeleted: true); dbContext.Planners.Add(deletedPlanner); dbContext.SaveChanges(); var service = new PlannerService(dbContext, _mockLogger.Object); // Act var result = service.GetListAsync().GetAwaiter().GetResult(); // Assert: 已删除的记录不应出现在结果中 return result.Count == 1 && result[0].Id == activePlanner.Id; } /// /// Property 1: 规划师列表不返回禁用状态的记录 /// /// **Feature: miniapp-api, Property 1: 列表查询过滤正确性** /// **Validates: Requirements 10.1** /// [Property(MaxTest = 100)] public bool PlannerListExcludesDisabledRecords(PositiveInt seed) { // Arrange using var dbContext = CreateDbContext(); // 创建一个启用的规划师 var enabledPlanner = CreatePlanner(seed.Get, status: 1, isDeleted: false); dbContext.Planners.Add(enabledPlanner); // 创建禁用的规划师 var disabledPlanner = CreatePlanner(seed.Get + 1, status: 0, isDeleted: false); dbContext.Planners.Add(disabledPlanner); dbContext.SaveChanges(); var service = new PlannerService(dbContext, _mockLogger.Object); // Act var result = service.GetListAsync().GetAwaiter().GetResult(); // Assert: 禁用的记录不应出现在结果中 return result.Count == 1 && result[0].Id == enabledPlanner.Id; } #endregion #region Property 2: 列表查询排序正确性 /// /// Property 2: 规划师列表按Sort字段降序排列 /// *For any* Planner list query, the returned items SHALL be ordered by Sort /// field in descending order. /// /// **Feature: miniapp-api, Property 2: 列表查询排序正确性** /// **Validates: Requirements 10.1** /// [Property(MaxTest = 100)] public bool PlannerListSortedBySortDescending(PositiveInt seed) { // Arrange using var dbContext = CreateDbContext(); var random = new Random(seed.Get); // 创建具有不同Sort值的规划师 var sortValues = new List(); for (int i = 0; i < 5; i++) { var sortValue = random.Next(1, 1000); sortValues.Add(sortValue); dbContext.Planners.Add(CreatePlannerWithSort(seed.Get + i, sortValue)); } dbContext.SaveChanges(); var service = new PlannerService(dbContext, _mockLogger.Object); // Act var result = service.GetListAsync().GetAwaiter().GetResult(); // Assert: 验证列表按Sort降序排列 if (result.Count < 2) return true; for (int i = 0; i < result.Count - 1; i++) { // 获取当前和下一个规划师的Sort值 var currentPlanner = dbContext.Planners.First(p => p.Id == result[i].Id); var nextPlanner = dbContext.Planners.First(p => p.Id == result[i + 1].Id); if (currentPlanner.Sort < nextPlanner.Sort) { return false; } } return true; } /// /// Property 2: 规划师列表排序稳定性 /// /// **Feature: miniapp-api, Property 2: 列表查询排序正确性** /// **Validates: Requirements 10.1** /// [Property(MaxTest = 50)] public bool PlannerListSortingIsStable(PositiveInt seed) { // Arrange using var dbContext = CreateDbContext(); // 创建具有相同Sort值的规划师 var sameSort = seed.Get % 100; for (int i = 0; i < 3; i++) { var planner = CreatePlannerWithSort(seed.Get + i, sameSort); dbContext.Planners.Add(planner); } dbContext.SaveChanges(); var service = new PlannerService(dbContext, _mockLogger.Object); // Act: 多次调用应该返回相同的顺序 var result1 = service.GetListAsync().GetAwaiter().GetResult(); var result2 = service.GetListAsync().GetAwaiter().GetResult(); // Assert: 两次调用返回的顺序应该相同 if (result1.Count != result2.Count) return false; for (int i = 0; i < result1.Count; i++) { if (result1[i].Id != result2[i].Id) return false; } return true; } /// /// Property 2: 规划师列表排序正确性 - 验证最大Sort值在最前 /// /// **Feature: miniapp-api, Property 2: 列表查询排序正确性** /// **Validates: Requirements 10.1** /// [Property(MaxTest = 100)] public bool PlannerListFirstItemHasHighestSort(PositiveInt seed) { // Arrange using var dbContext = CreateDbContext(); var random = new Random(seed.Get); // 创建具有不同Sort值的规划师 var planners = new List(); for (int i = 0; i < 5; i++) { var sortValue = random.Next(1, 1000); var planner = CreatePlannerWithSort(seed.Get + i, sortValue); planners.Add(planner); dbContext.Planners.Add(planner); } dbContext.SaveChanges(); var service = new PlannerService(dbContext, _mockLogger.Object); // Act var result = service.GetListAsync().GetAwaiter().GetResult(); // Assert: 第一个元素应该是Sort值最大的 if (result.Count == 0) return true; var maxSort = planners.Max(p => p.Sort); var firstPlanner = dbContext.Planners.First(p => p.Id == result[0].Id); return firstPlanner.Sort == maxSort; } #endregion #region 综合属性测试 /// /// Property 1: 空数据库返回空列表 /// /// **Feature: miniapp-api, Property 1: 列表查询过滤正确性** /// **Validates: Requirements 10.1** /// [Fact] public void EmptyDatabaseReturnsEmptyList() { // Arrange using var dbContext = CreateDbContext(); var service = new PlannerService(dbContext, _mockLogger.Object); // Act var planners = service.GetListAsync().GetAwaiter().GetResult(); // Assert Assert.Empty(planners); } /// /// Property 1: 所有记录都被过滤时返回空列表 /// /// **Feature: miniapp-api, Property 1: 列表查询过滤正确性** /// **Validates: Requirements 10.1** /// [Property(MaxTest = 50)] public bool AllFilteredRecordsReturnsEmptyList(PositiveInt seed) { // Arrange using var dbContext = CreateDbContext(); // 只创建不符合条件的记录 // 禁用的规划师 dbContext.Planners.Add(CreatePlanner(seed.Get, status: 0, isDeleted: false)); // 已删除的规划师 dbContext.Planners.Add(CreatePlanner(seed.Get + 1, status: 1, isDeleted: true)); // 禁用且已删除的规划师 dbContext.Planners.Add(CreatePlanner(seed.Get + 2, status: 0, isDeleted: true)); dbContext.SaveChanges(); var service = new PlannerService(dbContext, _mockLogger.Object); // Act var planners = service.GetListAsync().GetAwaiter().GetResult(); // Assert: 列表应该为空 return planners.Count == 0; } /// /// Property 1 & 2: 过滤和排序同时正确 /// /// **Feature: miniapp-api, Property 1: 列表查询过滤正确性, Property 2: 列表查询排序正确性** /// **Validates: Requirements 10.1** /// [Property(MaxTest = 100)] public bool FilteringAndSortingWorkTogether(PositiveInt seed) { // Arrange using var dbContext = CreateDbContext(); var random = new Random(seed.Get); // 创建启用的规划师(不同Sort值) var enabledPlanners = new List(); for (int i = 0; i < 3; i++) { var sortValue = random.Next(1, 1000); var planner = CreatePlannerWithSort(seed.Get + i, sortValue); enabledPlanners.Add(planner); dbContext.Planners.Add(planner); } // 创建禁用的规划师(高Sort值,不应出现在结果中) var disabledPlanner = CreatePlanner(seed.Get + 100, status: 0, isDeleted: false); disabledPlanner.Sort = 9999; // 最高Sort值 dbContext.Planners.Add(disabledPlanner); // 创建已删除的规划师(高Sort值,不应出现在结果中) var deletedPlanner = CreatePlanner(seed.Get + 200, status: 1, isDeleted: true); deletedPlanner.Sort = 9998; // 第二高Sort值 dbContext.Planners.Add(deletedPlanner); dbContext.SaveChanges(); var service = new PlannerService(dbContext, _mockLogger.Object); // Act var result = service.GetListAsync().GetAwaiter().GetResult(); // Assert: // 1. 只返回启用且未删除的记录 if (result.Count != 3) return false; // 2. 不包含禁用或已删除的记录 var returnedIds = result.Select(p => p.Id).ToHashSet(); if (returnedIds.Contains(disabledPlanner.Id) || returnedIds.Contains(deletedPlanner.Id)) { return false; } // 3. 按Sort降序排列 for (int i = 0; i < result.Count - 1; i++) { var currentPlanner = dbContext.Planners.First(p => p.Id == result[i].Id); var nextPlanner = dbContext.Planners.First(p => p.Id == result[i + 1].Id); if (currentPlanner.Sort < nextPlanner.Sort) { return false; } } return true; } #endregion #region 辅助方法 /// /// 创建内存数据库上下文 /// private MiAssessmentDbContext CreateDbContext() { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) .Options; return new MiAssessmentDbContext(options); } /// /// 创建测试规划师 /// private Planner CreatePlanner(int seed, int status, bool isDeleted) { return new Planner { Name = $"Test Planner {seed}", Avatar = $"https://example.com/avatar_{seed}.jpg", Introduction = $"Introduction for planner {seed}", Price = 100.00m + (seed % 100), Sort = seed % 100, Status = status, CreateTime = DateTime.Now, UpdateTime = DateTime.Now, IsDeleted = isDeleted }; } /// /// 创建指定Sort值的测试规划师 /// private Planner CreatePlannerWithSort(int seed, int sort) { return new Planner { Name = $"Test Planner {seed}", Avatar = $"https://example.com/avatar_{seed}.jpg", Introduction = $"Introduction for planner {seed}", Price = 100.00m + (seed % 100), Sort = sort, Status = 1, CreateTime = DateTime.Now, UpdateTime = DateTime.Now, IsDeleted = false }; } #endregion }