#!/bin/bash # ZCLAW 真实环境集成测试 # 连接真实 ZCLAW Kernel 验证完整数据流 # # 使用方法: # 1. 确保 ZCLAW Kernel 正在运行: zclaw 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" "请确保 ZCLAW 正在运行: zclaw 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/.zclaw/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: ZCLAW 配置文件 ZCLAW_CONFIG="$HOME/.zclaw/config.toml" if [ -f "$ZCLAW_CONFIG" ]; then log_test "RI-CFG-03" "ZCLAW 配置文件存在" "PASS" else log_test "RI-CFG-03" "ZCLAW 配置文件存在" "FAIL" "请运行: zclaw 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