mi-assessment/server/MiAssessment/tests/MiAssessment.Tests/Services/AdminConfigServiceTests.cs
zpc 8489b4300c refactor(config): 统一配置读取架构,运营配置从Admin库读取
- Model层新增AdminConfig实体和AdminConfigReadDbContext(只读连接Admin库)
- API项目新增AdminConnection连接字符串,注册AdminConfigReadDbContext
- Core层ConfigService按key路由:运营配置走Admin库,业务配置走业务库
- WechatPayConfigService改为从Admin库读取支付/小程序配置
- WechatService新增AdminConfigReadDbContext注入,配置读取改为Admin库
- Autofac注册同步更新三个服务的依赖注入
- Admin.Business的AdminConfigService改用AdminConfigDbContext连接Admin库
2026-02-20 15:48:16 +08:00

335 lines
9.6 KiB
C#

using System.Text.Json;
using MiAssessment.Admin.Business.Data;
using MiAssessment.Admin.Business.Entities;
using MiAssessment.Admin.Business.Models;
using MiAssessment.Admin.Business.Models.Config;
using MiAssessment.Admin.Business.Services;
using MiAssessment.Core.Interfaces;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;
namespace MiAssessment.Tests.Services;
/// <summary>
/// AdminConfigService 单元测试
/// </summary>
public class AdminConfigServiceTests : IDisposable
{
private readonly AdminConfigDbContext _adminDbContext;
private readonly Mock<IRedisService> _mockRedisService;
private readonly Mock<ILogger<AdminConfigService>> _mockLogger;
private readonly AdminConfigService _configService;
public AdminConfigServiceTests()
{
// 使用 InMemory 数据库
var options = new DbContextOptionsBuilder<AdminConfigDbContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
_adminDbContext = new AdminConfigDbContext(options);
_mockRedisService = new Mock<IRedisService>();
_mockLogger = new Mock<ILogger<AdminConfigService>>();
// 默认 Redis 返回空值(模拟缓存未命中)
_mockRedisService.Setup(x => x.GetStringAsync(It.IsAny<string>()))
.ReturnsAsync((string?)null);
_configService = new AdminConfigService(
_adminDbContext,
_mockRedisService.Object,
_mockLogger.Object);
}
public void Dispose()
{
_adminDbContext.Dispose();
}
#region GetConfigAsync Tests
[Fact]
public async Task GetConfigAsync_WithExistingConfig_ReturnsConfig()
{
// Arrange
var testConfig = new { name = "test", value = 123 };
var configJson = JsonSerializer.Serialize(testConfig);
_adminDbContext.AdminConfigs.Add(new AdminConfig
{
ConfigKey = "test_config",
ConfigValue = configJson
});
await _adminDbContext.SaveChangesAsync();
// Act
var result = await _configService.GetConfigRawAsync("test_config");
// Assert
Assert.NotNull(result);
Assert.Equal(configJson, result);
}
[Fact]
public async Task GetConfigAsync_WithNonExistingConfig_ReturnsNull()
{
// Act
var result = await _configService.GetConfigRawAsync("non_existing_config");
// Assert
Assert.Null(result);
}
[Fact]
public async Task GetConfigAsync_WithCachedValue_ReturnsCachedValue()
{
// Arrange
var cachedValue = "{\"cached\":true}";
_mockRedisService.Setup(x => x.GetStringAsync("config:cached_config"))
.ReturnsAsync(cachedValue);
// Act
var result = await _configService.GetConfigRawAsync("cached_config");
// Assert
Assert.Equal(cachedValue, result);
// 验证没有查询数据库
_mockRedisService.Verify(x => x.GetStringAsync("config:cached_config"), Times.Once);
}
#endregion
#region UpdateConfigAsync Tests
[Fact]
public async Task UpdateConfigAsync_WithNewConfig_CreatesConfig()
{
// Arrange
var testConfig = new { name = "new_config", value = 456 };
// Act
var result = await _configService.UpdateConfigAsync("new_config", testConfig);
// Assert
Assert.True(result);
var savedConfig = await _adminDbContext.AdminConfigs.FirstOrDefaultAsync(c => c.ConfigKey == "new_config");
Assert.NotNull(savedConfig);
Assert.Contains("new_config", savedConfig.ConfigValue!);
}
[Fact]
public async Task UpdateConfigAsync_WithExistingConfig_UpdatesConfig()
{
// Arrange
_adminDbContext.AdminConfigs.Add(new AdminConfig
{
ConfigKey = "existing_config",
ConfigValue = "{\"old\":true}"
});
await _adminDbContext.SaveChangesAsync();
var newConfig = new { updated = true, value = 789 };
// Act
var result = await _configService.UpdateConfigAsync("existing_config", newConfig);
// Assert
Assert.True(result);
var savedConfig = await _adminDbContext.AdminConfigs.FirstOrDefaultAsync(c => c.ConfigKey == "existing_config");
Assert.NotNull(savedConfig);
Assert.Contains("updated", savedConfig.ConfigValue!);
}
[Fact]
public async Task UpdateConfigAsync_ClearsCache()
{
// Arrange
var testConfig = new { name = "cache_test" };
// Act
await _configService.UpdateConfigAsync("cache_test", testConfig);
// Assert
_mockRedisService.Verify(x => x.DeleteAsync("config:cache_test"), Times.Once);
}
#endregion
#region Validation Tests
[Fact]
public async Task ValidateConfigAsync_WeixinPaySetting_WithValidPrefixes_ReturnsNull()
{
// Arrange
var setting = new WeixinPaySetting
{
Merchants = new List<WeixinPayMerchant>
{
new() { Name = "商户1", MchId = "123", OrderPrefix = "ABC" },
new() { Name = "商户2", MchId = "456", OrderPrefix = "DEF" }
}
};
var json = JsonSerializer.Serialize(setting);
// Act
var result = await _configService.ValidateConfigAsync(ConfigKeys.WeixinPaySetting, json);
// Assert
Assert.Null(result);
}
[Fact]
public async Task ValidateConfigAsync_WeixinPaySetting_WithInvalidPrefixLength_ReturnsError()
{
// Arrange
var setting = new WeixinPaySetting
{
Merchants = new List<WeixinPayMerchant>
{
new() { Name = "商户1", MchId = "123", OrderPrefix = "AB" } // 只有2位
}
};
var json = JsonSerializer.Serialize(setting);
// Act
var result = await _configService.ValidateConfigAsync(ConfigKeys.WeixinPaySetting, json);
// Assert
Assert.NotNull(result);
Assert.Contains("3位字符", result);
}
[Fact]
public async Task ValidateConfigAsync_WeixinPaySetting_WithDuplicatePrefixes_ReturnsError()
{
// Arrange
var setting = new WeixinPaySetting
{
Merchants = new List<WeixinPayMerchant>
{
new() { Name = "商户1", MchId = "123", OrderPrefix = "ABC" },
new() { Name = "商户2", MchId = "456", OrderPrefix = "ABC" } // 重复
}
};
var json = JsonSerializer.Serialize(setting);
// Act
var result = await _configService.ValidateConfigAsync(ConfigKeys.WeixinPaySetting, json);
// Assert
Assert.NotNull(result);
Assert.Contains("重复", result);
}
[Fact]
public async Task ValidateConfigAsync_MiniprogramSetting_WithDefault_ReturnsNull()
{
// Arrange
var setting = new MiniprogramSetting
{
Miniprograms = new List<MiniprogramConfig>
{
new() { Name = "小程序1", AppId = "wx123", IsDefault = 1, OrderPrefix = "AB" }
}
};
var json = JsonSerializer.Serialize(setting);
// Act
var result = await _configService.ValidateConfigAsync(ConfigKeys.MiniprogramSetting, json);
// Assert
Assert.Null(result);
}
[Fact]
public async Task ValidateConfigAsync_MiniprogramSetting_WithoutDefault_ReturnsError()
{
// Arrange
var setting = new MiniprogramSetting
{
Miniprograms = new List<MiniprogramConfig>
{
new() { Name = "小程序1", AppId = "wx123", IsDefault = 0 }
}
};
var json = JsonSerializer.Serialize(setting);
// Act
var result = await _configService.ValidateConfigAsync(ConfigKeys.MiniprogramSetting, json);
// Assert
Assert.NotNull(result);
Assert.Contains("默认", result);
}
[Fact]
public async Task ValidateConfigAsync_H5Setting_WithDefault_ReturnsNull()
{
// Arrange
var setting = new H5Setting
{
H5Apps = new List<H5AppConfig>
{
new() { Name = "H5应用1", IsDefault = 1, OrderPrefix = "AB" }
}
};
var json = JsonSerializer.Serialize(setting);
// Act
var result = await _configService.ValidateConfigAsync(ConfigKeys.H5Setting, json);
// Assert
Assert.Null(result);
}
[Fact]
public async Task ValidateConfigAsync_H5Setting_WithoutDefault_ReturnsError()
{
// Arrange
var setting = new H5Setting
{
H5Apps = new List<H5AppConfig>
{
new() { Name = "H5应用1", IsDefault = 0 }
}
};
var json = JsonSerializer.Serialize(setting);
// Act
var result = await _configService.ValidateConfigAsync(ConfigKeys.H5Setting, json);
// Assert
Assert.NotNull(result);
Assert.Contains("默认", result);
}
[Fact]
public async Task ValidateConfigAsync_InvalidJson_ReturnsError()
{
// Act
var result = await _configService.ValidateConfigAsync(ConfigKeys.Base, "invalid json {");
// Assert
Assert.NotNull(result);
Assert.Contains("无效", result);
}
#endregion
#region ClearConfigCacheAsync Tests
[Fact]
public async Task ClearConfigCacheAsync_CallsRedisDelete()
{
// Act
await _configService.ClearConfigCacheAsync("test_key");
// Assert
_mockRedisService.Verify(x => x.DeleteAsync("config:test_key"), Times.Once);
}
#endregion
}