fengling-gateway/tests/YarpGateway.Tests/Unit/Services/RouteCacheTests.cs.bak
movingsam 0c08620565 refactor: 升级 Fengling.Platform.Infrastructure 到 1.0.12 并迁移到新模型
- 升级 Fengling.Platform.Infrastructure 包到 1.0.12
- DatabaseRouteConfigProvider: 使用 GwTenantRoutes 和 GwRouteMatch
- DatabaseClusterConfigProvider: 使用 GwClusters 和内嵌 Destinations
- GatewayDbContext: 添加兼容性别名 (TenantRoutes, ServiceInstances)
- RouteCache: 更新使用新的模型结构
- 暂时禁用 GatewayConfigController 和测试 (需要重写以适配新模型)
2026-03-04 13:30:35 +08:00

434 lines
13 KiB
C#

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;
using FluentAssertions;
using YarpGateway.Data;
using YarpGateway.Models;
using YarpGateway.Services;
namespace YarpGateway.Tests.Unit.Services;
public class RouteCacheTests
{
private readonly Mock<IDbContextFactory<GatewayDbContext>> _dbContextFactoryMock;
private readonly Mock<ILogger<RouteCache>> _loggerMock;
public RouteCacheTests()
{
_dbContextFactoryMock = new Mock<IDbContextFactory<GatewayDbContext>>();
_loggerMock = new Mock<ILogger<RouteCache>>();
}
private GatewayDbContext CreateInMemoryDbContext(List<GwTenantRoute> routes)
{
var options = new DbContextOptionsBuilder<GatewayDbContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
var context = new GatewayDbContext(options);
context.TenantRoutes.AddRange(routes);
context.SaveChanges();
return context;
}
private RouteCache CreateRouteCache(GatewayDbContext context)
{
_dbContextFactoryMock
.Setup(x => x.CreateDbContext())
.Returns(context);
return new RouteCache(
dbContextFactory: _dbContextFactoryMock.Object,
logger: _loggerMock.Object
);
}
[Fact]
public async Task InitializeAsync_ShouldLoadGlobalRoutes()
{
// Arrange
var routes = new List<GwTenantRoute>
{
new GwTenantRoute
{
Id = 1,
TenantCode = "",
ServiceName = "user-service",
ClusterId = "cluster-user",
PathPattern = "/api/user/**",
Priority = 1,
Status = 1,
IsGlobal = true,
IsDeleted = false
},
new GwTenantRoute
{
Id = 2,
TenantCode = "",
ServiceName = "order-service",
ClusterId = "cluster-order",
PathPattern = "/api/order/**",
Priority = 1,
Status = 1,
IsGlobal = true,
IsDeleted = false
}
};
var context = CreateInMemoryDbContext(routes);
var routeCache = CreateRouteCache(context);
// Act
await routeCache.InitializeAsync();
// Assert
var result = routeCache.GetRoute("any-tenant", "user-service");
result.Should().NotBeNull();
result!.ClusterId.Should().Be("cluster-user");
}
[Fact]
public async Task InitializeAsync_ShouldLoadTenantRoutes()
{
// Arrange
var routes = new List<GwTenantRoute>
{
new GwTenantRoute
{
Id = 1,
TenantCode = "tenant-1",
ServiceName = "user-service",
ClusterId = "cluster-tenant-user",
PathPattern = "/api/user/**",
Priority = 1,
Status = 1,
IsGlobal = false,
IsDeleted = false
}
};
var context = CreateInMemoryDbContext(routes);
var routeCache = CreateRouteCache(context);
// Act
await routeCache.InitializeAsync();
// Assert
var result = routeCache.GetRoute("tenant-1", "user-service");
result.Should().NotBeNull();
result!.ClusterId.Should().Be("cluster-tenant-user");
}
[Fact]
public async Task GetRoute_WithTenantRouteAvailable_ShouldReturnTenantRoute()
{
// Arrange
var routes = new List<GwTenantRoute>
{
new GwTenantRoute
{
Id = 1,
TenantCode = "tenant-1",
ServiceName = "user-service",
ClusterId = "tenant-cluster",
PathPattern = "/api/user/**",
Priority = 1,
Status = 1,
IsGlobal = false,
IsDeleted = false
},
new GwTenantRoute
{
Id = 2,
TenantCode = "",
ServiceName = "user-service",
ClusterId = "global-cluster",
PathPattern = "/api/user/**",
Priority = 1,
Status = 1,
IsGlobal = true,
IsDeleted = false
}
};
var context = CreateInMemoryDbContext(routes);
var routeCache = CreateRouteCache(context);
await routeCache.InitializeAsync();
// Act
var result = routeCache.GetRoute("tenant-1", "user-service");
// Assert - tenant route should be prioritized
result.Should().NotBeNull();
result!.ClusterId.Should().Be("tenant-cluster");
}
[Fact]
public async Task GetRoute_WithoutTenantRoute_ShouldFallbackToGlobal()
{
// Arrange
var routes = new List<GwTenantRoute>
{
new GwTenantRoute
{
Id = 1,
TenantCode = "",
ServiceName = "user-service",
ClusterId = "global-cluster",
PathPattern = "/api/user/**",
Priority = 1,
Status = 1,
IsGlobal = true,
IsDeleted = false
}
};
var context = CreateInMemoryDbContext(routes);
var routeCache = CreateRouteCache(context);
await routeCache.InitializeAsync();
// Act
var result = routeCache.GetRoute("unknown-tenant", "user-service");
// Assert
result.Should().NotBeNull();
result!.ClusterId.Should().Be("global-cluster");
}
[Fact]
public async Task GetRoute_WithMissingRoute_ShouldReturnNull()
{
// Arrange
var routes = new List<GwTenantRoute>();
var context = CreateInMemoryDbContext(routes);
var routeCache = CreateRouteCache(context);
await routeCache.InitializeAsync();
// Act
var result = routeCache.GetRoute("tenant-1", "non-existent");
// Assert
result.Should().BeNull();
}
[Fact]
public async Task GetRouteByPath_WithValidPath_ShouldReturnRoute()
{
// Arrange
var routes = new List<GwTenantRoute>
{
new GwTenantRoute
{
Id = 1,
TenantCode = "",
ServiceName = "user-service",
ClusterId = "cluster-user",
PathPattern = "/api/user/**",
Priority = 1,
Status = 1,
IsGlobal = true,
IsDeleted = false
}
};
var context = CreateInMemoryDbContext(routes);
var routeCache = CreateRouteCache(context);
await routeCache.InitializeAsync();
// Act
var result = routeCache.GetRouteByPath("/api/user/users");
// Assert
result.Should().NotBeNull();
result!.ClusterId.Should().Be("cluster-user");
}
[Fact]
public async Task GetRouteByPath_WithMissingPath_ShouldReturnNull()
{
// Arrange
var routes = new List<GwTenantRoute>();
var context = CreateInMemoryDbContext(routes);
var routeCache = CreateRouteCache(context);
await routeCache.InitializeAsync();
// Act
var result = routeCache.GetRouteByPath("/unknown/path");
// Assert
result.Should().BeNull();
}
[Fact]
public async Task ReloadAsync_ShouldClearOldRoutes()
{
// Arrange
var initialRoutes = new List<GwTenantRoute>
{
new GwTenantRoute
{
Id = 1,
TenantCode = "",
ServiceName = "old-service",
ClusterId = "old-cluster",
PathPattern = "/api/old/**",
Priority = 1,
Status = 1,
IsGlobal = true,
IsDeleted = false
}
};
var context = CreateInMemoryDbContext(initialRoutes);
var routeCache = CreateRouteCache(context);
await routeCache.InitializeAsync();
// Verify initial state
routeCache.GetRoute("any", "old-service").Should().NotBeNull();
// Modify the database (replace routes)
context.TenantRoutes.RemoveRange(context.TenantRoutes);
context.TenantRoutes.Add(new GwTenantRoute
{
Id = 2,
TenantCode = "",
ServiceName = "new-service",
ClusterId = "new-cluster",
PathPattern = "/api/new/**",
Priority = 1,
Status = 1,
IsGlobal = true,
IsDeleted = false
});
context.SaveChanges();
// Act
await routeCache.ReloadAsync();
// Assert - old route should be gone, new route should exist
routeCache.GetRoute("any", "old-service").Should().BeNull();
routeCache.GetRoute("any", "new-service").Should().NotBeNull();
}
[Fact]
public async Task InitializeAsync_ShouldExcludeDeletedRoutes()
{
// Arrange
var routes = new List<GwTenantRoute>
{
new GwTenantRoute
{
Id = 1,
TenantCode = "",
ServiceName = "active-service",
ClusterId = "cluster-1",
PathPattern = "/api/active/**",
Priority = 1,
Status = 1,
IsGlobal = true,
IsDeleted = false
},
new GwTenantRoute
{
Id = 2,
TenantCode = "",
ServiceName = "deleted-service",
ClusterId = "cluster-2",
PathPattern = "/api/deleted/**",
Priority = 1,
Status = 1,
IsGlobal = true,
IsDeleted = true
}
};
var context = CreateInMemoryDbContext(routes);
var routeCache = CreateRouteCache(context);
await routeCache.InitializeAsync();
// Assert
routeCache.GetRoute("any", "active-service").Should().NotBeNull();
routeCache.GetRoute("any", "deleted-service").Should().BeNull();
}
[Fact]
public async Task InitializeAsync_ShouldExcludeInactiveRoutes()
{
// Arrange
var routes = new List<GwTenantRoute>
{
new GwTenantRoute
{
Id = 1,
TenantCode = "",
ServiceName = "active-service",
ClusterId = "cluster-1",
PathPattern = "/api/active/**",
Priority = 1,
Status = 1,
IsGlobal = true,
IsDeleted = false
},
new GwTenantRoute
{
Id = 2,
TenantCode = "",
ServiceName = "inactive-service",
ClusterId = "cluster-2",
PathPattern = "/api/inactive/**",
Priority = 1,
Status = 0, // Inactive
IsGlobal = true,
IsDeleted = false
}
};
var context = CreateInMemoryDbContext(routes);
var routeCache = CreateRouteCache(context);
await routeCache.InitializeAsync();
// Assert
routeCache.GetRoute("any", "active-service").Should().NotBeNull();
routeCache.GetRoute("any", "inactive-service").Should().BeNull();
}
[Fact]
public async Task GetRoute_ConcurrentReads_ShouldBeThreadSafe()
{
// Arrange
var routes = new List<GwTenantRoute>
{
new GwTenantRoute
{
Id = 1,
TenantCode = "",
ServiceName = "user-service",
ClusterId = "cluster-user",
PathPattern = "/api/user/**",
Priority = 1,
Status = 1,
IsGlobal = true,
IsDeleted = false
}
};
var context = CreateInMemoryDbContext(routes);
var routeCache = CreateRouteCache(context);
await routeCache.InitializeAsync();
// Act & Assert - multiple concurrent reads should not throw
var tasks = Enumerable.Range(0, 100)
.Select(_ => Task.Run(() => routeCache.GetRoute("any", "user-service")))
.ToList();
var results = await Task.WhenAll(tasks);
// All results should be consistent
results.Should().AllSatisfy(r => r.Should().NotBeNull());
}
}