xiangyixiangqin/server/tests/XiangYi.Application.Tests/Services/InteractServicePropertyTests.cs
2026-01-02 18:00:49 +08:00

551 lines
19 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using FsCheck;
using FsCheck.Xunit;
using Xunit;
using XiangYi.Application.Services;
using XiangYi.Core.Entities.Biz;
namespace XiangYi.Application.Tests.Services;
/// <summary>
/// InteractService属性测试 - 浏览记录一致性
/// </summary>
public class ViewRecordPropertyTests
{
/// <summary>
/// **Feature: backend-api, Property 7: 浏览记录一致性**
/// **Validates: Requirements 4.1**
///
/// *For any* 用户查看他人资料操作, 应创建或更新浏览记录,且被浏览者的"看过我"列表应包含该浏览者
/// </summary>
[Property(MaxTest = 100)]
public Property ViewRecord_ShouldBeConsistent()
{
// 生成浏览者ID1-500
var viewerIdArb = Gen.Choose(1, 500);
// 生成被浏览者ID501-1000
var targetUserIdArb = Gen.Choose(501, 1000);
return Prop.ForAll(
viewerIdArb.ToArbitrary(),
targetUserIdArb.ToArbitrary(),
(viewerId, targetUserId) =>
{
// Arrange - 创建空的浏览记录列表
var viewRecords = new List<UserView>();
// 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;
});
}
/// <summary>
/// 浏览记录 - 同一用户多次浏览应更新次数而非创建新记录
/// </summary>
[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<UserView>
{
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;
});
}
/// <summary>
/// 浏览记录 - 不同日期应创建新记录
/// </summary>
[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<UserView>
{
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;
});
}
/// <summary>
/// 浏览记录 - 浏览者和被浏览者不能是同一人(业务规则验证)
/// </summary>
[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;
});
}
}
/// <summary>
/// InteractService属性测试 - 收藏操作幂等性
/// </summary>
public class FavoriteIdempotencyPropertyTests
{
/// <summary>
/// **Feature: backend-api, Property 8: 收藏操作幂等性**
/// **Validates: Requirements 4.2**
///
/// *For any* 用户收藏操作, 重复收藏同一用户不应创建重复记录
/// </summary>
[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<UserFavorite>
{
new UserFavorite
{
UserId = userId,
TargetUserId = targetUserId,
CreateTime = DateTime.Now
}
};
// Act - 计算再次收藏后的记录数
var countAfterOperation = InteractService.CalculateFavoriteCountAfterOperation(
favorites, userId, targetUserId);
// Assert - 记录数应该保持不变(幂等性)
return countAfterOperation == favorites.Count;
});
}
/// <summary>
/// 收藏 - 首次收藏应增加记录
/// </summary>
[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<UserFavorite>();
// Act - 计算首次收藏后的记录数
var countAfterOperation = InteractService.CalculateFavoriteCountAfterOperation(
favorites, userId, targetUserId);
// Assert - 记录数应该增加1
return countAfterOperation == 1;
});
}
/// <summary>
/// 收藏 - 收藏不同用户应创建不同记录
/// </summary>
[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<UserFavorite>
{
new UserFavorite
{
UserId = userId,
TargetUserId = targetUserId1,
CreateTime = DateTime.Now
}
};
// Act - 收藏第二个用户
var countAfterOperation = InteractService.CalculateFavoriteCountAfterOperation(
favorites, userId, targetUserId2);
// Assert - 记录数应该增加1
return countAfterOperation == 2;
});
}
/// <summary>
/// 收藏 - 检查收藏是否存在
/// </summary>
[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<UserFavorite>
{
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;
});
}
}
/// <summary>
/// InteractService属性测试 - 解锁联系次数扣减
/// </summary>
public class UnlockContactCountPropertyTests
{
/// <summary>
/// **Feature: backend-api, Property 9: 解锁联系次数扣减**
/// **Validates: Requirements 4.3**
///
/// *For any* 非会员用户解锁操作, 解锁成功后联系次数应减少1且应创建聊天会话
/// </summary>
[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;
});
}
/// <summary>
/// 解锁 - 非会员解锁后次数应正好减少1
/// </summary>
[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;
});
}
}
/// <summary>
/// InteractService属性测试 - 联系次数为0时拒绝解锁
/// </summary>
public class UnlockZeroContactCountPropertyTests
{
/// <summary>
/// **Feature: backend-api, Property 10: 联系次数为0时拒绝解锁**
/// **Validates: Requirements 4.4**
///
/// *For any* 联系次数为0的非会员用户, 解锁操作应被拒绝并返回错误提示
/// </summary>
[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;
});
}
/// <summary>
/// 解锁 - 负数联系次数也应该被拒绝
/// </summary>
[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;
});
}
/// <summary>
/// 解锁 - 已解锁的用户应该被拒绝
/// </summary>
[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;
});
}
}
/// <summary>
/// InteractService属性测试 - 会员解锁不扣次数
/// </summary>
public class MemberUnlockPropertyTests
{
/// <summary>
/// **Feature: backend-api, Property 11: 会员解锁不扣次数**
/// **Validates: Requirements 7.5**
///
/// *For any* 会员用户解锁操作, 解锁成功后联系次数应保持不变
/// </summary>
[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;
});
}
/// <summary>
/// 会员解锁 - 即使联系次数为0也应该允许解锁
/// </summary>
[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;
});
}
/// <summary>
/// 会员解锁 - 任意联系次数都应该保持不变
/// </summary>
[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;
});
}
/// <summary>
/// 会员 vs 非会员 - 会员解锁次数应大于等于非会员
/// </summary>
[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;
});
}
}