1
0
Files
nex/openspec/specs/test-coverage/spec.md
lanyuanxiaoyao 5513f0c13d feat: 区分 server 与 desktop 配置加载入口,取消自动创建配置文件
- config.go 重构:抽取 loadConfig 共享逻辑,新增 LoadServerConfig/LoadDesktopConfig/LoadDesktopConfigAtPath,LoadConfig 保持向后兼容
- setupConfigFile 移除 SafeWriteConfigAs 自动创建逻辑,文件不存在时仅使用默认值
- cmd/desktop 切换为 LoadDesktopConfig,端口/HTTP/浏览器/托盘统一使用 cfg.Server.Port
- cmd/server 显式使用 LoadServerConfig 明确入口语义
- 提取 desktop 可测 helper:desktopListenAddr/desktopURL/desktopPortMenuTitle/desktopConfigErrorMessage
- 新增测试:desktop 忽略 CLI/env/未知参数、配置快照不变、无效配置文件不静默回退、端口 helper 一致性
- README 区分 server/desktop 配置源,移除首次启动自动创建配置文件描述
- 同步 delta specs 到 openspec/specs/ 主规范
2026-05-06 11:59:19 +08:00

14 KiB
Raw Blame History

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: LoadConfigFromPath 默认值验证

  • WHEN 运行 config 加载管道的集成测试
  • THEN SHALL 验证 LoadConfigFromPath 正确加载默认值
  • THEN SHALL 验证 YAML 配置文件正确读取
  • THEN SHALL 验证优先级链CLI 参数 > 环境变量 > YAML 文件 > 默认值
  • THEN SHALL 验证配置文件缺失时使用默认值,不自动创建配置文件
  • THEN SHALL 验证 SaveConfig 后重新 LoadConfig 数据一致

Scenario: 环境变量覆盖验证

  • WHEN 设置 NEX_SERVER_PORT=9000NEX_LOG_LEVEL=debug
  • THEN SHALL 成功加载
  • THEN 配置值 SHALL 反映环境变量覆盖

Scenario: 配置文件缺失时使用默认值

  • WHEN 调用 LoadConfigFromPath 并指向不存在的文件路径
  • THEN SHALL 成功加载(不返回 missing configuration for 'configPath' 错误)
  • THEN SHALL 返回默认配置对象
  • THEN SHALL NOT 自动创建配置文件

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 失败时返回原始 bodypassthrough
  • THEN SHALL 验证 convertRerankBody 在 decode 失败时返回原始 bodypassthrough
  • 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 同时集成到根目录公共流程和 backend 局部流程中。

Scenario: 运行测试命令

  • WHEN 执行 make test 命令
  • THEN SHALL 运行 backend 核心测试、frontend 的 Vitest 单元/组件测试和 desktop 专属测试
  • THEN SHALL NOT 运行 MySQL 专项测试和 frontend E2E 测试
  • THEN SHALL 显示测试结果
  • THEN SHALL 在测试失败时返回非零退出码

Scenario: 运行 backend 局部测试命令

  • WHENbackend/ 目录执行 make test 命令
  • THEN SHALL 运行 backend 核心测试
  • THEN SHALL NOT 运行 frontend 的 Vitest 单元/组件测试、desktop 专属测试、MySQL 专项测试或 frontend E2E 测试

Scenario: 分类测试命令

  • WHENbackend/ 目录执行 make test-unit 命令

  • THEN SHALL 仅运行 ./internal/..../pkg/... 下的单元测试

  • WHENbackend/ 目录执行 make test-integration 命令

  • THEN SHALL 仅运行 ./tests/... 下的集成测试

Scenario: 覆盖率检查命令

  • WHENbackend/ 目录执行 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 为 ProviderServiceModelServiceRoutingServiceStatsService 接口生成 mock
  • THEN SHALL 为 ModelRepositoryProviderRepositoryStatsRepository 接口生成 mock
  • THEN SHALL 为 ProviderClient 接口生成 mock
  • THEN 生成的 mock SHALL 输出到 tests/mocks/ 目录

