1
0

feat: 增强桌面启动失败提示与测试覆盖

This commit is contained in:
2026-05-08 23:42:48 +08:00
parent c524e8f928
commit 2dec9e5c54
21 changed files with 1857 additions and 297 deletions

View File

@@ -166,6 +166,34 @@
测试 workflow 中的各测试步骤 SHALL 使用隔离的资源,不干扰主环境。
### Requirement: Desktop 原生 UI 测试不依赖真实图形环境
CI 测试门禁 SHALL 允许验证 desktop 启动失败报告的 UI 无关逻辑,但 SHALL NOT 要求 GitHub Actions runner 或本地 CI 环境具备真实系统通知、模态弹窗或托盘可见性。
#### Scenario: CI 运行 desktop 启动失败测试
- **WHEN** `check` job 执行 `make test`
- **THEN** desktop 专属测试 SHALL 可以覆盖启动失败分类、提示通道选择、fallback 顺序和托盘 ready/timeout 逻辑
- **THEN** 测试 SHALL 使用 mock、fake runner 或接口注入验证调用意图
#### Scenario: CI 不验证真实原生 UI 展示
- **WHEN** `check` job 在 Linux、macOS 或 Windows runner 上运行
- **THEN** 测试 SHALL NOT 要求真实系统通知可见
- **THEN** 测试 SHALL NOT 要求真实模态弹窗被显示或被人工点击
- **THEN** 测试 SHALL NOT 要求真实托盘图标可见
- **THEN** runner 的通知权限、勿扰模式、DBus 状态或桌面会话差异 SHALL NOT 导致正常 CI 失败
#### Scenario: Linux CI 系统依赖边界
- **WHEN** Linux `check` job 安装 desktop 构建和测试所需系统依赖
- **THEN** 该依赖安装 SHALL NOT 被解释为需要在 CI 中验证真实 Linux 通知或弹窗展示
- **THEN** Linux 通知/弹窗命令 SHALL 在测试中通过 fake runner 覆盖
### Requirement: 测试 workflow 资源隔离
测试 workflow 中的各测试步骤 SHALL 使用隔离的资源,不干扰主环境。
#### Scenario: E2E 临时资源隔离
- **WHEN** E2E 测试运行

View File

