using System.Text.Json; using HoneyBox.Admin.Business.Models; using HoneyBox.Admin.Business.Models.Config; using HoneyBox.Admin.Business.Services; using HoneyBox.Core.Interfaces; using HoneyBox.Model.Data; using HoneyBox.Model.Entities; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Moq; using Xunit; namespace HoneyBox.Tests.Services; /// /// AdminConfigService 单元测试 /// public class AdminConfigServiceTests : IDisposable { private readonly HoneyBoxDbContext _dbContext; private readonly Mock _mockRedisService; private readonly Mock> _mockLogger; private readonly AdminConfigService _configService; public AdminConfigServiceTests() { // 使用 InMemory 数据库 var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) .Options; _dbContext = new HoneyBoxDbContext(options); _mockRedisService = new Mock(); _mockLogger = new Mock>(); // 默认 Redis 返回空值(模拟缓存未命中) _mockRedisService.Setup(x => x.GetStringAsync(It.IsAny())) .ReturnsAsync((string?)null); _configService = new AdminConfigService( _dbContext, _mockRedisService.Object, _mockLogger.Object); } public void Dispose() { _dbContext.Dispose(); } #region GetConfigAsync Tests [Fact] public async Task GetConfigAsync_WithExistingConfig_ReturnsConfig() { // Arrange var testConfig = new { name = "test", value = 123 }; var configJson = JsonSerializer.Serialize(testConfig); _dbContext.Configs.Add(new Config { ConfigKey = "test_config", ConfigValue = configJson }); await _dbContext.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 _dbContext.Configs.FirstOrDefaultAsync(c => c.ConfigKey == "new_config"); Assert.NotNull(savedConfig); Assert.Contains("new_config", savedConfig.ConfigValue!); } [Fact] public async Task UpdateConfigAsync_WithExistingConfig_UpdatesConfig() { // Arrange _dbContext.Configs.Add(new Config { ConfigKey = "existing_config", ConfigValue = "{\"old\":true}" }); await _dbContext.SaveChangesAsync(); var newConfig = new { updated = true, value = 789 }; // Act var result = await _configService.UpdateConfigAsync("existing_config", newConfig); // Assert Assert.True(result); var savedConfig = await _dbContext.Configs.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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 }