- TestFixture: Base test infrastructure with WebApplicationFactory - K8sDiscoveryTests: K8s Service Label discovery flow tests - ConfigConfirmationTests: Pending config confirmation flow tests - MultiTenantRoutingTests: Tenant-specific vs default destination routing tests - ConfigReloadTests: Gateway hot-reload via NOTIFY mechanism tests - TestData: Mock data for K8s services, JWT tokens, database seeding Tests cover: 1. K8s Service discovery with valid labels 2. Config confirmation -> DB write -> NOTIFY 3. Multi-tenant routing (dedicated vs default destination) 4. Gateway config hot-reload without restart
172 lines
5.6 KiB
C#
172 lines
5.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 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(services =>
|
||
{
|
||
// 移除 PostgreSQL 数据库上下文
|
||
var descriptor = services.SingleOrDefault(
|
||
d => d.ServiceType == typeof(IDbContextFactory<GatewayDbContext>));
|
||
if (descriptor != null)
|
||
{
|
||
services.Remove(descriptor);
|
||
}
|
||
|
||
var dbContextDescriptor = services.SingleOrDefault(
|
||
d => d.ServiceType == typeof(DbContextOptions<GatewayDbContext>));
|
||
if (dbContextDescriptor != null)
|
||
{
|
||
services.Remove(dbContextDescriptor);
|
||
}
|
||
|
||
// 使用内存数据库替换
|
||
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));
|
||
|
||
// 添加内存缓存
|
||
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
|
||
var routeCache = scope.ServiceProvider.GetRequiredService<IRouteCache>();
|
||
await routeCache.InitializeAsync();
|
||
|
||
// 初始化配置提供程序
|
||
var configProvider = scope.ServiceProvider.GetRequiredService<DynamicProxyConfigProvider>();
|
||
configProvider.UpdateConfig();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 集合定义,确保使用 TestFixture 的测试不会并行执行
|
||
/// </summary>
|
||
[CollectionDefinition("Integration Tests")]
|
||
public class IntegrationTestCollection : ICollectionFixture<TestFixture>
|
||
{
|
||
}
|