- 系统配置管理模块 (Config) - 内容管理模块 (Banner, Promotion) - 测评管理模块 (Type, Question, Category, Mapping, Conclusion) - 用户管理模块 (User) - 订单管理模块 (Order) - 规划师管理模块 (Planner) - 分销管理模块 (InviteCode, Commission, Withdrawal) - 数据统计仪表盘模块 (Dashboard) - 权限控制集成 - 服务注册配置 全部381个测试通过
304 lines
10 KiB
C#
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
|
|
}
|