mi-assessment/server/MiAssessment/tests/MiAssessment.Tests/Admin/PromotionPropertyTests.cs
zpc 6bf2ea595c feat(admin-business): 完成后台管理系统全部业务模块
- 系统配置管理模块 (Config)
- 内容管理模块 (Banner, Promotion)
- 测评管理模块 (Type, Question, Category, Mapping, Conclusion)
- 用户管理模块 (User)
- 订单管理模块 (Order)
- 规划师管理模块 (Planner)
- 分销管理模块 (InviteCode, Commission, Withdrawal)
- 数据统计仪表盘模块 (Dashboard)
- 权限控制集成
- 服务注册配置

全部381个测试通过
2026-02-03 20:50:51 +08:00

304 lines
10 KiB
C#

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;
/// <summary>
/// Promotion 属性测试
/// 验证宣传图服务的正确性属性
/// </summary>
public class PromotionPropertyTests
{
private readonly Mock<ILogger<ContentService>> _mockLogger = new();
#region Property 2: Status Update Persistence
/// <summary>
/// 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**
/// </summary>
[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;
}
/// <summary>
/// Property 2: 状态从禁用更新为启用后应正确持久化
///
/// **Validates: Requirements 3.5**
/// </summary>
[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;
}
/// <summary>
/// Property 2: 状态从启用更新为禁用后应正确持久化
///
/// **Validates: Requirements 3.5**
/// </summary>
[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;
}
/// <summary>
/// Property 2: 状态更新后 UpdateTime 应该被更新
///
/// **Validates: Requirements 3.5**
/// </summary>
[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;
}
/// <summary>
/// Property 2: 多次状态更新应该正确持久化最后一次的值
///
/// **Validates: Requirements 3.5**
/// </summary>
[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;
}
/// <summary>
/// Property 2: 不存在的宣传图更新状态应抛出异常
///
/// **Validates: Requirements 3.5**
/// </summary>
[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;
}
}
/// <summary>
/// Property 2: 已删除的宣传图更新状态应抛出异常
///
/// **Validates: Requirements 3.5**
/// </summary>
[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;
}
}
/// <summary>
/// Property 2: 状态更新不应影响其他字段
///
/// **Validates: Requirements 3.5**
/// </summary>
[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
/// <summary>
/// 创建内存数据库上下文
/// </summary>
private AdminBusinessDbContext CreateDbContext()
{
var options = new DbContextOptionsBuilder<AdminBusinessDbContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
return new AdminBusinessDbContext(options);
}
/// <summary>
/// 创建测试宣传图
/// </summary>
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
}