Scenario: 替换手写 mock

  • WHEN mockgen 生成的 mock 就绪
  • THEN handler 测试中的手写 mock SHALL 被替换为生成的 mock
  • THEN 所有测试 SHALL 继续通过,行为不变

Requirement: 运行时迁移资源测试覆盖

系统 SHALL 覆盖打包迁移资源解析和启动迁移回归场景,确保发布产物不依赖源码迁移目录。

Scenario: 运行时迁移资源解析测试

  • WHEN 运行 database 包单元测试
  • THEN SHALL 验证 database.Init 在当前工作目录不是仓库根目录或 backend/ 目录时仍能执行迁移
  • THEN SHALL 验证迁移资源不依赖 runtime.Caller 推导的源码路径
  • THEN SHALL 覆盖 SQLite 方言迁移资源解析

Scenario: 双方言迁移资源选择测试

  • WHEN 运行迁移资源选择相关测试
  • THEN SHALL 验证 SQLite 方言资源可被解析
  • THEN SHALL 验证 MySQL 方言资源可被解析
  • THEN SHALL 验证未知或非法 driver 不会被静默映射到错误方言资源

Scenario: desktop 打包迁移资源测试

  • WHEN 运行 desktop 专属测试
  • THEN SHALL 验证 desktop 模式启动数据库迁移时使用打包迁移资源
  • THEN SHALL 验证应用在发布产物环境中可执行迁移而不依赖源码迁移目录

Requirement: Desktop 配置源隔离测试覆盖

系统 SHALL 为 desktop 配置加载行为建立测试覆盖,验证 desktop 只使用默认配置文件和默认值,不受 CLI 参数或 NEX_* 环境变量影响。

Scenario: Desktop 配置文件端口生效

  • WHEN 运行 desktop 配置加载相关测试
  • THEN SHALL 验证 ~/.nex/config.yaml 或等价测试配置文件中的 server.port 会进入 desktop 启动配置快照
  • THEN SHALL 验证 desktop 端口检测、HTTP 监听地址、浏览器打开地址和托盘端口显示使用同一个配置端口

Scenario: Desktop 忽略 CLI 参数

  • WHEN 测试进程参数包含 --server-port 9000--database-path /tmp/test.db--config /tmp/custom.yaml
  • THEN desktop 配置加载 SHALL 忽略这些参数
  • THEN desktop 配置加载 SHALL 使用默认配置文件路径和配置文件值

Scenario: Desktop 忽略未知参数

  • WHEN 测试进程参数包含未知命令行参数
  • THEN desktop 配置加载 SHALL 成功或仅因配置文件本身无效而失败
  • THEN desktop 配置加载 SHALL NOT 因未知参数返回参数解析错误

Scenario: Desktop 忽略环境变量

  • WHEN 测试环境设置 NEX_SERVER_PORTNEX_DATABASE_PATHNEX_LOG_LEVEL 或其他 NEX_* 环境变量
  • THEN desktop 配置加载 SHALL NOT 使用这些环境变量覆盖配置文件值
  • THEN server 配置加载的环境变量覆盖测试 SHALL 继续通过

Scenario: Desktop 配置快照不随文件变化自动更新

  • WHEN desktop 配置已加载为内存中的启动快照
  • AND 测试修改配置文件中的 server.port 或其他配置项
  • THEN 已加载的配置对象 SHALL 保持原值
  • THEN 重新启动或重新执行 desktop 配置加载时 SHALL 读取修改后的配置值

Scenario: Desktop 无效配置错误提示

  • WHEN desktop 启动时配置文件存在但 YAML 无法解析或配置验证失败
  • THEN 测试 SHALL 验证启动流程返回或显示包含配置路径和失败原因的错误
  • THEN 测试 SHALL 验证 desktop 不会静默回退默认配置继续启动

Scenario: 配置文件缺失时使用默认值

  • WHEN 测试配置加载时指定不存在的配置文件路径
  • THEN SHALL 返回默认配置值,不自动创建配置文件
  • THEN 测试 SHALL 验证配置文件未被创建