using FsCheck; using FsCheck.Xunit; using Xunit; using XiangYi.Application.Services; using XiangYi.Core.Entities.Biz; using XiangYi.Core.Enums; namespace XiangYi.Application.Tests.Services; /// /// AdminReportService属性测试 - 举报处理状态流转 /// public class AdminReportServicePropertyTests { // 预定义的举报原因列表 private static readonly string[] ValidReasons = new[] { "虚假信息", "骚扰行为", "不当言论", "欺诈行为", "色情内容", "广告推销" }; // 预定义的有效举报类型 private static readonly int[] ValidReportTypes = new[] { (int)ReportType.User, (int)ReportType.Message, (int)ReportType.Profile }; /// /// **Feature: backend-api, Property 27: 举报处理状态流转** /// **Validates: Requirements 16.3** /// /// *For any* 举报记录, 状态应从"待处理"流转到"已处理",且处理后应记录处理结果 /// /// 此测试验证:从待处理状态可以流转到已处理或已忽略 /// [Property(MaxTest = 100)] public Property StatusTransition_FromPending_ShouldAllowHandledOrIgnored() { // Generate target status (Handled or Ignored) var targetStatusArb = Gen.Elements( (int)ReportStatus.Handled, (int)ReportStatus.Ignored); return Prop.ForAll( targetStatusArb.ToArbitrary(), targetStatus => { // Act var isValid = AdminReportService.IsValidStatusTransitionStatic( (int)ReportStatus.Pending, targetStatus); // Assert - From Pending, should allow transition to Handled or Ignored return isValid; }); } /// /// 状态流转 - 从待处理不能流转到待处理 /// [Fact] public void StatusTransition_FromPending_ToPending_ShouldBeInvalid() { var isValid = AdminReportService.IsValidStatusTransitionStatic( (int)ReportStatus.Pending, (int)ReportStatus.Pending); Assert.False(isValid); } /// /// 状态流转 - 从已处理不能流转到任何状态 /// [Property(MaxTest = 100)] public Property StatusTransition_FromHandled_ShouldNotAllowAnyTransition() { // Generate any target status var targetStatusArb = Gen.Elements( (int)ReportStatus.Pending, (int)ReportStatus.Handled, (int)ReportStatus.Ignored); return Prop.ForAll( targetStatusArb.ToArbitrary(), targetStatus => { // Act var isValid = AdminReportService.IsValidStatusTransitionStatic( (int)ReportStatus.Handled, targetStatus); // Assert - From Handled, should not allow any transition return !isValid; }); } /// /// 状态流转 - 从已忽略不能流转到任何状态 /// [Property(MaxTest = 100)] public Property StatusTransition_FromIgnored_ShouldNotAllowAnyTransition() { // Generate any target status var targetStatusArb = Gen.Elements( (int)ReportStatus.Pending, (int)ReportStatus.Handled, (int)ReportStatus.Ignored); return Prop.ForAll( targetStatusArb.ToArbitrary(), targetStatus => { // Act var isValid = AdminReportService.IsValidStatusTransitionStatic( (int)ReportStatus.Ignored, targetStatus); // Assert - From Ignored, should not allow any transition return !isValid; }); } /// /// 状态流转 - 任何状态都不能手动流转到待处理 /// [Property(MaxTest = 100)] public Property StatusTransition_ToPending_ShouldAlwaysBeInvalid() { // Generate any current status var currentStatusArb = Gen.Elements( (int)ReportStatus.Pending, (int)ReportStatus.Handled, (int)ReportStatus.Ignored); return Prop.ForAll( currentStatusArb.ToArbitrary(), currentStatus => { // Act var isValid = AdminReportService.IsValidStatusTransitionStatic( currentStatus, (int)ReportStatus.Pending); // Assert - Should never allow transition to Pending return !isValid; }); } /// /// 处理结果 - 处理后应记录处理结果 /// [Property(MaxTest = 100)] public Property HandleReport_ShouldRecordHandleResult() { return Prop.ForAll( Arb.Default.PositiveInt(), positiveInt => { var seed = positiveInt.Get; var reporterId = (seed % 500) + 1; var reportedUserId = ((seed % 500) + 501); var reportType = ValidReportTypes[seed % ValidReportTypes.Length]; var reason = ValidReasons[seed % ValidReasons.Length]; var adminId = (seed % 100) + 1; var handleResult = $"处理结果_{seed}"; // Create a pending report var report = new Report { Id = seed, ReporterId = reporterId, ReportedUserId = reportedUserId, ReportType = reportType, Reason = reason, Status = (int)ReportStatus.Pending, CreateTime = DateTime.Now, UpdateTime = DateTime.Now }; // Act - Handle the report var handledReport = AdminReportService.HandleReportStatic(report, adminId, handleResult); // Assert - Should have handle result recorded var hasHandleResult = AdminReportService.HasHandleResult(handledReport); var statusIsHandled = handledReport.Status == (int)ReportStatus.Handled; var hasCorrectResult = handledReport.HandleResult == handleResult; var hasAdminId = handledReport.HandleAdminId == adminId; var hasHandleTime = handledReport.HandleTime.HasValue; return hasHandleResult && statusIsHandled && hasCorrectResult && hasAdminId && hasHandleTime; }); } /// /// 处理结果 - 忽略后应记录处理结果 /// [Property(MaxTest = 100)] public Property IgnoreReport_ShouldRecordHandleResult() { return Prop.ForAll( Arb.Default.PositiveInt(), positiveInt => { var seed = positiveInt.Get; var reporterId = (seed % 500) + 1; var reportedUserId = ((seed % 500) + 501); var reportType = ValidReportTypes[seed % ValidReportTypes.Length]; var reason = ValidReasons[seed % ValidReasons.Length]; var adminId = (seed % 100) + 1; var ignoreReason = $"忽略原因_{seed}"; // Create a pending report var report = new Report { Id = seed, ReporterId = reporterId, ReportedUserId = reportedUserId, ReportType = reportType, Reason = reason, Status = (int)ReportStatus.Pending, CreateTime = DateTime.Now, UpdateTime = DateTime.Now }; // Act - Ignore the report var ignoredReport = AdminReportService.IgnoreReportStatic(report, adminId, ignoreReason); // Assert - Should have handle result recorded var hasHandleResult = AdminReportService.HasHandleResult(ignoredReport); var statusIsIgnored = ignoredReport.Status == (int)ReportStatus.Ignored; var hasCorrectResult = ignoredReport.HandleResult == ignoreReason; var hasAdminId = ignoredReport.HandleAdminId == adminId; var hasHandleTime = ignoredReport.HandleTime.HasValue; return hasHandleResult && statusIsIgnored && hasCorrectResult && hasAdminId && hasHandleTime; }); } /// /// 处理结果 - 忽略时不提供原因应使用默认原因 /// [Property(MaxTest = 100)] public Property IgnoreReport_WithoutReason_ShouldUseDefaultReason() { return Prop.ForAll( Arb.Default.PositiveInt(), positiveInt => { var seed = positiveInt.Get; var reporterId = (seed % 500) + 1; var reportedUserId = ((seed % 500) + 501); var reportType = ValidReportTypes[seed % ValidReportTypes.Length]; var reason = ValidReasons[seed % ValidReasons.Length]; var adminId = (seed % 100) + 1; // Create a pending report var report = new Report { Id = seed, ReporterId = reporterId, ReportedUserId = reportedUserId, ReportType = reportType, Reason = reason, Status = (int)ReportStatus.Pending, CreateTime = DateTime.Now, UpdateTime = DateTime.Now }; // Act - Ignore the report without reason var ignoredReport = AdminReportService.IgnoreReportStatic(report, adminId, null); // Assert - Should have default handle result var hasDefaultResult = ignoredReport.HandleResult == "举报已忽略"; return hasDefaultResult; }); } /// /// 状态流转 - 无效状态值应该被拒绝 /// [Property(MaxTest = 100)] public Property StatusTransition_InvalidStatusValues_ShouldBeRejected() { // Generate invalid status values (outside 0, 1, 2) var invalidStatusArb = Gen.Choose(-10, 10) .Where(s => s != 0 && s != 1 && s != 2); var validStatusArb = Gen.Elements(0, 1, 2); return Prop.ForAll( invalidStatusArb.ToArbitrary(), validStatusArb.ToArbitrary(), (invalidStatus, validStatus) => { // Act - Invalid current status var isValid1 = AdminReportService.IsValidStatusTransitionStatic( invalidStatus, validStatus); // Act - Invalid target status var isValid2 = AdminReportService.IsValidStatusTransitionStatic( validStatus, invalidStatus); // Assert - Both should be invalid return !isValid1 && !isValid2; }); } /// /// 待处理状态 - 不需要处理结果 /// [Property(MaxTest = 100)] public Property PendingReport_ShouldNotRequireHandleResult() { return Prop.ForAll( Arb.Default.PositiveInt(), positiveInt => { var seed = positiveInt.Get; var reporterId = (seed % 500) + 1; var reportedUserId = ((seed % 500) + 501); var reportType = ValidReportTypes[seed % ValidReportTypes.Length]; var reason = ValidReasons[seed % ValidReasons.Length]; // Create a pending report without handle result var report = new Report { Id = seed, ReporterId = reporterId, ReportedUserId = reportedUserId, ReportType = reportType, Reason = reason, Status = (int)ReportStatus.Pending, HandleResult = null, HandleAdminId = null, HandleTime = null, CreateTime = DateTime.Now, UpdateTime = DateTime.Now }; // Assert - Pending report should pass HasHandleResult check return AdminReportService.HasHandleResult(report); }); } /// /// 状态流转完整性 - 从待处理到已处理或已忽略的流转应该是完整的 /// [Property(MaxTest = 100)] public Property StatusTransition_CompleteWorkflow_ShouldBeValid() { // Generate a sequence of valid transitions var workflowArb = Gen.Elements( new[] { (int)ReportStatus.Pending, (int)ReportStatus.Handled }, new[] { (int)ReportStatus.Pending, (int)ReportStatus.Ignored }); return Prop.ForAll( workflowArb.ToArbitrary(), workflow => { // Verify each transition in the workflow is valid for (int i = 0; i < workflow.Length - 1; i++) { var isValid = AdminReportService.IsValidStatusTransitionStatic( workflow[i], workflow[i + 1]); if (!isValid) return false; } return true; }); } /// /// 处理时间 - 处理后应记录处理时间 /// [Property(MaxTest = 100)] public Property HandleReport_HandleTime_ShouldBeRecent() { return Prop.ForAll( Arb.Default.PositiveInt(), positiveInt => { var seed = positiveInt.Get; var reporterId = (seed % 500) + 1; var reportedUserId = ((seed % 500) + 501); var reportType = ValidReportTypes[seed % ValidReportTypes.Length]; var reason = ValidReasons[seed % ValidReasons.Length]; var adminId = (seed % 100) + 1; var handleResult = $"处理结果_{seed}"; // Create a pending report var report = new Report { Id = seed, ReporterId = reporterId, ReportedUserId = reportedUserId, ReportType = reportType, Reason = reason, Status = (int)ReportStatus.Pending, CreateTime = DateTime.Now.AddDays(-1), // Created yesterday UpdateTime = DateTime.Now.AddDays(-1) }; var beforeHandle = DateTime.Now; // Act - Handle the report var handledReport = AdminReportService.HandleReportStatic(report, adminId, handleResult); var afterHandle = DateTime.Now; // Assert - Handle time should be between beforeHandle and afterHandle return handledReport.HandleTime.HasValue && handledReport.HandleTime.Value >= beforeHandle && handledReport.HandleTime.Value <= afterHandle; }); } }