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; /// /// AdminLogService属性测试 /// public class AdminLogServicePropertyTests { /// /// **Feature: backend-api, Property 22: 操作日志记录** /// **Validates: Requirements 13.5** /// /// *For any* 管理员执行的敏感操作(增删改), 应记录操作日志包含操作人、操作类型、操作时间 /// [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>(); mockRepository.AddAsync(Arg.Any()) .Returns(callInfo => { capturedLog = callInfo.Arg(); capturedLog.Id = 1; // Simulate ID assignment return Task.FromResult(capturedLog); }); var mockLogger = Substitute.For>(); 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; }); } /// /// 操作日志应记录所有可选字段 /// [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>(); mockRepository.AddAsync(Arg.Any()) .Returns(callInfo => { capturedLog = callInfo.Arg(); capturedLog.Id = 1; return Task.FromResult(capturedLog); }); var mockLogger = Substitute.For>(); 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; }); } /// /// 失败操作应正确记录错误信息 /// [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>(); mockRepository.AddAsync(Arg.Any()) .Returns(callInfo => { capturedLog = callInfo.Arg(); capturedLog.Id = 1; return Task.FromResult(capturedLog); }); var mockLogger = Substitute.For>(); 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; }); } /// /// 日志记录应返回有效的日志ID /// [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>(); mockRepository.AddAsync(Arg.Any()) .Returns(callInfo => { var log = callInfo.Arg(); log.Id = expectedLogId; return Task.FromResult(log); }); var mockLogger = Substitute.For>(); var service = new AdminLogService(mockRepository, mockLogger); // Act var result = service.LogOperationAsync( adminUserId, "testuser", "TestModule", "Update" ).Result; // Assert - 返回的日志ID应该是有效的正数 return result == expectedLogId && result > 0; }); } /// /// 敏感操作类型(增删改)应被正确记录 /// [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>(); mockRepository.AddAsync(Arg.Any()) .Returns(callInfo => { capturedLog = callInfo.Arg(); capturedLog.Id = 1; return Task.FromResult(capturedLog); }); var mockLogger = Substitute.For>(); 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); }); } }