# Test Coverage ## Purpose 定义测试覆盖规范,建立后端和前端的单元测试、集成测试及 E2E 测试体系,确保核心业务逻辑的测试覆盖率达到目标水平。 ## Requirements ### Requirement: 建立单元测试体系 系统 SHALL 建立完整的单元测试体系,覆盖核心业务逻辑。 #### Scenario: config 包测试覆盖 - **WHEN** 运行 config 包的单元测试 - **THEN** SHALL 覆盖 Provider、Model、Stats 的 CRUD 操作 - **THEN** SHALL 测试正常场景和错误场景 - **THEN** SHALL 验证数据库操作的准确性 #### Scenario: router 包测试覆盖 - **WHEN** 运行 router 包的单元测试 - **THEN** SHALL 覆盖模型路由逻辑 - **THEN** SHALL 测试模型不存在、模型禁用、供应商禁用等场景 - **THEN** SHALL 验证路由结果的正确性 #### Scenario: protocol 包测试覆盖 - **WHEN** 运行 protocol 包的单元测试 - **THEN** SHALL 覆盖 OpenAI 和 Anthropic 协议转换逻辑 - **THEN** SHALL 测试请求转换、响应转换、流式转换 - **THEN** SHALL 验证转换的准确性和完整性 #### Scenario: config 加载管道集成测试 - **WHEN** 运行 config 加载管道的集成测试 - **THEN** SHALL 验证 LoadConfigFromPath 正确加载默认值 - **THEN** SHALL 验证环境变量(`NEX_` 前缀)覆盖默认值 - **THEN** SHALL 验证 YAML 配置文件正确读取 - **THEN** SHALL 验证优先级链:CLI 参数 > 环境变量 > YAML 文件 > 默认值 - **THEN** SHALL 验证首次启动自动创建配置文件 - **THEN** SHALL 验证 SaveConfig 后重新 LoadConfig 数据一致 #### Scenario: handler 错误分支测试 - **WHEN** 运行 handler 层的单元测试 - **THEN** SHALL 覆盖 ModelHandler.CreateModel 的所有错误分支:ErrProviderNotFound(400)、ErrDuplicateModel(409)、通用错误(500) - **THEN** SHALL 覆盖 ProviderHandler.UpdateProvider 的所有错误分支:ErrRecordNotFound(404)、ErrImmutableField(400)、通用错误(500) - **THEN** SHALL 验证每个错误分支返回正确的 HTTP 状态码和错误响应格式 #### Scenario: service 聚合逻辑测试 - **WHEN** 运行 service 层的单元测试 - **THEN** SHALL 覆盖 StatsService.Aggregate 的所有分组模式:byProvider、byModel、byDate - **THEN** SHALL 验证 aggregateByModel 正确拼接和拆分 providerID/modelName key - **THEN** SHALL 验证 aggregateByDate 正确格式化日期并聚合 - **THEN** SHALL 覆盖空结果集、同名模型不同 provider 等边界场景 #### Scenario: provider service 工具方法测试 - **WHEN** 运行 provider service 的单元测试 - **THEN** SHALL 验证 isUniqueConstraintError 正确识别 SQLite 唯一约束冲突错误消息 - **THEN** SHALL 验证 List 方法对每个 provider 调用 MaskAPIKey #### Scenario: engine 降级路径测试 - **WHEN** 运行 conversion engine 的单元测试 - **THEN** SHALL 验证 convertEmbeddingBody 在 decode 失败时返回原始 body(passthrough) - **THEN** SHALL 验证 convertRerankBody 在 decode 失败时返回原始 body(passthrough) - **THEN** SHALL 验证降级过程不 panic、不返回空 body #### Scenario: openai decoder 边界场景测试 - **WHEN** 运行 openai decoder 的单元测试 - **THEN** SHALL 覆盖 assistant message content 为 JSON 数组格式的解析(text/refusal 类型) - **THEN** SHALL 验证 decodeContentParts 正确提取文本内容和拒绝消息 #### Scenario: 业务逻辑负面测试 - **WHEN** 运行业务逻辑负面测试 - **THEN** SHALL 覆盖 JSON 格式错误请求体的处理 - **THEN** SHALL 覆盖并发创建相同 provider + model 的重复检测 - **THEN** SHALL 覆盖空结果集查询的正确处理 ### Requirement: 建立集成测试体系 系统 SHALL 建立集成测试体系,覆盖 API 端到端流程。 #### Scenario: OpenAI 协议集成测试 - **WHEN** 运行 OpenAI 协议的集成测试 - **THEN** SHALL 测试完整的请求-响应流程 - **THEN** SHALL 测试流式响应流程 - **THEN** SHALL 测试错误处理流程 #### Scenario: Anthropic 协议集成测试 - **WHEN** 运行 Anthropic 协议的集成测试 - **THEN** SHALL 测试完整的请求-响应流程 - **THEN** SHALL 测试流式响应流程 - **THEN** SHALL 测试协议转换的准确性 #### Scenario: 管理接口集成测试 - **WHEN** 运行管理接口的集成测试 - **THEN** SHALL 测试 Provider、Model、Stats 的 CRUD 操作 - **THEN** SHALL 验证 API 响应格式 - **THEN** SHALL 测试错误场景 ### Requirement: 提供测试工具函数 系统 SHALL 提供测试工具函数,简化测试编写。 #### Scenario: 测试数据库初始化 - **WHEN** 编写需要数据库的测试 - **THEN** SHALL 提供统一的测试数据库初始化函数 `SetupTestDB` - **THEN** SHALL 统一使用 SQLite `:memory:` + `MaxOpenConns(1)` 策略 - **THEN** SHALL 在测试结束后自动清理 - **THEN** 所有测试包 SHALL 通过 `tests.SetupTestDB()` 获取测试数据库,不允许各自独立实现 #### Scenario: Mock 工具 - **WHEN** 编写需要 Mock 的测试 - **THEN** SHALL 使用 mockgen 自动生成 mock 实现 - **THEN** SHALL 在接口定义文件中使用 `//go:generate` 注解标注生成命令 - **THEN** 生成的 mock SHALL 放置在 `tests/mocks/` 目录下 - **THEN** SHALL 覆盖 service 和 repository 接口的 mock 生成 ### Requirement: 达到测试覆盖率目标 系统 SHALL 达到 > 85% 的测试覆盖率。 #### Scenario: 总体覆盖率 - **WHEN** 运行所有测试并生成覆盖率报告 - **THEN** 总体覆盖率 SHALL 大于 85% - **THEN** 核心包(config、service、handler、conversion、repository)覆盖率 SHALL 大于 85% #### Scenario: 覆盖率报告生成 - **WHEN** 运行测试覆盖率命令 - **THEN** SHALL 生成覆盖率报告文件 - **THEN** SHALL 支持生成 HTML 格式报告 - **THEN** SHALL 显示每个文件的覆盖率 ### Requirement: 集成到构建流程 测试 SHALL 集成到构建流程中。 #### Scenario: 运行测试命令 - **WHEN** 执行 `make test` 命令 - **THEN** SHALL 运行所有单元测试和集成测试 - **THEN** SHALL 显示测试结果 - **THEN** SHALL 在测试失败时返回非零退出码 #### Scenario: 分类测试命令 - **WHEN** 执行 `make test-unit` 命令 - **THEN** SHALL 仅运行 `./internal/...` 和 `./pkg/...` 下的单元测试 - **WHEN** 执行 `make test-integration` 命令 - **THEN** SHALL 仅运行 `./tests/...` 下的集成测试 #### Scenario: 覆盖率检查命令 - **WHEN** 执行 `make test-coverage` 命令 - **THEN** SHALL 运行测试并生成覆盖率报告 - **THEN** SHALL 检查覆盖率是否达标 - **THEN** SHALL 在覆盖率不足时返回非零退出码 ### Requirement: 建立前端单元测试覆盖 前端代码 SHALL 建立单元测试覆盖,纳入整体测试覆盖率统计。 #### Scenario: API 层测试覆盖 - **WHEN** 运行前端 API 层的单元测试 - **THEN** SHALL 覆盖 api/client.ts 的请求封装和字段转换逻辑 - **THEN** SHALL 覆盖 api/providers.ts、api/models.ts、api/stats.ts 的所有函数 - **THEN** SHALL 使用 MSW mock API 响应 #### Scenario: Hooks 测试覆盖 - **WHEN** 运行前端 Hooks 的单元测试 - **THEN** SHALL 覆盖 useProviders、useModels、useStats 的查询和变更逻辑 - **THEN** SHALL 验证缓存失效和自动刷新行为 ### Requirement: 建立前端组件测试覆盖 前端 SHALL 使用 React Testing Library 建立组件测试覆盖。 #### Scenario: 页面组件测试覆盖 - **WHEN** 运行前端组件测试 - **THEN** SHALL 覆盖 ProviderTable 的列表渲染和交互操作 - **THEN** SHALL 覆盖 ProviderForm 的表单校验和提交 - **THEN** SHALL 覆盖 ModelForm 的表单校验和提交 - **THEN** SHALL 覆盖 StatsTable 的筛选和数据展示 ### Requirement: 建立前端 E2E 测试覆盖 前端 SHALL 使用 Playwright 建立 E2E 测试覆盖。 #### Scenario: 供应商管理 E2E 覆盖 - **WHEN** 运行 E2E 测试 - **THEN** SHALL 覆盖供应商创建、编辑、删除的完整用户流程 - **THEN** SHALL 覆盖模型创建、编辑、删除的完整用户流程 #### Scenario: 统计查询 E2E 覆盖 - **WHEN** 运行 E2E 测试 - **THEN** SHALL 覆盖统计页面的加载和筛选查询流程 - **THEN** SHALL 覆盖页面间的导航切换 ### Requirement: 前端测试集成到构建流程 前端测试 SHALL 集成到项目的构建和验证流程中。 #### Scenario: 运行前端测试命令 - **WHEN** 在 frontend/ 目录执行测试命令 - **THEN** SHALL 运行所有 Vitest 单元测试和组件测试 - **THEN** SHALL 显示测试结果 - **THEN** SHALL 在测试失败时返回非零退出码 #### Scenario: 运行前端 E2E 测试命令 - **WHEN** 在 frontend/ 目录执行 E2E 测试命令 - **THEN** SHALL 启动 Playwright 运行 E2E 测试 - **THEN** SHALL 在测试失败时返回非零退出码 ### Requirement: 清理 ProviderRepository 死代码 系统 SHALL 移除 ProviderRepository 中未被调用的重复方法。 #### Scenario: 移除死代码方法 - **WHEN** 审查 ProviderRepository 接口 - **THEN** SHALL 移除 `ListEnabledModels()` 方法声明和实现 - **THEN** SHALL 移除 `FindByProviderAndModelName()` 方法声明和实现 - **THEN** SHALL 确保所有现有调用者通过 ModelRepository 访问等效功能 - **THEN** SHALL 不影响任何运行时行为 ### Requirement: 使用 mockgen 生成 mock 系统 SHALL 使用 mockgen 为接口自动生成 mock 实现,替代手写 mock。 #### Scenario: mock 生成配置 - **WHEN** 在接口定义文件中添加 `//go:generate mockgen` 注解 - **THEN** SHALL 为 `ProviderService`、`ModelService`、`RoutingService`、`StatsService` 接口生成 mock - **THEN** SHALL 为 `ModelRepository`、`ProviderRepository`、`StatsRepository` 接口生成 mock - **THEN** SHALL 为 `ProviderClient` 接口生成 mock - **THEN** 生成的 mock SHALL 输出到 `tests/mocks/` 目录 #### Scenario: 替换手写 mock - **WHEN** mockgen 生成的 mock 就绪 - **THEN** handler 测试中的手写 mock SHALL 被替换为生成的 mock - **THEN** 所有测试 SHALL 继续通过,行为不变