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 }