diff --git a/plans/shimmering-wishing-boot.md b/plans/shimmering-wishing-boot.md index 7d3cfa9..06844ec 100644 --- a/plans/shimmering-wishing-boot.md +++ b/plans/shimmering-wishing-boot.md @@ -21,22 +21,27 @@ ## 二、仍需推进的工作 🔴 -### 2.1 P0: 真实集成测试(最高优先级) +### 2.1 P0: 真实集成测试(最高优先级)🚧 进行中 **问题:** 317 个单元测试通过不代表产品可用 | 验证项 | 当前状态 | 需要做的工作 | |--------|----------|--------------| -| Gateway 连接 | Mock 测试通过 | 需要连接真实 OpenFang Kernel 验证 | -| 真实模型对话 | Mock 测试通过 | 需要配置 API Key 测试流式响应 | -| 飞书 Channel | 未验证 | OAuth → 消息收发 → Agent 处理 | -| Hands 触发流程 | Mock 测试通过 | 意图识别 → 审批 → 执行 → 结果 | -| 记忆持久化 | 代码存在 | 重启后验证记忆保留 | +| Gateway 连接 | ✅ 脚本就绪 | 运行 `scripts/tests/real-integration-test.sh` | +| 真实模型对话 | ✅ 脚本就绪 | 设置 ZHIPU_API_KEY 后运行测试 | +| 飞书 Channel | ⏳ 待验证 | OAuth → 消息收发 → Agent 处理 | +| Hands 触发流程 | ✅ 脚本就绪 | 意图识别 → 审批 → 执行 → 结果 | +| 记忆持久化 | ✅ 脚本就绪 | 重启后验证记忆保留 | -**建议行动:** -1. 创建真实环境测试脚本 `scripts/real-integration-test.sh` -2. 编写集成测试清单文档 -3. 逐项验证并记录结果 +**已完成:** +1. ✅ 创建真实环境测试脚本 `scripts/tests/real-integration-test.sh` (Bash) +2. ✅ 创建 Windows 版本 `scripts/tests/real-integration-test.ps1` (PowerShell) +3. ✅ 编写集成测试清单文档 `docs/testing/INTEGRATION-CHECKLIST.md` + +**待执行:** +1. 启动 OpenFang Kernel: `openfang start` +2. 设置 API Key: `export ZHIPU_API_KEY=your_key` +3. 运行测试: `./scripts/tests/real-integration-test.sh` --- @@ -71,17 +76,19 @@ --- -### 2.4 P2: 低集成度组件验证 +### 2.4 P2: 低集成度组件验证 ✅ 已完成 -根据文档提到的组件: +根据文档提到的组件,已全部验证: -| 组件 | 当前状态 | 需要验证 | -|------|----------|----------| -| HeartbeatConfig | ✅ 已集成 SettingsLayout | 功能是否正常 | -| CreateTriggerModal | ✅ 已迁移到 useHandStore | 功能是否正常 | -| PersonalitySelector | ❓ 需检查 | 是否已集成 | -| ScenarioTags | ❓ 需检查 | 是否已集成 | -| DevQALoop | ❓ 需检查 | 是否已集成 | +| 组件 | 当前状态 | 说明 | +|------|----------|------| +| HeartbeatConfig | ✅ 已集成 | 通过 SettingsLayout 间接使用 | +| CreateTriggerModal | ✅ 已集成 | 通过 useHandStore 正常工作 | +| PersonalitySelector | ✅ 已集成 | 通过 CreateAgentModal 间接使用 | +| ScenarioTags | ✅ 已集成 | 通过 CreateAgentModal 间接使用 | +| DevQALoop | ✅ 已集成 | 已添加到 TeamOrchestrator Review 标签 | + +详见: `docs/analysis/COMPONENT-INTEGRATION-STATUS.md` --- @@ -116,16 +123,16 @@ ### 🔥 本周 -1. **真实集成测试验证** - - 连接真实 OpenFang Kernel - - 配置中文模型 API Key - - 验证基础对话流程 +1. **✅ 真实集成测试脚本已创建** + - Bash 版本: `scripts/tests/real-integration-test.sh` + - PowerShell 版本: `scripts/tests/real-integration-test.ps1` + - 下一步: 运行测试验证真实环境 ### 📌 短期(2周) -1. **完成低集成度组件验证** -2. **补充 Tauri 进程健康检查** -3. **更新文档反映当前状态** +1. **✅ 完成低集成度组件验证** - DevQALoop 已集成到 TeamOrchestrator +2. **✅ 补充 Tauri 进程健康检查** - health-check.ts 已实现 +3. **✅ 更新文档反映当前状态** - 多份文档已更新 ### 🎯 中期(1-2月) @@ -140,6 +147,20 @@ **ZCLAW 项目已完成大部分架构优化工作**(Store 迁移、gateway-client 模块化、E2E 框架)。 -**最关键的缺口是真实环境验证** —— 需要用真实的 OpenFang Kernel 和中文模型 API 验证完整数据流。 +**真实环境测试脚本已就绪** —— 现在可以连接真实的 OpenFang Kernel 和中文模型 API 验证完整数据流。 + +**低集成度组件已全部验证并集成** —— DevQALoop 已添加到 TeamOrchestrator。 **智能层迁移是长期工作**,可以在产品验证稳定后再逐步推进。 + +--- + +## 五、更新日志 + +| 日期 | 更新内容 | +|------|----------| +| 2026-03-21 | 创建真实环境集成测试脚本 (Bash + PowerShell) | +| 2026-03-21 | 完成低集成度组件验证,DevQALoop 集成到 TeamOrchestrator | +| 2026-03-21 | 更新集成测试清单文档 | +| 2026-03-21 | 创建健康检查系统和错误通知组件 | +| 2026-03-21 | 创建 OpenFang 设置指南和快速启动文档 | diff --git a/scripts/tests/real-integration-test.ps1 b/scripts/tests/real-integration-test.ps1 new file mode 100644 index 0000000..9003ebd --- /dev/null +++ b/scripts/tests/real-integration-test.ps1 @@ -0,0 +1,472 @@ +# ZCLAW 真实环境集成测试 (Windows PowerShell) +# 连接真实 OpenFang Kernel 验证完整数据流 +# +# 使用方法: +# 1. 确保 OpenFang Kernel 正在运行: openfang start +# 2. 设置 API Key: $env:ZHIPU_API_KEY="your_key" +# 3. 运行测试: .\scripts\tests\real-integration-test.ps1 + +param( + [string]$GatewayHost = "127.0.0.1", + [int]$GatewayPort = 50051, + [switch]$Verbose +) + +# 配置 +$GatewayUrl = "http://${GatewayHost}:${GatewayPort}" +$Timestamp = Get-Date -Format "yyyyMMdd_HHmmss" +$ResultsDir = "test-results/integration" +$LogFile = "$ResultsDir/integration_$Timestamp.log" + +# 测试计数器 +$script:TestsRun = 0 +$script:TestsPassed = 0 +$script:TestsFailed = 0 +$script:TestsSkipped = 0 + +# 创建结果目录 +if (-not (Test-Path $ResultsDir)) { + New-Item -ItemType Directory -Path $ResultsDir -Force | Out-Null +} + +# 日志函数 +function Write-Log { + param([string]$Message) + Write-Host $Message + Add-Content -Path $LogFile -Value $Message +} + +function Write-TestResult { + param( + [string]$Id, + [string]$Description, + [string]$Status, + [string]$Details = "" + ) + + $script:TestsRun++ + + switch ($Status) { + "PASS" { + $script:TestsPassed++ + Write-Log "${Green}✅ [$Id] $Description${Reset}" + } + "FAIL" { + $script:TestsFailed++ + Write-Log "${Red}❌ [$Id] $Description${Reset}" + } + "SKIP" { + $script:TestsSkipped++ + Write-Log "${Yellow}⏭️ [$Id] $Description (跳过)${Reset}" + } + } + + if ($Details) { + Write-Log " $Details" + } +} + +# 颜色定义 +$Red = "`e[0;31m" +$Green = "`e[0;32m" +$Yellow = "`e[1;33m" +$Blue = "`e[0;34m" +$Reset = "`e[0m" + +# ============================================================================= +# 测试开始 +# ============================================================================= + +Write-Log "${Blue}========================================${Reset}" +Write-Log "${Blue} ZCLAW 真实环境集成测试 (Windows)${Reset}" +Write-Log "${Blue}========================================${Reset}" +Write-Log "" +Write-Log "测试时间: $(Get-Date)" +Write-Log "Gateway URL: $GatewayUrl" +Write-Log "日志文件: $LogFile" +Write-Log "" + +# ============================================================================= +# 1. 环境检查 +# ============================================================================= + +Write-Log "${Yellow}[1. 环境检查]${Reset}" + +# RI-ENV-01: 检查 PowerShell 版本 +$PSVersionTable.PSVersion +Write-TestResult "RI-ENV-01" "PowerShell 版本 $($PSVersionTable.PSVersion)" "PASS" + +# RI-ENV-02: 检查 curl (Invoke-WebRequest) +try { + $null = Get-Command Invoke-WebRequest -ErrorAction Stop + Write-TestResult "RI-ENV-02" "Invoke-WebRequest 可用" "PASS" +} catch { + Write-TestResult "RI-ENV-02" "Invoke-WebRequest 可用" "FAIL" + exit 1 +} + +# RI-ENV-03: 检查 Node.js +try { + $nodeVersion = node -v + Write-TestResult "RI-ENV-03" "Node.js 可用 ($nodeVersion)" "PASS" +} catch { + Write-TestResult "RI-ENV-03" "Node.js 可用" "FAIL" "需要安装 Node.js" + exit 1 +} + +Write-Log "" + +# ============================================================================= +# 2. Gateway 连接测试 +# ============================================================================= + +Write-Log "${Yellow}[2. Gateway 连接测试]${Reset}" + +# RI-GW-01: 检查端口可达性 +try { + $tcpClient = New-Object System.Net.Sockets.TcpClient + $connect = $tcpClient.BeginConnect($GatewayHost, $GatewayPort, $null, $null) + $wait = $connect.AsyncWaitHandle.WaitOne(5000) + + if ($wait -and $tcpClient.Connected) { + Write-TestResult "RI-GW-01" "Gateway 端口 $GatewayPort 可达" "PASS" + $tcpClient.EndConnect($connect) + } else { + Write-TestResult "RI-GW-01" "Gateway 端口 $GatewayPort 可达" "FAIL" "请确保 OpenFang 正在运行: openfang start" + } + $tcpClient.Close() +} catch { + Write-TestResult "RI-GW-01" "Gateway 端口 $GatewayPort 可达" "FAIL" $_.Exception.Message +} + +# RI-GW-02: Health 端点 +try { + $response = Invoke-WebRequest -Uri "$GatewayUrl/api/health" -Method Get -TimeoutSec 10 -ErrorAction Stop + if ($response.StatusCode -eq 200) { + Write-TestResult "RI-GW-02" "Health 端点返回 200" "PASS" "响应: $($response.Content)" + } else { + Write-TestResult "RI-GW-02" "Health 端点返回 200" "FAIL" "HTTP $($response.StatusCode)" + } +} catch { + Write-TestResult "RI-GW-02" "Health 端点返回 200" "FAIL" $_.Exception.Message +} + +# RI-GW-03: Health 响应结构 +try { + $response = Invoke-WebRequest -Uri "$GatewayUrl/api/health" -Method Get -TimeoutSec 10 -ErrorAction Stop + $content = $response.Content | ConvertFrom-Json + if ($content.status) { + Write-TestResult "RI-GW-03" "Health 响应包含 status 字段" "PASS" + } else { + Write-TestResult "RI-GW-03" "Health 响应包含 status 字段" "FAIL" "响应: $($response.Content)" + } +} catch { + Write-TestResult "RI-GW-03" "Health 响应包含 status 字段" "FAIL" $_.Exception.Message +} + +Write-Log "" + +# ============================================================================= +# 3. 模型配置测试 +# ============================================================================= + +Write-Log "${Yellow}[3. 模型配置测试]${Reset}" + +# RI-MOD-01: 获取可用模型列表 +try { + $response = Invoke-WebRequest -Uri "$GatewayUrl/api/models" -Method Get -TimeoutSec 10 -ErrorAction Stop + if ($response.StatusCode -eq 200) { + Write-TestResult "RI-MOD-01" "Models 端点返回 200" "PASS" + + $models = $response.Content | ConvertFrom-Json + $modelCount = ($models | Measure-Object).Count + if ($modelCount -gt 0) { + Write-TestResult "RI-MOD-02" "检测到 $modelCount 个可用模型" "PASS" + } else { + Write-TestResult "RI-MOD-02" "检测到可用模型" "FAIL" "响应: $($response.Content)" + } + } else { + Write-TestResult "RI-MOD-01" "Models 端点返回 200" "FAIL" "HTTP $($response.StatusCode)" + Write-TestResult "RI-MOD-02" "检测到可用模型" "SKIP" + } +} catch { + Write-TestResult "RI-MOD-01" "Models 端点返回 200" "FAIL" $_.Exception.Message + Write-TestResult "RI-MOD-02" "检测到可用模型" "SKIP" +} + +# RI-MOD-03: 检查中文模型配置 +if (Test-Path "config/chinese-providers.toml") { + Write-TestResult "RI-MOD-03" "中文模型配置文件存在" "PASS" +} else { + Write-TestResult "RI-MOD-03" "中文模型配置文件存在" "FAIL" "缺少 config/chinese-providers.toml" +} + +Write-Log "" + +# ============================================================================= +# 4. Agent 管理测试 +# ============================================================================= + +Write-Log "${Yellow}[4. Agent 管理测试]${Reset}" + +# RI-AGT-01: 获取 Agent 列表 +try { + $response = Invoke-WebRequest -Uri "$GatewayUrl/api/agents" -Method Get -TimeoutSec 10 -ErrorAction Stop + if ($response.StatusCode -eq 200) { + Write-TestResult "RI-AGT-01" "Agents 端点返回 200" "PASS" + + $agents = $response.Content | ConvertFrom-Json + if ($agents -and ($agents.id -or $agents.name)) { + Write-TestResult "RI-AGT-02" "检测到 Agent 配置" "PASS" + } else { + Write-TestResult "RI-AGT-02" "检测到 Agent 配置" "FAIL" "响应: $($response.Content)" + } + } else { + Write-TestResult "RI-AGT-01" "Agents 端点返回 200" "FAIL" "HTTP $($response.StatusCode)" + } +} catch { + Write-TestResult "RI-AGT-01" "Agents 端点返回 200" "FAIL" $_.Exception.Message +} + +Write-Log "" + +# ============================================================================= +# 5. API Key 验证测试 +# ============================================================================= + +Write-Log "${Yellow}[5. API Key 验证测试]${Reset}" + +# RI-KEY-01: 检查智谱 API Key +if ($env:ZHIPU_API_KEY) { + Write-TestResult "RI-KEY-01" "智谱 API Key 已设置" "PASS" "长度: $($env:ZHIPU_API_KEY.Length) 字符" +} else { + Write-TestResult "RI-KEY-01" "智谱 API Key 已设置" "FAIL" "请设置: `$env:ZHIPU_API_KEY='your_key'" +} + +# RI-KEY-02: 检查通义千问 API Key +if ($env:DASHSCOPE_API_KEY) { + Write-TestResult "RI-KEY-02" "通义千问 API Key 已设置" "PASS" +} else { + Write-TestResult "RI-KEY-02" "通义千问 API Key 已设置" "SKIP" "可选" +} + +# RI-KEY-03: 检查 Kimi API Key +if ($env:MOONSHOT_API_KEY) { + Write-TestResult "RI-KEY-03" "Kimi API Key 已设置" "PASS" +} else { + Write-TestResult "RI-KEY-03" "Kimi API Key 已设置" "SKIP" "可选" +} + +Write-Log "" + +# ============================================================================= +# 6. 对话功能测试(需要 API Key) +# ============================================================================= + +Write-Log "${Yellow}[6. 对话功能测试]${Reset}" + +if ($env:ZHIPU_API_KEY) { + # RI-CHAT-01: 发送测试消息 + $chatPayload = @{ + messages = @( + @{ + role = "user" + content = "你好,这是一个测试消息。请简短回复。" + } + ) + model = "glm-4-flash" + stream = $false + } | ConvertTo-Json -Depth 3 + + Write-Log "发送测试消息..." + + try { + $response = Invoke-WebRequest -Uri "$GatewayUrl/api/chat" ` + -Method Post ` + -ContentType "application/json" ` + -Body $chatPayload ` + -TimeoutSec 60 ` + -ErrorAction Stop + + if ($response.StatusCode -eq 200) { + Write-TestResult "RI-CHAT-01" "对话请求成功" "PASS" + + $chatResult = $response.Content | ConvertFrom-Json + if ($chatResult.content -or $chatResult.text -or $chatResult.message) { + Write-TestResult "RI-CHAT-02" "对话响应包含内容" "PASS" + } else { + Write-TestResult "RI-CHAT-02" "对话响应包含内容" "FAIL" "响应: $($response.Content)" + } + } else { + Write-TestResult "RI-CHAT-01" "对话请求成功" "FAIL" "HTTP $($response.StatusCode)" + Write-TestResult "RI-CHAT-02" "对话响应包含内容" "SKIP" + } + } catch { + Write-TestResult "RI-CHAT-01" "对话请求成功" "FAIL" $_.Exception.Message + Write-TestResult "RI-CHAT-02" "对话响应包含内容" "SKIP" + } +} else { + Write-TestResult "RI-CHAT-01" "对话请求成功" "SKIP" "需要设置 ZHIPU_API_KEY" + Write-TestResult "RI-CHAT-02" "对话响应包含内容" "SKIP" +} + +Write-Log "" + +# ============================================================================= +# 7. Hands 触发测试 +# ============================================================================= + +Write-Log "${Yellow}[7. Hands 触发测试]${Reset}" + +# RI-HAND-01: 获取可用 Hands +try { + $response = Invoke-WebRequest -Uri "$GatewayUrl/api/hands" -Method Get -TimeoutSec 10 -ErrorAction Stop + if ($response.StatusCode -eq 200) { + Write-TestResult "RI-HAND-01" "Hands 端点返回 200" "PASS" + + $hands = $response.Content | ConvertFrom-Json + $handCount = ($hands | Measure-Object).Count + if ($handCount -gt 0) { + Write-TestResult "RI-HAND-02" "检测到 $handCount 个可用 Hands" "PASS" + } else { + Write-TestResult "RI-HAND-02" "检测到可用 Hands" "SKIP" "无 Hands 配置" + } + } else { + Write-TestResult "RI-HAND-01" "Hands 端点返回 200" "FAIL" "HTTP $($response.StatusCode)" + Write-TestResult "RI-HAND-02" "检测到可用 Hands" "SKIP" + } +} catch { + Write-TestResult "RI-HAND-01" "Hands 端点返回 200" "SKIP" "端点可能未实现" + Write-TestResult "RI-HAND-02" "检测到可用 Hands" "SKIP" +} + +Write-Log "" + +# ============================================================================= +# 8. 记忆持久化测试 +# ============================================================================= + +Write-Log "${Yellow}[8. 记忆持久化测试]${Reset}" + +$memoryDir = "$env:USERPROFILE\.openfang\data\memory" + +# RI-MEM-01: 检查记忆存储目录 +if (Test-Path $memoryDir) { + Write-TestResult "RI-MEM-01" "记忆存储目录存在" "PASS" + + $memoryFiles = Get-ChildItem -Path $memoryDir -Filter "*.json" -ErrorAction SilentlyContinue + $memoryCount = ($memoryFiles | Measure-Object).Count + if ($memoryCount -gt 0) { + Write-TestResult "RI-MEM-02" "检测到 $memoryCount 个记忆文件" "PASS" + } else { + Write-TestResult "RI-MEM-02" "检测到记忆文件" "SKIP" "尚无记忆数据" + } +} else { + Write-TestResult "RI-MEM-01" "记忆存储目录存在" "FAIL" "目录: $memoryDir" + Write-TestResult "RI-MEM-02" "检测到记忆文件" "SKIP" +} + +Write-Log "" + +# ============================================================================= +# 9. 配置验证测试 +# ============================================================================= + +Write-Log "${Yellow}[9. 配置验证测试]${Reset}" + +# RI-CFG-01: 主配置文件 +if (Test-Path "config/config.toml") { + Write-TestResult "RI-CFG-01" "主配置文件存在" "PASS" +} else { + Write-TestResult "RI-CFG-01" "主配置文件存在" "FAIL" +} + +# RI-CFG-02: OpenFang 配置文件 +$openfangConfig = "$env:USERPROFILE\.openfang\config.toml" +if (Test-Path $openfangConfig) { + Write-TestResult "RI-CFG-02" "OpenFang 配置文件存在" "PASS" +} else { + Write-TestResult "RI-CFG-02" "OpenFang 配置文件存在" "FAIL" "请运行: openfang init" +} + +# RI-CFG-03: 检查前端依赖 +if (Test-Path "desktop\node_modules") { + Write-TestResult "RI-CFG-03" "前端依赖已安装" "PASS" +} else { + Write-TestResult "RI-CFG-03" "前端依赖已安装" "FAIL" "请运行: cd desktop; pnpm install" +} + +Write-Log "" + +# ============================================================================= +# 10. 前端构建测试 +# ============================================================================= + +Write-Log "${Yellow}[10. 前端构建测试]${Reset}" + +# RI-UI-01: 检查 Tauri 配置 +if (Test-Path "desktop\src-tauri\tauri.conf.json") { + Write-TestResult "RI-UI-01" "Tauri 配置文件存在" "PASS" +} else { + Write-TestResult "RI-UI-01" "Tauri 配置文件存在" "FAIL" +} + +# RI-UI-02: 检查 package.json +if (Test-Path "desktop\package.json") { + Write-TestResult "RI-UI-02" "前端 package.json 存在" "PASS" +} else { + Write-TestResult "RI-UI-02" "前端 package.json 存在" "FAIL" +} + +Write-Log "" + +# ============================================================================= +# 测试总结 +# ============================================================================= + +Write-Log "${Blue}========================================${Reset}" +Write-Log "${Blue} 测试总结${Reset}" +Write-Log "${Blue}========================================${Reset}" +Write-Log "" +Write-Log "总测试数: $script:TestsRun" +Write-Log "${Green}通过: $script:TestsPassed${Reset}" +Write-Log "${Red}失败: $script:TestsFailed${Reset}" +Write-Log "${Yellow}跳过: $script:TestsSkipped${Reset}" +Write-Log "" + +# 计算成功率 +if ($script:TestsRun -gt 0) { + $successRate = [math]::Round(($script:TestsPassed / $script:TestsRun) * 100, 2) +} else { + $successRate = 0 +} + +# 生成 JSON 报告 +$reportFile = "$ResultsDir/report_$Timestamp.json" +$report = @{ + timestamp = (Get-Date -Format "o") + gateway_url = $GatewayUrl + summary = @{ + total = $script:TestsRun + passed = $script:TestsPassed + failed = $script:TestsFailed + skipped = $script:TestsSkipped + } + success_rate = $successRate + log_file = $LogFile +} | ConvertTo-Json -Depth 3 + +$report | Out-File -FilePath $reportFile -Encoding UTF8 + +Write-Log "报告已保存到: $reportFile" +Write-Log "日志已保存到: $LogFile" +Write-Log "" + +# 退出码 +if ($script:TestsFailed -gt 0) { + Write-Log "${Red}存在失败的测试,请检查上述详情${Reset}" + exit 1 +} else { + Write-Log "${Green}所有测试通过!${Reset}" + exit 0 +} diff --git a/scripts/tests/real-integration-test.sh b/scripts/tests/real-integration-test.sh new file mode 100644 index 0000000..0f61344 --- /dev/null +++ b/scripts/tests/real-integration-test.sh @@ -0,0 +1,479 @@ +#!/bin/bash +# ZCLAW 真实环境集成测试 +# 连接真实 OpenFang Kernel 验证完整数据流 +# +# 使用方法: +# 1. 确保 OpenFang Kernel 正在运行: openfang start +# 2. 设置 API Key: export ZHIPU_API_KEY=your_key +# 3. 运行测试: ./scripts/tests/real-integration-test.sh + +set -e + +# 颜色定义 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +# 配置 +GATEWAY_HOST="${GATEWAY_HOST:-127.0.0.1}" +GATEWAY_PORT="${GATEWAY_PORT:-50051}" +GATEWAY_URL="http://$GATEWAY_HOST:$GATEWAY_PORT" +RESULTS_DIR="test-results/integration" +TIMESTAMP=$(date +%Y%m%d_%H%M%S) +LOG_FILE="$RESULTS_DIR/integration_$TIMESTAMP.log" + +# 测试计数器 +TESTS_RUN=0 +TESTS_PASSED=0 +TESTS_FAILED=0 +TESTS_SKIPPED=0 + +# 创建结果目录 +mkdir -p "$RESULTS_DIR" + +# 日志函数 +log() { + echo -e "$1" | tee -a "$LOG_FILE" +} + +log_test() { + local id="$1" + local description="$2" + local status="$3" + local details="${4:-}" + + TESTS_RUN=$((TESTS_RUN + 1)) + + case "$status" in + PASS) + TESTS_PASSED=$((TESTS_PASSED + 1)) + log "${GREEN}✅ [$id] $description${NC}" + ;; + FAIL) + TESTS_FAILED=$((TESTS_FAILED + 1)) + log "${RED}❌ [$id] $description${NC}" + ;; + SKIP) + TESTS_SKIPPED=$((TESTS_SKIPPED + 1)) + log "${YELLOW}⏭️ [$id] $description (跳过)${NC}" + ;; + esac + + if [ -n "$details" ]; then + echo " $details" | tee -a "$LOG_FILE" + fi +} + +# 检查命令是否存在 +check_command() { + command -v "$1" >/dev/null 2>&1 +} + +# HTTP 请求辅助函数 +http_get() { + curl -s -w "\n%{http_code}" "$1" 2>/dev/null +} + +http_post() { + curl -s -w "\n%{http_code}" -X POST -H "Content-Type: application/json" -d "$2" "$1" 2>/dev/null +} + +# ============================================================================= +# 测试开始 +# ============================================================================= + +log "${BLUE}========================================${NC}" +log "${BLUE} ZCLAW 真实环境集成测试${NC}" +log "${BLUE}========================================${NC}" +log "" +log "测试时间: $(date)" +log "Gateway URL: $GATEWAY_URL" +log "日志文件: $LOG_FILE" +log "" + +# ============================================================================= +# 1. 环境检查 +# ============================================================================= + +log "${YELLOW}[1. 环境检查]${NC}" + +# RI-ENV-01: 检查 curl +if check_command curl; then + log_test "RI-ENV-01" "curl 可用" "PASS" +else + log_test "RI-ENV-01" "curl 可用" "FAIL" "需要安装 curl" + exit 1 +fi + +# RI-ENV-02: 检查 jq (可选,用于 JSON 解析) +if check_command jq; then + log_test "RI-ENV-02" "jq 可用" "PASS" + HAS_JQ=true +else + log_test "RI-ENV-02" "jq 可用" "SKIP" "建议安装 jq 以获得更好的 JSON 解析" + HAS_JQ=false +fi + +# RI-ENV-03: 检查 Node.js +if check_command node; then + NODE_VERSION=$(node -v) + log_test "RI-ENV-03" "Node.js 可用 ($NODE_VERSION)" "PASS" +else + log_test "RI-ENV-03" "Node.js 可用" "FAIL" "需要安装 Node.js" + exit 1 +fi + +log "" + +# ============================================================================= +# 2. Gateway 连接测试 +# ============================================================================= + +log "${YELLOW}[2. Gateway 连接测试]${NC}" + +# RI-GW-01: 检查端口可达性 +if check_command nc; then + if nc -z -w5 "$GATEWAY_HOST" "$GATEWAY_PORT" 2>/dev/null; then + log_test "RI-GW-01" "Gateway 端口 $GATEWAY_PORT 可达" "PASS" + else + log_test "RI-GW-01" "Gateway 端口 $GATEWAY_PORT 可达" "FAIL" "请确保 OpenFang 正在运行: openfang start" + fi +else + log_test "RI-GW-01" "Gateway 端口可达性" "SKIP" "nc 命令不可用" +fi + +# RI-GW-02: Health 端点 +RESPONSE=$(http_get "$GATEWAY_URL/api/health") +HTTP_CODE=$(echo "$RESPONSE" | tail -n1) +BODY=$(echo "$RESPONSE" | sed '$d') + +if [ "$HTTP_CODE" = "200" ]; then + log_test "RI-GW-02" "Health 端点返回 200" "PASS" "响应: $BODY" +else + log_test "RI-GW-02" "Health 端点返回 200" "FAIL" "HTTP $HTTP_CODE" +fi + +# RI-GW-03: Health 响应结构 +if [ "$HTTP_CODE" = "200" ]; then + if echo "$BODY" | grep -q '"status"'; then + log_test "RI-GW-03" "Health 响应包含 status 字段" "PASS" + else + log_test "RI-GW-03" "Health 响应包含 status 字段" "FAIL" "响应: $BODY" + fi +fi + +log "" + +# ============================================================================= +# 3. 模型配置测试 +# ============================================================================= + +log "${YELLOW}[3. 模型配置测试]${NC}" + +# RI-MOD-01: 获取可用模型列表 +RESPONSE=$(http_get "$GATEWAY_URL/api/models") +HTTP_CODE=$(echo "$RESPONSE" | tail -n1) +BODY=$(echo "$RESPONSE" | sed '$d') + +if [ "$HTTP_CODE" = "200" ]; then + log_test "RI-MOD-01" "Models 端点返回 200" "PASS" + + # 检查是否有模型 + if echo "$BODY" | grep -q '"id"'; then + MODEL_COUNT=$(echo "$BODY" | grep -o '"id"' | wc -l) + log_test "RI-MOD-02" "检测到 $MODEL_COUNT 个可用模型" "PASS" + else + log_test "RI-MOD-02" "检测到可用模型" "FAIL" "响应: $BODY" + fi +else + log_test "RI-MOD-01" "Models 端点返回 200" "FAIL" "HTTP $HTTP_CODE" + log_test "RI-MOD-02" "检测到可用模型" "SKIP" +fi + +# RI-MOD-03: 检查中文模型配置 +if [ -f "config/chinese-providers.toml" ]; then + log_test "RI-MOD-03" "中文模型配置文件存在" "PASS" +else + log_test "RI-MOD-03" "中文模型配置文件存在" "FAIL" "缺少 config/chinese-providers.toml" +fi + +log "" + +# ============================================================================= +# 4. Agent 管理测试 +# ============================================================================= + +log "${YELLOW}[4. Agent 管理测试]${NC}" + +# RI-AGT-01: 获取 Agent 列表 +RESPONSE=$(http_get "$GATEWAY_URL/api/agents") +HTTP_CODE=$(echo "$RESPONSE" | tail -n1) +BODY=$(echo "$RESPONSE" | sed '$d') + +if [ "$HTTP_CODE" = "200" ]; then + log_test "RI-AGT-01" "Agents 端点返回 200" "PASS" +else + log_test "RI-AGT-01" "Agents 端点返回 200" "FAIL" "HTTP $HTTP_CODE" +fi + +# RI-AGT-02: 检查默认 Agent +if [ "$HTTP_CODE" = "200" ]; then + if echo "$BODY" | grep -q '"id"\|"name"'; then + log_test "RI-AGT-02" "检测到 Agent 配置" "PASS" + else + log_test "RI-AGT-02" "检测到 Agent 配置" "FAIL" "响应: $BODY" + fi +fi + +log "" + +# ============================================================================= +# 5. API Key 验证测试 +# ============================================================================= + +log "${YELLOW}[5. API Key 验证测试]${NC}" + +# RI-KEY-01: 检查智谱 API Key +if [ -n "$ZHIPU_API_KEY" ]; then + log_test "RI-KEY-01" "智谱 API Key 已设置" "PASS" "长度: ${#ZHIPU_API_KEY} 字符" +else + log_test "RI-KEY-01" "智谱 API Key 已设置" "FAIL" "请设置: export ZHIPU_API_KEY=your_key" +fi + +# RI-KEY-02: 检查通义千问 API Key +if [ -n "$DASHSCOPE_API_KEY" ]; then + log_test "RI-KEY-02" "通义千问 API Key 已设置" "PASS" +else + log_test "RI-KEY-02" "通义千问 API Key 已设置" "SKIP" "可选: export DASHSCOPE_API_KEY=your_key" +fi + +# RI-KEY-03: 检查 Kimi API Key +if [ -n "$MOONSHOT_API_KEY" ]; then + log_test "RI-KEY-03" "Kimi API Key 已设置" "PASS" +else + log_test "RI-KEY-03" "Kimi API Key 已设置" "SKIP" "可选: export MOONSHOT_API_KEY=your_key" +fi + +log "" + +# ============================================================================= +# 6. 对话功能测试(需要 API Key) +# ============================================================================= + +log "${YELLOW}[6. 对话功能测试]${NC}" + +if [ -n "$ZHIPU_API_KEY" ]; then + # RI-CHAT-01: 发送测试消息 + TEST_MESSAGE="你好,这是一个测试消息。请简短回复。" + CHAT_PAYLOAD="{\"messages\":[{\"role\":\"user\",\"content\":\"$TEST_MESSAGE\"}],\"model\":\"glm-4-flash\",\"stream\":false}" + + log "发送测试消息: $TEST_MESSAGE" + + RESPONSE=$(http_post "$GATEWAY_URL/api/chat" "$CHAT_PAYLOAD") + HTTP_CODE=$(echo "$RESPONSE" | tail -n1) + BODY=$(echo "$RESPONSE" | sed '$d') + + if [ "$HTTP_CODE" = "200" ]; then + log_test "RI-CHAT-01" "对话请求成功" "PASS" + + # 检查响应内容 + if echo "$BODY" | grep -q '"content"\|"text"\|"message"'; then + log_test "RI-CHAT-02" "对话响应包含内容" "PASS" + + # 显示响应摘要 + if [ "$HAS_JQ" = true ]; then + CONTENT=$(echo "$BODY" | jq -r '.content // .text // .message // .choices[0].message.content // empty' 2>/dev/null | head -c 100) + if [ -n "$CONTENT" ]; then + log " 响应摘要: ${CONTENT}..." + fi + fi + else + log_test "RI-CHAT-02" "对话响应包含内容" "FAIL" "响应: $BODY" + fi + else + log_test "RI-CHAT-01" "对话请求成功" "FAIL" "HTTP $HTTP_CODE, 响应: $BODY" + log_test "RI-CHAT-02" "对话响应包含内容" "SKIP" + fi +else + log_test "RI-CHAT-01" "对话请求成功" "SKIP" "需要设置 ZHIPU_API_KEY" + log_test "RI-CHAT-02" "对话响应包含内容" "SKIP" +fi + +log "" + +# ============================================================================= +# 7. Hands 触发测试 +# ============================================================================= + +log "${YELLOW}[7. Hands 触发测试]${NC}" + +# RI-HAND-01: 获取可用 Hands +RESPONSE=$(http_get "$GATEWAY_URL/api/hands") +HTTP_CODE=$(echo "$RESPONSE" | tail -n1) +BODY=$(echo "$RESPONSE" | sed '$d') + +if [ "$HTTP_CODE" = "200" ]; then + log_test "RI-HAND-01" "Hands 端点返回 200" "PASS" + + # 检查可用的 Hands + if echo "$BODY" | grep -q '"name"\|"id"'; then + HAND_COUNT=$(echo "$BODY" | grep -o '"name"' | wc -l) + log_test "RI-HAND-02" "检测到 $HAND_COUNT 个可用 Hands" "PASS" + else + log_test "RI-HAND-02" "检测到可用 Hands" "SKIP" "无 Hands 配置" + fi +else + log_test "RI-HAND-01" "Hands 端点返回 200" "FAIL" "HTTP $HTTP_CODE (端点可能未实现)" + log_test "RI-HAND-02" "检测到可用 Hands" "SKIP" +fi + +log "" + +# ============================================================================= +# 8. 记忆持久化测试 +# ============================================================================= + +log "${YELLOW}[8. 记忆持久化测试]${NC}" + +# RI-MEM-01: 检查记忆存储目录 +MEMORY_DIR="$HOME/.openfang/data/memory" +if [ -d "$MEMORY_DIR" ]; then + log_test "RI-MEM-01" "记忆存储目录存在" "PASS" + + # 检查记忆文件 + MEMORY_COUNT=$(find "$MEMORY_DIR" -name "*.json" 2>/dev/null | wc -l) + if [ "$MEMORY_COUNT" -gt 0 ]; then + log_test "RI-MEM-02" "检测到 $MEMORY_COUNT 个记忆文件" "PASS" + else + log_test "RI-MEM-02" "检测到记忆文件" "SKIP" "尚无记忆数据" + fi +else + log_test "RI-MEM-01" "记忆存储目录存在" "FAIL" "目录: $MEMORY_DIR" + log_test "RI-MEM-02" "检测到记忆文件" "SKIP" +fi + +log "" + +# ============================================================================= +# 9. 配置验证测试 +# ============================================================================= + +log "${YELLOW}[9. 配置验证测试]${NC}" + +# RI-CFG-01: 主配置文件 +if [ -f "config/config.toml" ]; then + log_test "RI-CFG-01" "主配置文件存在" "PASS" +else + log_test "RI-CFG-01" "主配置文件存在" "FAIL" +fi + +# RI-CFG-02: 检查配置文件语法 +if [ -f "config/config.toml" ] && check_command node; then + if node -e "const fs = require('fs'); const content = fs.readFileSync('config/config.toml', 'utf-8'); if (content.includes('[') && content.includes('=')) process.exit(0); else process.exit(1);" 2>/dev/null; then + log_test "RI-CFG-02" "主配置文件语法正确" "PASS" + else + log_test "RI-CFG-02" "主配置文件语法正确" "FAIL" + fi +else + log_test "RI-CFG-02" "主配置文件语法正确" "SKIP" +fi + +# RI-CFG-03: OpenFang 配置文件 +OPENFANG_CONFIG="$HOME/.openfang/config.toml" +if [ -f "$OPENFANG_CONFIG" ]; then + log_test "RI-CFG-03" "OpenFang 配置文件存在" "PASS" +else + log_test "RI-CFG-03" "OpenFang 配置文件存在" "FAIL" "请运行: openfang init" +fi + +log "" + +# ============================================================================= +# 10. WebSocket 连接测试 +# ============================================================================= + +log "${YELLOW}[10. WebSocket 连接测试]${NC}" + +# RI-WS-01: WebSocket 升级检查 +WS_RESPONSE=$(curl -s -i -N \ + -H "Connection: Upgrade" \ + -H "Upgrade: websocket" \ + -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \ + -H "Sec-WebSocket-Version: 13" \ + "$GATEWAY_URL/ws" 2>/dev/null | head -1) + +if echo "$WS_RESPONSE" | grep -q "101"; then + log_test "RI-WS-01" "WebSocket 升级返回 101" "PASS" +else + log_test "RI-WS-01" "WebSocket 升级返回 101" "SKIP" "可能需要特定端点" +fi + +log "" + +# ============================================================================= +# 11. 前端构建测试 +# ============================================================================= + +log "${YELLOW}[11. 前端构建测试]${NC}" + +# RI-UI-01: 检查前端依赖 +if [ -d "desktop/node_modules" ]; then + log_test "RI-UI-01" "前端依赖已安装" "PASS" +else + log_test "RI-UI-01" "前端依赖已安装" "FAIL" "请运行: cd desktop && pnpm install" +fi + +# RI-UI-02: 检查 Tauri 配置 +if [ -f "desktop/src-tauri/tauri.conf.json" ]; then + log_test "RI-UI-02" "Tauri 配置文件存在" "PASS" +else + log_test "RI-UI-02" "Tauri 配置文件存在" "FAIL" +fi + +log "" + +# ============================================================================= +# 测试总结 +# ============================================================================= + +log "${BLUE}========================================${NC}" +log "${BLUE} 测试总结${NC}" +log "${BLUE}========================================${NC}" +log "" +log "总测试数: $TESTS_RUN" +log "${GREEN}通过: $TESTS_PASSED${NC}" +log "${RED}失败: $TESTS_FAILED${NC}" +log "${YELLOW}跳过: $TESTS_SKIPPED${NC}" +log "" + +# 生成 JSON 报告 +REPORT_FILE="$RESULTS_DIR/report_$TIMESTAMP.json" +cat > "$REPORT_FILE" << EOF +{ + "timestamp": "$(date -Iseconds)", + "gateway_url": "$GATEWAY_URL", + "summary": { + "total": $TESTS_RUN, + "passed": $TESTS_PASSED, + "failed": $TESTS_FAILED, + "skipped": $TESTS_SKIPPED + }, + "success_rate": $(echo "scale=2; $TESTS_PASSED * 100 / $TESTS_RUN" | bc 2>/dev/null || echo "0"), + "log_file": "$LOG_FILE" +} +EOF + +log "报告已保存到: $REPORT_FILE" +log "日志已保存到: $LOG_FILE" +log "" + +# 退出码 +if [ $TESTS_FAILED -gt 0 ]; then + log "${RED}存在失败的测试,请检查上述详情${NC}" + exit 1 +else + log "${GREEN}所有测试通过!${NC}" + exit 0 +fi