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

419 lines
15 KiB
C#

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;
/// <summary>
/// AdminReportService属性测试 - 举报处理状态流转
/// </summary>
public class AdminReportServicePropertyTests
{
// 预定义的举报原因列表
private static readonly string[] ValidReasons = new[]
{
"虚假信息", "骚扰行为", "不当言论", "欺诈行为", "色情内容", "广告推销"
};
// 预定义的有效举报类型
private static readonly int[] ValidReportTypes = new[]
{
(int)ReportType.User, (int)ReportType.Message, (int)ReportType.Profile
};
/// <summary>
/// **Feature: backend-api, Property 27: 举报处理状态流转**
/// **Validates: Requirements 16.3**
///
/// *For any* 举报记录, 状态应从"待处理"流转到"已处理",且处理后应记录处理结果
///
/// 此测试验证:从待处理状态可以流转到已处理或已忽略
/// </summary>
[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;
});
}
/// <summary>
/// 状态流转 - 从待处理不能流转到待处理
/// </summary>
[Fact]
public void StatusTransition_FromPending_ToPending_ShouldBeInvalid()
{
var isValid = AdminReportService.IsValidStatusTransitionStatic(
(int)ReportStatus.Pending,
(int)ReportStatus.Pending);
Assert.False(isValid);
}
/// <summary>
/// 状态流转 - 从已处理不能流转到任何状态
/// </summary>
[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;
});
}
/// <summary>
/// 状态流转 - 从已忽略不能流转到任何状态
/// </summary>
[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;
});
}
/// <summary>
/// 状态流转 - 任何状态都不能手动流转到待处理
/// </summary>
[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;
});
}
/// <summary>
/// 处理结果 - 处理后应记录处理结果
/// </summary>
[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;
});
}
/// <summary>
/// 处理结果 - 忽略后应记录处理结果
/// </summary>
[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;
});
}
/// <summary>
/// 处理结果 - 忽略时不提供原因应使用默认原因
/// </summary>
[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;
});
}
/// <summary>
/// 状态流转 - 无效状态值应该被拒绝
/// </summary>
[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;
});
}
/// <summary>
/// 待处理状态 - 不需要处理结果
/// </summary>
[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);
});
}
/// <summary>
/// 状态流转完整性 - 从待处理到已处理或已忽略的流转应该是完整的
/// </summary>
[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;
});
}
/// <summary>
/// 处理时间 - 处理后应记录处理时间
/// </summary>
[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;
});
}
}