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

294 lines
11 KiB
C#

using FsCheck;
using FsCheck.Xunit;
using NSubstitute;
using Microsoft.Extensions.Logging;
using XiangYi.Application.Services;
using XiangYi.Core.Entities.Admin;
using XiangYi.Core.Interfaces;
namespace XiangYi.Application.Tests.Services;
/// <summary>
/// AdminLogService属性测试
/// </summary>
public class AdminLogServicePropertyTests
{
/// <summary>
/// **Feature: backend-api, Property 22: 操作日志记录**
/// **Validates: Requirements 13.5**
///
/// *For any* 管理员执行的敏感操作(增删改), 应记录操作日志包含操作人、操作类型、操作时间
/// </summary>
[Property(MaxTest = 100)]
public Property LogOperation_ShouldContainRequiredFields()
{
return Prop.ForAll(
Arb.Default.PositiveInt(),
adminIdArb =>
{
var adminUserId = (long)adminIdArb.Get;
var adminUsername = $"admin_{adminUserId}";
var module = "TestModule";
var action = "Create";
// Arrange
AdminOperationLog? capturedLog = null;
var mockRepository = Substitute.For<IRepository<AdminOperationLog>>();
mockRepository.AddAsync(Arg.Any<AdminOperationLog>())
.Returns(callInfo =>
{
capturedLog = callInfo.Arg<AdminOperationLog>();
capturedLog.Id = 1; // Simulate ID assignment
return Task.FromResult(capturedLog);
});
var mockLogger = Substitute.For<ILogger<AdminLogService>>();
var service = new AdminLogService(mockRepository, mockLogger);
// Act
var result = service.LogOperationAsync(
adminUserId,
adminUsername,
module,
action,
description: "Test operation",
status: 1
).Result;
// Assert
if (capturedLog == null)
{
return false;
}
// 1. 操作人ID必须正确记录
var hasCorrectAdminId = capturedLog.AdminUserId == adminUserId;
// 2. 操作人用户名必须正确记录
var hasCorrectUsername = capturedLog.AdminUsername == adminUsername;
// 3. 操作模块必须正确记录
var hasCorrectModule = capturedLog.Module == module;
// 4. 操作类型必须正确记录
var hasCorrectAction = capturedLog.Action == action;
// 5. 状态必须正确记录
var hasCorrectStatus = capturedLog.Status == 1;
return hasCorrectAdminId && hasCorrectUsername && hasCorrectModule && hasCorrectAction && hasCorrectStatus;
});
}
/// <summary>
/// 操作日志应记录所有可选字段
/// </summary>
[Property(MaxTest = 100)]
public Property LogOperation_ShouldRecordOptionalFields()
{
return Prop.ForAll(
Arb.Default.PositiveInt(),
adminIdArb =>
{
var adminUserId = (long)adminIdArb.Get;
var requestUrl = $"/api/admin/test/{adminUserId}";
var ip = "192.168.1.1";
// Arrange
AdminOperationLog? capturedLog = null;
var mockRepository = Substitute.For<IRepository<AdminOperationLog>>();
mockRepository.AddAsync(Arg.Any<AdminOperationLog>())
.Returns(callInfo =>
{
capturedLog = callInfo.Arg<AdminOperationLog>();
capturedLog.Id = 1;
return Task.FromResult(capturedLog);
});
var mockLogger = Substitute.For<ILogger<AdminLogService>>();
var service = new AdminLogService(mockRepository, mockLogger);
// Act
var result = service.LogOperationAsync(
adminUserId,
"testuser",
"TestModule",
"Create",
description: "Test description",
requestUrl: requestUrl,
requestMethod: "POST",
requestParams: "{\"test\":1}",
responseResult: "{\"success\":true}",
targetId: "123",
ip: ip,
userAgent: "TestAgent",
executeTime: 100,
status: 1
).Result;
// Assert
if (capturedLog == null)
{
return false;
}
// 验证所有可选字段都被正确记录
var hasDescription = capturedLog.Description == "Test description";
var hasRequestUrl = capturedLog.RequestUrl == requestUrl;
var hasRequestMethod = capturedLog.RequestMethod == "POST";
var hasRequestParams = capturedLog.RequestParams == "{\"test\":1}";
var hasResponseResult = capturedLog.ResponseResult == "{\"success\":true}";
var hasTargetId = capturedLog.TargetId == "123";
var hasIp = capturedLog.Ip == ip;
var hasUserAgent = capturedLog.UserAgent == "TestAgent";
var hasExecuteTime = capturedLog.ExecuteTime == 100;
return hasDescription && hasRequestUrl && hasRequestMethod &&
hasRequestParams && hasResponseResult && hasTargetId &&
hasIp && hasUserAgent && hasExecuteTime;
});
}
/// <summary>
/// 失败操作应正确记录错误信息
/// </summary>
[Property(MaxTest = 100)]
public Property LogOperation_ShouldRecordErrorInfoForFailedOperations()
{
return Prop.ForAll(
Arb.Default.PositiveInt(),
adminIdArb =>
{
var adminUserId = (long)adminIdArb.Get;
var errorMsg = $"Error occurred for admin {adminUserId}";
// Arrange
AdminOperationLog? capturedLog = null;
var mockRepository = Substitute.For<IRepository<AdminOperationLog>>();
mockRepository.AddAsync(Arg.Any<AdminOperationLog>())
.Returns(callInfo =>
{
capturedLog = callInfo.Arg<AdminOperationLog>();
capturedLog.Id = 1;
return Task.FromResult(capturedLog);
});
var mockLogger = Substitute.For<ILogger<AdminLogService>>();
var service = new AdminLogService(mockRepository, mockLogger);
// Act - 记录失败操作
var result = service.LogOperationAsync(
adminUserId,
"testuser",
"TestModule",
"Delete",
description: "Failed operation",
status: 2, // 失败状态
errorMsg: errorMsg
).Result;
// Assert
if (capturedLog == null)
{
return false;
}
// 1. 状态应为失败(2)
var hasFailedStatus = capturedLog.Status == 2;
// 2. 错误信息应正确记录
var hasErrorMsg = capturedLog.ErrorMsg == errorMsg;
return hasFailedStatus && hasErrorMsg;
});
}
/// <summary>
/// 日志记录应返回有效的日志ID
/// </summary>
[Property(MaxTest = 100)]
public Property LogOperation_ShouldReturnValidLogId()
{
return Prop.ForAll(
Arb.Default.PositiveInt(),
adminIdArb =>
{
var adminUserId = (long)adminIdArb.Get;
var expectedLogId = adminUserId + 1000; // 模拟生成的日志ID
// Arrange
var mockRepository = Substitute.For<IRepository<AdminOperationLog>>();
mockRepository.AddAsync(Arg.Any<AdminOperationLog>())
.Returns(callInfo =>
{
var log = callInfo.Arg<AdminOperationLog>();
log.Id = expectedLogId;
return Task.FromResult(log);
});
var mockLogger = Substitute.For<ILogger<AdminLogService>>();
var service = new AdminLogService(mockRepository, mockLogger);
// Act
var result = service.LogOperationAsync(
adminUserId,
"testuser",
"TestModule",
"Update"
).Result;
// Assert - 返回的日志ID应该是有效的正数
return result == expectedLogId && result > 0;
});
}
/// <summary>
/// 敏感操作类型(增删改)应被正确记录
/// </summary>
[Property(MaxTest = 100)]
public Property LogOperation_ShouldRecordSensitiveOperationTypes()
{
var sensitiveActions = new[] { "Create", "Update", "Delete", "Add", "Modify", "Remove" };
return Prop.ForAll(
Arb.Default.PositiveInt(),
adminIdArb =>
{
var adminUserId = (long)adminIdArb.Get;
var actionIndex = (int)(adminUserId % sensitiveActions.Length);
var action = sensitiveActions[actionIndex];
// Arrange
AdminOperationLog? capturedLog = null;
var mockRepository = Substitute.For<IRepository<AdminOperationLog>>();
mockRepository.AddAsync(Arg.Any<AdminOperationLog>())
.Returns(callInfo =>
{
capturedLog = callInfo.Arg<AdminOperationLog>();
capturedLog.Id = 1;
return Task.FromResult(capturedLog);
});
var mockLogger = Substitute.For<ILogger<AdminLogService>>();
var service = new AdminLogService(mockRepository, mockLogger);
// Act
var result = service.LogOperationAsync(
adminUserId,
"testuser",
"UserManagement",
action
).Result;
// Assert
if (capturedLog == null)
{
return false;
}
// 敏感操作类型应被正确记录
return capturedLog.Action == action && sensitiveActions.Contains(capturedLog.Action);
});
}
}