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; /// /// TeamService 属性测试 /// 验证团队服务的列表查询过滤和排序正确性 /// public class TeamServicePropertyTests { private readonly Mock> _mockLogger = new(); #region Property 1: 列表查询过滤正确性 /// /// Property 1: 团队介绍只返回Position=2的宣传图 /// *For any* Team info query, the returned items SHALL only include records /// with Position=2, Status=1, and IsDeleted=false. /// /// **Feature: miniapp-api, Property 1: 列表查询过滤正确性** /// **Validates: Requirements 15.1** /// [Property(MaxTest = 100)] public bool TeamInfoOnlyReturnsTeamPagePromotions(PositiveInt seed) { // Arrange: 创建包含不同Position的Promotion数据 using var dbContext = CreateDbContext(); // 创建团队页位置(Position=2)且启用的宣传图 for (int i = 0; i < 3; i++) { dbContext.Promotions.Add(CreatePromotion(seed.Get + i, position: 2, status: 1, isDeleted: false)); } // 创建首页位置(Position=1)的宣传图 for (int i = 0; i < 2; i++) { dbContext.Promotions.Add(CreatePromotion(seed.Get + 100 + i, position: 1, status: 1, isDeleted: false)); } dbContext.SaveChanges(); var service = new TeamService(dbContext, _mockLogger.Object); // Act: 调用服务方法 var result = service.GetInfoAsync().GetAwaiter().GetResult(); // Assert: 验证返回结果只包含Position=2的记录 // 1. 返回的图片数应该等于团队页位置且启用且未删除的记录数 if (result.Images.Count != 3) return false; // 2. 验证数据库中确实存在首页位置的记录 var allPromotions = dbContext.Promotions.ToList(); var homePagePromotions = allPromotions.Where(p => p.Position == 1 && p.Status == 1 && !p.IsDeleted).ToList(); if (homePagePromotions.Count != 2) return false; // 3. 验证返回的图片URL都来自Position=2的记录 var teamPageImageUrls = allPromotions .Where(p => p.Position == 2 && p.Status == 1 && !p.IsDeleted) .Select(p => p.ImageUrl) .ToHashSet(); return result.Images.All(img => teamPageImageUrls.Contains(img)); } /// /// Property 1: 团队介绍只返回启用状态的记录 /// /// **Feature: miniapp-api, Property 1: 列表查询过滤正确性** /// **Validates: Requirements 15.1** /// [Property(MaxTest = 100)] public bool TeamInfoOnlyReturnsEnabledRecords(PositiveInt seed) { // Arrange using var dbContext = CreateDbContext(); // 创建团队页位置且启用的宣传图 var enabledPromotion = CreatePromotion(seed.Get, position: 2, status: 1, isDeleted: false); dbContext.Promotions.Add(enabledPromotion); // 创建团队页位置但禁用的宣传图 var disabledPromotion = CreatePromotion(seed.Get + 1, position: 2, status: 0, isDeleted: false); dbContext.Promotions.Add(disabledPromotion); dbContext.SaveChanges(); var service = new TeamService(dbContext, _mockLogger.Object); // Act var result = service.GetInfoAsync().GetAwaiter().GetResult(); // Assert: 禁用的记录不应出现在结果中 return result.Images.Count == 1 && result.Images[0] == enabledPromotion.ImageUrl; } /// /// Property 1: 团队介绍不返回已删除的记录 /// /// **Feature: miniapp-api, Property 1: 列表查询过滤正确性** /// **Validates: Requirements 15.1** /// [Property(MaxTest = 100)] public bool TeamInfoExcludesDeletedRecords(PositiveInt seed) { // Arrange using var dbContext = CreateDbContext(); // 创建团队页位置且启用的宣传图 var activePromotion = CreatePromotion(seed.Get, position: 2, status: 1, isDeleted: false); dbContext.Promotions.Add(activePromotion); // 创建团队页位置但已删除的宣传图(即使Status=1) var deletedPromotion = CreatePromotion(seed.Get + 1, position: 2, status: 1, isDeleted: true); dbContext.Promotions.Add(deletedPromotion); dbContext.SaveChanges(); var service = new TeamService(dbContext, _mockLogger.Object); // Act var result = service.GetInfoAsync().GetAwaiter().GetResult(); // Assert: 已删除的记录不应出现在结果中 return result.Images.Count == 1 && result.Images[0] == activePromotion.ImageUrl; } /// /// Property 1: 团队介绍不返回首页位置的记录 /// /// **Feature: miniapp-api, Property 1: 列表查询过滤正确性** /// **Validates: Requirements 15.1** /// [Property(MaxTest = 100)] public bool TeamInfoExcludesHomePageRecords(PositiveInt seed) { // Arrange using var dbContext = CreateDbContext(); // 创建团队页位置的宣传图 var teamPromotion = CreatePromotion(seed.Get, position: 2, status: 1, isDeleted: false); dbContext.Promotions.Add(teamPromotion); // 创建首页位置的宣传图 var homePromotion = CreatePromotion(seed.Get + 1, position: 1, status: 1, isDeleted: false); dbContext.Promotions.Add(homePromotion); dbContext.SaveChanges(); var service = new TeamService(dbContext, _mockLogger.Object); // Act var result = service.GetInfoAsync().GetAwaiter().GetResult(); // Assert: 只返回团队页位置的记录 return result.Images.Count == 1 && result.Images[0] == teamPromotion.ImageUrl; } /// /// Property 1: 团队介绍按Sort字段降序排列 /// /// **Feature: miniapp-api, Property 1: 列表查询过滤正确性** /// **Validates: Requirements 15.1** /// [Property(MaxTest = 100)] public bool TeamInfoSortedBySortDescending(PositiveInt seed) { // Arrange using var dbContext = CreateDbContext(); var random = new Random(seed.Get); // 创建具有不同Sort值的团队页宣传图 var promotions = new List(); for (int i = 0; i < 5; i++) { var sortValue = random.Next(1, 1000); var promotion = CreatePromotionWithSort(seed.Get + i, sortValue); promotions.Add(promotion); dbContext.Promotions.Add(promotion); } dbContext.SaveChanges(); var service = new TeamService(dbContext, _mockLogger.Object); // Act var result = service.GetInfoAsync().GetAwaiter().GetResult(); // Assert: 验证列表按Sort降序排列 if (result.Images.Count < 2) return true; // 获取按Sort降序排列的预期顺序 var expectedOrder = promotions .OrderByDescending(p => p.Sort) .Select(p => p.ImageUrl) .ToList(); for (int i = 0; i < result.Images.Count; i++) { if (result.Images[i] != expectedOrder[i]) { return false; } } return true; } /// /// Property 1: 团队介绍第一个元素应该是Sort值最大的 /// /// **Feature: miniapp-api, Property 1: 列表查询过滤正确性** /// **Validates: Requirements 15.1** /// [Property(MaxTest = 100)] public bool TeamInfoFirstItemHasHighestSort(PositiveInt seed) { // Arrange using var dbContext = CreateDbContext(); var random = new Random(seed.Get); // 创建具有不同Sort值的团队页宣传图 var promotions = new List(); for (int i = 0; i < 5; i++) { var sortValue = random.Next(1, 1000); var promotion = CreatePromotionWithSort(seed.Get + i, sortValue); promotions.Add(promotion); dbContext.Promotions.Add(promotion); } dbContext.SaveChanges(); var service = new TeamService(dbContext, _mockLogger.Object); // Act var result = service.GetInfoAsync().GetAwaiter().GetResult(); // Assert: 第一个元素应该是Sort值最大的 if (result.Images.Count == 0) return true; var maxSortPromotion = promotions.OrderByDescending(p => p.Sort).First(); return result.Images[0] == maxSortPromotion.ImageUrl; } #endregion #region 综合属性测试 /// /// Property 1: 空数据库返回空列表 /// /// **Feature: miniapp-api, Property 1: 列表查询过滤正确性** /// **Validates: Requirements 15.1** /// [Fact] public void EmptyDatabaseReturnsEmptyImageList() { // Arrange using var dbContext = CreateDbContext(); var service = new TeamService(dbContext, _mockLogger.Object); // Act var result = service.GetInfoAsync().GetAwaiter().GetResult(); // Assert Assert.Empty(result.Images); } /// /// Property 1: 所有记录都被过滤时返回空列表 /// /// **Feature: miniapp-api, Property 1: 列表查询过滤正确性** /// **Validates: Requirements 15.1** /// [Property(MaxTest = 50)] public bool AllFilteredRecordsReturnsEmptyList(PositiveInt seed) { // Arrange using var dbContext = CreateDbContext(); // 只创建不符合条件的记录 // 首页位置的宣传图(Position=1) dbContext.Promotions.Add(CreatePromotion(seed.Get, position: 1, status: 1, isDeleted: false)); // 团队页位置但禁用的宣传图 dbContext.Promotions.Add(CreatePromotion(seed.Get + 1, position: 2, status: 0, isDeleted: false)); // 团队页位置但已删除的宣传图 dbContext.Promotions.Add(CreatePromotion(seed.Get + 2, position: 2, status: 1, isDeleted: true)); // 禁用且已删除的宣传图 dbContext.Promotions.Add(CreatePromotion(seed.Get + 3, position: 2, status: 0, isDeleted: true)); dbContext.SaveChanges(); var service = new TeamService(dbContext, _mockLogger.Object); // Act var result = service.GetInfoAsync().GetAwaiter().GetResult(); // Assert: 列表应该为空 return result.Images.Count == 0; } /// /// Property 1: 过滤和排序同时正确 /// /// **Feature: miniapp-api, Property 1: 列表查询过滤正确性** /// **Validates: Requirements 15.1** /// [Property(MaxTest = 100)] public bool FilteringAndSortingWorkTogether(PositiveInt seed) { // Arrange using var dbContext = CreateDbContext(); var random = new Random(seed.Get); // 创建团队页位置且启用的宣传图(不同Sort值) var teamPromotions = new List(); for (int i = 0; i < 3; i++) { var sortValue = random.Next(1, 1000); var promotion = CreatePromotionWithSort(seed.Get + i, sortValue); teamPromotions.Add(promotion); dbContext.Promotions.Add(promotion); } // 创建首页位置的宣传图(高Sort值,不应出现在结果中) var homePromotion = CreatePromotion(seed.Get + 100, position: 1, status: 1, isDeleted: false); homePromotion.Sort = 9999; // 最高Sort值 dbContext.Promotions.Add(homePromotion); // 创建团队页位置但禁用的宣传图(高Sort值,不应出现在结果中) var disabledPromotion = CreatePromotion(seed.Get + 200, position: 2, status: 0, isDeleted: false); disabledPromotion.Sort = 9998; // 第二高Sort值 dbContext.Promotions.Add(disabledPromotion); // 创建团队页位置但已删除的宣传图(高Sort值,不应出现在结果中) var deletedPromotion = CreatePromotion(seed.Get + 300, position: 2, status: 1, isDeleted: true); deletedPromotion.Sort = 9997; // 第三高Sort值 dbContext.Promotions.Add(deletedPromotion); dbContext.SaveChanges(); var service = new TeamService(dbContext, _mockLogger.Object); // Act var result = service.GetInfoAsync().GetAwaiter().GetResult(); // Assert: // 1. 只返回团队页位置且启用且未删除的记录 if (result.Images.Count != 3) return false; // 2. 不包含首页位置、禁用或已删除的记录 var excludedImageUrls = new HashSet { homePromotion.ImageUrl, disabledPromotion.ImageUrl, deletedPromotion.ImageUrl }; if (result.Images.Any(img => excludedImageUrls.Contains(img))) { return false; } // 3. 按Sort降序排列 var expectedOrder = teamPromotions .OrderByDescending(p => p.Sort) .Select(p => p.ImageUrl) .ToList(); for (int i = 0; i < result.Images.Count; i++) { if (result.Images[i] != expectedOrder[i]) { return false; } } return true; } /// /// Property 1: 排序稳定性 /// /// **Feature: miniapp-api, Property 1: 列表查询过滤正确性** /// **Validates: Requirements 15.1** /// [Property(MaxTest = 50)] public bool TeamInfoSortingIsStable(PositiveInt seed) { // Arrange using var dbContext = CreateDbContext(); // 创建具有相同Sort值的团队页宣传图 var sameSort = seed.Get % 100; for (int i = 0; i < 3; i++) { var promotion = CreatePromotionWithSort(seed.Get + i, sameSort); dbContext.Promotions.Add(promotion); } dbContext.SaveChanges(); var service = new TeamService(dbContext, _mockLogger.Object); // Act: 多次调用应该返回相同的顺序 var result1 = service.GetInfoAsync().GetAwaiter().GetResult(); var result2 = service.GetInfoAsync().GetAwaiter().GetResult(); // Assert: 两次调用返回的顺序应该相同 if (result1.Images.Count != result2.Images.Count) return false; for (int i = 0; i < result1.Images.Count; i++) { if (result1.Images[i] != result2.Images[i]) return false; } return true; } #endregion #region 辅助方法 /// /// 创建内存数据库上下文 /// private MiAssessmentDbContext CreateDbContext() { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) .Options; return new MiAssessmentDbContext(options); } /// /// 创建测试宣传图 /// private Promotion CreatePromotion(int seed, int position, int status, bool isDeleted) { return new Promotion { Title = $"Test Promotion {seed}", ImageUrl = $"https://example.com/promotion_{seed}.jpg", Position = position, Sort = seed % 100, Status = status, CreateTime = DateTime.Now, UpdateTime = DateTime.Now, IsDeleted = isDeleted }; } /// /// 创建指定Sort值的测试宣传图(团队页位置) /// private Promotion CreatePromotionWithSort(int seed, int sort) { return new Promotion { Title = $"Test Promotion {seed}", ImageUrl = $"https://example.com/promotion_{seed}.jpg", Position = 2, // 团队页位置 Sort = sort, Status = 1, CreateTime = DateTime.Now, UpdateTime = DateTime.Now, IsDeleted = false }; } #endregion }