419 lines
15 KiB
C#
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;
|
|
});
|
|
}
|
|
}
|