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:
Vendored
+383
@@ -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
|
||||
Reference in New Issue
Block a user