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; /// /// 集成测试基类 /// 提供 WebApplicationFactory 和 HttpClient 的共享实例 /// public class TestFixture : IAsyncLifetime { public WebApplicationFactory 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() .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(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(); var mockDb = new Mock(); mockDb.Setup(db => db.StringSet(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(true); mockDb.Setup(db => db.StringGet(It.IsAny(), It.IsAny())).Returns(RedisValue.Null); mockDb.Setup(db => db.ScriptEvaluate(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(RedisResult.Create(1)); mockRedis.Setup(r => r.GetDatabase(It.IsAny(), It.IsAny())).Returns(mockDb.Object); services.AddSingleton(mockRedis.Object); // 添加内存缓存 services.AddMemoryCache(); // 确保 RouteCache 被正确注册 services.RemoveAll(typeof(IRouteCache)); services.AddSingleton(); }); }); Client = Factory.CreateClient(); Services = Factory.Services; // 初始化数据库种子数据 await SeedDatabaseAsync(); } public async Task DisposeAsync() { Client?.Dispose(); if (Factory != null) { await Factory.DisposeAsync(); } } /// /// 获取新的数据库上下文实例 /// public GatewayDbContext CreateDbContext() { var factory = Services.GetRequiredService>(); return factory.CreateDbContext(); } /// /// 获取 RouteCache 实例 /// public IRouteCache GetRouteCache() { return Services.GetRequiredService(); } /// /// 获取 DynamicProxyConfigProvider 实例 /// public DynamicProxyConfigProvider GetConfigProvider() { return Services.GetRequiredService(); } /// /// 获取内存缓存实例 /// public IMemoryCache GetMemoryCache() { return Services.GetRequiredService(); } /// /// 重新加载配置 /// public async Task ReloadConfigurationAsync() { var routeCache = GetRouteCache(); await routeCache.ReloadAsync(); var configProvider = GetConfigProvider(); configProvider.UpdateConfig(); } /// /// 初始化数据库种子数据 /// private async Task SeedDatabaseAsync() { using var scope = Services.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService(); // 添加测试租户 await TestData.SeedTenantsAsync(dbContext); // 添加测试路由 await TestData.SeedRoutesAsync(dbContext); // 添加测试集群和目标 await TestData.SeedClustersAsync(dbContext); await dbContext.SaveChangesAsync(); // 初始化 RouteCache(注意:不要在初始化后调用 UpdateConfig, // 因为 DynamicProxyConfigProvider 构造函数中已经调用了它, // 额外的调用可能导致 YARP 配置管理器的递归问题) var routeCache = scope.ServiceProvider.GetRequiredService(); await routeCache.InitializeAsync(); } } /// /// 集合定义,确保使用 TestFixture 的测试不会并行执行 /// [CollectionDefinition("Integration Tests")] public class IntegrationTestCollection : ICollectionFixture { }