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;
});
}
}