using FsCheck; using FsCheck.Xunit; using Xunit; using XiangYi.Application.Services; using XiangYi.Core.Entities.Biz; namespace XiangYi.Application.Tests.Services; /// /// InteractService属性测试 - 浏览记录一致性 /// public class ViewRecordPropertyTests { /// /// **Feature: backend-api, Property 7: 浏览记录一致性** /// **Validates: Requirements 4.1** /// /// *For any* 用户查看他人资料操作, 应创建或更新浏览记录,且被浏览者的"看过我"列表应包含该浏览者 /// [Property(MaxTest = 100)] public Property ViewRecord_ShouldBeConsistent() { // 生成浏览者ID(1-500) var viewerIdArb = Gen.Choose(1, 500); // 生成被浏览者ID(501-1000) var targetUserIdArb = Gen.Choose(501, 1000); return Prop.ForAll( viewerIdArb.ToArbitrary(), targetUserIdArb.ToArbitrary(), (viewerId, targetUserId) => { // Arrange - 创建空的浏览记录列表 var viewRecords = new List(); // Act - 模拟添加浏览记录 var newRecord = new UserView { UserId = viewerId, TargetUserId = targetUserId, ViewDate = DateTime.Today, ViewCount = 1, LastViewTime = DateTime.Now, CreateTime = DateTime.Now }; viewRecords.Add(newRecord); // Assert - 验证浏览记录存在 var viewerExists = InteractService.IsViewRecordExistsStatic(viewRecords, viewerId, targetUserId); // 验证被浏览者的"看过我"列表包含浏览者 var viewedMeList = viewRecords.Where(v => v.TargetUserId == targetUserId).ToList(); var viewerInViewedMeList = viewedMeList.Any(v => v.UserId == viewerId); return viewerExists && viewerInViewedMeList; }); } /// /// 浏览记录 - 同一用户多次浏览应更新次数而非创建新记录 /// [Property(MaxTest = 100)] public Property ViewRecord_MultipleViews_ShouldUpdateCount() { var viewerIdArb = Gen.Choose(1, 500); var targetUserIdArb = Gen.Choose(501, 1000); var viewCountArb = Gen.Choose(1, 10); return Prop.ForAll( viewerIdArb.ToArbitrary(), targetUserIdArb.ToArbitrary(), viewCountArb.ToArbitrary(), (viewerId, targetUserId, viewCount) => { // Arrange - 创建初始浏览记录 var viewRecords = new List { new UserView { UserId = viewerId, TargetUserId = targetUserId, ViewDate = DateTime.Today, ViewCount = viewCount, LastViewTime = DateTime.Now } }; // Act - 模拟再次浏览(更新次数) var existingRecord = viewRecords.FirstOrDefault(v => v.UserId == viewerId && v.TargetUserId == targetUserId && v.ViewDate == DateTime.Today); if (existingRecord != null) { existingRecord.ViewCount++; } // Assert - 验证次数增加且记录数不变 var recordCount = viewRecords.Count(v => v.UserId == viewerId && v.TargetUserId == targetUserId); var updatedCount = viewRecords.First(v => v.UserId == viewerId && v.TargetUserId == targetUserId).ViewCount; return recordCount == 1 && updatedCount == viewCount + 1; }); } /// /// 浏览记录 - 不同日期应创建新记录 /// [Property(MaxTest = 100)] public Property ViewRecord_DifferentDays_ShouldCreateNewRecord() { var viewerIdArb = Gen.Choose(1, 500); var targetUserIdArb = Gen.Choose(501, 1000); return Prop.ForAll( viewerIdArb.ToArbitrary(), targetUserIdArb.ToArbitrary(), (viewerId, targetUserId) => { // Arrange - 创建昨天的浏览记录 var viewRecords = new List { new UserView { UserId = viewerId, TargetUserId = targetUserId, ViewDate = DateTime.Today.AddDays(-1), ViewCount = 1, LastViewTime = DateTime.Now.AddDays(-1) } }; // Act - 今天再次浏览,应创建新记录 var todayRecord = viewRecords.FirstOrDefault(v => v.UserId == viewerId && v.TargetUserId == targetUserId && v.ViewDate == DateTime.Today); if (todayRecord == null) { viewRecords.Add(new UserView { UserId = viewerId, TargetUserId = targetUserId, ViewDate = DateTime.Today, ViewCount = 1, LastViewTime = DateTime.Now }); } // Assert - 应该有两条记录(昨天和今天) var totalRecords = viewRecords.Count(v => v.UserId == viewerId && v.TargetUserId == targetUserId); return totalRecords == 2; }); } /// /// 浏览记录 - 浏览者和被浏览者不能是同一人(业务规则验证) /// [Property(MaxTest = 100)] public Property ViewRecord_SelfView_ShouldBeInvalid() { var userIdArb = Gen.Choose(1, 1000); return Prop.ForAll( userIdArb.ToArbitrary(), userId => { // 验证业务规则:自己浏览自己应该是无效的 // 这里验证的是:当viewerId == targetUserId时,应该被拒绝 var viewerId = userId; var targetUserId = userId; var isSelfView = viewerId == targetUserId; // 自己浏览自己应该返回true(表示是无效的自我浏览) return isSelfView; }); } } /// /// InteractService属性测试 - 收藏操作幂等性 /// public class FavoriteIdempotencyPropertyTests { /// /// **Feature: backend-api, Property 8: 收藏操作幂等性** /// **Validates: Requirements 4.2** /// /// *For any* 用户收藏操作, 重复收藏同一用户不应创建重复记录 /// [Property(MaxTest = 100)] public Property Favorite_ShouldBeIdempotent() { var userIdArb = Gen.Choose(1, 500); var targetUserIdArb = Gen.Choose(501, 1000); return Prop.ForAll( userIdArb.ToArbitrary(), targetUserIdArb.ToArbitrary(), (userId, targetUserId) => { // Arrange - 创建已有收藏记录 var favorites = new List { new UserFavorite { UserId = userId, TargetUserId = targetUserId, CreateTime = DateTime.Now } }; // Act - 计算再次收藏后的记录数 var countAfterOperation = InteractService.CalculateFavoriteCountAfterOperation( favorites, userId, targetUserId); // Assert - 记录数应该保持不变(幂等性) return countAfterOperation == favorites.Count; }); } /// /// 收藏 - 首次收藏应增加记录 /// [Property(MaxTest = 100)] public Property Favorite_FirstTime_ShouldAddRecord() { var userIdArb = Gen.Choose(1, 500); var targetUserIdArb = Gen.Choose(501, 1000); return Prop.ForAll( userIdArb.ToArbitrary(), targetUserIdArb.ToArbitrary(), (userId, targetUserId) => { // Arrange - 空收藏列表 var favorites = new List(); // Act - 计算首次收藏后的记录数 var countAfterOperation = InteractService.CalculateFavoriteCountAfterOperation( favorites, userId, targetUserId); // Assert - 记录数应该增加1 return countAfterOperation == 1; }); } /// /// 收藏 - 收藏不同用户应创建不同记录 /// [Property(MaxTest = 100)] public Property Favorite_DifferentTargets_ShouldCreateSeparateRecords() { var userIdArb = Gen.Choose(1, 500); var targetUserId1Arb = Gen.Choose(501, 750); var targetUserId2Arb = Gen.Choose(751, 1000); return Prop.ForAll( userIdArb.ToArbitrary(), targetUserId1Arb.ToArbitrary(), targetUserId2Arb.ToArbitrary(), (userId, targetUserId1, targetUserId2) => { // Arrange - 已收藏第一个用户 var favorites = new List { new UserFavorite { UserId = userId, TargetUserId = targetUserId1, CreateTime = DateTime.Now } }; // Act - 收藏第二个用户 var countAfterOperation = InteractService.CalculateFavoriteCountAfterOperation( favorites, userId, targetUserId2); // Assert - 记录数应该增加1 return countAfterOperation == 2; }); } /// /// 收藏 - 检查收藏是否存在 /// [Property(MaxTest = 100)] public Property Favorite_Exists_ShouldReturnCorrectResult() { var userIdArb = Gen.Choose(1, 500); var targetUserIdArb = Gen.Choose(501, 1000); return Prop.ForAll( userIdArb.ToArbitrary(), targetUserIdArb.ToArbitrary(), (userId, targetUserId) => { // Arrange - 创建收藏记录 var favorites = new List { new UserFavorite { UserId = userId, TargetUserId = targetUserId, CreateTime = DateTime.Now } }; // Act var exists = InteractService.IsFavoriteExistsStatic(favorites, userId, targetUserId); var notExists = InteractService.IsFavoriteExistsStatic(favorites, userId, targetUserId + 1); // Assert return exists && !notExists; }); } } /// /// InteractService属性测试 - 解锁联系次数扣减 /// public class UnlockContactCountPropertyTests { /// /// **Feature: backend-api, Property 9: 解锁联系次数扣减** /// **Validates: Requirements 4.3** /// /// *For any* 非会员用户解锁操作, 解锁成功后联系次数应减少1,且应创建聊天会话 /// [Property(MaxTest = 100)] public Property Unlock_NonMember_ShouldDeductContactCount() { // 生成有效的联系次数(1-10) var contactCountArb = Gen.Choose(1, 10); return Prop.ForAll( contactCountArb.ToArbitrary(), contactCount => { // Arrange var isMember = false; var isAlreadyUnlocked = false; // Act var countAfterUnlock = InteractService.CalculateContactCountAfterUnlock( contactCount, isMember, isAlreadyUnlocked); // Assert - 联系次数应减少1 return countAfterUnlock == contactCount - 1; }); } /// /// 解锁 - 非会员解锁后次数应正好减少1 /// [Property(MaxTest = 100)] public Property Unlock_NonMember_ShouldDeductExactlyOne() { var contactCountArb = Gen.Choose(1, 100); return Prop.ForAll( contactCountArb.ToArbitrary(), contactCount => { var countAfterUnlock = InteractService.CalculateContactCountAfterUnlock( contactCount, isMember: false, isAlreadyUnlocked: false); // 验证扣减正好是1 return contactCount - countAfterUnlock == 1; }); } } /// /// InteractService属性测试 - 联系次数为0时拒绝解锁 /// public class UnlockZeroContactCountPropertyTests { /// /// **Feature: backend-api, Property 10: 联系次数为0时拒绝解锁** /// **Validates: Requirements 4.4** /// /// *For any* 联系次数为0的非会员用户, 解锁操作应被拒绝并返回错误提示 /// [Property(MaxTest = 100)] public Property Unlock_ZeroContactCount_ShouldBeRejected() { return Prop.ForAll( Arb.Default.PositiveInt(), _ => { // Arrange var contactCount = 0; var isMember = false; var isAlreadyUnlocked = false; // Act var shouldReject = InteractService.ShouldRejectUnlock( contactCount, isMember, isAlreadyUnlocked); var countAfterUnlock = InteractService.CalculateContactCountAfterUnlock( contactCount, isMember, isAlreadyUnlocked); // Assert - 应该拒绝解锁,返回-1表示无法解锁 return shouldReject && countAfterUnlock == -1; }); } /// /// 解锁 - 负数联系次数也应该被拒绝 /// [Property(MaxTest = 100)] public Property Unlock_NegativeContactCount_ShouldBeRejected() { var negativeCountArb = Gen.Choose(-100, -1); return Prop.ForAll( negativeCountArb.ToArbitrary(), negativeCount => { var shouldReject = InteractService.ShouldRejectUnlock( negativeCount, isMember: false, isAlreadyUnlocked: false); var countAfterUnlock = InteractService.CalculateContactCountAfterUnlock( negativeCount, isMember: false, isAlreadyUnlocked: false); return shouldReject && countAfterUnlock == -1; }); } /// /// 解锁 - 已解锁的用户应该被拒绝 /// [Property(MaxTest = 100)] public Property Unlock_AlreadyUnlocked_ShouldBeRejected() { var contactCountArb = Gen.Choose(0, 10); var isMemberArb = Gen.Elements(true, false); return Prop.ForAll( contactCountArb.ToArbitrary(), isMemberArb.ToArbitrary(), (contactCount, isMember) => { // 已解锁的情况 var shouldReject = InteractService.ShouldRejectUnlock( contactCount, isMember, isAlreadyUnlocked: true); return shouldReject; }); } } /// /// InteractService属性测试 - 会员解锁不扣次数 /// public class MemberUnlockPropertyTests { /// /// **Feature: backend-api, Property 11: 会员解锁不扣次数** /// **Validates: Requirements 7.5** /// /// *For any* 会员用户解锁操作, 解锁成功后联系次数应保持不变 /// [Property(MaxTest = 100)] public Property Unlock_Member_ShouldNotDeductContactCount() { var contactCountArb = Gen.Choose(0, 10); return Prop.ForAll( contactCountArb.ToArbitrary(), contactCount => { // Arrange var isMember = true; var isAlreadyUnlocked = false; // Act var countAfterUnlock = InteractService.CalculateContactCountAfterUnlock( contactCount, isMember, isAlreadyUnlocked); // Assert - 会员解锁后联系次数应保持不变 return countAfterUnlock == contactCount; }); } /// /// 会员解锁 - 即使联系次数为0也应该允许解锁 /// [Property(MaxTest = 100)] public Property Unlock_MemberWithZeroCount_ShouldBeAllowed() { return Prop.ForAll( Arb.Default.PositiveInt(), _ => { // Arrange var contactCount = 0; var isMember = true; var isAlreadyUnlocked = false; // Act var shouldReject = InteractService.ShouldRejectUnlock( contactCount, isMember, isAlreadyUnlocked); var countAfterUnlock = InteractService.CalculateContactCountAfterUnlock( contactCount, isMember, isAlreadyUnlocked); // Assert - 会员不应该被拒绝,次数保持为0 return !shouldReject && countAfterUnlock == 0; }); } /// /// 会员解锁 - 任意联系次数都应该保持不变 /// [Property(MaxTest = 100)] public Property Unlock_Member_ContactCountShouldRemainUnchanged() { var contactCountArb = Gen.Choose(-10, 100); return Prop.ForAll( contactCountArb.ToArbitrary(), contactCount => { var countAfterUnlock = InteractService.CalculateContactCountAfterUnlock( contactCount, isMember: true, isAlreadyUnlocked: false); // 会员解锁后次数应该完全不变 return countAfterUnlock == contactCount; }); } /// /// 会员 vs 非会员 - 会员解锁次数应大于等于非会员 /// [Property(MaxTest = 100)] public Property Unlock_MemberVsNonMember_MemberShouldHaveMoreOrEqualCount() { var contactCountArb = Gen.Choose(1, 10); return Prop.ForAll( contactCountArb.ToArbitrary(), contactCount => { var memberCountAfter = InteractService.CalculateContactCountAfterUnlock( contactCount, isMember: true, isAlreadyUnlocked: false); var nonMemberCountAfter = InteractService.CalculateContactCountAfterUnlock( contactCount, isMember: false, isAlreadyUnlocked: false); // 会员解锁后的次数应该大于等于非会员 return memberCountAfter >= nonMemberCountAfter; }); } }