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

426 lines
15 KiB
C#
Raw 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.Enums;
namespace XiangYi.Application.Tests.Services;
/// <summary>
/// ProfileService属性测试
/// </summary>
public class ProfileServicePropertyTests
{
/// <summary>
/// **Feature: backend-api, Property 2: 昵称生成规则正确性**
/// **Validates: Requirements 2.1**
///
/// *For any* 用户资料提交, 系统生成的昵称应符合规则:
/// 父亲->"X家长父亲",母亲->"X家长母亲"
/// 本人男->"X先生本人",本人女->"X女士本人"
/// </summary>
[Property(MaxTest = 100)]
public Property Nickname_ShouldFollowGenerationRules()
{
// 生成有效的姓氏(非空非纯空白字符串)
var surnameArb = Arb.Default.NonEmptyString()
.Filter(s => !string.IsNullOrWhiteSpace(s.Get))
.Generator
.Select(s => s.Get.Trim().Substring(0, Math.Min(s.Get.Trim().Length, 2)));
// 生成有效的关系类型1父亲, 2母亲, 3本人
var relationshipArb = Gen.Elements(1, 2, 3);
// 生成有效的性别1男, 2女
var genderArb = Gen.Elements(1, 2);
return Prop.ForAll(
surnameArb.ToArbitrary(),
relationshipArb.ToArbitrary(),
genderArb.ToArbitrary(),
(surname, relationship, gender) =>
{
// Act
var nickname = ProfileService.GenerateNicknameStatic(relationship, surname, gender);
// Assert
return relationship switch
{
(int)Relationship.Father => nickname == $"{surname}家长(父亲)",
(int)Relationship.Mother => nickname == $"{surname}家长(母亲)",
(int)Relationship.Self when gender == (int)Gender.Male => nickname == $"{surname}先生(本人)",
(int)Relationship.Self when gender == (int)Gender.Female => nickname == $"{surname}女士(本人)",
_ => false
};
});
}
/// <summary>
/// 昵称生成 - 空姓氏应使用默认值"某"
/// </summary>
[Property(MaxTest = 100)]
public Property Nickname_EmptySurname_ShouldUseDefault()
{
var relationshipArb = Gen.Elements(1, 2, 3);
var genderArb = Gen.Elements(1, 2);
var emptySurnameArb = Gen.Elements("", " ", " ", null!);
return Prop.ForAll(
emptySurnameArb.ToArbitrary(),
relationshipArb.ToArbitrary(),
genderArb.ToArbitrary(),
(surname, relationship, gender) =>
{
// Act
var nickname = ProfileService.GenerateNicknameStatic(relationship, surname, gender);
// Assert - 应该使用默认姓氏"某"
return nickname.StartsWith("某");
});
}
/// <summary>
/// 昵称生成 - 父亲关系应包含"父亲"
/// </summary>
[Property(MaxTest = 100)]
public Property Nickname_Father_ShouldContainFatherSuffix()
{
var surnameArb = Gen.Elements("李", "王", "张", "刘", "陈");
var genderArb = Gen.Elements(1, 2);
return Prop.ForAll(
surnameArb.ToArbitrary(),
genderArb.ToArbitrary(),
(surname, gender) =>
{
var nickname = ProfileService.GenerateNicknameStatic((int)Relationship.Father, surname, gender);
return nickname.Contains("父亲") && nickname.Contains("家长");
});
}
/// <summary>
/// 昵称生成 - 母亲关系应包含"母亲"
/// </summary>
[Property(MaxTest = 100)]
public Property Nickname_Mother_ShouldContainMotherSuffix()
{
var surnameArb = Gen.Elements("李", "王", "张", "刘", "陈");
var genderArb = Gen.Elements(1, 2);
return Prop.ForAll(
surnameArb.ToArbitrary(),
genderArb.ToArbitrary(),
(surname, gender) =>
{
var nickname = ProfileService.GenerateNicknameStatic((int)Relationship.Mother, surname, gender);
return nickname.Contains("母亲") && nickname.Contains("家长");
});
}
/// <summary>
/// 昵称生成 - 本人男性应包含"先生"
/// </summary>
[Property(MaxTest = 100)]
public Property Nickname_SelfMale_ShouldContainMrSuffix()
{
var surnameArb = Gen.Elements("李", "王", "张", "刘", "陈");
return Prop.ForAll(
surnameArb.ToArbitrary(),
surname =>
{
var nickname = ProfileService.GenerateNicknameStatic((int)Relationship.Self, surname, (int)Gender.Male);
return nickname.Contains("先生") && nickname.Contains("本人");
});
}
/// <summary>
/// 昵称生成 - 本人女性应包含"女士"
/// </summary>
[Property(MaxTest = 100)]
public Property Nickname_SelfFemale_ShouldContainMsSuffix()
{
var surnameArb = Gen.Elements("李", "王", "张", "刘", "陈");
return Prop.ForAll(
surnameArb.ToArbitrary(),
surname =>
{
var nickname = ProfileService.GenerateNicknameStatic((int)Relationship.Self, surname, (int)Gender.Female);
return nickname.Contains("女士") && nickname.Contains("本人");
});
}
}
/// <summary>
/// 照片数量限制属性测试
/// </summary>
public class PhotoLimitPropertyTests
{
/// <summary>
/// **Feature: backend-api, Property 3: 照片数量限制**
/// **Validates: Requirements 2.2**
///
/// *For any* 用户上传照片操作, 当照片总数超过5张时应拒绝上传
///
/// 此测试验证CanUploadPhotos方法正确判断是否可以上传照片
/// </summary>
[Property(MaxTest = 100)]
public Property PhotoUpload_ShouldRejectWhenExceedsLimit()
{
// 生成当前照片数量0-10
var currentCountArb = Gen.Choose(0, 10);
// 生成要上传的照片数量1-10
var uploadCountArb = Gen.Choose(1, 10);
return Prop.ForAll(
currentCountArb.ToArbitrary(),
uploadCountArb.ToArbitrary(),
(currentCount, uploadCount) =>
{
// Act - 使用静态方法验证逻辑
var canUpload = CanUploadPhotosStatic(currentCount, uploadCount, ProfileService.MaxPhotoCount);
// Assert
// 如果当前数量 + 上传数量 <= 5应该允许上传
// 如果当前数量 + 上传数量 > 5应该拒绝上传
var expectedResult = currentCount + uploadCount <= ProfileService.MaxPhotoCount;
return canUpload == expectedResult;
});
}
/// <summary>
/// 照片数量限制 - 边界测试恰好5张时不能再上传
/// </summary>
[Property(MaxTest = 100)]
public Property PhotoUpload_AtMaxLimit_ShouldRejectAnyUpload()
{
var uploadCountArb = Gen.Choose(1, 10);
return Prop.ForAll(
uploadCountArb.ToArbitrary(),
uploadCount =>
{
// 当前已有5张照片
var currentCount = ProfileService.MaxPhotoCount;
var canUpload = CanUploadPhotosStatic(currentCount, uploadCount, ProfileService.MaxPhotoCount);
// 应该拒绝任何上传
return !canUpload;
});
}
/// <summary>
/// 照片数量限制 - 边界测试4张时只能上传1张
/// </summary>
[Property(MaxTest = 100)]
public Property PhotoUpload_OneBelowLimit_ShouldAllowOnlyOne()
{
var uploadCountArb = Gen.Choose(1, 10);
return Prop.ForAll(
uploadCountArb.ToArbitrary(),
uploadCount =>
{
// 当前已有4张照片
var currentCount = ProfileService.MaxPhotoCount - 1;
var canUpload = CanUploadPhotosStatic(currentCount, uploadCount, ProfileService.MaxPhotoCount);
// 只有上传1张时才允许
return canUpload == (uploadCount == 1);
});
}
/// <summary>
/// 照片数量限制 - 0张时可以上传最多5张
/// </summary>
[Property(MaxTest = 100)]
public Property PhotoUpload_EmptyPhotos_ShouldAllowUpToMax()
{
var uploadCountArb = Gen.Choose(1, 10);
return Prop.ForAll(
uploadCountArb.ToArbitrary(),
uploadCount =>
{
// 当前没有照片
var currentCount = 0;
var canUpload = CanUploadPhotosStatic(currentCount, uploadCount, ProfileService.MaxPhotoCount);
// 上传数量 <= 5 时允许
return canUpload == (uploadCount <= ProfileService.MaxPhotoCount);
});
}
/// <summary>
/// 照片数量限制 - 最大照片数量常量应为5
/// </summary>
[Fact]
public void MaxPhotoCount_ShouldBe5()
{
Assert.Equal(5, ProfileService.MaxPhotoCount);
}
/// <summary>
/// 静态方法:验证是否可以上传照片
/// </summary>
private static bool CanUploadPhotosStatic(int currentCount, int uploadCount, int maxCount)
{
return currentCount + uploadCount <= maxCount;
}
}
/// <summary>
/// 照片隐私控制属性测试
/// </summary>
public class PhotoPrivacyPropertyTests
{
/// <summary>
/// **Feature: backend-api, Property 4: 照片隐私控制**
/// **Validates: Requirements 2.5**
///
/// *For any* 设置照片不公开的用户, 在推荐列表中返回时照片字段应为空或模糊处理
///
/// 此测试验证FilterPhotosForDisplay方法正确处理照片隐私
/// </summary>
[Property(MaxTest = 100)]
public Property PhotoPrivacy_WhenNotPublic_ShouldHidePhotos()
{
// 生成照片URL列表1-5张
var photoCountArb = Gen.Choose(1, 5);
return Prop.ForAll(
photoCountArb.ToArbitrary(),
count =>
{
// Arrange
var photos = Enumerable.Range(1, count)
.Select(i => $"https://example.com/photo{i}.jpg")
.ToList();
// Act - 照片不公开时
var filteredPhotos = ProfileService.FilterPhotosForDisplay(photos, isPhotoPublic: false, isViewerMember: false);
// Assert - 照片应该被隐藏(返回空列表)
return filteredPhotos.Count == 0;
});
}
/// <summary>
/// 照片隐私 - 公开照片应该正常显示
/// </summary>
[Property(MaxTest = 100)]
public Property PhotoPrivacy_WhenPublic_ShouldShowPhotos()
{
var photoCountArb = Gen.Choose(1, 5);
return Prop.ForAll(
photoCountArb.ToArbitrary(),
count =>
{
// Arrange
var photos = Enumerable.Range(1, count)
.Select(i => $"https://example.com/photo{i}.jpg")
.ToList();
// Act - 照片公开时
var filteredPhotos = ProfileService.FilterPhotosForDisplay(photos, isPhotoPublic: true, isViewerMember: false);
// Assert - 照片应该正常显示
return filteredPhotos.Count == photos.Count &&
filteredPhotos.SequenceEqual(photos);
});
}
/// <summary>
/// 照片隐私 - 会员查看不公开照片时应该显示(交换照片后)
/// 注:此测试验证会员特权逻辑,实际实现中会员可能需要通过交换请求才能查看
/// </summary>
[Property(MaxTest = 100)]
public Property PhotoPrivacy_MemberViewing_ShouldRespectPrivacySetting()
{
var photoCountArb = Gen.Choose(1, 5);
var isPublicArb = Gen.Elements(true, false);
return Prop.ForAll(
photoCountArb.ToArbitrary(),
isPublicArb.ToArbitrary(),
(count, isPublic) =>
{
// Arrange
var photos = Enumerable.Range(1, count)
.Select(i => $"https://example.com/photo{i}.jpg")
.ToList();
// Act - 会员查看时
var filteredPhotos = ProfileService.FilterPhotosForDisplay(photos, isPhotoPublic: isPublic, isViewerMember: true);
// Assert
// 如果照片公开,会员可以看到
// 如果照片不公开,会员也看不到(需要通过交换请求)
if (isPublic)
{
return filteredPhotos.Count == photos.Count;
}
else
{
// 不公开时,即使是会员也看不到(需要交换)
return filteredPhotos.Count == 0;
}
});
}
/// <summary>
/// 照片隐私 - 空照片列表应该返回空
/// </summary>
[Property(MaxTest = 100)]
public Property PhotoPrivacy_EmptyPhotos_ShouldReturnEmpty()
{
var isPublicArb = Gen.Elements(true, false);
var isMemberArb = Gen.Elements(true, false);
return Prop.ForAll(
isPublicArb.ToArbitrary(),
isMemberArb.ToArbitrary(),
(isPublic, isMember) =>
{
// Arrange
var photos = new List<string>();
// Act
var filteredPhotos = ProfileService.FilterPhotosForDisplay(photos, isPhotoPublic: isPublic, isViewerMember: isMember);
// Assert - 空列表应该返回空
return filteredPhotos.Count == 0;
});
}
/// <summary>
/// 照片隐私 - 隐私设置不应影响照片数量(只影响是否显示)
/// </summary>
[Property(MaxTest = 100)]
public Property PhotoPrivacy_PrivacySetting_ShouldNotAffectOriginalList()
{
var photoCountArb = Gen.Choose(1, 5);
return Prop.ForAll(
photoCountArb.ToArbitrary(),
count =>
{
// Arrange
var originalPhotos = Enumerable.Range(1, count)
.Select(i => $"https://example.com/photo{i}.jpg")
.ToList();
var photosCopy = new List<string>(originalPhotos);
// Act - 调用过滤方法
_ = ProfileService.FilterPhotosForDisplay(originalPhotos, isPhotoPublic: false, isViewerMember: false);
// Assert - 原始列表不应被修改
return originalPhotos.SequenceEqual(photosCopy);
});
}
}