Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
180 lines
7.9 KiB
Markdown
180 lines
7.9 KiB
Markdown
# 🧪 YARP 网关测试覆盖计划
|
||
|
||
> 分析日期:2026-02-28
|
||
> 当前状态:**无任何测试代码**
|
||
|
||
---
|
||
|
||
## 测试项目结构
|
||
|
||
```
|
||
tests/
|
||
└── YarpGateway.Tests/
|
||
├── YarpGateway.Tests.csproj
|
||
├── Unit/
|
||
│ ├── Middleware/
|
||
│ │ ├── JwtTransformMiddlewareTests.cs
|
||
│ │ └── TenantRoutingMiddlewareTests.cs
|
||
│ ├── Services/
|
||
│ │ ├── RouteCacheTests.cs
|
||
│ │ ├── RedisConnectionManagerTests.cs
|
||
│ │ └── PgSqlConfigChangeListenerTests.cs
|
||
│ ├── LoadBalancing/
|
||
│ │ └── DistributedWeightedRoundRobinPolicyTests.cs
|
||
│ └── Config/
|
||
│ ├── DatabaseRouteConfigProviderTests.cs
|
||
│ └── DatabaseClusterConfigProviderTests.cs
|
||
├── Integration/
|
||
│ ├── Controllers/
|
||
│ │ ├── GatewayConfigControllerTests.cs
|
||
│ │ └── PendingServicesControllerTests.cs
|
||
│ └── Middleware/
|
||
│ └── MiddlewarePipelineTests.cs
|
||
└── TestHelpers/
|
||
├── MockDbContext.cs
|
||
├── MockRedis.cs
|
||
└── TestFixtures.cs
|
||
```
|
||
|
||
---
|
||
|
||
## P0 - 必须覆盖(核心安全)
|
||
|
||
### JwtTransformMiddlewareTests
|
||
|
||
| 测试用例 | 描述 | 输入 | 预期输出 | Mock 需求 |
|
||
|---------|------|------|----------|-----------|
|
||
| `ShouldValidateJwtSignature` | 应验证 JWT 签名 | 有效签名的 JWT | 解析成功,Claims 正确 | `IOptions<JwtConfig>` |
|
||
| `ShouldRejectInvalidToken` | 应拒绝无效 Token | 伪造/过期 JWT | 返回 401 或跳过处理 | - |
|
||
| `ShouldExtractTenantClaim` | 应正确提取租户 ID | 含 tenant claim 的 JWT | X-Tenant-Id header 设置正确 | - |
|
||
| `ShouldHandleMissingToken` | 应处理无 Token 请求 | 无 Authorization header | 继续处理(不设置 headers) | - |
|
||
| `ShouldHandleMalformedToken` | 应处理格式错误 Token | 无效 JWT 格式 | 记录警告,继续处理 | - |
|
||
|
||
### TenantRoutingMiddlewareTests
|
||
|
||
| 测试用例 | 描述 | 输入 | 预期输出 | Mock 需求 |
|
||
|---------|------|------|----------|-----------|
|
||
| `ShouldValidateTenantIdAgainstJwt` | 应验证 header 与 JWT 一致 | X-Tenant-Id ≠ JWT tenant | 返回 403 Forbidden | `IRouteCache` |
|
||
| `ShouldExtractServiceNameFromPath` | 应正确解析服务名 | `/api/user-service/users` | serviceName = "user-service" | - |
|
||
| `ShouldFindRouteInCache` | 应从缓存找到路由 | 有效租户+服务名 | 设置正确的 clusterId | `IRouteCache` |
|
||
| `ShouldHandleRouteNotFound` | 应处理路由未找到 | 不存在的服务名 | 记录警告,继续处理 | - |
|
||
| `ShouldPrioritizeTenantRouteOverGlobal` | 租户路由优先于全局 | 同时存在两种路由 | 使用租户路由 | - |
|
||
|
||
---
|
||
|
||
## P1 - 重要覆盖(核心业务)
|
||
|
||
### RouteCacheTests
|
||
|
||
| 测试用例 | 描述 | 输入 | 预期输出 | Mock 需求 |
|
||
|---------|------|------|----------|-----------|
|
||
| `ShouldLoadGlobalRoutes` | 应加载全局路由 | 全局路由数据 | `_globalRoutes` 填充 | `IDbContextFactory<GatewayDbContext>` |
|
||
| `ShouldLoadTenantRoutes` | 应加载租户路由 | 租户路由数据 | `_tenantRoutes` 填充 | - |
|
||
| `ShouldReturnCorrectRoute` | 应返回正确路由 | 查询请求 | 正确的 `RouteInfo` | - |
|
||
| `ShouldReturnNullForMissingRoute` | 不存在路由返回 null | 不存在的服务名 | `null` | - |
|
||
| `ShouldHandleConcurrentReads` | 并发读取应安全 | 多线程读取 | 无异常,数据一致 | - |
|
||
| `ShouldReloadCorrectly` | 应正确重载 | Reload 调用 | 旧数据清除,新数据加载 | - |
|
||
|
||
### RedisConnectionManagerTests
|
||
|
||
| 测试用例 | 描述 | 输入 | 预期输出 | Mock 需求 |
|
||
|---------|------|------|----------|-----------|
|
||
| `ShouldAcquireLock` | 应获取分布式锁 | 有效 key | 锁获取成功 | `IConnectionMultiplexer` |
|
||
| `ShouldReleaseLockCorrectly` | 应正确释放锁 | 已获取的锁 | 锁释放成功 | - |
|
||
| `ShouldNotReleaseOthersLock` | 不应释放他人锁 | 其他实例的锁 | 释放失败(安全) | - |
|
||
| `ShouldHandleConnectionFailure` | 应处理连接失败 | Redis 不可用 | 记录错误,返回失败 | - |
|
||
| `ShouldExecuteInLock` | 应在锁内执行操作 | 操作委托 | 操作执行,锁正确释放 | - |
|
||
|
||
### DistributedWeightedRoundRobinPolicyTests
|
||
|
||
| 测试用例 | 描述 | 输入 | 预期输出 | Mock 需求 |
|
||
|---------|------|------|----------|-----------|
|
||
| `ShouldSelectByWeight` | 应按权重选择 | 权重 [3, 1, 1] | 约 60% 选第一个 | `IConnectionMultiplexer` |
|
||
| `ShouldFallbackOnLockFailure` | 锁失败应降级 | Redis 不可用 | 降级选择第一个可用 | - |
|
||
| `ShouldReturnNullWhenNoDestinations` | 无目标返回 null | 空目标列表 | `null` | - |
|
||
| `ShouldPersistStateToRedis` | 状态应持久化到 Redis | 多次选择 | 状态存储正确 | - |
|
||
| `ShouldExpireStateAfterTTL` | 状态应在 TTL 后过期 | 1 小时后 | 状态重新初始化 | - |
|
||
|
||
### PgSqlConfigChangeListenerTests
|
||
|
||
| 测试用例 | 描述 | 输入 | 预期输出 | Mock 需求 |
|
||
|---------|------|------|----------|-----------|
|
||
| `ShouldListenForNotifications` | 应监听通知 | NOTIFY 事件 | 触发重载 | `NpgsqlConnection` |
|
||
| `ShouldFallbackToPolling` | 应回退到轮询 | 通知失败 | 定时轮询检测 | - |
|
||
| `ShouldReconnectOnFailure` | 失败应重连 | 连接断开 | 自动重连 | - |
|
||
| `ShouldDetectVersionChange` | 应检测版本变化 | 版本号增加 | 触发重载 | - |
|
||
|
||
---
|
||
|
||
## P2 - 推荐覆盖(业务逻辑)
|
||
|
||
### GatewayConfigControllerTests
|
||
|
||
| 测试用例 | 描述 | 输入 | 预期输出 | Mock 需求 |
|
||
|---------|------|------|----------|-----------|
|
||
| `ShouldCreateTenant` | 应创建租户 | 有效 DTO | 201 Created | `IDbContextFactory` |
|
||
| `ShouldRejectDuplicateTenant` | 应拒绝重复租户 | 已存在的 TenantCode | 400 BadRequest | - |
|
||
| `ShouldCreateRoute` | 应创建路由 | 有效 DTO | 201 Created | - |
|
||
| `ShouldDeleteTenant` | 应删除租户 | 有效 ID | 204 NoContent | - |
|
||
| `ShouldReturn404ForMissingTenant` | 不存在租户返回 404 | 无效 ID | 404 NotFound | - |
|
||
| `ShouldReloadConfig` | 应重载配置 | POST /config/reload | 200 OK | `IRouteCache` |
|
||
|
||
### PendingServicesControllerTests
|
||
|
||
| 测试用例 | 描述 | 输入 | 预期输出 | Mock 需求 |
|
||
|---------|------|------|----------|-----------|
|
||
| `ShouldListPendingServices` | 应列出待处理服务 | GET 请求 | 待处理服务列表 | `IDbContextFactory` |
|
||
| `ShouldAssignService` | 应分配服务 | 有效请求 | 服务实例创建 | - |
|
||
| `ShouldRejectInvalidCluster` | 应拒绝无效集群 | 不存在的 ClusterId | 400 BadRequest | - |
|
||
| `ShouldRejectService` | 应拒绝服务 | reject 请求 | 状态更新为 Rejected | - |
|
||
|
||
---
|
||
|
||
## 测试依赖
|
||
|
||
```xml
|
||
<!-- YarpGateway.Tests.csproj -->
|
||
<ItemGroup>
|
||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
||
<PackageReference Include="xunit" Version="2.7.0" />
|
||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.7" />
|
||
<PackageReference Include="Moq" Version="4.20.70" />
|
||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="10.0.0" />
|
||
<PackageReference Include="Testcontainers.Redis" Version="3.7.0" />
|
||
<PackageReference Include="Testcontainers.PostgreSql" Version="3.7.0" />
|
||
</ItemGroup>
|
||
```
|
||
|
||
---
|
||
|
||
## 运行测试命令
|
||
|
||
```bash
|
||
# 运行所有测试
|
||
dotnet test
|
||
|
||
# 运行特定测试类
|
||
dotnet test --filter "FullyQualifiedName~JwtTransformMiddlewareTests"
|
||
|
||
# 生成覆盖率报告
|
||
dotnet test --collect:"XPlat Code Coverage"
|
||
reportgenerator -reports:**/coverage.cobertura.xml -targetdir:coverage
|
||
```
|
||
|
||
---
|
||
|
||
## 覆盖率目标
|
||
|
||
| 组件 | 目标覆盖率 | 优先级 |
|
||
|------|-----------|--------|
|
||
| JwtTransformMiddleware | 90% | P0 |
|
||
| TenantRoutingMiddleware | 85% | P0 |
|
||
| RouteCache | 80% | P1 |
|
||
| DistributedWeightedRoundRobinPolicy | 80% | P1 |
|
||
| Controllers | 70% | P2 |
|
||
| 整体项目 | 75% | - |
|
||
|
||
---
|
||
|
||
*测试计划由分析生成,建议按优先级逐步实现。* |