@@ -6,6 +6,130 @@ TBD - 提供跨平台桌面应用支持,将后端服务与前端静态资源
## Requirements
### Requirement: Desktop 启动失败报告
Desktop SHALL 将无法进入可用状态的启动失败统一转换为包含阶段、用户消息和底层原因的启动错误,并通过统一报告器提示用户。
#### Scenario: 配置加载或验证失败
- **WHEN** desktop 启动时 `~/.nex/config.yaml` 无法解析或配置验证失败
- **THEN** 系统 SHALL 生成 `config` 阶段启动错误
- **THEN** 错误提示 SHALL 包含配置文件路径和失败原因
- **THEN** 应用 SHALL 退出
#### Scenario: 日志初始化失败
- **WHEN** desktop 启动时完整 logger 初始化失败
- **THEN** 系统 SHALL 生成 `logger` 阶段启动错误
- **THEN** 错误提示 SHALL 描述日志初始化失败并包含可操作的路径或权限线索
- **THEN** 应用 SHALL 退出
#### Scenario: 数据库初始化或迁移失败
- **WHEN** desktop 启动时数据库打开、连接、初始化或迁移失败
- **THEN** 系统 SHALL 生成 `database``migration` 阶段启动错误
- **THEN** 错误提示 SHALL 描述数据库初始化失败或迁移失败
- **THEN** 错误提示 SHALL NOT 暴露 MySQL password、完整 DSN 或 API key 等敏感信息
- **THEN** 应用 SHALL 退出
#### Scenario: 嵌入资源或内部组件初始化失败
- **WHEN** desktop 启动时前端嵌入资源、协议 adapter 或其他内部组件初始化失败
- **THEN** 系统 SHALL 生成对应启动阶段的启动错误
- **THEN** 错误提示 SHALL 描述应用初始化失败并建议查看日志或重新安装
- **THEN** 应用 SHALL 退出
#### Scenario: 启动失败提示降级链
- **WHEN** desktop 生成 fatal 启动错误
- **THEN** 系统 SHALL 先尝试平台系统通知
- **THEN** 系统 SHALL 在通知不可用或返回失败时尝试平台模态弹窗
- **THEN** 系统 SHALL 在模态弹窗不可用或返回失败时输出到 stderr 或可用启动日志
- **THEN** 每一次提示通道失败 SHALL 被记录但 SHALL NOT 阻止后续 fallback
#### Scenario: 提示通道调用前可用性检查
- **WHEN** desktop 启动失败报告器准备调用任一系统通知或模态弹窗通道
- **THEN** 系统 SHALL 在调用前检查该通道对应命令、系统 API 或图形会话条件是否可用
- **THEN** 系统 SHALL 在通道不可用时跳过该通道并进入下一 fallback
- **THEN** 系统 SHALL NOT 因某个通知或弹窗工具缺失而中断后续降级链
#### Scenario: 浏览器打开失败为非 fatal
- **WHEN** desktop 后端服务和托盘已启动但浏览器自动打开失败
- **THEN** 系统 SHALL 记录 warning
- **THEN** 系统 SHALL 尝试通过非 fatal 提示告知用户可手动访问 `http://localhost:<server.port>`
- **THEN** 应用 SHALL 继续运行
### Requirement: macOS 通知和对话框降级策略
系统 SHALL 在 macOS 上优先使用通知中心提示 desktop 启动错误,并在通知不可用时降级到 AppleScript 模态告警。
#### Scenario: macOS 通知可用
- **WHEN** desktop 在 macOS 上生成启动错误且通知命令可用
- **THEN** 系统 SHALL 使用 `osascript display notification` 发送系统通知
- **THEN** 通知 SHALL 包含应用名称和错误描述文本
#### Scenario: macOS 通知失败
- **WHEN** macOS 系统通知命令不可用或返回失败
- **THEN** 系统 SHALL 使用 `osascript display alert` 显示模态告警
- **THEN** 模态告警 SHALL 使用 critical 错误语义
- **THEN** 告警 SHALL 包含应用名称和错误描述文本
#### Scenario: macOS UI 提示均失败
- **WHEN** macOS 系统通知和模态告警均失败
- **THEN** 系统 SHALL 降级输出到 stderr 或可用启动日志
### Requirement: Linux 启动错误提示降级策略
系统 SHALL 在 Linux 上为 desktop 启动错误按优先级使用可用的通知或对话框工具,优先使用系统通知栏,再降级到模态弹窗,最后降级到标准错误输出。该策略 SHALL 不移除既有通用信息提示能力。
#### Scenario: notify-send 可用
- **WHEN** desktop 在 Linux 上生成启动错误且 `notify-send` 命令可用并存在图形会话
- **THEN** 使用 `notify-send` 显示系统通知
- **AND** 错误通知使用 `-u critical` 参数
- **AND** 通知 SHALL 包含应用名称和错误描述文本
#### Scenario: kdialog passive popup 可用
- **WHEN** `notify-send` 不可用或返回失败且 `kdialog` 命令可用
- **THEN** 系统 SHALL 优先尝试使用 `kdialog --passivepopup` 显示系统通知式提示
#### Scenario: zenity 可用
- **WHEN** Linux 通知工具不可用或返回失败且系统检测到 `zenity` 命令可用
- **THEN** 使用 zenity 显示 GTK 风格错误对话框
- **AND** 错误对话框使用 `zenity --error` 命令
#### Scenario: kdialog 模态对话框可用
- **WHEN** `zenity` 不可用或返回失败且 `kdialog` 命令可用
- **THEN** 使用 kdialog 显示 KDE 风格错误对话框
- **AND** 错误对话框使用 `kdialog --error` 命令
#### Scenario: xmessage 可用
- **WHEN** `zenity``kdialog` 模态对话框均不可用或返回失败且 `xmessage` 命令可用
- **THEN** 使用 xmessage 显示基础 X11 对话框
- **AND** 对话框居中显示(`-center` 参数)
#### Scenario: 无 UI 工具可用
- **WHEN** 所有通知和对话框工具均不可用、图形会话不存在或所有 UI 提示均返回失败
- **THEN** 降级到标准错误输出
- **AND** 输出格式为 `错误: <title>: <message>`
#### Scenario: 工具检测缓存
- **WHEN** desktop 在 Linux 上启动
- **THEN** 系统检测一次可用通知和对话框工具
- **AND** 检测结果缓存在包级变量
- **AND** 后续提示调用直接使用缓存结果,不重复检测
### Requirement: 桌面应用启动
系统 SHALL 支持作为桌面应用启动,将后端服务与前端静态资源打包为单一可执行文件,并使用启动配置中的 `server.port` 作为本次运行端口。
@@ -36,7 +160,7 @@ TBD - 提供跨平台桌面应用支持,将后端服务与前端静态资源
### Requirement: 系统托盘
系统 SHALL 提供跨平台系统托盘功能,支持托盘图标和菜单。图标格式 SHALL 根据平台自动选择,端口显示 SHALL 使用启动配置中的 `server.port`
系统 SHALL 提供跨平台系统托盘功能,支持托盘图标和菜单。图标格式 SHALL 根据平台自动选择,端口显示 SHALL 使用启动配置中的 `server.port`托盘初始化失败 SHALL 被视为 desktop fatal 启动失败。
#### Scenario: 托盘图标显示
@@ -60,11 +184,19 @@ TBD - 提供跨平台桌面应用支持,将后端服务与前端静态资源
- **WHEN** 用户点击托盘菜单"打开管理界面"
- **THEN** 系统在浏览器中打开 `http://localhost:<server.port>`
#### Scenario: 托盘 ready 后自动打开浏览器
- **WHEN** desktop 启动并完成托盘图标、菜单和点击事件初始化
- **THEN** 系统 SHALL 将托盘标记为 ready
- **THEN** 系统 SHALL 在托盘 ready 后自动打开 `http://localhost:<server.port>`
- **THEN** 系统 SHALL NOT 在托盘初始化失败时自动打开浏览器
#### Scenario: 浏览器打开失败
- **WHEN** 系统无法打开浏览器(浏览器未安装等)
- **THEN** 托盘菜单仍可正常使用
- **AND** 用户可手动访问 `http://localhost:<server.port>`
- **AND** 系统 SHALL 尝试显示非 fatal 提示,告知用户手动访问地址
#### Scenario: 退出应用
@@ -73,6 +205,13 @@ TBD - 提供跨平台桌面应用支持,将后端服务与前端静态资源
- **AND** 托盘图标消失
- **AND** 应用进程退出
#### Scenario: 托盘初始化失败
- **WHEN** desktop 启动时托盘未在限定时间内 ready、托盘图标资源无法加载或托盘菜单无法完成初始化
- **THEN** 系统 SHALL 生成 `tray` 阶段启动错误
- **THEN** 系统 SHALL 通过启动失败提示降级链提示用户
- **THEN** 应用 SHALL 退出
### Requirement: 静态文件服务
系统 SHALL 通过 Gin 同时服务 API、协议代理和前端静态资源。
@@ -126,19 +265,28 @@ TBD - 提供跨平台桌面应用支持,将后端服务与前端静态资源
### Requirement: 端口冲突检测
系统 SHALL 在启动前检测启动配置中的 `server.port` 是否可用
系统 SHALL 在启动时获取启动配置中的 `server.port` 对应监听端口,并使用同一个 listener 启动后端服务,避免端口预检测与实际监听之间的竞态
#### Scenario: 配置端口可用
- **WHEN** 启动配置中的 `server.port` 未被占用
- **THEN** 服务正常启动
- **THEN** 系统 SHALL 成功创建该端口的 listener
- **THEN** 后端服务 SHALL 使用该 listener 正常启动
#### Scenario: 配置端口被占用
- **WHEN** 启动配置中的 `server.port` 已被其他程序占用
- **THEN** 显示错误提示"端口 <server.port> 已被占用"
- **THEN** 系统 SHALL 生成 `port` 阶段启动错误
- **THEN** 错误提示 SHALL 包含"端口 <server.port> 已被占用"
- **AND** 应用退出
#### Scenario: 单实例优先于端口监听
- **WHEN** 用户尝试启动第二个 desktop 实例
- **THEN** 系统 SHALL 优先检测到已有实例持有文件锁
- **THEN** 系统 SHALL 显示"已有 Nex 实例运行"相关错误提示
- **THEN** 系统 SHALL NOT 将该场景误报为端口占用
### Requirement: 桌面配置源隔离和启动快照
desktop SHALL 仅在启动时从默认配置文件 `~/.nex/config.yaml` 和默认值加载运行时配置,并将加载结果作为本次运行的 immutable runtime snapshot。
@@ -312,74 +460,52 @@ desktop 内嵌前端 SHALL 使用同源相对路径访问后端 API不主动
### Requirement: Windows 原生对话框
系统 SHALL 在 Windows 上使用 `user32.dll``MessageBoxW` API 显示错误对话框,替代 `msg *` 命令。
系统 SHALL 在 Windows 上优先使用系统通知提示启动错误,并在通知不可用或失败时使用 `user32.dll``MessageBoxW` API 显示错误对话框,替代 `msg *` 命令。
#### Scenario: Windows 通知优先
- **WHEN** 应用在 Windows 上遇到启动错误(端口占用、配置加载失败等)
- **THEN** 系统 SHALL 优先尝试发送 Windows 系统通知
- **THEN** 通知 SHALL 包含应用名称和错误描述文本
#### Scenario: 错误提示对话框
- **WHEN** 应用在 Windows 上遇到启动错误(端口占用、配置加载失败等)
- **WHEN** Windows 系统通知不可用或返回失败
- **THEN** 使用 `MessageBoxW` 显示模态对话框
- **AND** 对话框标题栏显示应用名称
- **AND** 对话框包含错误描述文本
- **AND** 对话框显示错误图标MB_ICONERROR
- **AND** 对话框 SHALL 尽量置于前台或顶层显示
#### Scenario: Windows UI 提示均失败
- **WHEN** Windows 系统通知和 `MessageBoxW` 均失败
- **THEN** 系统 SHALL 记录提示失败原因
- **THEN** 系统 SHALL 降级输出到可用启动日志
#### Scenario: 非 Windows 平台不受影响
- **WHEN** 应用运行在 macOS 或 Linux 上
- **THEN** 错误对话框仍使用平台原有实现osascript / zenity
### Requirement: Linux 对话框降级策略
系统 SHALL 在 Linux 上按优先级检测并使用可用的对话框工具,确保在不同桌面环境下都能显示对话框。
#### Scenario: zenity 可用
- **WHEN** 系统检测到 `zenity` 命令可用
- **THEN** 使用 zenity 显示 GTK 风格对话框
- **AND** 错误对话框使用 `zenity --error` 命令
- **AND** 信息对话框使用 `zenity --info` 命令
#### Scenario: kdialog 可用
- **WHEN** zenity 不可用且 `kdialog` 命令可用
- **THEN** 使用 kdialog 显示 KDE 风格对话框
- **AND** 错误对话框使用 `kdialog --error` 命令
- **AND** 信息对话框使用 `kdialog --msgbox` 命令
#### Scenario: notify-send 可用
- **WHEN** zenity 和 kdialog 均不可用且 `notify-send` 命令可用
- **THEN** 使用 notify-send 显示系统通知
- **AND** 错误通知使用 `-u critical` 参数
- **AND** 信息通知使用默认参数
#### Scenario: xmessage 可用
- **WHEN** zenity、kdialog、notify-send 均不可用且 `xmessage` 命令可用
- **THEN** 使用 xmessage 显示基础 X11 对话框
- **AND** 对话框居中显示(`-center` 参数)
#### Scenario: 无对话框工具可用
- **WHEN** 所有对话框工具均不可用
- **THEN** 降级到标准错误输出
- **AND** 输出格式为 `错误: <title>: <message>`
#### Scenario: 工具检测缓存
- **WHEN** 应用启动
- **THEN** 系统检测一次可用对话框工具
- **AND** 检测结果缓存在包级变量
- **AND** 后续对话框调用直接使用缓存结果,不重复检测
- **THEN** 错误提示 SHALL 使用对应平台的通知和弹窗降级策略
### Requirement: macOS AppleScript 字符转义
系统 SHALL 对 AppleScript 对话框中的特殊字符进行转义,确保脚本正确执行。
系统 SHALL 对 AppleScript 通知、模态告警和对话框中的特殊字符进行转义,确保脚本正确执行。
#### Scenario: 转义反斜杠
- **WHEN** 对话框消息包含反斜杠字符 `\`
- **WHEN** AppleScript 提示文本包含反斜杠字符 `\`
- **THEN** 转义为 `\\`
#### Scenario: 转义双引号
- **WHEN** 对话框消息包含双引号字符 `"`
- **WHEN** AppleScript 提示文本包含双引号字符 `"`
- **THEN** 转义为 `\"`
#### Scenario: 多行文本处理
- **WHEN** 对话框消息包含换行符 `\n`
- **THEN** AppleScript 正确显示多行文本
- **WHEN** AppleScript 提示文本包含换行符 `\n`
- **THEN** AppleScript 通知或模态告警 SHALL 正确显示多行文本或保留可读换行语义
### Requirement: 桌面应用打包迁移资源

