- Model层新增AdminConfig实体和AdminConfigReadDbContext(只读连接Admin库) - API项目新增AdminConnection连接字符串,注册AdminConfigReadDbContext - Core层ConfigService按key路由:运营配置走Admin库,业务配置走业务库 - WechatPayConfigService改为从Admin库读取支付/小程序配置 - WechatService新增AdminConfigReadDbContext注入,配置读取改为Admin库 - Autofac注册同步更新三个服务的依赖注入 - Admin.Business的AdminConfigService改用AdminConfigDbContext连接Admin库
335 lines
9.6 KiB
C#
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
|
|
}
|