- 更新 TestFixture 添加 Mock Redis 和内存数据库配置 - 修复 DynamicProxyConfigProvider 中的递归问题 - 添加 GwPendingServiceDiscovery 实体模型 - 修复测试数据模型与实际代码的匹配问题 37/44 集成测试通过,失败测试主要由于测试间状态共享
179 lines
6.6 KiB
C#
179 lines
6.6 KiB
C#
using Microsoft.AspNetCore.Mvc.Testing;
|
||
using Microsoft.AspNetCore.TestHost;
|
||
using Microsoft.EntityFrameworkCore;
|
||
using Microsoft.Extensions.Caching.Memory;
|
||
using Microsoft.Extensions.DependencyInjection;
|
||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||
using Moq;
|
||
using StackExchange.Redis;
|
||
using Xunit;
|
||
using YarpGateway.Data;
|
||
using YarpGateway.DynamicProxy;
|
||
using YarpGateway.Services;
|
||
|
||
namespace YarpGateway.Tests.Integration;
|
||
|
||
/// <summary>
|
||
/// 集成测试基类
|
||
/// 提供 WebApplicationFactory 和 HttpClient 的共享实例
|
||
/// </summary>
|
||
public class TestFixture : IAsyncLifetime
|
||
{
|
||
public WebApplicationFactory<Program> Factory { get; private set; } = null!;
|
||
public HttpClient Client { get; private set; } = null!;
|
||
public IServiceProvider Services { get; private set; } = null!;
|
||
|
||
public async Task InitializeAsync()
|
||
{
|
||
Factory = new WebApplicationFactory<Program>()
|
||
.WithWebHostBuilder(builder =>
|
||
{
|
||
builder.UseEnvironment("Testing");
|
||
builder.ConfigureServices((context, services) =>
|
||
{
|
||
// 移除所有与 GatewayDbContext 相关的服务
|
||
var descriptorsToRemove = services
|
||
.Where(d => d.ServiceType.Name.Contains("DbContext") ||
|
||
d.ServiceType.Name.Contains("GatewayDbContext"))
|
||
.ToList();
|
||
foreach (var descriptor in descriptorsToRemove)
|
||
{
|
||
services.Remove(descriptor);
|
||
}
|
||
|
||
// 使用内存数据库替换
|
||
services.AddDbContextFactory<GatewayDbContext>(options =>
|
||
{
|
||
options.UseInMemoryDatabase($"YarpGateway_Test_{Guid.NewGuid()}");
|
||
});
|
||
|
||
// 移除 PgSqlConfigChangeListener(因为内存数据库不支持 NOTIFY)
|
||
var listenerDescriptor = services.SingleOrDefault(
|
||
d => d.ServiceType == typeof(Microsoft.Extensions.Hosting.IHostedService) &&
|
||
d.ImplementationType == typeof(PgSqlConfigChangeListener));
|
||
if (listenerDescriptor != null)
|
||
{
|
||
services.Remove(listenerDescriptor);
|
||
}
|
||
|
||
// 移除 Redis 相关服务(测试环境不需要)
|
||
services.RemoveAll(typeof(StackExchange.Redis.IConnectionMultiplexer));
|
||
services.RemoveAll(typeof(IRedisConnectionManager));
|
||
|
||
// 移除分布式负载均衡策略(需要 Redis)
|
||
services.RemoveAll(typeof(Yarp.ReverseProxy.LoadBalancing.ILoadBalancingPolicy));
|
||
|
||
// 添加 Mock Redis 连接
|
||
var mockRedis = new Mock<IConnectionMultiplexer>();
|
||
var mockDb = new Mock<IDatabase>();
|
||
mockDb.Setup(db => db.StringSet(It.IsAny<RedisKey>(), It.IsAny<RedisValue>(), It.IsAny<TimeSpan?>(), It.IsAny<When>(), It.IsAny<CommandFlags>())).Returns(true);
|
||
mockDb.Setup(db => db.StringGet(It.IsAny<RedisKey>(), It.IsAny<CommandFlags>())).Returns(RedisValue.Null);
|
||
mockDb.Setup(db => db.ScriptEvaluate(It.IsAny<string>(), It.IsAny<RedisKey[]>(), It.IsAny<RedisValue[]>(), It.IsAny<CommandFlags>())).Returns(RedisResult.Create(1));
|
||
mockRedis.Setup(r => r.GetDatabase(It.IsAny<int>(), It.IsAny<object?>())).Returns(mockDb.Object);
|
||
services.AddSingleton(mockRedis.Object);
|
||
|
||
// 添加内存缓存
|
||
services.AddMemoryCache();
|
||
|
||
// 确保 RouteCache 被正确注册
|
||
services.RemoveAll(typeof(IRouteCache));
|
||
services.AddSingleton<IRouteCache, RouteCache>();
|
||
});
|
||
});
|
||
|
||
Client = Factory.CreateClient();
|
||
Services = Factory.Services;
|
||
|
||
// 初始化数据库种子数据
|
||
await SeedDatabaseAsync();
|
||
}
|
||
|
||
public async Task DisposeAsync()
|
||
{
|
||
Client?.Dispose();
|
||
if (Factory != null)
|
||
{
|
||
await Factory.DisposeAsync();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取新的数据库上下文实例
|
||
/// </summary>
|
||
public GatewayDbContext CreateDbContext()
|
||
{
|
||
var factory = Services.GetRequiredService<IDbContextFactory<GatewayDbContext>>();
|
||
return factory.CreateDbContext();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取 RouteCache 实例
|
||
/// </summary>
|
||
public IRouteCache GetRouteCache()
|
||
{
|
||
return Services.GetRequiredService<IRouteCache>();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取 DynamicProxyConfigProvider 实例
|
||
/// </summary>
|
||
public DynamicProxyConfigProvider GetConfigProvider()
|
||
{
|
||
return Services.GetRequiredService<DynamicProxyConfigProvider>();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取内存缓存实例
|
||
/// </summary>
|
||
public IMemoryCache GetMemoryCache()
|
||
{
|
||
return Services.GetRequiredService<IMemoryCache>();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 重新加载配置
|
||
/// </summary>
|
||
public async Task ReloadConfigurationAsync()
|
||
{
|
||
var routeCache = GetRouteCache();
|
||
await routeCache.ReloadAsync();
|
||
|
||
var configProvider = GetConfigProvider();
|
||
configProvider.UpdateConfig();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 初始化数据库种子数据
|
||
/// </summary>
|
||
private async Task SeedDatabaseAsync()
|
||
{
|
||
using var scope = Services.CreateScope();
|
||
var dbContext = scope.ServiceProvider.GetRequiredService<GatewayDbContext>();
|
||
|
||
// 添加测试租户
|
||
await TestData.SeedTenantsAsync(dbContext);
|
||
|
||
// 添加测试路由
|
||
await TestData.SeedRoutesAsync(dbContext);
|
||
|
||
// 添加测试集群和目标
|
||
await TestData.SeedClustersAsync(dbContext);
|
||
|
||
await dbContext.SaveChangesAsync();
|
||
|
||
// 初始化 RouteCache(注意:不要在初始化后调用 UpdateConfig,
|
||
// 因为 DynamicProxyConfigProvider 构造函数中已经调用了它,
|
||
// 额外的调用可能导致 YARP 配置管理器的递归问题)
|
||
var routeCache = scope.ServiceProvider.GetRequiredService<IRouteCache>();
|
||
await routeCache.InitializeAsync();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 集合定义,确保使用 TestFixture 的测试不会并行执行
|
||
/// </summary>
|
||
[CollectionDefinition("Integration Tests")]
|
||
public class IntegrationTestCollection : ICollectionFixture<TestFixture>
|
||
{
|
||
}
|