using FsCheck; using FsCheck.Xunit; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using MiAssessment.Admin.Business.Data; using MiAssessment.Admin.Business.Entities; using MiAssessment.Admin.Business.Models; using MiAssessment.Admin.Business.Models.Common; using MiAssessment.Admin.Business.Models.Content; using MiAssessment.Admin.Business.Services; using Moq; using Xunit; namespace MiAssessment.Tests.Admin; /// /// Promotion 属性测试 /// 验证宣传图服务的正确性属性 /// public class PromotionPropertyTests { private readonly Mock> _mockLogger = new(); #region Property 2: Status Update Persistence /// /// Property 2: 状态更新后查询应返回新状态值 /// *For any* entity with a Status field and any valid status value, when the status is updated, /// querying the entity SHALL return the new status value. /// /// **Validates: Requirements 3.5** /// [Property(MaxTest = 100)] public bool StatusUpdatePersistence_QueryReturnsNewStatus(PositiveInt seed) { // Arrange: 创建宣传图 using var dbContext = CreateDbContext(); var promotionId = CreateTestPromotion(dbContext, seed.Get, 1); // 初始状态为启用 var service = new ContentService(dbContext, _mockLogger.Object); // 确定新状态(与初始状态相反) var newStatus = 0; // 禁用 // Act: 更新状态 var updateResult = service.UpdatePromotionStatusAsync(promotionId, newStatus).GetAwaiter().GetResult(); // 查询宣传图 var promotion = service.GetPromotionByIdAsync(promotionId).GetAwaiter().GetResult(); // Assert: 查询结果应返回新状态 return updateResult && promotion.Status == newStatus; } /// /// Property 2: 状态从禁用更新为启用后应正确持久化 /// /// **Validates: Requirements 3.5** /// [Property(MaxTest = 100)] public bool StatusUpdatePersistence_DisabledToEnabled(PositiveInt seed) { // Arrange: 创建禁用状态的宣传图 using var dbContext = CreateDbContext(); var promotionId = CreateTestPromotion(dbContext, seed.Get, 0); // 初始状态为禁用 var service = new ContentService(dbContext, _mockLogger.Object); // Act: 更新为启用状态 var updateResult = service.UpdatePromotionStatusAsync(promotionId, 1).GetAwaiter().GetResult(); // 直接从数据库查询验证 var promotion = dbContext.Promotions.FirstOrDefault(p => p.Id == promotionId); // Assert: 数据库中的状态应为启用 return updateResult && promotion != null && promotion.Status == 1; } /// /// Property 2: 状态从启用更新为禁用后应正确持久化 /// /// **Validates: Requirements 3.5** /// [Property(MaxTest = 100)] public bool StatusUpdatePersistence_EnabledToDisabled(PositiveInt seed) { // Arrange: 创建启用状态的宣传图 using var dbContext = CreateDbContext(); var promotionId = CreateTestPromotion(dbContext, seed.Get, 1); // 初始状态为启用 var service = new ContentService(dbContext, _mockLogger.Object); // Act: 更新为禁用状态 var updateResult = service.UpdatePromotionStatusAsync(promotionId, 0).GetAwaiter().GetResult(); // 直接从数据库查询验证 var promotion = dbContext.Promotions.FirstOrDefault(p => p.Id == promotionId); // Assert: 数据库中的状态应为禁用 return updateResult && promotion != null && promotion.Status == 0; } /// /// Property 2: 状态更新后 UpdateTime 应该被更新 /// /// **Validates: Requirements 3.5** /// [Property(MaxTest = 50)] public bool StatusUpdatePersistence_UpdateTimeIsSet(PositiveInt seed) { // Arrange using var dbContext = CreateDbContext(); var originalUpdateTime = DateTime.Now.AddDays(-1); var promotion = new Promotion { Title = $"Test Promotion {seed.Get}", ImageUrl = $"https://example.com/promo_{seed.Get}.jpg", Position = 1, Sort = seed.Get % 100, Status = 1, CreateTime = originalUpdateTime, UpdateTime = originalUpdateTime, IsDeleted = false }; dbContext.Promotions.Add(promotion); dbContext.SaveChanges(); var service = new ContentService(dbContext, _mockLogger.Object); var beforeUpdate = DateTime.Now; // Act: 更新状态 service.UpdatePromotionStatusAsync(promotion.Id, 0).GetAwaiter().GetResult(); var afterUpdate = DateTime.Now; // Assert: UpdateTime 应该在更新前后的时间范围内 var updatedPromotion = dbContext.Promotions.First(p => p.Id == promotion.Id); return updatedPromotion.UpdateTime >= beforeUpdate && updatedPromotion.UpdateTime <= afterUpdate && updatedPromotion.UpdateTime > originalUpdateTime; } /// /// Property 2: 多次状态更新应该正确持久化最后一次的值 /// /// **Validates: Requirements 3.5** /// [Property(MaxTest = 50)] public bool StatusUpdatePersistence_MultipleUpdates_LastValuePersisted(PositiveInt seed) { // Arrange using var dbContext = CreateDbContext(); var promotionId = CreateTestPromotion(dbContext, seed.Get, 1); var service = new ContentService(dbContext, _mockLogger.Object); // Act: 多次更新状态 service.UpdatePromotionStatusAsync(promotionId, 0).GetAwaiter().GetResult(); service.UpdatePromotionStatusAsync(promotionId, 1).GetAwaiter().GetResult(); service.UpdatePromotionStatusAsync(promotionId, 0).GetAwaiter().GetResult(); // 查询最终状态 var promotion = service.GetPromotionByIdAsync(promotionId).GetAwaiter().GetResult(); // Assert: 应该返回最后一次更新的状态 return promotion.Status == 0; } /// /// Property 2: 不存在的宣传图更新状态应抛出异常 /// /// **Validates: Requirements 3.5** /// [Property(MaxTest = 50)] public bool StatusUpdatePersistence_NonExistentPromotion_ThrowsException(PositiveInt seed) { // Arrange using var dbContext = CreateDbContext(); var service = new ContentService(dbContext, _mockLogger.Object); var nonExistentId = seed.Get + 999999; // Act & Assert try { service.UpdatePromotionStatusAsync(nonExistentId, 1).GetAwaiter().GetResult(); return false; // 应该抛出异常 } catch (BusinessException ex) { return ex.Code == ErrorCodes.PromotionNotFound; } } /// /// Property 2: 已删除的宣传图更新状态应抛出异常 /// /// **Validates: Requirements 3.5** /// [Property(MaxTest = 50)] public bool StatusUpdatePersistence_DeletedPromotion_ThrowsException(PositiveInt seed) { // Arrange using var dbContext = CreateDbContext(); var promotionId = CreateTestPromotion(dbContext, seed.Get, 1); var service = new ContentService(dbContext, _mockLogger.Object); // 先删除宣传图 service.DeletePromotionAsync(promotionId).GetAwaiter().GetResult(); // Act & Assert: 尝试更新已删除的宣传图状态 try { service.UpdatePromotionStatusAsync(promotionId, 0).GetAwaiter().GetResult(); return false; // 应该抛出异常 } catch (BusinessException ex) { return ex.Code == ErrorCodes.PromotionNotFound; } } /// /// Property 2: 状态更新不应影响其他字段 /// /// **Validates: Requirements 3.5** /// [Property(MaxTest = 50)] public bool StatusUpdatePersistence_OtherFieldsUnchanged(PositiveInt seed) { // Arrange using var dbContext = CreateDbContext(); var title = $"Test Promotion {seed.Get}"; var imageUrl = $"https://example.com/promo_{seed.Get}.jpg"; var position = (seed.Get % 2) + 1; // 1 或 2 var sort = seed.Get % 100; var promotion = new Promotion { Title = title, ImageUrl = imageUrl, Position = position, Sort = sort, Status = 1, CreateTime = DateTime.Now, UpdateTime = DateTime.Now, IsDeleted = false }; dbContext.Promotions.Add(promotion); dbContext.SaveChanges(); var service = new ContentService(dbContext, _mockLogger.Object); // Act: 更新状态 service.UpdatePromotionStatusAsync(promotion.Id, 0).GetAwaiter().GetResult(); // 查询更新后的宣传图 var updatedPromotion = dbContext.Promotions.First(p => p.Id == promotion.Id); // Assert: 其他字段应保持不变 return updatedPromotion.Title == title && updatedPromotion.ImageUrl == imageUrl && updatedPromotion.Position == position && updatedPromotion.Sort == sort && updatedPromotion.Status == 0; } #endregion #region 辅助方法 /// /// 创建内存数据库上下文 /// private AdminBusinessDbContext CreateDbContext() { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) .Options; return new AdminBusinessDbContext(options); } /// /// 创建测试宣传图 /// private long CreateTestPromotion(AdminBusinessDbContext dbContext, int seed, int status) { var promotion = new Promotion { Title = $"Test Promotion {seed}", ImageUrl = $"https://example.com/promo_{seed}.jpg", Position = (seed % 2) + 1, // 1 或 2 Sort = seed % 100, Status = status, CreateTime = DateTime.Now, UpdateTime = DateTime.Now, IsDeleted = false }; dbContext.Promotions.Add(promotion); dbContext.SaveChanges(); return promotion.Id; } #endregion }