fengling-gateway/.planning/codebase/TEST_PLAN.md
movingsam 52f4b7616e docs: add security audit and test plan
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-02-28 18:38:38 +08:00

180 lines
7.9 KiB
Markdown
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.

# 🧪 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% | - |
---
*测试计划由分析生成,建议按优先级逐步实现。*