using FsCheck;
using FsCheck.Xunit;
using Xunit;
using XiangYi.Application.DTOs.Requests;
using XiangYi.Application.Services;
using XiangYi.Core.Entities.Biz;
using XiangYi.Core.Enums;
namespace XiangYi.Application.Tests.Services;
///
/// SearchService属性测试 - 搜索条件过滤正确性
///
public class SearchCriteriaFilterPropertyTests
{
///
/// **Feature: backend-api, Property 12: 搜索条件过滤正确性**
/// **Validates: Requirements 5.1**
///
/// *For any* 搜索请求, 返回的用户列表中每个用户都应满足所有搜索条件
///
[Property(MaxTest = 100)]
public Property SearchCriteria_AllMatchedUsersShouldSatisfyAllConditions()
{
// 生成有效的用户资料参数
var birthYearArb = Gen.Choose(1970, 2005);
var heightArb = Gen.Choose(150, 200);
return Prop.ForAll(
birthYearArb.ToArbitrary(),
heightArb.ToArbitrary(),
(birthYear, height) =>
{
// 创建用户资料
var profile = CreateBasicProfile(birthYear, height);
// 创建空搜索条件
var request = new SearchRequest();
// Act
var matches = SearchService.MatchesSearchCriteriaStatic(profile, request);
// Assert - 空条件应匹配所有用户
return matches;
});
}
///
/// 年龄过滤应正确工作
///
[Property(MaxTest = 100)]
public Property AgeFilter_ShouldWorkCorrectly()
{
var birthYearArb = Gen.Choose(1970, 2005);
var ageMinArb = Gen.Choose(18, 40);
var ageMaxArb = Gen.Choose(25, 60);
return Prop.ForAll(
birthYearArb.ToArbitrary(),
ageMinArb.ToArbitrary(),
ageMaxArb.ToArbitrary(),
(birthYear, ageMin, ageMax) =>
{
// 确保ageMin <= ageMax
var actualMin = Math.Min(ageMin, ageMax);
var actualMax = Math.Max(ageMin, ageMax);
var profile = CreateBasicProfile(birthYear);
var request = new SearchRequest { AgeMin = actualMin, AgeMax = actualMax };
var matches = SearchService.MatchesSearchCriteriaStatic(profile, request);
var age = DateTime.Now.Year - birthYear;
// 验证:匹配当且仅当年龄在范围内
var expectedMatch = age >= actualMin && age <= actualMax;
return matches == expectedMatch;
});
}
///
/// 身高过滤应正确工作
///
[Property(MaxTest = 100)]
public Property HeightFilter_ShouldWorkCorrectly()
{
var heightArb = Gen.Choose(150, 200);
var heightMinArb = Gen.Choose(155, 175);
var heightMaxArb = Gen.Choose(170, 195);
return Prop.ForAll(
heightArb.ToArbitrary(),
heightMinArb.ToArbitrary(),
heightMaxArb.ToArbitrary(),
(height, heightMin, heightMax) =>
{
// 确保heightMin <= heightMax
var actualMin = Math.Min(heightMin, heightMax);
var actualMax = Math.Max(heightMin, heightMax);
var profile = CreateBasicProfile(1990);
profile.Height = height;
var request = new SearchRequest { HeightMin = actualMin, HeightMax = actualMax };
var matches = SearchService.MatchesSearchCriteriaStatic(profile, request);
// 验证:匹配当且仅当身高在范围内
var expectedMatch = height >= actualMin && height <= actualMax;
return matches == expectedMatch;
});
}
///
/// 月收入过滤应正确工作
///
[Property(MaxTest = 100)]
public Property MonthlyIncomeFilter_ShouldWorkCorrectly()
{
var incomeArb = Gen.Choose(1, 10);
var incomeMinArb = Gen.Choose(1, 5);
var incomeMaxArb = Gen.Choose(5, 10);
return Prop.ForAll(
incomeArb.ToArbitrary(),
incomeMinArb.ToArbitrary(),
incomeMaxArb.ToArbitrary(),
(income, incomeMin, incomeMax) =>
{
// 确保incomeMin <= incomeMax
var actualMin = Math.Min(incomeMin, incomeMax);
var actualMax = Math.Max(incomeMin, incomeMax);
var profile = CreateBasicProfile(1990);
profile.MonthlyIncome = income;
var request = new SearchRequest { MonthlyIncomeMin = actualMin, MonthlyIncomeMax = actualMax };
var matches = SearchService.MatchesSearchCriteriaStatic(profile, request);
// 验证:匹配当且仅当收入在范围内
var expectedMatch = income >= actualMin && income <= actualMax;
return matches == expectedMatch;
});
}
///
/// 工作城市过滤应正确工作
///
[Property(MaxTest = 100)]
public Property WorkCityFilter_ShouldWorkCorrectly()
{
var cities = new[] { "北京", "上海", "广州", "深圳", "杭州" };
var cityArb = Gen.Elements(cities);
return Prop.ForAll(
cityArb.ToArbitrary(),
cityArb.ToArbitrary(),
(profileCity, searchCity) =>
{
var profile = CreateBasicProfile(1990);
profile.WorkCity = profileCity;
var request = new SearchRequest { WorkCity = searchCity };
var matches = SearchService.MatchesSearchCriteriaStatic(profile, request);
// 验证:匹配当且仅当城市相同(忽略大小写)
var expectedMatch = string.Equals(profileCity, searchCity, StringComparison.OrdinalIgnoreCase);
return matches == expectedMatch;
});
}
///
/// 房产情况过滤应正确工作(OR逻辑)
///
[Property(MaxTest = 100)]
public Property HouseStatusFilter_ShouldWorkWithOrLogic()
{
var houseStatusArb = Gen.Choose(1, 6);
var houseStatus1Arb = Gen.Choose(1, 3);
var houseStatus2Arb = Gen.Choose(4, 6);
return Prop.ForAll(
houseStatusArb.ToArbitrary(),
houseStatus1Arb.ToArbitrary(),
houseStatus2Arb.ToArbitrary(),
(profileHouseStatus, status1, status2) =>
{
var searchHouseStatuses = new List { status1, status2 };
var profile = CreateBasicProfile(1990);
profile.HouseStatus = profileHouseStatus;
var request = new SearchRequest { HouseStatus = searchHouseStatuses };
var matches = SearchService.MatchesSearchCriteriaStatic(profile, request);
// 验证:匹配当且仅当资料的房产状态在搜索列表中(OR逻辑)
var expectedMatch = searchHouseStatuses.Contains(profileHouseStatus);
return matches == expectedMatch;
});
}
///
/// 车辆情况过滤应正确工作(OR逻辑)
///
[Property(MaxTest = 100)]
public Property CarStatusFilter_ShouldWorkWithOrLogic()
{
var carStatusArb = Gen.Choose(1, 3);
var carStatus1Arb = Gen.Choose(1, 2);
var carStatus2Arb = Gen.Choose(2, 3);
return Prop.ForAll(
carStatusArb.ToArbitrary(),
carStatus1Arb.ToArbitrary(),
carStatus2Arb.ToArbitrary(),
(profileCarStatus, status1, status2) =>
{
var searchCarStatuses = new List { status1, status2 }.Distinct().ToList();
var profile = CreateBasicProfile(1990);
profile.CarStatus = profileCarStatus;
var request = new SearchRequest { CarStatus = searchCarStatuses };
var matches = SearchService.MatchesSearchCriteriaStatic(profile, request);
// 验证:匹配当且仅当资料的车辆状态在搜索列表中(OR逻辑)
var expectedMatch = searchCarStatuses.Contains(profileCarStatus);
return matches == expectedMatch;
});
}
///
/// 婚姻状态过滤应正确工作(OR逻辑)
///
[Property(MaxTest = 100)]
public Property MarriageStatusFilter_ShouldWorkWithOrLogic()
{
var marriageStatusArb = Gen.Choose(1, 3);
var marriageStatus1Arb = Gen.Choose(1, 2);
var marriageStatus2Arb = Gen.Choose(2, 3);
return Prop.ForAll(
marriageStatusArb.ToArbitrary(),
marriageStatus1Arb.ToArbitrary(),
marriageStatus2Arb.ToArbitrary(),
(profileMarriageStatus, status1, status2) =>
{
var searchMarriageStatuses = new List { status1, status2 }.Distinct().ToList();
var profile = CreateBasicProfile(1990);
profile.MarriageStatus = profileMarriageStatus;
var request = new SearchRequest { MarriageStatus = searchMarriageStatuses };
var matches = SearchService.MatchesSearchCriteriaStatic(profile, request);
// 验证:匹配当且仅当资料的婚姻状态在搜索列表中(OR逻辑)
var expectedMatch = searchMarriageStatuses.Contains(profileMarriageStatus);
return matches == expectedMatch;
});
}
///
/// 空搜索条件应匹配所有用户
///
[Property(MaxTest = 100)]
public Property EmptySearchCriteria_ShouldMatchAllUsers()
{
var birthYearArb = Gen.Choose(1970, 2005);
return Prop.ForAll(
birthYearArb.ToArbitrary(),
birthYear =>
{
var profile = CreateBasicProfile(birthYear);
var request = new SearchRequest(); // 空搜索条件
var matches = SearchService.MatchesSearchCriteriaStatic(profile, request);
// 空条件应匹配所有用户
return matches;
});
}
///
/// 诚意会员每日搜索次数限制应为5
///
[Fact]
public void SincereMemberDailySearchLimit_ShouldBe5()
{
Assert.Equal(5, SearchService.SincereMemberDailySearchLimit);
}
#region Helper Methods
private static UserProfile CreateBasicProfile(int birthYear, int height = 175, int education = 4, int income = 5)
{
return new UserProfile
{
Id = 1,
UserId = 1,
BirthYear = birthYear,
Height = height,
Education = education,
MonthlyIncome = income,
HouseStatus = 1,
CarStatus = 1,
MarriageStatus = 1,
WorkCity = "北京",
WorkProvince = "北京",
ChildGender = 1,
Relationship = 1,
Surname = "张",
Occupation = "工程师",
Weight = 70,
ExpectMarryTime = 1,
Introduction = "测试",
IsPhotoPublic = true,
WeChatNo = "test123",
AuditStatus = (int)AuditStatus.Approved
};
}
#endregion
}
///
/// SearchService属性测试 - 多选学历OR逻辑
///
public class EducationOrLogicPropertyTests
{
///
/// **Feature: backend-api, Property 13: 多选学历OR逻辑**
/// **Validates: Requirements 5.4**
///
/// *For any* 包含多个学历条件的搜索, 返回的用户应满足其中任一学历条件
///
[Property(MaxTest = 100)]
public Property EducationFilter_ShouldUseOrLogic()
{
// 生成用户的学历(1-6)
var profileEducationArb = Gen.Choose(1, 6);
// 生成两个不同的学历用于搜索
var edu1Arb = Gen.Choose(1, 3);
var edu2Arb = Gen.Choose(4, 6);
return Prop.ForAll(
profileEducationArb.ToArbitrary(),
edu1Arb.ToArbitrary(),
edu2Arb.ToArbitrary(),
(profileEducation, edu1, edu2) =>
{
// Arrange
var searchEducations = new List { edu1, edu2 };
var profile = CreateBasicProfile(profileEducation);
var request = new SearchRequest { Education = searchEducations };
// Act
var matches = SearchService.MatchesSearchCriteriaStatic(profile, request);
// Assert - 匹配当且仅当用户学历在搜索列表中(OR逻辑)
var expectedMatch = searchEducations.Contains(profileEducation);
return matches == expectedMatch;
});
}
///
/// 单个学历条件应正确匹配
///
[Property(MaxTest = 100)]
public Property SingleEducation_ShouldMatchExactly()
{
var educationArb = Gen.Choose(1, 6);
return Prop.ForAll(
educationArb.ToArbitrary(),
educationArb.ToArbitrary(),
(profileEducation, searchEducation) =>
{
var profile = CreateBasicProfile(profileEducation);
var request = new SearchRequest { Education = new List { searchEducation } };
var matches = SearchService.MatchesSearchCriteriaStatic(profile, request);
// 单个学历条件:匹配当且仅当学历相同
var expectedMatch = profileEducation == searchEducation;
return matches == expectedMatch;
});
}
///
/// 多个学历条件应使用OR逻辑 - 三个学历
///
[Property(MaxTest = 100)]
public Property ThreeEducations_ShouldMatchAny()
{
var profileEducationArb = Gen.Choose(1, 6);
var edu1Arb = Gen.Choose(1, 2);
var edu2Arb = Gen.Choose(3, 4);
var edu3Arb = Gen.Choose(5, 6);
return Prop.ForAll(
profileEducationArb.ToArbitrary(),
edu1Arb.ToArbitrary(),
edu2Arb.ToArbitrary(),
(profileEducation, edu1, edu2) =>
{
var searchEducations = new List { edu1, edu2, 5 }; // 固定第三个为5
var profile = CreateBasicProfile(profileEducation);
var request = new SearchRequest { Education = searchEducations };
var matches = SearchService.MatchesSearchCriteriaStatic(profile, request);
// 多个学历条件:匹配当且仅当用户学历在列表中任一
var expectedMatch = searchEducations.Contains(profileEducation);
return matches == expectedMatch;
});
}
///
/// 空学历列表应匹配所有用户
///
[Property(MaxTest = 100)]
public Property EmptyEducationList_ShouldMatchAll()
{
var profileEducationArb = Gen.Choose(1, 6);
return Prop.ForAll(
profileEducationArb.ToArbitrary(),
profileEducation =>
{
var profile = CreateBasicProfile(profileEducation);
var request = new SearchRequest { Education = new List() }; // 空列表
var matches = SearchService.MatchesSearchCriteriaStatic(profile, request);
// 空学历列表应匹配所有用户
return matches;
});
}
///
/// null学历条件应匹配所有用户
///
[Property(MaxTest = 100)]
public Property NullEducation_ShouldMatchAll()
{
var profileEducationArb = Gen.Choose(1, 6);
return Prop.ForAll(
profileEducationArb.ToArbitrary(),
profileEducation =>
{
var profile = CreateBasicProfile(profileEducation);
var request = new SearchRequest { Education = null }; // null
var matches = SearchService.MatchesSearchCriteriaStatic(profile, request);
// null学历条件应匹配所有用户
return matches;
});
}
///
/// 包含所有学历的列表应匹配所有用户
///
[Property(MaxTest = 100)]
public Property AllEducations_ShouldMatchAll()
{
var profileEducationArb = Gen.Choose(1, 6);
return Prop.ForAll(
profileEducationArb.ToArbitrary(),
profileEducation =>
{
var profile = CreateBasicProfile(profileEducation);
var request = new SearchRequest
{
Education = new List { 1, 2, 3, 4, 5, 6 } // 所有学历
};
var matches = SearchService.MatchesSearchCriteriaStatic(profile, request);
// 包含所有学历的列表应匹配所有用户
return matches;
});
}
///
/// 学历枚举值验证
///
[Fact]
public void EducationEnum_ShouldHaveCorrectValues()
{
Assert.Equal(0, (int)Education.None);
Assert.Equal(1, (int)Education.HighSchool);
Assert.Equal(2, (int)Education.Technical);
Assert.Equal(3, (int)Education.College);
Assert.Equal(4, (int)Education.Bachelor);
Assert.Equal(5, (int)Education.Master);
Assert.Equal(6, (int)Education.Doctor);
}
#region Helper Methods
private static UserProfile CreateBasicProfile(int education)
{
return new UserProfile
{
Id = 1,
UserId = 1,
BirthYear = 1990,
Height = 175,
Education = education,
MonthlyIncome = 5,
HouseStatus = 1,
CarStatus = 1,
MarriageStatus = 1,
WorkCity = "北京",
WorkProvince = "北京",
ChildGender = 1,
Relationship = 1,
Surname = "张",
Occupation = "工程师",
Weight = 70,
ExpectMarryTime = 1,
Introduction = "测试",
IsPhotoPublic = true,
WeChatNo = "test123",
AuditStatus = (int)AuditStatus.Approved
};
}
#endregion
}