docs: v1.0.0 独立代码审计 - 重新评估(58/100) + 制定v1.1~v1.5优化路线图 + TDD计划
This commit is contained in:
1 parent
c4bf43f58f
commit
60c3d868c5
3 files changed
+777
-875
No files matched your search
+270
-243
@@ -9,296 +9,323 @@
|
||||
| 日期 | 版本 | 评分 | 主要变更 |
|
||||
|------|------|------|----------|
|
||||
| 2026-04-04 | v0.1.0 | 56/100 | 初始骨架 |
|
||||
| 2026-04-04 | v0.2.0-alpha | 62/100 | UI 交互优化 |
|
||||
| 2026-04-05 | v0.2.0 | 82/100 | 功能基本完成 |
|
||||
| 2026-04-05 | v0.3.0 | 88/100 | 加密 + 端口转发 |
|
||||
| 2026-04-05 | v0.4.0 | 92/100 | 会话管理/UI 完善 |
|
||||
| 2026-04-05 | v0.5.0 | 94/100 | SFTP 传输队列优化 |
|
||||
| 2026-04-05 | v0.6.0 | 95/100 | 终端 rz/sz 支持 |
|
||||
| 2026-04-05 | v0.7.0 | 97/100 | 专业连接管理架构 |
|
||||
| 2026-04-05 | v0.8.0 | 98/100 | 架构模块化重构 |
|
||||
| 2026-04-05 | v0.9.0 | 99/100 | P0/P1 全面优化 |
|
||||
| 2026-04-06 | v0.10.0 | 95/100 | v0.3.0 核心功能发布 |
|
||||
| 2026-04-05 | v0.5.0 | 72/100 | SFTP 传输队列优化 |
|
||||
| 2026-04-06 | v0.10.0 | 78/100 | 核心功能实现发布 |
|
||||
| 2026-04-18 | v1.0.0-audit | **58/100** | 独立代码审计(本次) |
|
||||
|
||||
> **评分说明**: 此前评分(95-99)为开发过程自评,缺少客观基准。本次基于 7 维度独立审计重新评估,结果显著低于历史记录。这是正常的——自评往往偏高,独立审计才能暴露真实问题。
|
||||
|
||||
---
|
||||
|
||||
## v0.10.0 v0.3.0-release (2026-04-06)
|
||||
## v1.0.0 独立审计评估 (2026-04-18)
|
||||
|
||||
### 整体评分
|
||||
|
||||
| 维度 | 分数 | 趋势 | 说明 |
|
||||
|------|------|------|------|
|
||||
| **功能完成度** | 95/100 | 📈 | SSH/SFTP/端口转发全部实现 |
|
||||
| **代码质量** | 88/100 | ➡️ | 模块化架构,统一日志 |
|
||||
| **用户体验** | 92/100 | 📈 | 彩色日志,IP高亮,右键菜单 |
|
||||
| **安全合规** | 80/100 | 📈 | AES-256-GCM 加密 |
|
||||
| **测试覆盖** | 30/100 | 📈 | Vitest 框架,38+ 测试用例 |
|
||||
| 维度 | 分数 | 权重 | 加权分 | 说明 |
|
||||
|------|------|------|--------|------|
|
||||
| **功能完成度** | 82/100 | 25% | 20.5 | SSH/SFTP/端口转发主体完成,SFTP 上传为简化实现 |
|
||||
| **代码质量** | 45/100 | 20% | 9.0 | 调试日志残留严重,组件过大,Mock 数据残留 |
|
||||
| **测试覆盖** | 15/100 | 20% | 3.0 | 仅 34 测试用例,核心模块零覆盖 |
|
||||
| **架构设计** | 72/100 | 15% | 10.8 | IPC 分层合理,Store 有耦合,组件拆分不足 |
|
||||
| **安全合规** | 55/100 | 10% | 5.5 | 加密实现优秀,但密码明文传输风险、CSP 不严格 |
|
||||
| **依赖管理** | 50/100 | 5% | 2.5 | Electron v28 过旧,better-sqlite3 严重过时 |
|
||||
| **构建配置** | 75/100 | 5% | 3.75 | 配置合理,dev 脚本跨平台兼容性差 |
|
||||
|
||||
**综合评分**: 95/100 - v0.3.0 核心功能发布
|
||||
**综合评分: 58/100** — 功能原型阶段,距生产可用尚有差距
|
||||
|
||||
### v0.10.0 完成清单
|
||||
### 评分对比
|
||||
|
||||
| 模块 | 功能 | 状态 |
|
||||
```
|
||||
功能完成度 ████████████████████░░░░░ 82% 功能基本齐全
|
||||
代码质量 ██████████░░░░░░░░░░░░░░░ 45% 调试代码残留严重
|
||||
测试覆盖 ███░░░░░░░░░░░░░░░░░░░░░░ 15% 核心模块零测试
|
||||
架构设计 █████████████████░░░░░░░░ 72% IPC 分层好,Store 有耦合
|
||||
安全合规 █████████████░░░░░░░░░░░░ 55% 加密好,传输链路有风险
|
||||
依赖管理 ████████████░░░░░░░░░░░░░ 50% 多个依赖严重过时
|
||||
构建配置 ██████████████████░░░░░░░ 75% 基本合理
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 代码统计
|
||||
|
||||
### 总量
|
||||
|
||||
| 目录 | 文件数 | 代码行数 | 占比 |
|
||||
|------|--------|----------|------|
|
||||
| `electron/` | 17 | 2,862 | 25.3% |
|
||||
| `src/` | 55 | 7,480 | 66.0% |
|
||||
| `shared/` | 5 | 441 | 3.9% |
|
||||
| `test/` | 4 | 475 | 4.2% |
|
||||
| **总计** | **81** | **11,258** | **100%** |
|
||||
|
||||
### 需重点关注的文件
|
||||
|
||||
| 文件 | 行数 | 问题 |
|
||||
|------|------|------|
|
||||
| SSH 终端 | 密码/密钥认证 | ✅ |
|
||||
| SSH 终端 | XTerm.js 终端 | ✅ |
|
||||
| SSH 终端 | 多主题配色 | ✅ |
|
||||
| SSH 终端 | IP 高亮显示 | ✅ |
|
||||
| SFTP | 双栏文件管理 | ✅ |
|
||||
| SFTP | 上传/下载 | ✅ |
|
||||
| SFTP | 传输队列 | ✅ |
|
||||
| 端口转发 | 本地/远程转发 | ✅ |
|
||||
| 端口转发 | SOCKS5 动态代理 | ✅ |
|
||||
| 架构 | IPC 模块化 | ✅ |
|
||||
| 架构 | 统一日志系统 | ✅ |
|
||||
| 架构 | 连接池管理 | ✅ |
|
||||
| `src/components/TerminalView.tsx` | 645 | 🔴 单组件过大,含 30+ console.log |
|
||||
| `electron/services/ssh-service.ts` | 685 | 🔴 核心服务,20+ console.log |
|
||||
| `src/hooks/useSFTP.ts` | 510 | 🟡 Mock 数据残留,上传未实现 |
|
||||
| `src/stores/connection/poolStore.ts` | 402 | 🟡 合理但与 lifecycleStore 耦合 |
|
||||
|
||||
---
|
||||
|
||||
## v0.9.0 P0/P1 优化 (2026-04-05)
|
||||
## SWOT 分析
|
||||
|
||||
### 整体评分
|
||||
### Strengths (优势)
|
||||
- **加密实现专业**: AES-256-GCM + PBKDF2(100K迭代) + 随机 Salt/IV,业界最佳实践
|
||||
- **IPC 架构清晰**: 8 个模块化 IPC Handler,通道常量统一管理
|
||||
- **连接管理设计**: 状态机 + 连接池 + LRU 淘汰 + 保活,架构先进
|
||||
- **错误处理体系**: 统一错误码 + 错误推断 + ErrorBoundary + 高阶包装函数
|
||||
- **共享类型**: `shared/types/` 主进程/渲染进程共用类型
|
||||
|
||||
| 维度 | 分数 | 趋势 | vs v0.8.0 |
|
||||
|------|------|------|------------|
|
||||
| **功能完成度** | 99/100 | 📈 | +1 |
|
||||
| **代码质量** | 90/100 | 📈 | +2 |
|
||||
| **用户体验** | 97/100 | 📈 | +1 |
|
||||
| **安全合规** | 75/100 | ➡️ | +0 |
|
||||
| **测试覆盖** | 25/100 | 📈 | +10 |
|
||||
### Weaknesses (劣势)
|
||||
- **调试代码残留**: `TerminalView.tsx` 有 `const DEBUG = true`,50+ 处彩色调试函数
|
||||
- **测试严重不足**: 11,258 行代码仅 34 个测试用例,估算覆盖率 10-15%
|
||||
- **组件过大**: TerminalView 645 行承担终端初始化、连接、Shell 数据、快照恢复全部逻辑
|
||||
- **依赖过时**: Electron v28(当前v33+)、better-sqlite3 v9(当前v11)、ESLint v8(已停止维护)
|
||||
- **SFTP 上传未完成**: `useSFTP.ts:399` 标注 "Actual upload implementation would go here"
|
||||
|
||||
**综合评分**: 99/100 - P0/P1 全面优化 (↑1)
|
||||
### Opportunities (机会)
|
||||
- **Electron 升级**: v33+ 性能提升 30%+,内存占用更低
|
||||
- **Zustand v5**: 细粒度订阅,减少不必要的重渲染
|
||||
- **XTerm.js 插件生态**: Image addon、Web Links addon 可增强终端功能
|
||||
- **打包优化**: electron-builder + asar 可显著减小安装包体积
|
||||
|
||||
### v0.9.0 完成清单
|
||||
|
||||
| 任务 | 文件 | 状态 |
|
||||
|------|------|------|
|
||||
| 测试框架建立 | `vitest.config.ts` | ✅ |
|
||||
| SFTP 右键菜单 | `SFTPView.tsx` | ✅ 新建文件夹/复制路径/属性 |
|
||||
| WebGL 渲染优化 | `TerminalView.tsx` | ✅ WebglAddon |
|
||||
| 会话导入完善 | `App.tsx` | ✅ JSON + SSH Config |
|
||||
### Threats (威胁)
|
||||
- **安全风险**: 密码可能通过 IPC 明文传输;`sandbox: false` 降低安全性
|
||||
- **竞品压力**: Electerm、Tabby、Termius 等成熟竞品功能更完善
|
||||
- **跨平台兼容**: `dev` 脚本用 `&` 后台运行,Windows 不可用
|
||||
- **依赖锁定**: ssh2、socksv5 版本锁定,存在已知漏洞风险
|
||||
|
||||
---
|
||||
|
||||
## v0.7.0 完成评估 (2026-04-05)
|
||||
## 关键问题清单
|
||||
|
||||
### 整体评分
|
||||
### 🔴 Critical — 必须修复
|
||||
|
||||
| 维度 | 分数 | 趋势 | vs v0.7.0 |
|
||||
|------|------|------|------------|
|
||||
| **功能完成度** | 98/100 | ➡️ | +0 |
|
||||
| **代码质量** | 88/100 | 📈 | +6 |
|
||||
| **用户体验** | 96/100 | ➡️ | +0 |
|
||||
| **安全合规** | 75/100 | ➡️ | +0 |
|
||||
| **测试覆盖** | 15/100 | ➡️ | +0 |
|
||||
|
||||
**综合评分**: 98/100 - 架构模块化重构 (↑1)
|
||||
|
||||
### v0.8.0 架构重构亮点
|
||||
|
||||
| 重构项 | 文件 | 说明 |
|
||||
|--------|------|------|
|
||||
| IPC Handler 分层 | `electron/ipc/*` | 8 个独立文件,按领域分类 |
|
||||
| 共享类型统一 | `shared/types/*` | 统一类型定义,main/renderer 共用 |
|
||||
| 连接池拆分 | `src/stores/connection/*` | poolStore + lifecycleStore 分离 |
|
||||
| SFTP 组件拆分 | `src/components/sftp/*` | 6 个独立子组件 |
|
||||
|
||||
### 专业连接管理功能
|
||||
|
||||
#### 连接状态机
|
||||
|
||||
| 状态 | 说明 | 转换条件 |
|
||||
|------|------|----------|
|
||||
| `disconnected` | 未连接 | 初始/断开状态 |
|
||||
| `connecting` | 连接中 | 正在建立 SSH 连接 |
|
||||
| `connected` | 已连接 | 当前活跃标签 |
|
||||
| `background` | 后台保持 | 切换到其他标签 |
|
||||
| `suspended` | 已挂起 | 空闲 5 分钟超时 |
|
||||
| `error` | 错误 | 连接失败 |
|
||||
|
||||
#### 连接池管理
|
||||
|
||||
| 指标 | 配置 | 实现 |
|
||||
|------|------|------|
|
||||
| 最大连接数 | 10 | ✅ LRU 淘汰 |
|
||||
| 最大后台连接 | 5 | ✅ 自动断开旧连接 |
|
||||
| 空闲超时 | 5 分钟 | ✅ 进入 suspended |
|
||||
| 挂起超时 | 30 分钟 | ✅ 彻底断开 |
|
||||
| 保活间隔 | 60 秒 | ✅ 发送心跳包 |
|
||||
|
||||
#### 终端快照
|
||||
|
||||
| 功能 | 状态 | 说明 |
|
||||
|------|------|------|
|
||||
| 保存缓冲区 | ✅ | 最后 1000 行 |
|
||||
| 恢复光标位置 | ✅ | 准确定位 |
|
||||
| 快速切换 | ✅ | 无黑屏体验 |
|
||||
| 内存优化 | ✅ | 仅后台标签保存 |
|
||||
|
||||
### 功能完成度更新
|
||||
|
||||
| 模块 | 功能点 | 已完成 | 未完成 | 完成率 |
|
||||
|------|--------|--------|--------|--------|
|
||||
| **SSH 连接** | 连接/断开/重连/认证/状态机 | 6 | 0 | 100% |
|
||||
| **连接管理** | 连接池/LRU/保活/超时 | 5 | 0 | 100% |
|
||||
| **终端仿真** | 显示/输入/复制/粘贴/rz/sz/快照 | 7 | 0 | 100% |
|
||||
| **SFTP** | 浏览/上传/下载/队列 | 5 | 0 | 100% |
|
||||
| **端口转发** | 本地/远程/动态 | 3 | 0 | 100% |
|
||||
| **会话管理** | 创建/编辑/删除/复制/分组 | 5 | 0 | 100% |
|
||||
| **多标签管理** | 创建/切换/克隆/关闭/隔离 | 5 | 0 | 100% |
|
||||
| **设置系统** | 界面/持久化/生效 | 4 | 0 | 100% |
|
||||
| **主题系统** | 主题选择/应用 | 2 | 0 | 100% |
|
||||
|
||||
---
|
||||
|
||||
## 当前状态评估
|
||||
|
||||
### 整体评分 (2026-04-05)
|
||||
|
||||
| 维度 | 分数 | 趋势 | vs v0.5.0 |
|
||||
|------|------|------|------------|
|
||||
| **功能完成度** | 97/100 | 📈 | +2 |
|
||||
| **代码质量** | 80/100 | ➡️ | +0 |
|
||||
| **用户体验** | 94/100 | 📈 | +1 |
|
||||
| **安全合规** | 75/100 | ➡️ | +0 |
|
||||
| **测试覆盖** | 15/100 | ➡️ | +0 |
|
||||
|
||||
**综合评分**: 95/100 - 功能完善阶段 (↑1)
|
||||
|
||||
---
|
||||
|
||||
## v0.5.0 完成优化
|
||||
|
||||
### SFTP 传输队列优化
|
||||
|
||||
| 优化项 | 文件 | 状态 |
|
||||
|--------|------|------|
|
||||
| 传输队列显示 | `SFTPView.tsx` | ✅ |
|
||||
| 传输历史记录 | `SFTPView.tsx` | ✅ |
|
||||
| 可收起面板 | `SFTPView.tsx` | ✅ |
|
||||
| 多文件上传队列 | `SFTPView.tsx` | ✅ |
|
||||
| Downloads 目录默认路径 | `SFTPView.tsx` | ✅ |
|
||||
| 跨平台路径支持 | `preload.ts`, `main.ts` | ✅ |
|
||||
|
||||
### UI/UX 优化
|
||||
|
||||
| 优化项 | 文件 | 状态 |
|
||||
|--------|------|------|
|
||||
| SFTP 传输队列面板 | `SFTPView.tsx` | ✅ |
|
||||
| 移除 SFTP 删除按钮 | `SFTPView.tsx` | ✅ |
|
||||
|
||||
### 终端 rz/sz 支持
|
||||
|
||||
| 优化项 | 文件 | 状态 |
|
||||
|--------|------|------|
|
||||
| rz 命令拦截 | `TerminalView.tsx` | ✅ |
|
||||
| sz 文件接收 | `TerminalView.tsx` | ✅ |
|
||||
| 文件读写 API | `preload.ts`, `main.ts` | ✅ |
|
||||
|
||||
### 功能完成度评估
|
||||
|
||||
| 模块 | 功能点 | 已完成 | 未完成 | 完成率 |
|
||||
|------|--------|--------|--------|--------|
|
||||
| **SSH 连接** | 连接/断开/重连/认证 | 4 | 0 | 100% |
|
||||
| **终端仿真** | 显示/输入/复制/粘贴/rz/sz | 6 | 0 | 100% |
|
||||
| **SFTP** | 浏览/上传/下载/队列 | 5 | 0 | 100% |
|
||||
| **端口转发** | 本地/远程/动态 | 3 | 0 | 100% |
|
||||
| **会话管理** | 创建/编辑/删除/复制/分组 | 5 | 0 | 100% |
|
||||
| **设置系统** | 界面/持久化/生效 | 3 | 0 | 100% |
|
||||
| **主题系统** | 主题选择/应用 | 2 | 0 | 100% |
|
||||
| **加密存储** | AES-256/密钥环 | 1 | 1 | 50% |
|
||||
|
||||
---
|
||||
|
||||
## 待完成问题清单
|
||||
|
||||
#### 🟡 Minor (可优化)
|
||||
|
||||
| ID | 问题 | 模块 | 状态 |
|
||||
| ID | 问题 | 位置 | 影响 |
|
||||
|----|------|------|------|
|
||||
| m-1 | 文件列表右键菜单 | SFTP | 待实现 |
|
||||
| m-2 | 全局快捷键 | 快捷键 | 待优化 |
|
||||
| m-3 | WebGL 渲染优化 | 终端 | 待优化 |
|
||||
| C-1 | 生产代码中 `DEBUG = true` + 50+ 调试函数 | `TerminalView.tsx:16` | 控制台信息泄露、性能损耗 |
|
||||
| C-2 | 核心模块零测试(SSH/加密/IPC) | `electron/services/*` | 重构/修改无安全网 |
|
||||
| C-3 | SFTP 上传功能为占位实现 | `useSFTP.ts:399` | 核心功能不可用 |
|
||||
| C-4 | 密码可能明文通过 IPC 传输 | `preload.ts`, `session.types.ts` | 安全漏洞 |
|
||||
| C-5 | Mock 数据残留生产代码 | `useSFTP.ts:154-161` | 生产环境显示假文件 |
|
||||
|
||||
#### ✅ 已解决问题 (v0.7.0)
|
||||
### 🟡 Warning — 建议修复
|
||||
|
||||
| ID | 问题 | 模块 | 解决方案 |
|
||||
|----|------|------|----------|
|
||||
| f-1 | 多标签连接隔离 | 连接管理 | 独立 connectionInstance |
|
||||
| f-2 | 标签切换黑屏 | 终端 | 快照保存/恢复机制 |
|
||||
| f-3 | 连接无法复用 | 连接池 | LRU + 引用计数 |
|
||||
| f-4 | 连接资源泄漏 | 连接池 | 空闲/挂起超时机制 |
|
||||
| ID | 问题 | 位置 | 影响 |
|
||||
|----|------|------|------|
|
||||
| W-1 | TerminalView 645 行,职责过多 | `TerminalView.tsx` | 可维护性差 |
|
||||
| W-2 | Store 间直接耦合 | `lifecycleStore.ts:39` | 改一处动全局 |
|
||||
| W-3 | Electron v28 严重过时 | `package.json` | 性能和安全更新缺失 |
|
||||
| W-4 | better-sqlite3 v9 过时 | `package.json` | 功能和性能缺失 |
|
||||
| W-5 | react-router-dom 未使用 | `package.json` | 无用依赖增大包体积 |
|
||||
| W-6 | IPC 通道名硬编码 | `preload.ts` | 未引用 IPC_CHANNELS 常量 |
|
||||
| W-7 | `Math.random()` 生成 ID | `TerminalView.tsx:383` | 非唯一,应用 uuid |
|
||||
| W-8 | disconnect() 几乎空实现 | `useSFTP.ts:359-373` | 资源不释放 |
|
||||
| W-9 | CSP 允许 unsafe-inline | `index.html:7` | 削弱 XSS 防护 |
|
||||
| W-10 | 主进程 tsconfig 无未使用检查 | `electron/tsconfig.json` | 坏代码无编译警告 |
|
||||
|
||||
### 🟢 Good — 做得好的
|
||||
|
||||
| ID | 实践 | 位置 |
|
||||
|----|------|------|
|
||||
| G-1 | AES-256-GCM 加密 + PBKDF2 密钥派生 | `crypto.ts` |
|
||||
| G-2 | IPC Handler 按领域分 8 个模块 | `electron/ipc/*` |
|
||||
| G-3 | 共享类型定义 main/renderer 共用 | `shared/types/*` |
|
||||
| G-4 | contextIsolation: true, nodeIntegration: false | `main.ts` |
|
||||
| G-5 | 统一错误码系统 + 错误推断 | `error-handler.ts` |
|
||||
| G-6 | 连接状态机 6 状态 + LRU 淘汰 | `connectionPool.ts` |
|
||||
|
||||
---
|
||||
|
||||
## 安全合规评估
|
||||
## 依赖评估
|
||||
|
||||
| ID | 问题 | 严重性 | 状态 |
|
||||
|----|------|--------|------|
|
||||
| S-1 | 密码明文存储在 SQLite | 🔴 Critical | ✅ 已修复 (AES-256-GCM) |
|
||||
| S-2 | SSH 私钥内容未加密 | 🔴 Critical | 🟡 待实现 (路径不敏感) |
|
||||
| S-3 | `sandbox: false` | 🟠 Major | 待优化 |
|
||||
| S-4 | `contextIsolation: true` | ✅ 已配置 | - |
|
||||
|
||||
### 安全目标 (v1.0.0)
|
||||
|
||||
- [x] 实现 AES-256-GCM 加密
|
||||
- [x] 主密钥自动管理
|
||||
- [ ] 集成系统密钥环 (keytar)
|
||||
- [ ] 启用 sandbox 模式
|
||||
| 依赖 | 当前 | 最新 | 状态 | 风险 |
|
||||
|------|------|------|------|------|
|
||||
| `electron` | ^28.3.3 | v33+ | 🔴 严重过时 | 性能、安全、API 兼容 |
|
||||
| `better-sqlite3` | ^9.4.0 | v11.x | 🟡 过时 | 性能改进 |
|
||||
| `zustand` | ^4.4.7 | v5.x | 🟡 非最新 | 细粒度订阅改进 |
|
||||
| `lucide-react` | ^0.294.0 | 0.4xx | 🟡 过时 | 图标缺失 |
|
||||
| `socksv5` | ^0.0.6 | — | 🟡 不稳定 | 0.0.x 版本 |
|
||||
| `react-router-dom` | ^6.21.0 | 6.x | 🟢 未使用 | 死依赖 |
|
||||
| `eslint` | ^8.55.0 | v9.x | 🟡 已停维 | flat config 迁移 |
|
||||
| `react` / `react-dom` | ^18.2.0 | 18.3.x | 🟢 稳定 | — |
|
||||
| `@xterm/xterm` | ^5.5.0 | 5.5.x | 🟢 最新 | — |
|
||||
| `ssh2` | ^1.15.0 | 1.15.x | 🟢 稳定 | — |
|
||||
|
||||
---
|
||||
|
||||
## 测试策略
|
||||
## 测试评估
|
||||
|
||||
### 测试覆盖率目标
|
||||
### 现状
|
||||
|
||||
| 版本 | 单元测试 | 集成测试 | E2E |
|
||||
|------|----------|----------|-----|
|
||||
| v0.5.0 | 20% | 10% | 5% |
|
||||
| v1.0.0 | 60% | 30% | 15% |
|
||||
| 测试文件 | 用例数 | 覆盖模块 |
|
||||
|----------|--------|----------|
|
||||
| `test/stores/tabStore.test.ts` | 15 | tabStore(完整) |
|
||||
| `test/stores/connectionPool.test.ts` | 7 | poolStore(仅接口验证) |
|
||||
| `test/components/sftp/utils.test.ts` | 12 | sftp/utils.ts(完整) |
|
||||
| **总计** | **34** | — |
|
||||
|
||||
### 未覆盖的关键模块
|
||||
|
||||
| 模块 | 代码行数 | 优先级 | 说明 |
|
||||
|------|----------|--------|------|
|
||||
| `ssh-service.ts` | 685 | P0 | SSH 连接/Shell/端口转发核心 |
|
||||
| `crypto.ts` | ~100 | P0 | 加密/解密必须验证正确性 |
|
||||
| `database-service.ts` | 310 | P1 | 数据持久化 |
|
||||
| `TerminalView.tsx` | 645 | P1 | 核心终端组件 |
|
||||
| `useSFTP.ts` | 510 | P1 | SFTP 全部操作 |
|
||||
| `ipc/*.ts` (8个) | ~600 | P1 | IPC 通道处理 |
|
||||
| `error-handler.ts` | 330 | P2 | 错误处理工具 |
|
||||
| `poolStore.ts` | 402 | P2 | 连接池逻辑 |
|
||||
| `lifecycleStore.ts` | 235 | P2 | 生命周期管理 |
|
||||
|
||||
**估算覆盖率: ~10-15%**(vitest.config.ts 设定 50% 阈值,实际远未达到)
|
||||
|
||||
---
|
||||
|
||||
## 安全评估
|
||||
|
||||
### 加密实现 ✅ 优秀
|
||||
|
||||
| 评估项 | 状态 | 说明 |
|
||||
|--------|------|------|
|
||||
| AES-256-GCM | ✅ | 认证加密,防篡改 |
|
||||
| PBKDF2 密钥派生 | ✅ | 100,000 迭代 |
|
||||
| 随机 Salt + IV | ✅ | 每次加密独立 |
|
||||
| Master Key 文件权限 | ✅ | 0o600 |
|
||||
| Auth Tag 验证 | ✅ | 解密时校验完整性 |
|
||||
|
||||
### 安全隐患 ⚠️
|
||||
|
||||
| 问题 | 严重性 | 说明 |
|
||||
|------|--------|------|
|
||||
| 密码可能明文通过 IPC | 🔴 | Session 类型含 password 字段,需确认传输时是否加密 |
|
||||
| SSH 私钥通过 IPC 明文 | 🟡 | `sshConnect(config: any)` 私钥内容明文传递 |
|
||||
| `sandbox: false` | 🟡 | preload 未启用沙箱 |
|
||||
| CSP `unsafe-inline` | 🟡 | 削弱 XSS 防护 |
|
||||
| IPC 无输入验证 | 🟡 | 所有 IPC 调用参数无 schema 验证 |
|
||||
|
||||
---
|
||||
|
||||
## 性能指标
|
||||
|
||||
| 指标 | 当前值 | 目标值 | 说明 |
|
||||
| 指标 | 当前值 | 目标值 | 达标 |
|
||||
|------|--------|--------|------|
|
||||
| **冷启动时间** | ~3s | <2s | - |
|
||||
| **SSH 连接速度** | ~1s | <500ms | - |
|
||||
| **终端输入延迟** | <16ms | <10ms | - |
|
||||
| **内存占用** | ~200MB | <150MB | 10 标签并发 |
|
||||
| **标签切换延迟** | <50ms | <100ms | ✅ 达标 |
|
||||
| **后台连接保持** | 5 个 | - | ✅ 专业功能 |
|
||||
| **连接复用率** | >80% | - | 相同配置 |
|
||||
| **包体积** | ~120MB | <100MB | - |
|
||||
| 冷启动时间 | ~3s | <2s | ❌ |
|
||||
| SSH 连接速度 | ~1s | <500ms | ❌ |
|
||||
| 终端输入延迟 | <16ms | <10ms | ✅ |
|
||||
| 内存占用(10标签) | ~200MB | <150MB | ❌ |
|
||||
| 标签切换延迟 | <50ms | <100ms | ✅ |
|
||||
| 包体积 | ~120MB | <100MB | ❌ |
|
||||
|
||||
---
|
||||
|
||||
## 下一步计划
|
||||
## 优化方向与目标
|
||||
|
||||
### v1.0.0 Stable 完成条件
|
||||
### v1.1.0 — 代码质量清理 (预计 1 周)
|
||||
|
||||
| 优先级 | 功能 | 状态 |
|
||||
|--------|------|------|
|
||||
| P0 | 测试框架建立 | 🔴 待开始 |
|
||||
| P1 | 文件列表右键菜单 | 🟡 待实现 |
|
||||
| P1 | WebGL 渲染优化 | 🟡 待优化 |
|
||||
| P2 | sandbox 模式 | 🟡 待优化 |
|
||||
**目标**: 清除生产代码中的调试残留,提升代码质量评分至 70+
|
||||
|
||||
### v0.7.0 架构亮点
|
||||
| 任务 | 优先级 | 影响文件 | 预计工时 |
|
||||
|------|--------|----------|----------|
|
||||
| 移除所有 console.log / 调试函数 | P0 | `TerminalView.tsx`, `useSFTP.ts`, `ssh-service.ts` | 4h |
|
||||
| 移除 `DEBUG = true` 硬编码 | P0 | `TerminalView.tsx:16` | 0.5h |
|
||||
| 移除 Mock 数据 | P0 | `useSFTP.ts:154-161` | 1h |
|
||||
| 补全 SFTP 上传功能 | P0 | `useSFTP.ts` | 4h |
|
||||
| 补全 disconnect() 资源释放 | P1 | `useSFTP.ts:359-373` | 2h |
|
||||
| Math.random → uuid | P1 | `TerminalView.tsx` | 1h |
|
||||
| IPC 通道名引用常量 | P2 | `preload.ts` | 2h |
|
||||
| 移除未使用依赖 react-router-dom | P2 | `package.json` | 0.5h |
|
||||
|
||||
**验收标准**:
|
||||
- `grep -r "console.log" src/ electron/` 返回 0 结果
|
||||
- `grep -r "DEBUG = true" src/` 返回 0 结果
|
||||
- SFTP 上传功能端到端可用
|
||||
|
||||
### v1.2.0 — 组件拆分重构 (预计 1.5 周)
|
||||
|
||||
**目标**: TerminalView 拆分为可维护的模块,架构评分提升至 80+
|
||||
|
||||
| 任务 | 优先级 | 说明 |
|
||||
|------|--------|------|
|
||||
| TerminalView → useTerminal Hook | P0 | 提取连接管理、Shell 数据处理 |
|
||||
| TerminalView → useTerminalSnapshot Hook | P0 | 提取快照保存/恢复逻辑 |
|
||||
| TerminalView → useZmodem Hook | P1 | 提取 rz/sz 文件传输逻辑 |
|
||||
| useSFTP → useSFTPNavigation + useSFTPTransfer | P1 | 分离导航和传输职责 |
|
||||
| App.tsx → 导入/导出逻辑抽到 service | P2 | 分离业务逻辑 |
|
||||
| Store 解耦 | P2 | lifecycleStore 不直接操作 poolStore |
|
||||
|
||||
**验收标准**:
|
||||
- 单文件不超过 300 行
|
||||
- Hook 职责单一,可独立测试
|
||||
|
||||
### v1.3.0 — 测试覆盖达标 (预计 2 周)
|
||||
|
||||
**目标**: 核心模块测试覆盖率 60%+,测试评分提升至 50+
|
||||
|
||||
| 阶段 | 目标模块 | 用例目标 |
|
||||
|------|----------|----------|
|
||||
| 第一批(P0) | `crypto.ts`, `ssh-service.ts`(mock ssh2) | 30+ |
|
||||
| 第二批(P1) | `database-service.ts`, `error-handler.ts`, `poolStore.ts` | 40+ |
|
||||
| 第三批(P1) | `IPC handlers`(8个), `TerminalView`(组件测试) | 50+ |
|
||||
| 第四批(P2) | `useSFTP`, `lifecycleStore`, `tabStore`补充 | 30+ |
|
||||
|
||||
**验收标准**:
|
||||
- `npm test -- --coverage` 总覆盖率 ≥ 50%
|
||||
- 核心模块(crypto, ssh-service) 覆盖率 ≥ 80%
|
||||
|
||||
### v1.4.0 — 依赖升级 + 安全加固 (预计 1 周)
|
||||
|
||||
**目标**: 依赖更新到稳定版本,安全评分提升至 75+
|
||||
|
||||
| 任务 | 优先级 | 风险 |
|
||||
|------|--------|------|
|
||||
| Electron v28 → v33+ | P0 | Breaking changes 需逐一适配 |
|
||||
| better-sqlite3 v9 → v11 | P0 | API 有变化 |
|
||||
| ESLint v8 → v9 flat config | P1 | 配置迁移 |
|
||||
| Zustand v4 → v5 | P2 | 订阅 API 变化 |
|
||||
| 启用 sandbox 模式 | P1 | preload 需适配 |
|
||||
| CSP 移除 unsafe-inline | P1 | Tailwind 需配置 |
|
||||
| IPC 参数 schema 验证 | P2 | 引入 zod 或类似库 |
|
||||
|
||||
### v1.5.0 — 功能补全 + 性能优化 (预计 2 周)
|
||||
|
||||
**目标**: 补全缺失功能,性能指标全面达标
|
||||
|
||||
| 任务 | 优先级 | 说明 |
|
||||
|------|--------|------|
|
||||
| SFTP 拖拽上传/下载 | P1 | 界面就绪,补全事件处理 |
|
||||
| SFTP 断点续传 | P2 | 大文件传输可靠性 |
|
||||
| 会话分组树形显示 | P2 | 侧边栏优化 |
|
||||
| 终端分屏 | P3 | 水平/垂直分屏 |
|
||||
| 安装包体积优化 | P1 | asar + tree-shaking + 依赖瘦身 |
|
||||
| 冷启动优化 | P1 | 延迟加载非核心模块 |
|
||||
|
||||
---
|
||||
|
||||
## 里程碑总览
|
||||
|
||||
```
|
||||
专业级连接管理
|
||||
├── 连接状态机 (6 个状态)
|
||||
├── LRU 淘汰策略
|
||||
├── 终端快照机制
|
||||
├── 自动保活 & 超时
|
||||
└── 连接复用优化
|
||||
v1.1.0 代码质量清理 (1 周) → 代码质量 45→70
|
||||
v1.2.0 组件拆分重构 (1.5 周) → 架构 72→85
|
||||
v1.3.0 测试覆盖达标 (2 周) → 测试 15→50
|
||||
v1.4.0 依赖升级+安全 (1 周) → 安全 55→75
|
||||
v1.5.0 功能补全+性能 (2 周) → 功能 82→95
|
||||
|
||||
设计原则
|
||||
├── 资源隔离:每个标签独立连接实例
|
||||
├── 性能优化:快照避免重复渲染
|
||||
├── 资源控制:连接池上限管理
|
||||
└── 用户体验:无缝标签切换
|
||||
总计: ~7.5 周 → 综合评分 58→80+
|
||||
```
|
||||
|
||||
### 成功指标
|
||||
|
||||
| 指标 | v1.0 当前 | v1.5 目标 |
|
||||
|------|-----------|-----------|
|
||||
| 综合评分 | 58/100 | 80+/100 |
|
||||
| 测试覆盖率 | ~10% | ≥50% |
|
||||
| 代码质量(零console.log) | 50+ 处 | 0 处 |
|
||||
| 单文件最大行数 | 685 | ≤300 |
|
||||
| Electron 版本 | v28 | v33+ |
|
||||
| 安全评分 | 55 | 75+ |
|
||||
| 包体积 | ~120MB | <100MB |
|
||||
+198
-171
@@ -19,250 +19,277 @@
|
||||
| v0.9.0 | **Optimization** | P0/P1 全面优化 | ✅ 已完成 |
|
||||
| v0.10.0 | **v0.3.0-release** | 核心功能实现 | ✅ 已完成 |
|
||||
| v1.0.0 | **Stable** | 生产可用 | ✅ 已完成 |
|
||||
| **v1.1.0** | **Cleanup** | **代码质量清理** | 🔄 进行中 |
|
||||
| **v1.2.0** | **Refactor** | **组件拆分重构** | 📋 规划中 |
|
||||
| **v1.3.0** | **Testing** | **测试覆盖达标** | 📋 规划中 |
|
||||
| **v1.4.0** | **Upgrade** | **依赖升级+安全加固** | 📋 规划中 |
|
||||
| **v1.5.0** | **Polish** | **功能补全+性能优化** | 📋 规划中 |
|
||||
| v2.0.0 | **Professional** | 专业版发布 | 📋 远期规划 |
|
||||
|
||||
---
|
||||
|
||||
## v0.5.0 Pre-Stable ✅
|
||||
## v1.1.0 代码质量清理 🔄
|
||||
|
||||
**完成日期**: 2026-04-05
|
||||
**目标**: 清除生产代码中的调试残留,补全 SFTP 核心功能
|
||||
**预计**: 1 周
|
||||
**评分目标**: 代码质量 45→70
|
||||
|
||||
### ✅ 已完成功能清单
|
||||
### 任务清单
|
||||
|
||||
#### SFTP 传输队列优化
|
||||
| 功能 | 文件 | 说明 |
|
||||
|------|------|------|
|
||||
| 传输队列显示 | `SFTPView.tsx` | 显示进行中的上传/下载任务 |
|
||||
| 传输历史记录 | `SFTPView.tsx` | 显示最近完成的传输任务 |
|
||||
| 可收起面板 | `SFTPView.tsx` | 点击展开/收起传输面板 |
|
||||
| 多文件上传队列 | `SFTPView.tsx` | 逐个上传并显示各任务进度 |
|
||||
| Downloads 目录 | `SFTPView.tsx` | 跨平台默认下载路径 |
|
||||
| 移除删除按钮 | `SFTPView.tsx` | 简化界面 |
|
||||
| # | 任务 | 优先级 | 文件 | 状态 |
|
||||
|---|------|--------|------|------|
|
||||
| 1 | 移除所有 console.log 和彩色调试函数 | P0 | `TerminalView.tsx`, `useSFTP.ts`, `ssh-service.ts` | ⬜ |
|
||||
| 2 | 移除 `DEBUG = true` 硬编码 | P0 | `TerminalView.tsx:16` | ⬜ |
|
||||
| 3 | 移除 Mock 数据残留 | P0 | `useSFTP.ts:154-161` | ⬜ |
|
||||
| 4 | 补全 SFTP 上传 IPC 实现 | P0 | `useSFTP.ts:399`, `sftp.ipc.ts` | ⬜ |
|
||||
| 5 | 补全 disconnect() 资源释放 | P1 | `useSFTP.ts:359-373` | ⬜ |
|
||||
| 6 | Math.random → uuid 生成 ID | P1 | `TerminalView.tsx:383,463` | ⬜ |
|
||||
| 7 | IPC 通道名引用 IPC_CHANNELS 常量 | P2 | `preload.ts` | ⬜ |
|
||||
| 8 | 移除未使用依赖 react-router-dom | P2 | `package.json` | ⬜ |
|
||||
| 9 | 替换重复的彩色日志函数为统一 logger | P2 | `TerminalView.tsx`, `useSFTP.ts` | ⬜ |
|
||||
| 10 | 修复 `if (false)` 等无用代码 | P2 | `TerminalView.tsx:50` | ⬜ |
|
||||
|
||||
### 验收标准
|
||||
|
||||
```bash
|
||||
# 零调试残留
|
||||
grep -r "console.log" src/ electron/ | wc -l # → 0
|
||||
grep -r "DEBUG = true" src/ | wc -l # → 0
|
||||
grep -r "logHeader\|logInfo\|logSuccess\|logError\|logWarning" src/ electron/ | wc -l # → 0
|
||||
|
||||
# SFTP 上传功能可用
|
||||
# 手动测试:连接 → SFTP → 上传文件 → 验证远端文件存在
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## v0.6.0 Pre-Stable ✅
|
||||
## v1.2.0 组件拆分重构 📋
|
||||
|
||||
**完成日期**: 2026-04-05
|
||||
**目标**: 终端组件拆分为可维护的 Hook 体系
|
||||
**预计**: 1.5 周
|
||||
**评分目标**: 架构 72→85
|
||||
|
||||
### ✅ 已完成功能清单
|
||||
### 拆分计划
|
||||
|
||||
#### 终端 rz/sz 支持
|
||||
| 功能 | 文件 | 说明 |
|
||||
|------|------|------|
|
||||
| rz 命令拦截 | `TerminalView.tsx` | 检测 rz 并打开文件选择器 |
|
||||
| sz 文件接收 | `TerminalView.tsx` | 捕获 Zmodem 数据并保存 |
|
||||
| 文件读写 API | `preload.ts`, `main.ts` | 支持 rz 上传和 sz 下载 |
|
||||
#### TerminalView.tsx (645行) → 拆分
|
||||
|
||||
```
|
||||
src/components/terminal/TerminalView.tsx (~150行) 纯渲染层
|
||||
src/hooks/useTerminal.ts (~200行) 终端初始化 + 数据流
|
||||
src/hooks/useTerminalConnection.ts (~150行) SSH 连接管理
|
||||
src/hooks/useTerminalSnapshot.ts (~100行) 快照保存/恢复
|
||||
src/hooks/useZmodem.ts (~100行) rz/sz 文件传输
|
||||
```
|
||||
|
||||
#### useSFTP.ts (510行) → 拆分
|
||||
|
||||
```
|
||||
src/hooks/useSFTPNavigation.ts (~200行) 本地/远程目录导航
|
||||
src/hooks/useSFTPTransfer.ts (~200行) 上传/下载/传输队列
|
||||
src/hooks/useSFTPConnection.ts (~100行) SFTP 连接管理
|
||||
```
|
||||
|
||||
#### App.tsx (258行) → 业务逻辑外提
|
||||
|
||||
```
|
||||
src/components/App.tsx (~150行) 纯 UI 编排
|
||||
src/services/sessionImport.ts (~80行) 导入/导出逻辑
|
||||
```
|
||||
|
||||
### Store 解耦
|
||||
|
||||
```
|
||||
当前: lifecycleStore → 直接调用 poolStore.getState()
|
||||
目标: 引入 ConnectionFacade 统一接口,Store 通过事件通信
|
||||
```
|
||||
|
||||
### 验收标准
|
||||
|
||||
- 单文件不超过 300 行
|
||||
- Hook 可独立导入、独立测试
|
||||
- 现有功能无回归
|
||||
|
||||
---
|
||||
|
||||
## v0.7.0 Professional ✅
|
||||
## v1.3.0 测试覆盖达标 📋
|
||||
|
||||
**完成日期**: 2026-04-05
|
||||
**目标**: 核心模块测试覆盖率 ≥60%,总覆盖率 ≥50%
|
||||
**预计**: 2 周
|
||||
**评分目标**: 测试 15→50
|
||||
|
||||
### ✅ 已完成功能清单
|
||||
### 测试批次
|
||||
|
||||
#### 专业连接管理架构
|
||||
| 批次 | 目标模块 | 用例目标 | 优先级 |
|
||||
|------|----------|----------|--------|
|
||||
| 1 | `crypto.ts` (加密/解密正确性) | 15+ | P0 |
|
||||
| 2 | `ssh-service.ts` (mock ssh2) | 20+ | P0 |
|
||||
| 3 | `database-service.ts` (CRUD) | 15+ | P1 |
|
||||
| 4 | `error-handler.ts` (错误推断) | 10+ | P1 |
|
||||
| 5 | `poolStore.ts` + `lifecycleStore.ts` | 20+ | P1 |
|
||||
| 6 | `IPC handlers` (8个模块) | 25+ | P1 |
|
||||
| 7 | `TerminalView` 组件测试 | 10+ | P2 |
|
||||
| 8 | `useSFTP` hooks 测试 | 15+ | P2 |
|
||||
| **总计** | | **130+** | |
|
||||
|
||||
| 功能 | 文件 | 说明 |
|
||||
|------|------|------|
|
||||
| 连接状态机 | `connectionPool.ts` | disconnected/connecting/connected/background/suspended |
|
||||
| 连接池管理 | `connectionPool.ts` | 最大 10 并发,LRU 淘汰策略 |
|
||||
| 后台连接保持 | `connectionPool.ts` | 最大 5 个后台连接,空闲 5 分钟挂起 |
|
||||
| 自动保活机制 | `connectionPool.ts` | 每分钟发送心跳包 |
|
||||
| 连接复用 | `connectionPool.ts` | 相同配置标签共享 SSH 连接 |
|
||||
| 终端快照 | `terminalSnapshotStore.ts` | 切换标签保存/恢复终端内容 |
|
||||
| 快照恢复 | `TerminalView.tsx` | 快速恢复显示,避免黑屏 |
|
||||
| 挂起恢复 UI | `TerminalView.tsx` | 手动恢复或重新连接选项 |
|
||||
| 连接状态指示 | `TerminalView.tsx` | 实时显示连接状态(绿/蓝/黄/红) |
|
||||
| 专业设置面板 | `SettingsView.tsx` | 可配置连接池参数 |
|
||||
### 验收标准
|
||||
|
||||
#### 多标签连接隔离
|
||||
|
||||
| 功能 | 文件 | 说明 |
|
||||
|------|------|------|
|
||||
| 独立连接实例 | `tabStore.ts` | 每个标签独立的 connectionInstance |
|
||||
| 标签切换优化 | `TabContent.tsx` | opacity 过渡,保持组件实例 |
|
||||
| 连接引用计数 | `connectionPool.ts` | 正确管理连接生命周期 |
|
||||
| 关闭自动断开 | `TabBar.tsx` | 标签关闭时释放对应连接 |
|
||||
```bash
|
||||
npm test -- --coverage
|
||||
# Statements ≥ 50%
|
||||
# Branches ≥ 40%
|
||||
# Functions ≥ 50%
|
||||
# Lines ≥ 50%
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## v0.8.0 Modular ✅
|
||||
## v1.4.0 依赖升级 + 安全加固 📋
|
||||
|
||||
**完成日期**: 2026-04-05
|
||||
**目标**: 依赖更新到最新稳定版,安全漏洞清零
|
||||
**预计**: 1 周
|
||||
**评分目标**: 安全 55→75, 依赖 50→80
|
||||
|
||||
### ✅ 已完成架构重构清单
|
||||
### 依赖升级
|
||||
|
||||
#### IPC Handler 分层
|
||||
| 依赖 | 当前 | 目标 | 风险 |
|
||||
|------|------|------|------|
|
||||
| `electron` | v28 | v33+ | 高 - Breaking changes |
|
||||
| `better-sqlite3` | v9 | v11 | 中 - API 变化 |
|
||||
| `zustand` | v4 | v5 | 低 - 订阅 API 变化 |
|
||||
| `lucide-react` | 0.294 | 0.4xx | 低 - 图标名可能有变化 |
|
||||
| `eslint` | v8 | v9 | 中 - flat config 迁移 |
|
||||
|
||||
| 模块 | 文件 | 说明 |
|
||||
|------|------|------|
|
||||
| SSH IPC | `electron/ipc/ssh.ipc.ts` | 连接/断开/exec/Shell/端口转发 |
|
||||
| SFTP IPC | `electron/ipc/sftp.ipc.ts` | SFTP 操作/传输进度 |
|
||||
| 会话 IPC | `electron/ipc/session.ipc.ts` | CRUD 操作 |
|
||||
| 设置 IPC | `electron/ipc/settings.ipc.ts` | 读写设置 |
|
||||
| 对话框 IPC | `electron/ipc/dialog.ipc.ts` | 文件对话框 |
|
||||
| 文件 IPC | `electron/ipc/file.ipc.ts` | 本地文件操作 |
|
||||
| Zmodem IPC | `electron/ipc/zmodem.ipc.ts` | rz/sz 传输 |
|
||||
| 应用 IPC | `electron/ipc/app.ipc.ts` | 版本/路径信息 |
|
||||
### 安全加固
|
||||
|
||||
#### 共享类型统一
|
||||
|
||||
| 类型 | 文件 | 说明 |
|
||||
|------|------|------|
|
||||
| Session 类型 | `shared/types/session.types.ts` | 会话配置、认证类型 |
|
||||
| Connection 类型 | `shared/types/connection.types.ts` | 连接池、连接状态 |
|
||||
| SFTP 类型 | `shared/types/sftp.types.ts` | 文件项、传输任务 |
|
||||
| IPC 类型 | `shared/types/ipc.types.ts` | IPC 通道常量 |
|
||||
|
||||
#### 连接池 Store 拆分
|
||||
|
||||
| Store | 文件 | 说明 |
|
||||
|------|------|------|
|
||||
| Pool Store | `src/stores/connection/poolStore.ts` | 连接池核心:创建/获取/释放 |
|
||||
| Lifecycle Store | `src/stores/connection/lifecycleStore.ts` | 状态机、超时、事件 |
|
||||
| Store Hooks | `src/stores/connection/storeHooks.ts` | 组合接口 |
|
||||
|
||||
#### SFTP 组件拆分
|
||||
|
||||
| 组件 | 文件 | 说明 |
|
||||
|------|------|------|
|
||||
| Breadcrumb | `src/components/sftp/Breadcrumb.tsx` | 路径导航 |
|
||||
| SortHeader | `src/components/sftp/SortHeader.tsx` | 列排序 |
|
||||
| FileRow | `src/components/sftp/FileRow.tsx` | 文件行渲染 |
|
||||
| ContextMenu | `src/components/sftp/ContextMenu.tsx` | 右键菜单 |
|
||||
| TransferItem | `src/components/sftp/TransferItem.tsx` | 传输任务项 |
|
||||
| ConnectionSelector | `src/components/sftp/ConnectionSelector.tsx` | 服务器选择 |
|
||||
| 任务 | 说明 |
|
||||
|------|------|
|
||||
| 启用 sandbox 模式 | preload 适配 |
|
||||
| CSP 移除 unsafe-inline | Tailwind nonce 配置 |
|
||||
| IPC 参数验证 | zod schema |
|
||||
| 密码 IPC 传输审查 | 确保加密后传输 |
|
||||
| 审查 socksv5 安全性 | 评估是否替换 |
|
||||
|
||||
---
|
||||
|
||||
## v0.9.0 Optimization ✅
|
||||
## v1.5.0 功能补全 + 性能优化 📋
|
||||
|
||||
**完成日期**: 2026-04-05
|
||||
**目标**: 补全缺失功能,性能指标全面达标
|
||||
**预计**: 2 周
|
||||
**评分目标**: 功能 82→95, 综合 80+
|
||||
|
||||
### ✅ 已完成功能清单
|
||||
### 功能补全
|
||||
|
||||
#### 测试框架建立
|
||||
| 功能 | 文件 | 说明 |
|
||||
|------|------|------|
|
||||
| Vitest 配置 | `vitest.config.ts` | 测试环境配置 |
|
||||
| 测试设置 | `test/setup.ts` | Electron API mock |
|
||||
| TabStore 测试 | `test/stores/tabStore.test.ts` | 16 个测试用例 |
|
||||
| ConnectionPool 测试 | `test/stores/connectionPool.test.ts` | 8 个测试用例 |
|
||||
| SFTP Utils 测试 | `test/components/sftp/utils.test.ts` | 14 个测试用例 |
|
||||
| 功能 | 优先级 | 说明 |
|
||||
|------|--------|------|
|
||||
| SFTP 拖拽上传/下载 | P1 | 界面已就绪,补全 drag/drop 事件 |
|
||||
| SFTP 断点续传 | P2 | 大文件传输可靠性 |
|
||||
| 会话分组树形显示 | P2 | 侧边栏分组 + 文件夹 |
|
||||
| 终端分屏 | P3 | 水平/垂直分屏 |
|
||||
| 命令片段(Snippets) | P3 | 常用命令快速执行 |
|
||||
| 同步功能 | P3 | 多设备会话同步 |
|
||||
|
||||
#### SFTP 右键菜单完善
|
||||
| 功能 | 文件 | 说明 |
|
||||
|------|------|------|
|
||||
| 新建文件夹 | `SFTPView.tsx` | 本地新建文件夹 |
|
||||
| 复制路径 | `SFTPView.tsx` | 复制文件完整路径 |
|
||||
| 查看属性 | `SFTPView.tsx` | 显示文件详情 |
|
||||
| sftpMkdir IPC | `sftp.ipc.ts` | 远程创建目录支持 |
|
||||
### 性能优化
|
||||
|
||||
#### WebGL 渲染优化
|
||||
| 功能 | 文件 | 说明 |
|
||||
|------|------|------|
|
||||
| WebGL Addon | `TerminalView.tsx` | GPU 加速渲染 |
|
||||
| 自动降级 | `TerminalView.tsx` | WebGL 不可用时降级 |
|
||||
|
||||
#### 会话导入完善
|
||||
| 功能 | 文件 | 说明 |
|
||||
|------|------|------|
|
||||
| JSON 格式导入 | `App.tsx` | 支持数组和对象格式 |
|
||||
| SSH Config 导入 | `App.tsx` | 支持 Host/HostName/User |
|
||||
| 批量创建 | `App.tsx` | 自动创建所有会话 |
|
||||
| 指标 | 当前 | 目标 | 方案 |
|
||||
|------|------|------|------|
|
||||
| 冷启动 | ~3s | <2s | 延迟加载非核心模块 |
|
||||
| 包体积 | ~120MB | <100MB | asar + tree-shaking |
|
||||
| 内存(10标签) | ~200MB | <150MB | 连接池优化 + 终端回收 |
|
||||
| SSH 连接 | ~1s | <500ms | 连接预热 |
|
||||
|
||||
---
|
||||
|
||||
## v0.10.0 v0.3.0-release ✅
|
||||
## 远期规划 (v2.0.0)
|
||||
|
||||
**完成日期**: 2026-04-06
|
||||
### 专业版特性
|
||||
|
||||
### ✅ 已完成功能清单
|
||||
|
||||
#### SSH 终端核心功能
|
||||
| 功能 | 文件 | 说明 |
|
||||
|------|------|------|
|
||||
| 密码认证 | `ssh-service.ts` | 用户名密码连接 |
|
||||
| 密钥认证 | `ssh-service.ts` | RSA/ED25519 私钥认证 |
|
||||
| XTerm.js 终端 | `TerminalView.tsx` | 高性能终端仿真 |
|
||||
| 多主题配色 | `terminal.ts` | Dracula/Monokai/Solarized/One Dark |
|
||||
| IP 高亮显示 | `TerminalView.tsx` | Last login IP 绿色高亮 |
|
||||
| 端口转发 | `PortForwardView.tsx` | 本地/远程/SOCKS5 动态代理 |
|
||||
|
||||
#### SFTP 文件管理
|
||||
| 功能 | 文件 | 说明 |
|
||||
|------|------|------|
|
||||
| 双栏文件管理 | `SFTPView.tsx` | 本地/远程双栏对比 |
|
||||
| 上传下载 | `useSFTP.ts` | 文件传输队列 |
|
||||
| 传输进度 | `TransferPanel.tsx` | 实时进度显示 |
|
||||
| 服务器选择 | `ConnectionSelector.tsx` | 快速选择已保存会话 |
|
||||
|
||||
#### 架构改进
|
||||
| 功能 | 文件 | 说明 |
|
||||
|------|------|------|
|
||||
| 模块化 IPC | `electron/ipc/*.ts` | 8 个独立 IPC 模块 |
|
||||
| 连接池管理 | `poolStore.ts` | 连接复用和生命周期管理 |
|
||||
| 统一日志 | `logger.ts` | 彩色高亮调试日志 |
|
||||
| 错误处理 | `error-handler.ts` | 统一错误边界处理 |
|
||||
- 插件系统 — 支持第三方扩展
|
||||
- 主题编辑器 — 自定义配色方案
|
||||
- 脚本录制/回放 — 操作自动化
|
||||
- 协作共享 — 团队会话管理
|
||||
- 云同步 — 加密会话配置同步
|
||||
- API 网关 — 命令行/CI 集成
|
||||
- 容器管理 — Docker/Podman 集成
|
||||
|
||||
---
|
||||
|
||||
## v1.0.0 Stable - 生产可用 ✅
|
||||
## 已完成版本
|
||||
|
||||
<details>
|
||||
<summary>展开查看历史版本</summary>
|
||||
|
||||
### v1.0.0 Stable - 生产可用 ✅
|
||||
|
||||
**状态**: 已完成
|
||||
|
||||
### 功能完成清单
|
||||
#### 功能完成清单
|
||||
|
||||
#### SSH 终端
|
||||
##### SSH 终端
|
||||
- ✅ 密码/密钥认证
|
||||
- ✅ XTerm.js 高性能终端
|
||||
- ✅ 多主题配色方案
|
||||
- ✅ IP 地址高亮显示
|
||||
|
||||
#### SFTP 管理
|
||||
##### SFTP 管理
|
||||
- ✅ 双栏文件管理器
|
||||
- ✅ 上传/下载文件
|
||||
- ✅ 传输队列管理
|
||||
- 🟡 拖拽上传下载 (界面就绪,待完善)
|
||||
- 📋 断点续传 (规划中)
|
||||
|
||||
#### 端口转发
|
||||
##### 端口转发
|
||||
- ✅ 本地端口转发
|
||||
- ✅ 远程端口转发
|
||||
- ✅ 动态 SOCKS5 代理
|
||||
- ✅ 转发规则管理
|
||||
|
||||
#### 架构特性
|
||||
##### 架构特性
|
||||
- ✅ IPC Handler 模块化
|
||||
- ✅ 共享类型定义
|
||||
- ✅ 连接池 Store 拆分
|
||||
- ✅ 统一日志系统
|
||||
|
||||
---
|
||||
### v0.10.0 v0.3.0-release ✅
|
||||
|
||||
## 开发优先级
|
||||
**完成日期**: 2026-04-06
|
||||
|
||||
```
|
||||
v0.6.0 完成清单 ✅
|
||||
- ✅ SSH 终端核心功能(密码/密钥认证、XTerm.js、多主题、IP高亮、端口转发)
|
||||
- ✅ SFTP 文件管理(双栏、上传下载、传输队列、服务器选择)
|
||||
- ✅ 架构改进(模块化 IPC、连接池管理、统一日志、错误处理)
|
||||
|
||||
v1.0.0 开发顺序:
|
||||
├── 1. 测试框架建立 (P0)
|
||||
├── 2. 文件列表右键菜单 (P1)
|
||||
├── 3. WebGL 渲染优化 (P1)
|
||||
└── 4. 会话导入完整实现 (P1)
|
||||
```
|
||||
### v0.9.0 Optimization ✅
|
||||
|
||||
---
|
||||
**完成日期**: 2026-04-05
|
||||
|
||||
## 里程碑检查点
|
||||
- ✅ 测试框架建立(Vitest + 38 测试用例)
|
||||
- ✅ SFTP 右键菜单(新建文件夹/复制路径/属性)
|
||||
- ✅ WebGL 终端渲染优化
|
||||
- ✅ 会话导入完整实现
|
||||
|
||||
### v1.0.0 Stable 完成条件
|
||||
### v0.8.0 Modular ✅
|
||||
|
||||
- [x] 测试框架建立 (vitest + 38 测试用例)
|
||||
- [x] 文件列表右键菜单 (新建/复制/属性)
|
||||
- [x] WebGL 终端渲染优化
|
||||
- [x] 会话导入完整实现
|
||||
- [ ] 无 critical/major 级别 bug
|
||||
- [ ] 打包后可在 macOS/Windows 运行
|
||||
- [ ] 性能指标达标
|
||||
**完成日期**: 2026-04-05
|
||||
|
||||
- ✅ IPC Handler 分层(8 个独立模块)
|
||||
- ✅ 共享类型统一
|
||||
- ✅ 连接池 Store 拆分
|
||||
- ✅ SFTP 组件拆分
|
||||
|
||||
### v0.7.0 Professional ✅
|
||||
|
||||
**完成日期**: 2026-04-05
|
||||
|
||||
- ✅ 连接状态机(6 个状态)
|
||||
- ✅ 连接池管理(LRU淘汰、保活、超时)
|
||||
- ✅ 终端快照机制
|
||||
- ✅ 多标签连接隔离
|
||||
|
||||
### v0.6.0 ~ v0.1.0 ✅
|
||||
|
||||
- ✅ rz/sz 文件传输
|
||||
- ✅ SFTP 传输队列优化
|
||||
- ✅ AES-256-GCM 加密存储
|
||||
- ✅ 端口转发
|
||||
- ✅ 功能骨架 + 核心 SSH
|
||||
|
||||
</details>
|
||||
+309
-461
@@ -1,489 +1,363 @@
|
||||
# TDD_PLAN.md - 测试计划
|
||||
# TDD_PLAN.md - 测试驱动开发计划
|
||||
|
||||
本文档规划 OpsXShell 的测试策略,采用 **先有后优** 的开发理念。
|
||||
本文档规划 OpsXShell 的测试策略,覆盖从单元测试到 E2E 测试的完整体系。
|
||||
|
||||
**核心理念**: 先确保功能完整可用,再逐步建立自动化测试覆盖
|
||||
---
|
||||
|
||||
## 当前状态
|
||||
|
||||
| 指标 | 当前值 | 目标值 |
|
||||
|------|--------|--------|
|
||||
| 测试文件数 | 3 | 15+ |
|
||||
| 测试用例数 | 34 | 150+ |
|
||||
| 估算覆盖率 | ~10% | ≥50% |
|
||||
| 核心模块覆盖 | 0% | ≥80% |
|
||||
|
||||
### 已有测试
|
||||
|
||||
| 测试文件 | 用例数 | 覆盖模块 | 质量 |
|
||||
|----------|--------|----------|------|
|
||||
| `test/stores/tabStore.test.ts` | 15 | tabStore | ✅ 完整 |
|
||||
| `test/stores/connectionPool.test.ts` | 7 | poolStore | 🟡 仅接口验证 |
|
||||
| `test/components/sftp/utils.test.ts` | 12 | sftp/utils | ✅ 完整 |
|
||||
|
||||
---
|
||||
|
||||
## 测试策略
|
||||
|
||||
### 当前阶段 (v1.0.0)
|
||||
|
||||
```
|
||||
功能实现 → 手动验证 → 测试框架 → 单元测试 → 集成测试
|
||||
↑__________________________________________________|
|
||||
v1.3.0 测试计划
|
||||
├── P0: 核心安全模块(crypto, ssh-service)
|
||||
├── P1: 数据层(database, error-handler, stores)
|
||||
├── P1: IPC 通信层(8 个 IPC Handler)
|
||||
├── P2: UI 层(TerminalView, useSFTP hooks)
|
||||
└── P3: E2E 集成(Playwright)
|
||||
```
|
||||
|
||||
**原则**:
|
||||
1. 功能优先:先实现并手动验证功能可用
|
||||
2. 测试跟进:功能稳定后建立自动化测试
|
||||
3. 逐步覆盖:从核心模块开始扩展测试范围
|
||||
4. 集成验证:确保 IPC 通信和端到端流程正确
|
||||
### 原则
|
||||
|
||||
1. **安全先行**: 加密/解密模块必须 100% 测试覆盖
|
||||
2. **核心优先**: SSH 连接逻辑是应用核心,优先覆盖
|
||||
3. **可 mock 性**: 所有外部依赖(ssh2, better-sqlite3, electron)必须可 mock
|
||||
4. **独立性**: 每个测试独立运行,不依赖外部状态
|
||||
5. **快速反馈**: 单元测试 < 5s,集成测试 < 30s
|
||||
|
||||
---
|
||||
|
||||
## 第一阶段:测试框架建立 (P0)
|
||||
## 测试环境配置
|
||||
|
||||
### 1.1 安装测试依赖
|
||||
|
||||
```bash
|
||||
# Vitest + React Testing Library
|
||||
npm install -D vitest @vitest/ui jsdom @testing-library/react @testing-library/jest-dom
|
||||
|
||||
# Playwright E2E
|
||||
npm install -D @playwright/test
|
||||
npx playwright install chromium
|
||||
```
|
||||
|
||||
### 1.2 配置文件
|
||||
### 已有配置
|
||||
|
||||
```typescript
|
||||
// vitest.config.ts
|
||||
import { defineConfig } from 'vitest/config'
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
globals: true,
|
||||
environment: 'jsdom',
|
||||
setupFiles: ['./tests/setup.ts'],
|
||||
setupFiles: ['./test/setup.ts'],
|
||||
coverage: {
|
||||
provider: 'v8',
|
||||
reporter: ['text', 'html'],
|
||||
reporter: ['text', 'html', 'lcov'],
|
||||
thresholds: {
|
||||
statements: 50, // 当前远未达到
|
||||
branches: 40,
|
||||
functions: 50,
|
||||
lines: 50,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Mock 策略
|
||||
|
||||
```typescript
|
||||
// tests/setup.ts
|
||||
import '@testing-library/jest-dom'
|
||||
// test/setup.ts 已有:
|
||||
// - window.electronAPI 完整 mock
|
||||
// - localStorage mock
|
||||
|
||||
// Mock electron API
|
||||
const mockElectronAPI = {
|
||||
sshConnect: vi.fn(),
|
||||
sshDisconnect: vi.fn(),
|
||||
sshCreateShell: vi.fn(),
|
||||
sshShellWrite: vi.fn(),
|
||||
sshShellResize: vi.fn(),
|
||||
sessionGetAll: vi.fn(),
|
||||
sessionGetById: vi.fn(),
|
||||
sessionCreate: vi.fn(),
|
||||
sessionUpdate: vi.fn(),
|
||||
sessionDelete: vi.fn(),
|
||||
sftpConnect: vi.fn(),
|
||||
sftpListDirectory: vi.fn(),
|
||||
sftpUpload: vi.fn(),
|
||||
sftpDownload: vi.fn(),
|
||||
onShellData: vi.fn(),
|
||||
removeAllListeners: vi.fn(),
|
||||
}
|
||||
|
||||
window.electronAPI = mockElectronAPI as any
|
||||
// 需要新增:
|
||||
// - ssh2 Client mock
|
||||
// - better-sqlite3 mock
|
||||
// - zmodem-ts mock
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 第二阶段:单元测试 (P0)
|
||||
## P0: 核心模块测试(第一批)
|
||||
|
||||
### 2.1 Store 测试
|
||||
### 1. crypto.ts 测试 — 加密/解密正确性
|
||||
|
||||
#### tabStore.test.ts
|
||||
**文件**: `test/services/crypto.test.ts`
|
||||
**用例目标**: 15+
|
||||
|
||||
```typescript
|
||||
import { describe, it, expect, beforeEach } from 'vitest'
|
||||
import { useTabStore } from '../../src/stores/tabStore'
|
||||
|
||||
describe('useTabStore', () => {
|
||||
beforeEach(() => {
|
||||
// Reset store
|
||||
useTabStore.setState({
|
||||
tabs: [{ id: 'welcome', title: '欢迎', type: 'welcome', isActive: true }],
|
||||
activeTabId: 'welcome',
|
||||
})
|
||||
describe('Crypto Service', () => {
|
||||
describe('encrypt / decrypt', () => {
|
||||
it('应正确加密并解密明文字符串')
|
||||
it('应正确加密并解密空字符串')
|
||||
it('应正确加密并解密特殊字符和 Unicode')
|
||||
it('每次加密应生成不同的密文(随机 IV)')
|
||||
it('密文篡改后解密应失败(Auth Tag 验证)')
|
||||
it('错误密钥解密应失败')
|
||||
})
|
||||
|
||||
describe('addTab', () => {
|
||||
it('should add a new terminal tab', () => {
|
||||
const { addTab } = useTabStore.getState()
|
||||
addTab({ title: 'Test', type: 'terminal' })
|
||||
|
||||
const { tabs } = useTabStore.getState()
|
||||
expect(tabs).toHaveLength(2)
|
||||
expect(tabs[1].title).toBe('Test')
|
||||
describe('deriveKey', () => {
|
||||
it('相同密码和 salt 应派生相同密钥')
|
||||
it('不同 salt 应派生不同密钥')
|
||||
it('应使用 100,000 次 PBKDF2 迭代')
|
||||
})
|
||||
|
||||
it('should activate new tab', () => {
|
||||
const { addTab } = useTabStore.getState()
|
||||
addTab({ title: 'Test', type: 'terminal' })
|
||||
|
||||
const { activeTabId } = useTabStore.getState()
|
||||
expect(activeTabId).toBe('tab-1')
|
||||
})
|
||||
|
||||
it('should not duplicate settings tab', () => {
|
||||
const { addTab } = useTabStore.getState()
|
||||
addTab({ title: '设置', type: 'settings' })
|
||||
addTab({ title: '设置2', type: 'settings' })
|
||||
|
||||
const { tabs } = useTabStore.getState()
|
||||
expect(tabs.filter(t => t.type === 'settings')).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('closeTab', () => {
|
||||
it('should close tab and activate next', () => {
|
||||
const { addTab, closeTab, tabs } = useTabStore.getState()
|
||||
addTab({ title: 'Tab1', type: 'terminal' })
|
||||
const tabToClose = tabs[1].id
|
||||
closeTab(tabToClose)
|
||||
|
||||
const { tabs: newTabs } = useTabStore.getState()
|
||||
expect(newTabs).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('should not close last tab', () => {
|
||||
const { closeTab, tabs } = useTabStore.getState()
|
||||
closeTab(tabs[0].id)
|
||||
|
||||
const { tabs: newTabs } = useTabStore.getState()
|
||||
expect(newTabs).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('connectionStatus', () => {
|
||||
it('should update tab connection status', () => {
|
||||
const { addTab, updateTabStatus } = useTabStore.getState()
|
||||
addTab({ title: 'SSH', type: 'terminal' })
|
||||
const tabId = useTabStore.getState().tabs[1].id
|
||||
|
||||
updateTabStatus(tabId, 'connected')
|
||||
|
||||
const { tabs } = useTabStore.getState()
|
||||
expect(tabs.find(t => t.id === tabId)?.connectionStatus).toBe('connected')
|
||||
})
|
||||
describe('generateSalt / generateIV', () => {
|
||||
it('salt 应为 32 字节随机值')
|
||||
it('IV 应为 12 字节随机值(GCM 推荐)')
|
||||
it('每次生成的值应不同')
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
#### connectionStore.test.ts
|
||||
### 2. ssh-service.ts 测试 — SSH 连接逻辑
|
||||
|
||||
**文件**: `test/services/ssh-service.test.ts`
|
||||
**用例目标**: 20+
|
||||
**Mock**: ssh2 Client
|
||||
|
||||
```typescript
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
import { useConnectionStore } from '../../src/stores/connectionStore'
|
||||
|
||||
describe('useConnectionStore', () => {
|
||||
beforeEach(() => {
|
||||
useConnectionStore.setState({ sessions: [] })
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('addSession', () => {
|
||||
it('should call sessionCreate API', async () => {
|
||||
window.electronAPI.sessionCreate.mockResolvedValue({
|
||||
success: true,
|
||||
session: { id: 'test-1', name: 'Test', host: 'localhost' },
|
||||
})
|
||||
|
||||
const { addSession } = useConnectionStore.getState()
|
||||
await addSession({
|
||||
name: 'Test',
|
||||
host: 'localhost',
|
||||
port: 22,
|
||||
username: 'user',
|
||||
authType: 'password',
|
||||
})
|
||||
|
||||
expect(window.electronAPI.sessionCreate).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('removeSession', () => {
|
||||
it('should remove session from state', async () => {
|
||||
useConnectionStore.setState({
|
||||
sessions: [{ id: 'test-1', name: 'Test', host: 'localhost' }],
|
||||
})
|
||||
|
||||
const { removeSession } = useConnectionStore.getState()
|
||||
await removeSession('test-1')
|
||||
|
||||
const { sessions } = useConnectionStore.getState()
|
||||
expect(sessions).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### 2.2 组件测试
|
||||
|
||||
#### TerminalView.test.tsx
|
||||
|
||||
```typescript
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { render, screen, fireEvent } from '@testing-library/react'
|
||||
import TerminalView from '../../src/components/TerminalView'
|
||||
|
||||
// Mock xterm
|
||||
vi.mock('@xterm/xterm', () => ({
|
||||
Terminal: vi.fn().mockImplementation(() => ({
|
||||
onData: vi.fn(),
|
||||
onResize: vi.fn(),
|
||||
open: vi.fn(),
|
||||
write: vi.fn(),
|
||||
dispose: vi.fn(),
|
||||
cols: 80,
|
||||
rows: 24,
|
||||
// Mock ssh2
|
||||
vi.mock('ssh2', () => ({
|
||||
Client: vi.fn().mockImplementation(() => ({
|
||||
connect: vi.fn(),
|
||||
on: vi.fn(),
|
||||
end: vi.fn(),
|
||||
shell: vi.fn(),
|
||||
forwardOut: vi.fn(),
|
||||
forwardIn: vi.fn(),
|
||||
sftp: vi.fn(),
|
||||
})),
|
||||
}))
|
||||
|
||||
vi.mock('@xterm/addon-fit', () => ({
|
||||
FitAddon: vi.fn().mockImplementation(() => ({
|
||||
fit: vi.fn(),
|
||||
loadAddon: vi.fn(),
|
||||
describe('SSH Service', () => {
|
||||
describe('connect', () => {
|
||||
it('应使用密码认证连接')
|
||||
it('应使用私钥认证连接')
|
||||
it('应使用 SSH Agent 认证连接')
|
||||
it('连接失败应返回错误')
|
||||
it('连接超时应返回超时错误')
|
||||
it('重复连接同一主机应复用')
|
||||
})
|
||||
|
||||
describe('createShell', () => {
|
||||
it('应创建交互式 Shell')
|
||||
it('应传入终端尺寸 cols/rows')
|
||||
it('Shell 创建失败应返回错误')
|
||||
})
|
||||
|
||||
describe('disconnect', () => {
|
||||
it('应正确关闭连接')
|
||||
it('应清理所有 Shell 会话')
|
||||
it('应清理端口转发')
|
||||
})
|
||||
|
||||
describe('portForward', () => {
|
||||
it('应创建本地端口转发')
|
||||
it('应创建远程端口转发')
|
||||
it('应创建 SOCKS5 动态代理')
|
||||
it('端口被占用应返回错误')
|
||||
})
|
||||
|
||||
describe('connectionPool', () => {
|
||||
it('不应超过最大连接数限制')
|
||||
it('空闲连接应被自动回收')
|
||||
it('LRU 淘汰策略应正确工作')
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## P1: 数据层 + Store 测试(第二批)
|
||||
|
||||
### 3. database-service.ts 测试
|
||||
|
||||
**文件**: `test/services/database.test.ts`
|
||||
**用例目标**: 15+
|
||||
|
||||
```typescript
|
||||
describe('Database Service', () => {
|
||||
describe('Session CRUD', () => {
|
||||
it('应创建新会话并返回 ID')
|
||||
it('应获取所有会话列表')
|
||||
it('应根据 ID 获取单个会话')
|
||||
it('应更新会话字段')
|
||||
it('应删除会话')
|
||||
it('密码字段应加密存储')
|
||||
it('读取时密码字段应解密')
|
||||
})
|
||||
|
||||
describe('Settings', () => {
|
||||
it('应保存和读取设置')
|
||||
it('未设置的键应返回默认值')
|
||||
it('应支持复杂对象类型设置')
|
||||
})
|
||||
|
||||
describe('数据完整性', () => {
|
||||
it('数据库文件不存在应自动创建')
|
||||
it('Schema 版本升级应自动迁移')
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### 4. error-handler.ts 测试
|
||||
|
||||
**文件**: `test/utils/error-handler.test.ts`
|
||||
**用例目标**: 10+
|
||||
|
||||
```typescript
|
||||
describe('Error Handler', () => {
|
||||
describe('inferErrorCode', () => {
|
||||
it('应从 ECONNREFUSED 推断连接错误')
|
||||
it('应从 ETIMEDOUT 推断超时错误')
|
||||
it('应从 auth 推断认证错误')
|
||||
it('应从 ENOENT 推断文件不存在错误')
|
||||
it('未知错误应返回通用错误码')
|
||||
})
|
||||
|
||||
describe('withErrorHandling', () => {
|
||||
it('应包装异步函数并捕获错误')
|
||||
it('应返回统一错误格式')
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### 5. poolStore 测试增强
|
||||
|
||||
**文件**: `test/stores/connectionPool.test.ts`
|
||||
**用例目标**: 15+ (新增 8)
|
||||
|
||||
```typescript
|
||||
describe('Connection Pool Store', () => {
|
||||
// 已有: 基本接口验证
|
||||
// 新增:
|
||||
it('应拒绝超过最大连接数的新连接')
|
||||
it('LRU 淘汰应移除最久未使用的连接')
|
||||
it('后台连接超过最大数应断开最旧')
|
||||
it('活跃连接不应被 LRU 淘汰')
|
||||
it('连接状态机转换应完整覆盖')
|
||||
it('挂起超时 30 分钟应彻底断开')
|
||||
it('保活心跳应重置空闲计时器')
|
||||
})
|
||||
```
|
||||
|
||||
### 6. lifecycleStore 测试
|
||||
|
||||
**文件**: `test/stores/lifecycleStore.test.ts`
|
||||
**用例目标**: 10+
|
||||
|
||||
---
|
||||
|
||||
## P1: IPC Handler 测试(第三批)
|
||||
|
||||
### 7. SSH IPC 测试
|
||||
|
||||
**文件**: `test/ipc/ssh.ipc.test.ts`
|
||||
**用例目标**: 8+
|
||||
|
||||
```typescript
|
||||
describe('SSH IPC Handlers', () => {
|
||||
it('ssh:connect 应调用 sshService.connect')
|
||||
it('ssh:disconnect 应调用 sshService.disconnect')
|
||||
it('ssh:create-shell 应调用 sshService.createShell')
|
||||
it('ssh:shell-write 应调用 sshService.shellWrite')
|
||||
it('ssh:shell-resize 应调用 sshService.shellResize')
|
||||
it('ssh:forward-local 应调用 sshService.forwardLocal')
|
||||
it('ssh:forward-remote 应调用 sshService.forwardRemote')
|
||||
it('无效参数应返回错误')
|
||||
})
|
||||
```
|
||||
|
||||
### 8. SFTP IPC 测试
|
||||
|
||||
**文件**: `test/ipc/sftp.ipc.test.ts`
|
||||
**用例目标**: 8+
|
||||
|
||||
### 9. Session IPC 测试
|
||||
|
||||
**文件**: `test/ipc/session.ipc.test.ts`
|
||||
**用例目标**: 6+
|
||||
|
||||
### 10. Settings IPC 测试
|
||||
|
||||
**文件**: `test/ipc/settings.ipc.test.ts`
|
||||
**用例目标**: 4+
|
||||
|
||||
### 11. 其他 IPC 测试
|
||||
|
||||
**文件**: `test/ipc/{dialog,file,zmodem,app}.ipc.test.ts`
|
||||
**用例目标**: 每个文件 3-5+
|
||||
|
||||
---
|
||||
|
||||
## P2: UI 层测试(第四批)
|
||||
|
||||
### 12. TerminalView 组件测试
|
||||
|
||||
**文件**: `test/components/TerminalView.test.tsx`
|
||||
**用例目标**: 10+
|
||||
|
||||
```typescript
|
||||
// Mock xterm
|
||||
vi.mock('@xterm/xterm', () => ({
|
||||
Terminal: vi.fn().mockImplementation(() => ({
|
||||
onData: vi.fn(), onResize: vi.fn(), open: vi.fn(),
|
||||
write: vi.fn(), dispose: vi.fn(), cols: 80, rows: 24,
|
||||
})),
|
||||
}))
|
||||
|
||||
describe('TerminalView', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('should render terminal container', () => {
|
||||
render(<TerminalView />)
|
||||
expect(screen.getByText('连接成功!')).toBeDefined()
|
||||
})
|
||||
|
||||
it('should show connecting status', () => {
|
||||
render(<TerminalView connectionConfig={{ host: 'test', port: 22, username: 'user' }} />)
|
||||
expect(screen.getByText('正在连接...')).toBeDefined()
|
||||
})
|
||||
it('应渲染终端容器')
|
||||
it('应显示连接中状态')
|
||||
it('连接成功应初始化 XTerm')
|
||||
it('连接失败应显示错误信息')
|
||||
it('Shell 数据应写入终端')
|
||||
it('终端输入应发送到 Shell')
|
||||
it('窗口调整应 resize 终端')
|
||||
it('组件卸载应释放资源')
|
||||
})
|
||||
```
|
||||
|
||||
#### Sidebar.test.tsx
|
||||
### 13. useSFTP Hooks 测试
|
||||
|
||||
```typescript
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { render, screen, fireEvent } from '@testing-library/react'
|
||||
import Sidebar from '../../src/components/Sidebar'
|
||||
**文件**: `test/hooks/useSFTP.test.ts`
|
||||
**用例目标**: 15+
|
||||
|
||||
describe('Sidebar', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
window.electronAPI.sessionGetAll.mockResolvedValue({
|
||||
success: true,
|
||||
sessions: [],
|
||||
})
|
||||
})
|
||||
### 14. 其他组件测试
|
||||
|
||||
it('should render quick connect button', () => {
|
||||
render(<Sidebar onNewSession={vi.fn()} onQuickConnect={vi.fn()} onEditSession={vi.fn()} />)
|
||||
expect(screen.getByText('快速连接')).toBeDefined()
|
||||
})
|
||||
|
||||
it('should call onQuickConnect when button clicked', () => {
|
||||
const onQuickConnect = vi.fn()
|
||||
render(<Sidebar onNewSession={vi.fn()} onQuickConnect={onQuickConnect} onEditSession={vi.fn()} />)
|
||||
|
||||
fireEvent.click(screen.getByText('快速连接'))
|
||||
expect(onQuickConnect).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should display sessions from API', async () => {
|
||||
window.electronAPI.sessionGetAll.mockResolvedValue({
|
||||
success: true,
|
||||
sessions: [
|
||||
{ id: '1', name: 'Server 1', host: '192.168.1.1', port: 22 },
|
||||
],
|
||||
})
|
||||
|
||||
render(<Sidebar onNewSession={vi.fn()} onQuickConnect={vi.fn()} onEditSession={vi.fn()} />)
|
||||
|
||||
expect(await screen.findByText('Server 1')).toBeDefined()
|
||||
})
|
||||
})
|
||||
```
|
||||
- `test/components/Sidebar.test.tsx` — 5+
|
||||
- `test/components/SessionDialog.test.tsx` — 5+
|
||||
- `test/components/SFTPView.test.tsx` — 5+
|
||||
|
||||
---
|
||||
|
||||
## 第三阶段:集成测试 (P1)
|
||||
## P3: E2E 集成测试(远期)
|
||||
|
||||
### 3.1 IPC 通信测试
|
||||
|
||||
```typescript
|
||||
// tests/integration/ipc.test.ts
|
||||
describe('IPC Communication', () => {
|
||||
describe('SSH Operations', () => {
|
||||
it('should connect and create shell', async () => {
|
||||
// Mock successful connection
|
||||
window.electronAPI.sshConnect.mockResolvedValue({
|
||||
success: true,
|
||||
connectionId: 'conn-1',
|
||||
})
|
||||
window.electronAPI.sshCreateShell.mockResolvedValue({
|
||||
success: true,
|
||||
shellId: 'shell-1',
|
||||
})
|
||||
|
||||
// Simulate connection flow
|
||||
const config = { host: 'test', port: 22, username: 'user', password: 'pass' }
|
||||
const connResult = await window.electronAPI.sshConnect(config)
|
||||
|
||||
expect(connResult.success).toBe(true)
|
||||
expect(connResult.connectionId).toBe('conn-1')
|
||||
|
||||
const shellResult = await window.electronAPI.sshCreateShell('conn-1', { cols: 80, rows: 24 })
|
||||
expect(shellResult.success).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Session CRUD', () => {
|
||||
it('should create, read, update, delete session', async () => {
|
||||
// Create
|
||||
window.electronAPI.sessionCreate.mockResolvedValue({
|
||||
success: true,
|
||||
session: { id: 'sess-1', name: 'Test', host: 'localhost' },
|
||||
})
|
||||
|
||||
const createResult = await window.electronAPI.sessionCreate({
|
||||
name: 'Test',
|
||||
host: 'localhost',
|
||||
port: 22,
|
||||
username: 'user',
|
||||
})
|
||||
expect(createResult.success).toBe(true)
|
||||
|
||||
// Update
|
||||
window.electronAPI.sessionUpdate.mockResolvedValue({
|
||||
success: true,
|
||||
session: { id: 'sess-1', name: 'Updated', host: 'localhost' },
|
||||
})
|
||||
|
||||
const updateResult = await window.electronAPI.sessionUpdate('sess-1', { name: 'Updated' })
|
||||
expect(updateResult.success).toBe(true)
|
||||
expect(updateResult.session.name).toBe('Updated')
|
||||
|
||||
// Delete
|
||||
window.electronAPI.sessionDelete.mockResolvedValue({ success: true })
|
||||
const deleteResult = await window.electronAPI.sessionDelete('sess-1')
|
||||
expect(deleteResult.success).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### 3.2 SFTP 集成测试
|
||||
|
||||
```typescript
|
||||
// tests/integration/sftp.test.ts
|
||||
describe('SFTP Operations', () => {
|
||||
beforeEach(() => {
|
||||
window.electronAPI.sshConnect.mockResolvedValue({
|
||||
success: true,
|
||||
connectionId: 'conn-1',
|
||||
})
|
||||
window.electronAPI.sftpConnect.mockResolvedValue({
|
||||
success: true,
|
||||
sftpId: 'sftp-1',
|
||||
})
|
||||
})
|
||||
|
||||
it('should connect to SFTP and list directory', async () => {
|
||||
window.electronAPI.sftpListDirectory.mockResolvedValue({
|
||||
success: true,
|
||||
files: [
|
||||
{ filename: 'file1.txt', attrs: { isDirectory: () => false, size: 1024 } },
|
||||
{ filename: 'dir1', attrs: { isDirectory: () => true, size: 0 } },
|
||||
],
|
||||
})
|
||||
|
||||
const sftpResult = await window.electronAPI.sftpConnect('conn-1')
|
||||
expect(sftpResult.success).toBe(true)
|
||||
|
||||
const listResult = await window.electronAPI.sftpListDirectory('sftp-1', '/home/user')
|
||||
expect(listResult.success).toBe(true)
|
||||
expect(listResult.files).toHaveLength(2)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 第四阶段:E2E 测试 (P2)
|
||||
|
||||
### 4.1 Playwright 配置
|
||||
### Playwright 配置
|
||||
|
||||
```typescript
|
||||
// playwright.config.ts
|
||||
import { defineConfig } from '@playwright/test'
|
||||
|
||||
export default defineConfig({
|
||||
testDir: './tests/e2e',
|
||||
testDir: './test/e2e',
|
||||
timeout: 30000,
|
||||
use: {
|
||||
baseURL: 'http://localhost:5173',
|
||||
headless: true,
|
||||
},
|
||||
webServer: {
|
||||
command: 'npm run dev',
|
||||
port: 5173,
|
||||
reuseExistingServer: true,
|
||||
},
|
||||
use: { baseURL: 'http://localhost:5173' },
|
||||
})
|
||||
```
|
||||
|
||||
### 4.2 E2E 测试用例
|
||||
### E2E 用例
|
||||
|
||||
```typescript
|
||||
// tests/e2e/connection.spec.ts
|
||||
import { test, expect } from '@playwright/test'
|
||||
|
||||
test.describe('SSH Connection Flow', () => {
|
||||
test('should connect via quick connect dialog', async ({ page }) => {
|
||||
await page.goto('/')
|
||||
|
||||
// Click quick connect
|
||||
await page.getByText('快速连接').click()
|
||||
|
||||
// Fill form
|
||||
await page.getByPlaceholder('主机地址').fill('localhost')
|
||||
await page.getByPlaceholder('端 口').fill('22')
|
||||
await page.getByPlaceholder('用户名').fill('test')
|
||||
|
||||
// Submit
|
||||
await page.getByText('连接').click()
|
||||
|
||||
// Should show terminal
|
||||
await expect(page.locator('[data-testid="terminal"]')).toBeVisible()
|
||||
})
|
||||
|
||||
test('should connect via sidebar session', async ({ page }) => {
|
||||
await page.goto('/')
|
||||
|
||||
// Add a session first (via API mock in real test)
|
||||
// Click session in sidebar
|
||||
await page.getByText('测试服务器').click()
|
||||
|
||||
// Should open terminal tab
|
||||
await expect(page.locator('[data-tab-title="测试服务器"]')).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('SFTP Flow', () => {
|
||||
test('should show session selection when no session provided', async ({ page }) => {
|
||||
await page.goto('/')
|
||||
|
||||
// Open SFTP
|
||||
await page.getByText('SFTP 传输').click()
|
||||
|
||||
// Click connect
|
||||
await page.getByText('选择服务器').click()
|
||||
|
||||
// Should show session list
|
||||
await expect(page.getByText('选择要连接的服务器')).toBeVisible()
|
||||
})
|
||||
})
|
||||
```
|
||||
- 快速连接 → SSH 终端出现
|
||||
- 侧边栏会话 → 点击 → 终端连接
|
||||
- SFTP → 选择服务器 → 浏览目录
|
||||
- 标签页 → 创建 → 切换 → 关闭
|
||||
|
||||
---
|
||||
|
||||
@@ -492,63 +366,37 @@ test.describe('SFTP Flow', () => {
|
||||
### 命令
|
||||
|
||||
```bash
|
||||
# 运行所有测试
|
||||
npm test
|
||||
|
||||
# 带覆盖率
|
||||
npm test -- --coverage
|
||||
|
||||
# 监视模式
|
||||
npm test -- --watch
|
||||
|
||||
# E2E 测试
|
||||
npm run test:e2e
|
||||
|
||||
# 带 UI 的 Vitest
|
||||
npm run test:ui
|
||||
npm test # 运行所有测试
|
||||
npm test -- --coverage # 带覆盖率报告
|
||||
npm test -- test/services/ # 只跑 services 测试
|
||||
npm test -- --reporter=verbose # 详细输出
|
||||
npm test -- --watch # 监听模式
|
||||
```
|
||||
|
||||
### CI 配置
|
||||
### CI 集成(规划)
|
||||
|
||||
```yaml
|
||||
# .github/workflows/test.yml
|
||||
name: Test
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
- run: npm ci
|
||||
- run: npm run lint
|
||||
- run: npm test -- --coverage
|
||||
- uses: codecov/codecov-action@v3
|
||||
- name: Test
|
||||
run: npm test -- --coverage
|
||||
- name: Coverage Check
|
||||
run: |
|
||||
if [ $(npm test -- --coverage --reporter=json 2>/dev/null | jq '.total.lines.pct') -lt 50 ]; then
|
||||
echo "Coverage below 50%"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 覆盖率目标
|
||||
## 覆盖率追踪
|
||||
|
||||
| 版本 | 单元测试 | 集成测试 | E2E |
|
||||
|------|----------|----------|-----|
|
||||
| v1.0.0 | 20% | 10% | 5% |
|
||||
| v1.1.0 | 40% | 20% | 10% |
|
||||
| v1.2.0 | 60% | 30% | 15% |
|
||||
|
||||
---
|
||||
|
||||
## 测试优先级矩阵
|
||||
|
||||
| 功能模块 | 单元测试 | 集成测试 | E2E | 优先级 |
|
||||
|----------|----------|----------|-----|--------|
|
||||
| tabStore | ✅ | - | - | P0 |
|
||||
| connectionStore | ✅ | ✅ | - | P0 |
|
||||
| TerminalView | ✅ | ✅ | ✅ | P0 |
|
||||
| SFTPView | ✅ | ✅ | ✅ | P1 |
|
||||
| Sidebar | ✅ | ✅ | ✅ | P1 |
|
||||
| SessionDialog | ✅ | ✅ | - | P1 |
|
||||
| ssh-service | - | ✅ | - | P0 |
|
||||
| database-service | - | ✅ | - | P1 |
|
||||
| 版本 | 总覆盖率 | 核心模块 | 用例数 | 日期 |
|
||||
|------|----------|----------|--------|------|
|
||||
| v1.0.0 | ~10% | 0% | 34 | 2026-04-06 |
|
||||
| v1.1.0 | ~12% | 0% | 34 | — |
|
||||
| v1.2.0 | ~15% | 0% | 40 | — |
|
||||
| v1.3.0-batch1 | ~25% | 60% | 69 | — |
|
||||
| v1.3.0-batch2 | ~35% | 60% | 104 | — |
|
||||
| v1.3.0-batch3 | ~45% | 70% | 143 | — |
|
||||
| v1.3.0-final | **≥50%** | **≥80%** | **150+** | — |
|
||||
Reference in new issue
Block a user