fengling-gateway/tests/YarpGateway.Tests/Integration/TestFixture.cs
movingsam 9b77169b80 test: add end-to-end integration tests (IMPL-12)
- 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
2026-03-08 10:53:19 +08:00

172 lines
5.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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>
{
}