View File

@@ -351,3 +351,34 @@
- **WHEN** 测试配置加载时指定不存在的配置文件路径
- **THEN** SHALL 返回默认配置值,不自动创建配置文件
- **THEN** 测试 SHALL 验证配置文件未被创建
### Requirement: Desktop 启动失败提示测试边界
系统 SHALL 为 desktop 启动失败报告建立 UI 无关测试覆盖,验证启动错误分类、提示通道选择和 fallback 行为,但 SHALL NOT 要求测试真实系统通知、模态弹窗或托盘 UI 可见性。
#### Scenario: 启动错误分类测试
- **WHEN** 运行 desktop 专属测试
- **THEN** 测试 SHALL 覆盖配置、单实例、端口、日志、数据库、迁移、静态资源、HTTP server 和托盘初始化失败的错误分类
- **THEN** 测试 SHALL 验证每类错误包含正确 phase 和用户可读消息
- **THEN** 测试 SHALL 验证敏感信息不会出现在用户提示文本中
#### Scenario: 提示通道选择测试
- **WHEN** 运行跨平台提示逻辑测试
- **THEN** 测试 SHALL 使用 fake runner 或 fake notifier 验证通知、模态弹窗和 stderr/log fallback 的调用顺序
- **THEN** 测试 SHALL 验证命令参数构造、AppleScript 转义、Windows MessageBox flags 和 Linux 工具优先级
- **THEN** 测试 SHALL NOT 调用真实 `osascript``notify-send``zenity``kdialog``xmessage` 或显示真实 `MessageBoxW`
#### Scenario: 托盘 ready/timeout 测试
- **WHEN** 运行托盘启动封装测试
- **THEN** 测试 SHALL 使用 fake systray runner 验证 ready 成功路径
- **THEN** 测试 SHALL 使用 fake systray runner 验证 ready timeout 会返回 `tray` 阶段 fatal 启动错误
- **THEN** 测试 SHALL NOT 要求真实桌面托盘图标出现
#### Scenario: 浏览器打开失败测试
- **WHEN** 测试浏览器自动打开失败
- **THEN** 测试 SHALL 验证该错误被记录为非 fatal warning
- **THEN** 测试 SHALL 验证应用启动流程不会因浏览器打开失败退出