mi-assessment/server/MiAssessment/tests/MiAssessment.Tests/Admin/ConfigServicePropertyTests.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

534 lines
18 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}