- 系统配置管理模块 (Config) - 内容管理模块 (Banner, Promotion) - 测评管理模块 (Type, Question, Category, Mapping, Conclusion) - 用户管理模块 (User) - 订单管理模块 (Order) - 规划师管理模块 (Planner) - 分销管理模块 (InviteCode, Commission, Withdrawal) - 数据统计仪表盘模块 (Dashboard) - 权限控制集成 - 服务注册配置 全部381个测试通过
534 lines
18 KiB
C#
534 lines
18 KiB
C#
using FsCheck;
|
||
using FsCheck.Xunit;
|
||
using Microsoft.EntityFrameworkCore;
|
||
using Microsoft.Extensions.Logging;
|
||
using MiAssessment.Admin.Business.Data;
|
||
using MiAssessment.Admin.Business.Models;
|
||
using MiAssessment.Admin.Business.Models.Common;
|
||
using MiAssessment.Admin.Business.Services;
|
||
using Moq;
|
||
using Xunit;
|
||
|
||
// 使用别名解决命名冲突
|
||
using ConfigEntity = MiAssessment.Admin.Business.Entities.Config;
|
||
|
||
namespace MiAssessment.Tests.Admin;
|
||
|
||
/// <summary>
|
||
/// ConfigService 属性测试
|
||
/// 验证系统配置服务的正确性属性
|
||
/// </summary>
|
||
public class ConfigServicePropertyTests
|
||
{
|
||
private readonly Mock<ILogger<ConfigService>> _mockLogger = new();
|
||
|
||
#region Property 6: Config Value Validation - Price
|
||
|
||
/// <summary>
|
||
/// Property 6: 价格配置值必须是正数
|
||
/// *For any* price configuration update, the value SHALL be a positive decimal number.
|
||
///
|
||
/// **Validates: Requirements 1.3**
|
||
/// </summary>
|
||
[Property(MaxTest = 100)]
|
||
public bool PriceConfigMustBePositive_ValidPositiveValues(PositiveInt seed)
|
||
{
|
||
// Arrange: 创建包含价格配置的数据库
|
||
using var dbContext = CreateDbContext();
|
||
var configKey = $"test_price_{seed.Get}";
|
||
|
||
var config = new ConfigEntity
|
||
{
|
||
ConfigKey = configKey,
|
||
ConfigValue = "100.00",
|
||
ConfigType = "price",
|
||
Description = "测试价格配置",
|
||
Sort = 1,
|
||
CreateTime = DateTime.Now,
|
||
UpdateTime = DateTime.Now,
|
||
IsDeleted = false
|
||
};
|
||
dbContext.Configs.Add(config);
|
||
dbContext.SaveChanges();
|
||
|
||
var service = new ConfigService(dbContext, _mockLogger.Object);
|
||
|
||
// 生成正数价格值
|
||
var positivePrice = (seed.Get % 10000) + 0.01m;
|
||
var priceValue = positivePrice.ToString("F2");
|
||
|
||
// Act: 更新价格配置
|
||
var result = service.UpdateConfigAsync(configKey, priceValue).GetAwaiter().GetResult();
|
||
|
||
// Assert: 正数价格应该更新成功
|
||
var updatedConfig = dbContext.Configs.First(c => c.ConfigKey == configKey);
|
||
return result && updatedConfig.ConfigValue == priceValue;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Property 6: 价格配置值必须是正数 - 非正数值应抛出异常
|
||
/// *For any* price configuration update with non-positive value, the service SHALL throw BusinessException.
|
||
///
|
||
/// **Validates: Requirements 1.3**
|
||
/// </summary>
|
||
[Property(MaxTest = 100)]
|
||
public bool PriceConfigMustBePositive_NonPositiveValuesThrowException(PositiveInt seed)
|
||
{
|
||
// Arrange: 创建包含价格配置的数据库
|
||
using var dbContext = CreateDbContext();
|
||
var configKey = $"test_price_invalid_{seed.Get}";
|
||
|
||
var config = new ConfigEntity
|
||
{
|
||
ConfigKey = configKey,
|
||
ConfigValue = "100.00",
|
||
ConfigType = "price",
|
||
Description = "测试价格配置",
|
||
Sort = 1,
|
||
CreateTime = DateTime.Now,
|
||
UpdateTime = DateTime.Now,
|
||
IsDeleted = false
|
||
};
|
||
dbContext.Configs.Add(config);
|
||
dbContext.SaveChanges();
|
||
|
||
var service = new ConfigService(dbContext, _mockLogger.Object);
|
||
|
||
// 生成非正数价格值(0 或负数)
|
||
var nonPositivePrice = -(seed.Get % 1000);
|
||
var priceValue = nonPositivePrice.ToString();
|
||
|
||
// Act & Assert: 非正数价格应抛出 BusinessException
|
||
try
|
||
{
|
||
service.UpdateConfigAsync(configKey, priceValue).GetAwaiter().GetResult();
|
||
return false; // 应该抛出异常
|
||
}
|
||
catch (BusinessException ex)
|
||
{
|
||
return ex.Code == ErrorCodes.ConfigValueInvalid;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Property 6: 价格配置值必须是正数 - 零值应抛出异常
|
||
///
|
||
/// **Validates: Requirements 1.3**
|
||
/// </summary>
|
||
[Property(MaxTest = 50)]
|
||
public bool PriceConfigMustBePositive_ZeroValueThrowsException(PositiveInt seed)
|
||
{
|
||
// Arrange
|
||
using var dbContext = CreateDbContext();
|
||
var configKey = $"test_price_zero_{seed.Get}";
|
||
|
||
var config = new ConfigEntity
|
||
{
|
||
ConfigKey = configKey,
|
||
ConfigValue = "100.00",
|
||
ConfigType = "price",
|
||
Description = "测试价格配置",
|
||
Sort = 1,
|
||
CreateTime = DateTime.Now,
|
||
UpdateTime = DateTime.Now,
|
||
IsDeleted = false
|
||
};
|
||
dbContext.Configs.Add(config);
|
||
dbContext.SaveChanges();
|
||
|
||
var service = new ConfigService(dbContext, _mockLogger.Object);
|
||
|
||
// Act & Assert: 零值应抛出 BusinessException
|
||
try
|
||
{
|
||
service.UpdateConfigAsync(configKey, "0").GetAwaiter().GetResult();
|
||
return false;
|
||
}
|
||
catch (BusinessException ex)
|
||
{
|
||
return ex.Code == ErrorCodes.ConfigValueInvalid;
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Property 6: Config Value Validation - Commission Rate
|
||
|
||
/// <summary>
|
||
/// Property 6: 佣金比例配置值必须在 0-1 之间
|
||
/// *For any* commission rate configuration update, the value SHALL be between 0 and 1 (inclusive).
|
||
///
|
||
/// **Validates: Requirements 1.4**
|
||
/// </summary>
|
||
[Property(MaxTest = 100)]
|
||
public bool CommissionRateMustBeBetweenZeroAndOne_ValidValues(PositiveInt seed)
|
||
{
|
||
// Arrange: 创建包含佣金配置的数据库
|
||
using var dbContext = CreateDbContext();
|
||
var configKey = $"test_commission_{seed.Get}";
|
||
|
||
var config = new ConfigEntity
|
||
{
|
||
ConfigKey = configKey,
|
||
ConfigValue = "0.10",
|
||
ConfigType = "commission",
|
||
Description = "测试佣金配置",
|
||
Sort = 1,
|
||
CreateTime = DateTime.Now,
|
||
UpdateTime = DateTime.Now,
|
||
IsDeleted = false
|
||
};
|
||
dbContext.Configs.Add(config);
|
||
dbContext.SaveChanges();
|
||
|
||
var service = new ConfigService(dbContext, _mockLogger.Object);
|
||
|
||
// 生成 0-1 之间的佣金比例
|
||
var validRate = (seed.Get % 101) / 100.0m; // 0.00 到 1.00
|
||
var rateValue = validRate.ToString("F2");
|
||
|
||
// Act: 更新佣金配置
|
||
var result = service.UpdateConfigAsync(configKey, rateValue).GetAwaiter().GetResult();
|
||
|
||
// Assert: 有效佣金比例应该更新成功
|
||
var updatedConfig = dbContext.Configs.First(c => c.ConfigKey == configKey);
|
||
return result && updatedConfig.ConfigValue == rateValue;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Property 6: 佣金比例配置值必须在 0-1 之间 - 大于1的值应抛出异常
|
||
///
|
||
/// **Validates: Requirements 1.4**
|
||
/// </summary>
|
||
[Property(MaxTest = 100)]
|
||
public bool CommissionRateMustBeBetweenZeroAndOne_GreaterThanOneThrowsException(PositiveInt seed)
|
||
{
|
||
// Arrange
|
||
using var dbContext = CreateDbContext();
|
||
var configKey = $"test_commission_gt1_{seed.Get}";
|
||
|
||
var config = new ConfigEntity
|
||
{
|
||
ConfigKey = configKey,
|
||
ConfigValue = "0.10",
|
||
ConfigType = "commission",
|
||
Description = "测试佣金配置",
|
||
Sort = 1,
|
||
CreateTime = DateTime.Now,
|
||
UpdateTime = DateTime.Now,
|
||
IsDeleted = false
|
||
};
|
||
dbContext.Configs.Add(config);
|
||
dbContext.SaveChanges();
|
||
|
||
var service = new ConfigService(dbContext, _mockLogger.Object);
|
||
|
||
// 生成大于1的佣金比例
|
||
var invalidRate = 1.0m + ((seed.Get % 100) + 1) / 100.0m; // 1.01 到 2.00
|
||
var rateValue = invalidRate.ToString("F2");
|
||
|
||
// Act & Assert: 大于1的佣金比例应抛出 BusinessException
|
||
try
|
||
{
|
||
service.UpdateConfigAsync(configKey, rateValue).GetAwaiter().GetResult();
|
||
return false;
|
||
}
|
||
catch (BusinessException ex)
|
||
{
|
||
return ex.Code == ErrorCodes.ConfigValueInvalid;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Property 6: 佣金比例配置值必须在 0-1 之间 - 负数值应抛出异常
|
||
///
|
||
/// **Validates: Requirements 1.4**
|
||
/// </summary>
|
||
[Property(MaxTest = 100)]
|
||
public bool CommissionRateMustBeBetweenZeroAndOne_NegativeValuesThrowException(PositiveInt seed)
|
||
{
|
||
// Arrange
|
||
using var dbContext = CreateDbContext();
|
||
var configKey = $"test_commission_neg_{seed.Get}";
|
||
|
||
var config = new ConfigEntity
|
||
{
|
||
ConfigKey = configKey,
|
||
ConfigValue = "0.10",
|
||
ConfigType = "commission",
|
||
Description = "测试佣金配置",
|
||
Sort = 1,
|
||
CreateTime = DateTime.Now,
|
||
UpdateTime = DateTime.Now,
|
||
IsDeleted = false
|
||
};
|
||
dbContext.Configs.Add(config);
|
||
dbContext.SaveChanges();
|
||
|
||
var service = new ConfigService(dbContext, _mockLogger.Object);
|
||
|
||
// 生成负数佣金比例
|
||
var negativeRate = -((seed.Get % 100) + 1) / 100.0m; // -0.01 到 -1.00
|
||
var rateValue = negativeRate.ToString("F2");
|
||
|
||
// Act & Assert: 负数佣金比例应抛出 BusinessException
|
||
try
|
||
{
|
||
service.UpdateConfigAsync(configKey, rateValue).GetAwaiter().GetResult();
|
||
return false;
|
||
}
|
||
catch (BusinessException ex)
|
||
{
|
||
return ex.Code == ErrorCodes.ConfigValueInvalid;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Property 6: 佣金比例边界值测试 - 0 和 1 应该是有效值
|
||
///
|
||
/// **Validates: Requirements 1.4**
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task CommissionRateBoundaryValues_ZeroAndOneAreValid()
|
||
{
|
||
// Arrange
|
||
using var dbContext = CreateDbContext();
|
||
|
||
var configZero = new ConfigEntity
|
||
{
|
||
ConfigKey = "test_commission_zero",
|
||
ConfigValue = "0.50",
|
||
ConfigType = "commission",
|
||
Description = "测试佣金配置",
|
||
Sort = 1,
|
||
CreateTime = DateTime.Now,
|
||
UpdateTime = DateTime.Now,
|
||
IsDeleted = false
|
||
};
|
||
|
||
var configOne = new ConfigEntity
|
||
{
|
||
ConfigKey = "test_commission_one",
|
||
ConfigValue = "0.50",
|
||
ConfigType = "commission",
|
||
Description = "测试佣金配置",
|
||
Sort = 2,
|
||
CreateTime = DateTime.Now,
|
||
UpdateTime = DateTime.Now,
|
||
IsDeleted = false
|
||
};
|
||
|
||
dbContext.Configs.AddRange(configZero, configOne);
|
||
await dbContext.SaveChangesAsync();
|
||
|
||
var service = new ConfigService(dbContext, _mockLogger.Object);
|
||
|
||
// Act & Assert: 0 应该是有效值
|
||
var resultZero = await service.UpdateConfigAsync("test_commission_zero", "0");
|
||
Assert.True(resultZero);
|
||
|
||
// Act & Assert: 1 应该是有效值
|
||
var resultOne = await service.UpdateConfigAsync("test_commission_one", "1");
|
||
Assert.True(resultOne);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Property 17: Config Update Timestamp
|
||
|
||
/// <summary>
|
||
/// Property 17: 配置更新时自动设置 UpdateTime
|
||
/// *For any* configuration update, the UpdateTime field SHALL be automatically set to the current timestamp.
|
||
///
|
||
/// **Validates: Requirements 1.7**
|
||
/// </summary>
|
||
[Property(MaxTest = 100)]
|
||
public bool ConfigUpdateSetsTimestamp(PositiveInt seed)
|
||
{
|
||
// Arrange: 创建配置
|
||
using var dbContext = CreateDbContext();
|
||
var configKey = $"test_timestamp_{seed.Get}";
|
||
var originalUpdateTime = DateTime.Now.AddDays(-1); // 设置为昨天
|
||
|
||
var config = new ConfigEntity
|
||
{
|
||
ConfigKey = configKey,
|
||
ConfigValue = "original_value",
|
||
ConfigType = "content", // 使用不需要特殊验证的类型
|
||
Description = "测试时间戳配置",
|
||
Sort = 1,
|
||
CreateTime = originalUpdateTime,
|
||
UpdateTime = originalUpdateTime,
|
||
IsDeleted = false
|
||
};
|
||
dbContext.Configs.Add(config);
|
||
dbContext.SaveChanges();
|
||
|
||
var service = new ConfigService(dbContext, _mockLogger.Object);
|
||
var beforeUpdate = DateTime.Now;
|
||
|
||
// Act: 更新配置
|
||
var newValue = $"updated_value_{seed.Get}";
|
||
var result = service.UpdateConfigAsync(configKey, newValue).GetAwaiter().GetResult();
|
||
|
||
var afterUpdate = DateTime.Now;
|
||
|
||
// Assert: UpdateTime 应该在更新前后的时间范围内
|
||
var updatedConfig = dbContext.Configs.First(c => c.ConfigKey == configKey);
|
||
|
||
return result
|
||
&& updatedConfig.UpdateTime >= beforeUpdate
|
||
&& updatedConfig.UpdateTime <= afterUpdate
|
||
&& updatedConfig.UpdateTime > originalUpdateTime;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Property 17: 价格配置更新时自动设置 UpdateTime
|
||
///
|
||
/// **Validates: Requirements 1.7**
|
||
/// </summary>
|
||
[Property(MaxTest = 50)]
|
||
public bool PriceConfigUpdateSetsTimestamp(PositiveInt seed)
|
||
{
|
||
// Arrange
|
||
using var dbContext = CreateDbContext();
|
||
var configKey = $"test_price_timestamp_{seed.Get}";
|
||
var originalUpdateTime = DateTime.Now.AddHours(-1);
|
||
|
||
var config = new ConfigEntity
|
||
{
|
||
ConfigKey = configKey,
|
||
ConfigValue = "100.00",
|
||
ConfigType = "price",
|
||
Description = "测试价格时间戳",
|
||
Sort = 1,
|
||
CreateTime = originalUpdateTime,
|
||
UpdateTime = originalUpdateTime,
|
||
IsDeleted = false
|
||
};
|
||
dbContext.Configs.Add(config);
|
||
dbContext.SaveChanges();
|
||
|
||
var service = new ConfigService(dbContext, _mockLogger.Object);
|
||
var beforeUpdate = DateTime.Now;
|
||
|
||
// Act: 更新价格配置
|
||
var newPrice = ((seed.Get % 1000) + 1).ToString("F2");
|
||
var result = service.UpdateConfigAsync(configKey, newPrice).GetAwaiter().GetResult();
|
||
|
||
var afterUpdate = DateTime.Now;
|
||
|
||
// Assert
|
||
var updatedConfig = dbContext.Configs.First(c => c.ConfigKey == configKey);
|
||
|
||
return result
|
||
&& updatedConfig.UpdateTime >= beforeUpdate
|
||
&& updatedConfig.UpdateTime <= afterUpdate;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Property 17: 佣金配置更新时自动设置 UpdateTime
|
||
///
|
||
/// **Validates: Requirements 1.7**
|
||
/// </summary>
|
||
[Property(MaxTest = 50)]
|
||
public bool CommissionConfigUpdateSetsTimestamp(PositiveInt seed)
|
||
{
|
||
// Arrange
|
||
using var dbContext = CreateDbContext();
|
||
var configKey = $"test_commission_timestamp_{seed.Get}";
|
||
var originalUpdateTime = DateTime.Now.AddHours(-1);
|
||
|
||
var config = new ConfigEntity
|
||
{
|
||
ConfigKey = configKey,
|
||
ConfigValue = "0.10",
|
||
ConfigType = "commission",
|
||
Description = "测试佣金时间戳",
|
||
Sort = 1,
|
||
CreateTime = originalUpdateTime,
|
||
UpdateTime = originalUpdateTime,
|
||
IsDeleted = false
|
||
};
|
||
dbContext.Configs.Add(config);
|
||
dbContext.SaveChanges();
|
||
|
||
var service = new ConfigService(dbContext, _mockLogger.Object);
|
||
var beforeUpdate = DateTime.Now;
|
||
|
||
// Act: 更新佣金配置
|
||
var newRate = ((seed.Get % 101) / 100.0m).ToString("F2");
|
||
var result = service.UpdateConfigAsync(configKey, newRate).GetAwaiter().GetResult();
|
||
|
||
var afterUpdate = DateTime.Now;
|
||
|
||
// Assert
|
||
var updatedConfig = dbContext.Configs.First(c => c.ConfigKey == configKey);
|
||
|
||
return result
|
||
&& updatedConfig.UpdateTime >= beforeUpdate
|
||
&& updatedConfig.UpdateTime <= afterUpdate;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Property 17: 多次更新配置时 UpdateTime 应该递增
|
||
///
|
||
/// **Validates: Requirements 1.7**
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task MultipleConfigUpdates_UpdateTimeShouldIncrease()
|
||
{
|
||
// Arrange
|
||
using var dbContext = CreateDbContext();
|
||
var configKey = "test_multiple_updates";
|
||
|
||
var config = new ConfigEntity
|
||
{
|
||
ConfigKey = configKey,
|
||
ConfigValue = "initial_value",
|
||
ConfigType = "content",
|
||
Description = "测试多次更新",
|
||
Sort = 1,
|
||
CreateTime = DateTime.Now.AddDays(-1),
|
||
UpdateTime = DateTime.Now.AddDays(-1),
|
||
IsDeleted = false
|
||
};
|
||
dbContext.Configs.Add(config);
|
||
await dbContext.SaveChangesAsync();
|
||
|
||
var service = new ConfigService(dbContext, _mockLogger.Object);
|
||
|
||
// Act: 第一次更新
|
||
await service.UpdateConfigAsync(configKey, "value_1");
|
||
var firstUpdateTime = dbContext.Configs.First(c => c.ConfigKey == configKey).UpdateTime;
|
||
|
||
// 等待一小段时间确保时间戳不同
|
||
await Task.Delay(10);
|
||
|
||
// Act: 第二次更新
|
||
await service.UpdateConfigAsync(configKey, "value_2");
|
||
var secondUpdateTime = dbContext.Configs.First(c => c.ConfigKey == configKey).UpdateTime;
|
||
|
||
// Assert: 第二次更新时间应该大于等于第一次
|
||
Assert.True(secondUpdateTime >= firstUpdateTime);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 辅助方法
|
||
|
||
/// <summary>
|
||
/// 创建内存数据库上下文
|
||
/// </summary>
|
||
private AdminBusinessDbContext CreateDbContext()
|
||
{
|
||
var options = new DbContextOptionsBuilder<AdminBusinessDbContext>()
|
||
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
|
||
.Options;
|
||
|
||
return new AdminBusinessDbContext(options);
|
||
}
|
||
|
||
#endregion
|
||
}
|