fix: 第一轮修复 - 记忆管理/IoT操控/历史消息持久化/动作消息/链路优化/安全配置

- 修复记忆管理数据库连接不可用 (ai-core重编译+Unicode修复)
- 修复IoT子会话工具调用链路日志缺失
- 新增最终审查子会话(review_provider) 支持消息格式解析拆分
- 实现历史消息持久化(后端存储+前端分页加载)
- 前端新增动作消息(ActionMessage)类型和渲染
- 优化对话链路速度(非阻塞子会话+快速问候通道)
- JWT密钥环境变量化(无默认值启动panic)
- Token自动刷新机制(401拦截器+refresh接口)
- WebSocket指数退避重连(jitter+最大10次)
- localStorage清理一致性(cyrene_前缀+版本检查)
- IoT环境变量统一为IOT_SERVICE_URL
This commit is contained in:
2026-05-21 23:10:07 +08:00
parent 8b7d4ec19a
commit a058b0ab8e
53 changed files with 5535 additions and 241 deletions
+383
View File
@@ -0,0 +1,383 @@
#!/bin/bash
# Round 11 API Contract Test Script
# Tests all API endpoints for correctness, error handling, and boundary conditions
BASE="http://localhost:8080/api/v1"
PASS=0
FAIL=0
RESULTS=""
# Helper: run a test and record result
test_case() {
local name="$1"
local expected="$2"
local method="$3"
local url="$4"
local data="$5"
local auth="$6"
if [ -n "$data" ]; then
resp=$(curl -s -w "\n%{http_code}" -X "$method" "$url" \
-H "Content-Type: application/json" \
${auth:+-H "Authorization: Bearer $auth"} \
-d "$data" 2>&1)
else
resp=$(curl -s -w "\n%{http_code}" -X "$method" "$url" \
-H "Content-Type: application/json" \
${auth:+-H "Authorization: Bearer $auth"} 2>&1)
fi
actual=$(echo "$resp" | tail -1)
body=$(echo "$resp" | sed '$d')
if [ "$actual" = "$expected" ]; then
PASS=$((PASS + 1))
RESULTS+="PASS | $name | $expected | $actual\n"
else
FAIL=$((FAIL + 1))
RESULTS+="FAIL | $name | $expected | $actual | body=${body:0:120}\n"
fi
}
echo "============================================"
echo " Round 11 API Contract Test Suite"
echo " Gateway: $BASE"
echo " Started at $(date '+%Y-%m-%d %H:%M:%S')"
echo "============================================"
echo ""
# ============================================================
# PART 1: Health + Public
# ============================================================
echo "--- Part 1: Health & Public Endpoints ---"
test_case "GET /health" "200" "GET" "$BASE/health"
test_case "HEAD /health" "200" "HEAD" "$BASE/health"
# ============================================================
# PART 2: Auth - Register
# ============================================================
echo "--- Part 2: Auth Register ---"
# Use unique username to avoid conflicts
UN="testapi_$(date +%s)"
PW="TestPass123!"
test_case "Register: missing username" "400" "POST" "$BASE/auth/register" \
'{"password":"Test123!","email":"test@test.com","nickname":"Test","verify_code":"000000"}'
test_case "Register: missing password" "400" "POST" "$BASE/auth/register" \
'{"username":"testuser","email":"test@test.com","nickname":"Test","verify_code":"000000"}'
test_case "Register: username too short (<3)" "400" "POST" "$BASE/auth/register" \
'{"username":"ab","password":"Test123!","email":"test@test.com","nickname":"Test","verify_code":"000000"}'
test_case "Register: username too long (>32)" "400" "POST" "$BASE/auth/register" \
'{"username":"abcdefghijklmnopqrstuvwxyz1234567890","password":"Test123!","email":"test@test.com","nickname":"Test","verify_code":"000000"}'
test_case "Register: username with special chars" "400" "POST" "$BASE/auth/register" \
'{"username":"user@name!","password":"Test123!","email":"test@test.com","nickname":"Test","verify_code":"000000"}'
test_case "Register: password too short (<6)" "400" "POST" "$BASE/auth/register" \
'{"username":"test_abc","password":"Ab1!","email":"test@test.com","nickname":"Test","verify_code":"000000"}'
test_case "Register: normal registration" "201" "POST" "$BASE/auth/register" \
"{\"username\":\"$UN\",\"password\":\"$PW\",\"email\":\"test@test.com\",\"nickname\":\"TestUser\",\"verify_code\":\"000000\"}"
# Try to register same username again
test_case "Register: duplicate username" "409" "POST" "$BASE/auth/register" \
"{\"username\":\"$UN\",\"password\":\"$PW\",\"email\":\"test@test.com\",\"nickname\":\"TestUser\",\"verify_code\":\"000000\"}"
# ============================================================
# PART 3: Auth - Login
# ============================================================
echo "--- Part 3: Auth Login ---"
test_case "Login: wrong username" "401" "POST" "$BASE/auth/login" \
'{"username":"nonexistent_user_99","password":"TestPass123!"}'
test_case "Login: wrong password" "401" "POST" "$BASE/auth/login" \
"{\"username\":\"$UN\",\"password\":\"WrongPass999!\"}"
# Correct login
LOGIN_RESP=$(curl -s -w "\n%{http_code}" -X POST "$BASE/auth/login" \
-H "Content-Type: application/json" \
-d "{\"username\":\"$UN\",\"password\":\"$PW\"}")
LOGIN_CODE=$(echo "$LOGIN_RESP" | tail -1)
LOGIN_BODY=$(echo "$LOGIN_RESP" | sed '$d')
if [ "$LOGIN_CODE" = "200" ]; then
TOKEN=$(echo "$LOGIN_BODY" | python3 -c "import sys,json;print(json.load(sys.stdin).get('token',''))" 2>/dev/null)
USER_ID=$(echo "$LOGIN_BODY" | python3 -c "import sys,json;print(json.load(sys.stdin).get('user_id',''))" 2>/dev/null)
PASS=$((PASS + 1))
RESULTS+="PASS | Login: correct credentials | 200 | 200\n"
echo " -> Got token OK, user_id=$USER_ID"
else
FAIL=$((FAIL + 1))
RESULTS+="FAIL | Login: correct credentials | 200 | $LOGIN_CODE | body=${LOGIN_BODY:0:120}\n"
echo " -> Login FAILED: $LOGIN_CODE - $LOGIN_BODY"
TOKEN=""
fi
# Admin login
ADMIN_RESP=$(curl -s -w "\n%{http_code}" -X POST "$BASE/auth/login" \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin123"}')
ADMIN_CODE=$(echo "$ADMIN_RESP" | tail -1)
ADMIN_BODY=$(echo "$ADMIN_RESP" | sed '$d')
if [ "$ADMIN_CODE" = "200" ]; then
ADMIN_TOKEN=$(echo "$ADMIN_BODY" | python3 -c "import sys,json;print(json.load(sys.stdin).get('token',''))" 2>/dev/null)
PASS=$((PASS + 1))
RESULTS+="PASS | Login: admin credentials | 200 | 200\n"
echo " -> Admin token OK"
else
FAIL=$((FAIL + 1))
RESULTS+="FAIL | Login: admin credentials | 200 | $ADMIN_CODE | body=${ADMIN_BODY:0:120}\n"
echo " -> Admin login FAILED: $ADMIN_CODE"
ADMIN_TOKEN=""
fi
# Validate JWT token - try to use it
if [ -n "$TOKEN" ]; then
REFRESH_RESP=$(curl -s -w "\n%{http_code}" -X POST "$BASE/auth/refresh" \
-H "Authorization: Bearer $TOKEN")
REFRESH_CODE=$(echo "$REFRESH_RESP" | tail -1)
if [ "$REFRESH_CODE" = "200" ]; then
PASS=$((PASS + 1))
RESULTS+="PASS | JWT token valid (refresh) | 200 | 200\n"
else
FAIL=$((FAIL + 1))
RESULTS+="FAIL | JWT token valid (refresh) | 200 | $REFRESH_CODE\n"
fi
fi
# Login: missing fields
test_case "Login: missing password" "400" "POST" "$BASE/auth/login" \
'{"username":"testuser"}'
test_case "Login: empty body" "400" "POST" "$BASE/auth/login" '{}'
test_case "Login: username format invalid" "400" "POST" "$BASE/auth/login" \
'{"username":"ab","password":"Test123!"}'
# ============================================================
# PART 4: Session API
# ============================================================
echo "--- Part 4: Session API ---"
# Unauthenticated
test_case "Sessions: list no auth" "401" "GET" "$BASE/sessions"
test_case "Sessions: create no auth" "401" "POST" "$BASE/sessions" '{"title":"Test"}'
if [ -n "$TOKEN" ]; then
# Authenticated - List
test_case "Sessions: list with auth" "200" "GET" "$BASE/sessions" "" "$TOKEN"
# Create session
SESS_RESP=$(curl -s -w "\n%{http_code}" -X POST "$BASE/sessions" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"title":"Round 11 Test Session"}')
SESS_CODE=$(echo "$SESS_RESP" | tail -1)
SESS_BODY=$(echo "$SESS_RESP" | sed '$d')
SESS_ID=$(echo "$SESS_BODY" | python3 -c "import sys,json;print(json.load(sys.stdin).get('id',''))" 2>/dev/null)
if [ "$SESS_CODE" = "201" ]; then
PASS=$((PASS + 1))
RESULTS+="PASS | Sessions: create | 201 | 201\n"
echo " -> Created session: $SESS_ID"
else
FAIL=$((FAIL + 1))
RESULTS+="FAIL | Sessions: create | 201 | $SESS_CODE\n"
fi
# Get session
if [ -n "$SESS_ID" ]; then
test_case "Sessions: get existing" "200" "GET" "$BASE/sessions/$SESS_ID" "" "$TOKEN"
fi
# Get non-existent session
test_case "Sessions: get non-existent" "404" "GET" "$BASE/sessions/session_nonexistent123" "" "$TOKEN"
# Create with empty title (should default)
test_case "Sessions: create empty title" "201" "POST" "$BASE/sessions" '{}' "$TOKEN"
# Delete session
if [ -n "$SESS_ID" ]; then
test_case "Sessions: delete existing" "200" "DELETE" "$BASE/sessions/$SESS_ID" "" "$TOKEN"
fi
# Delete non-existent
test_case "Sessions: delete non-existent" "200" "DELETE" "$BASE/sessions/session_nonexistent" "" "$TOKEN"
# Get messages for non-existent session
test_case "Sessions: messages non-existent" "200" "GET" "$BASE/sessions/session_nonexistent/messages" "" "$TOKEN"
# Test cross-user access
if [ -n "$ADMIN_TOKEN" ] && [ -n "$SESS_ID" ]; then
echo " -> Testing cross-user access..."
fi
else
FAIL=$((FAIL + 6))
echo " -> SKIPPED (no token)"
fi
# ============================================================
# PART 5: Files API
# ============================================================
echo "--- Part 5: Files API ---"
test_case "Files: list no auth" "401" "GET" "$BASE/files"
if [ -n "$TOKEN" ]; then
test_case "Files: list with auth" "200" "GET" "$BASE/files" "" "$TOKEN"
test_case "Files: get non-existent" "404" "GET" "$BASE/files/file_nonexistent" "" "$TOKEN"
else
FAIL=$((FAIL + 2))
fi
# ============================================================
# PART 6: Knowledge API
# ============================================================
echo "--- Part 6: Knowledge API ---"
test_case "Knowledge: list bases no auth" "401" "GET" "$BASE/knowledge/bases"
if [ -n "$TOKEN" ]; then
test_case "Knowledge: list bases with auth" "200" "GET" "$BASE/knowledge/bases" "" "$TOKEN"
test_case "Knowledge: get non-existent" "404" "GET" "$BASE/knowledge/bases/kb_nonexistent" "" "$TOKEN"
else
FAIL=$((FAIL + 2))
fi
# ============================================================
# PART 7: Automation API
# ============================================================
echo "--- Part 7: Automation API ---"
test_case "Automation: list rules no auth" "401" "GET" "$BASE/automation/rules"
if [ -n "$TOKEN" ]; then
test_case "Automation: list rules with auth" "200" "GET" "$BASE/automation/rules" "" "$TOKEN"
test_case "Automation: list scenes with auth" "200" "GET" "$BASE/automation/scenes" "" "$TOKEN"
test_case "Automation: get non-existent rule" "404" "GET" "$BASE/automation/rules/rule_nonexistent" "" "$TOKEN"
else
FAIL=$((FAIL + 3))
fi
# ============================================================
# PART 8: Reminders API
# ============================================================
echo "--- Part 8: Reminders API ---"
test_case "Reminders: list no auth" "401" "GET" "$BASE/reminders"
if [ -n "$TOKEN" ]; then
test_case "Reminders: list with auth" "200" "GET" "$BASE/reminders" "" "$TOKEN"
test_case "Reminders: create missing fields" "400" "POST" "$BASE/reminders" '{}' "$TOKEN"
else
FAIL=$((FAIL + 2))
fi
# ============================================================
# PART 9: Briefings API
# ============================================================
echo "--- Part 9: Briefings API ---"
test_case "Briefings: get no auth" "401" "GET" "$BASE/briefings"
if [ -n "$TOKEN" ]; then
test_case "Briefings: get with auth" "200" "GET" "$BASE/briefings" "" "$TOKEN"
test_case "Briefings: latest with auth" "200" "GET" "$BASE/briefings/latest" "" "$TOKEN"
else
FAIL=$((FAIL + 2))
fi
# ============================================================
# PART 10: Notifications API
# ============================================================
echo "--- Part 10: Notifications API ---"
test_case "Notifications: push no auth" "401" "POST" "$BASE/notifications/push" '{"message":"test"}'
if [ -n "$TOKEN" ]; then
test_case "Notifications: push empty" "400" "POST" "$BASE/notifications/push" '{}' "$TOKEN"
else
FAIL=$((FAIL + 1))
fi
# ============================================================
# PART 11: Memories API
# ============================================================
echo "--- Part 11: Memories API ---"
test_case "Memories: search no auth" "401" "GET" "$BASE/memory/search"
if [ -n "$TOKEN" ]; then
test_case "Memories: search with auth" "200" "GET" "$BASE/memory/search?q=test" "" "$TOKEN"
test_case "Memories: list with auth" "200" "GET" "$BASE/memory" "" "$TOKEN"
test_case "Memories: add missing fields" "400" "POST" "$BASE/memory" '{}' "$TOKEN"
else
FAIL=$((FAIL + 3))
fi
# ============================================================
# PART 12: Voice API
# ============================================================
echo "--- Part 12: Voice API ---"
if [ -n "$TOKEN" ]; then
test_case "Voice: status with auth" "200" "GET" "$BASE/voice/status" "" "$TOKEN"
else
FAIL=$((FAIL + 1))
fi
# ============================================================
# PART 13: Admin endpoints
# ============================================================
echo "--- Part 13: Admin endpoints ---"
if [ -n "$ADMIN_TOKEN" ]; then
test_case "Admin: sessions with admin" "200" "GET" "$BASE/admin/sessions" "" "$ADMIN_TOKEN"
test_case "Admin: sessions/active with admin" "200" "GET" "$BASE/admin/sessions/active" "" "$ADMIN_TOKEN"
else
FAIL=$((FAIL + 2))
fi
# Non-admin user trying admin
if [ -n "$TOKEN" ]; then
test_case "Admin: sessions without admin" "403" "GET" "$BASE/admin/sessions" "" "$TOKEN"
else
FAIL=$((FAIL + 1))
fi
# ============================================================
# PART 14: Invalid token
# ============================================================
echo "--- Part 14: Invalid Token ---"
test_case "Sessions: invalid token" "401" "GET" "$BASE/sessions" "" "invalidtoken12345"
test_case "Sessions: expired/malformed" "401" "GET" "$BASE/sessions" "" "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8k"
# ============================================================
# PART 15: Token refresh
# ============================================================
echo "--- Part 15: Token Refresh ---"
test_case "Refresh: no auth" "401" "POST" "$BASE/auth/refresh"
test_case "Refresh: invalid token" "401" "POST" "$BASE/auth/refresh" "" "invalidtoken"
# ============================================================
# Summary
# ============================================================
echo ""
echo "============================================"
echo " TEST SUMMARY"
echo "============================================"
TOTAL=$((PASS + FAIL))
echo "Total: $TOTAL | Passed: $PASS | Failed: $FAIL"
echo ""
echo -e "$RESULTS" | column -t -s '|'
echo ""
echo "============================================"
echo " Test run completed at $(date '+%Y-%m-%d %H:%M:%S')"
echo "============================================"
# Save the token and user info for reference
if [ -n "$TOKEN" ]; then
echo "TOKEN=$TOKEN" > /tmp/round11_testenv
echo "USER_ID=$USER_ID" >> /tmp/round11_testenv
echo "ADMIN_TOKEN=$ADMIN_TOKEN" >> /tmp/round11_testenv
echo "TEST_USER=$UN" >> /tmp/round11_testenv
fi