Files
memoryweave/tests/integration_test.sh
T
小唯 e900e4073f GA-P0-7 (t_dd64345a): 退役裸路由 /api/v1/feedback 写点(曾写旧 not_useful_count)
- server.go: 删 mux.HandleFunc("/api/v1/feedback", api.Feedback) 注册
- core.go: 删 Feedback handler(按 useful 写 useful_count/not_useful_count)
- 回归守卫 server_route_guard_test.go: 断言裸路由不注册 + 4 子路由在
- tests/integration_test.sh: 反馈改走 /api/v1/feedback/useful
- README.md: 裸路由文档改为拆分后 4 子路由

AC2 证伪无调用方:grep 'api/v1/feedback"' 仅命中自身注册行(现已删)。
AC3 退废后 not_useful_count 仅剩 schema/struct 向后兼容声明、[DEPRECATED] IncrementNotUseful(无路由调用)、迁移工具读、注释。
2026-10-12 21:38:01 +08:00

498 lines
15 KiB
Bash
Executable File

#!/bin/bash
# =================================================================
# 织忆 (MemoryWeave) 端到端集成测试
# =================================================================
# API base: http://localhost:7821
# API Key: zhiyi-dev-key-2026
# =================================================================
# ── 配置 ──────────────────────────────────────────────────────
API_BASE="http://localhost:7821"
API_KEY="zhiyi-dev-key-2026"
CLI="/home/muc/.local/bin/zhiyi-cli"
NAMESPACE="test-integration"
AGENT_ID="test-agent-$$"
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 测试计数器
TESTS_RUN=0
TESTS_PASSED=0
TESTS_FAILED=0
# ── 辅助函数 ──────────────────────────────────────────────────
log_info() { echo -e "${BLUE}[INFO]${NC} $*"; }
log_pass() { echo -e "${GREEN}[PASS]${NC} $*"; TESTS_PASSED=$((TESTS_PASSED+1)); }
log_fail() { echo -e "${RED}[FAIL]${NC} $*" >&2; TESTS_FAILED=$((TESTS_FAILED+1)); }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
header() { echo ""; echo "══════════════════════════════════════"; echo " $*"; echo "══════════════════════════════════════"; }
# API 调用封装(不使用 -f,允许非 2xx 响应)
api_get() {
local path="$1"
curl -s -H "X-API-Key: ${API_KEY}" "${API_BASE}${path}" 2>&1
}
api_post() {
local path="$1"
local body="$2"
curl -s -H "X-API-Key: ${API_KEY}" \
-H "Content-Type: application/json" \
-d "${body}" \
"${API_BASE}${path}" 2>&1
}
# 断言函数
assert_eq() {
local expected="$1"
local actual="$2"
local msg="$3"
TESTS_RUN=$((TESTS_RUN+1))
if [[ "$expected" == "$actual" ]]; then
log_pass "${msg}"
return 0
else
log_fail "${msg} - expected: '${expected}', got: '${actual}'"
return 1
fi
}
assert_contains() {
local haystack="$1"
local needle="$2"
local msg="$3"
TESTS_RUN=$((TESTS_RUN+1))
if echo "$haystack" | grep -q "$needle"; then
log_pass "${msg}"
return 0
else
log_fail "${msg} - '$needle' not found in response"
return 1
fi
}
assert_not_empty() {
local value="$1"
local msg="$2"
TESTS_RUN=$((TESTS_RUN+1))
if [[ -n "$value" ]]; then
log_pass "${msg}"
return 0
else
log_fail "${msg} - empty value"
return 1
fi
}
assert_json_valid() {
local json_str="$1"
local msg="$2"
TESTS_RUN=$((TESTS_RUN+1))
if echo "$json_str" | python3 -c "import sys,json; json.load(sys.stdin); print('ok')" > /dev/null 2>&1; then
log_pass "${msg}"
return 0
else
log_fail "${msg} - invalid JSON"
return 1
fi
}
# ── SETUP ─────────────────────────────────────────────────────
setup() {
log_info "执行测试前检查..."
# 检查服务是否运行
local health_resp
health_resp=$(curl -s "${API_BASE}/health" 2>&1)
if ! echo "$health_resp" | python3 -c "import sys,json; json.load(sys.stdin)" > /dev/null 2>&1; then
log_fail "服务未运行,请先启动 zhiyid (health check failed)"
exit 1
fi
# 检查 CLI 是否存在
if [[ ! -x "$CLI" ]]; then
log_warn "CLI 未找到或不可执行: $CLI"
fi
log_info "SETUP 完成"
}
# ── TEARDOWN ──────────────────────────────────────────────────
teardown() {
log_info "清理测试数据..."
log_info "TEARDOWN 完成"
}
# ── 测试用例 ──────────────────────────────────────────────────
test_health() {
header "测试: 健康检查 /health"
local resp
resp=$(api_get "/health")
assert_contains "$resp" '"status":"ok"' "health 端点返回 ok 状态"
assert_contains "$resp" '"service":"zhiyid"' "health 端点包含服务名"
}
test_stats() {
header "测试: 统计接口 /api/v1/stats"
local resp
resp=$(api_get "/api/v1/stats")
assert_contains "$resp" '"total_memories"' "stats 包含记忆总数字段"
assert_contains "$resp" '"total_episodes"' "stats 包含 episode 总数字段"
assert_contains "$resp" '"backend"' "stats 包含后端类型字段"
log_info "stats 响应: $(echo $resp | head -c 200)..."
}
test_graph_stats() {
header "测试: 图谱统计 /api/v1/graph/stats"
local resp
resp=$(api_get "/api/v1/graph/stats")
assert_contains "$resp" '"node_count"' "graph/stats 包含节点数"
assert_contains "$resp" '"edge_count"' "graph/stats 包含边数"
}
test_commit_recall_feedback() {
header "测试: commit -> recall -> feedback 完整链路"
# Step 1: Commit
log_info "步骤1: 提交记忆 (commit)"
local unique_content="集成测试记忆 $(date +%s) - 随机内容: $RANDOM"
local commit_resp
commit_resp=$(api_post "/api/v1/commit" "{
\"agent_id\": \"${AGENT_ID}\",
\"namespace\": \"${NAMESPACE}\",
\"content\": \"${unique_content}\",
\"category\": \"test\"
}")
log_info "commit 响应: $commit_resp"
assert_contains "$commit_resp" '"status"' "commit 包含状态字段"
# 提取 memory_id(如果存在)
local memory_id=""
if echo "$commit_resp" | grep -q '"memory_id"'; then
memory_id=$(echo "$commit_resp" | grep -o '"memory_id":"[^"]*"' | cut -d'"' -f4 | head -1)
fi
local episode_id=""
if echo "$commit_resp" | grep -q '"episode_id"'; then
episode_id=$(echo "$commit_resp" | grep -o '"episode_id":"[^"]*"' | cut -d'"' -f4 | head -1)
fi
log_info "获取到 episode_id: ${episode_id}, memory_id: ${memory_id}"
assert_not_empty "$episode_id" "commit 返回 episode_id"
# 等待索引更新
sleep 1
# Step 2: Recall
log_info "步骤2: 检索记忆 (recall)"
local recall_resp
recall_resp=$(api_post "/api/v1/recall" "{
\"query\": \"集成测试记忆\",
\"namespace\": \"${NAMESPACE}\",
\"top_k\": 10
}")
log_info "recall 响应前200字符: $(echo $recall_resp | head -c 200)..."
assert_contains "$recall_resp" '"results"' "recall 包含 results 字段"
assert_contains "$recall_resp" '"count"' "recall 包含 count 字段"
# 尝试从 recall 结果中提取 memory_id
if [[ -z "$memory_id" ]]; then
memory_id=$(echo "$recall_resp" | grep -o '"id":"mem_[^"]*"' | head -1 | cut -d'"' -f4)
log_info "从 recall 结果提取 memory_id: ${memory_id}"
fi
# Step 3: Feedback
if [[ -n "$memory_id" ]]; then
log_info "步骤3: 反馈 (feedback)"
local feedback_resp
feedback_resp=$(api_post "/api/v1/feedback/useful" "{
\"memory_id\": \"${memory_id}\"
}")
log_info "feedback 响应: $feedback_resp"
assert_contains "$feedback_resp" '"status"' "feedback 包含状态字段"
else
log_warn "无法获取 memory_id,跳过 feedback 测试"
fi
log_info "commit -> recall -> feedback 链路测试完成"
}
test_navigate() {
header "测试: 图谱导航 /api/v1/graph/navigate"
# 先创建一条可导航的记忆
local nav_entity="测试实体_$$"
api_post "/api/v1/commit" "{
\"agent_id\": \"${AGENT_ID}\",
\"namespace\": \"${NAMESPACE}\",
\"content\": \"${nav_entity} 是一个测试实体,用于图谱导航测试\",
\"category\": \"test\"
}" > /dev/null 2>&1
sleep 1
# 执行导航查询
local nav_resp
nav_resp=$(api_post "/api/v1/graph/navigate" "{
\"entity\": \"${nav_entity}\",
\"max_hops\": 2,
\"namespace\": \"${NAMESPACE}\"
}")
log_info "navigate 响应: $(echo $nav_resp | head -c 200)..."
assert_json_valid "$nav_resp" "navigate 返回有效 JSON"
# 双向导航测试
local nav_bi_resp
nav_bi_resp=$(api_post "/api/v1/graph/navigate" "{
\"source\": \"${nav_entity}\",
\"target\": \"另一个实体\",
\"max_hops\": 2,
\"namespace\": \"${NAMESPACE}\"
}")
assert_json_valid "$nav_bi_resp" "双向 navigate 返回有效 JSON"
}
test_concurrent() {
header "测试: 并发请求"
local pids=()
local outputs=()
# 同时发起 5 个并发请求
log_info "发起 5 个并发 commit 请求..."
for i in {1..5}; do
(
api_post "/api/v1/commit" "{
\"agent_id\": \"${AGENT_ID}\",
\"namespace\": \"${NAMESPACE}\",
\"content\": \"并发测试记忆 ${i} - $(date +%s%N)\",
\"category\": \"concurrent-test\"
}" 2>&1
) &
pids+=($!)
done
# 等待所有请求完成
local all_ok=true
for pid in "${pids[@]}"; do
if ! wait "$pid"; then
all_ok=false
fi
done
TESTS_RUN=$((TESTS_RUN+1))
if $all_ok; then
log_pass "5 个并发 commit 请求全部成功"
else
log_fail "部分并发请求失败"
fi
# 测试并发 recall
log_info "发起 5 个并发 recall 请求..."
pids=()
for i in {1..5}; do
(
api_post "/api/v1/recall" "{
\"query\": \"并发测试\",
\"namespace\": \"${NAMESPACE}\",
\"top_k\": 5
}" 2>&1
) &
pids+=($!)
done
all_ok=true
for pid in "${pids[@]}"; do
if ! wait "$pid"; then
all_ok=false
fi
done
TESTS_RUN=$((TESTS_RUN+1))
if $all_ok; then
log_pass "5 个并发 recall 请求全部成功"
else
log_fail "部分并发 recall 请求失败"
fi
}
test_batch_commit() {
header "测试: 批量提交 /api/v1/batch-commit"
local batch_resp
batch_resp=$(api_post "/api/v1/batch-commit" "{
\"namespace\": \"${NAMESPACE}\",
\"items\": [
{\"content\": \"批量测试项1\", \"tags\": [\"test\", \"batch\"]},
{\"content\": \"批量测试项2\", \"tags\": [\"test\", \"batch\"]},
{\"content\": \"批量测试项3\", \"tags\": [\"test\", \"batch\"]}
]
}")
log_info "batch-commit 响应: $(echo $batch_resp | head -c 200)..."
assert_json_valid "$batch_resp" "batch-commit 返回有效 JSON"
}
test_cli_consistency() {
header "测试: CLI 与 API 一致性"
if [[ ! -x "$CLI" ]]; then
log_warn "CLI 不可用,跳过 CLI 一致性测试"
return
fi
# 测试 CLI stats 与 API stats 一致性
log_info "比较 CLI stats 与 API stats..."
local cli_stats
cli_stats=$("$CLI" -url "${API_BASE}" -key "${API_KEY}" -n "${NAMESPACE}" stats 2>&1)
log_info "CLI stats 输出: $cli_stats"
TESTS_RUN=$((TESTS_RUN+1))
if [[ $? -eq 0 ]]; then
log_pass "CLI stats 命令执行成功"
else
log_fail "CLI stats 命令执行失败"
fi
# 测试 CLI recall 与 API recall
log_info "比较 CLI recall 与 API recall..."
local cli_recall
cli_recall=$("$CLI" -url "${API_BASE}" -key "${API_KEY}" -n "${NAMESPACE}" recall "测试" 2>&1)
local api_recall
api_recall=$(api_post "/api/v1/recall" "{
\"query\": \"测试\",
\"namespace\": \"${NAMESPACE}\",
\"top_k\": 10
}")
log_info "CLI recall 输出前100字符: $(echo $cli_recall | head -c 100)..."
log_info "API recall 输出前100字符: $(echo $api_recall | head -c 100)..."
# API recall 应返回有效 JSON
assert_json_valid "$api_recall" "CLI 和 API recall 都返回有效响应"
}
test_recall_debug() {
header "测试: Recall 诊断接口 /api/v1/recall/debug"
local debug_resp
debug_resp=$(api_post "/api/v1/recall/debug" "{
\"query\": \"集成测试\",
\"namespace\": \"${NAMESPACE}\",
\"top_k\": 5
}")
log_info "recall/debug 响应: $(echo $debug_resp | head -c 200)..."
assert_json_valid "$debug_resp" "recall/debug 返回有效 JSON"
assert_contains "$debug_resp" '"steps"' "recall/debug 包含诊断步骤"
assert_contains "$debug_resp" '"total_ms"' "recall/debug 包含耗时信息"
}
test_pagerank() {
header "测试: PageRank /api/v1/graph/pagerank"
local pr_resp
pr_resp=$(api_get "/api/v1/graph/pagerank")
log_info "pagerank 响应: $(echo $pr_resp | head -c 200)..."
assert_json_valid "$pr_resp" "pagerank 返回有效 JSON"
}
test_distill_quota() {
header "测试: 蒸馏配额 /api/v1/distill/quota"
local quota_resp
quota_resp=$(api_get "/api/v1/distill/quota")
log_info "distill/quota 响应: $quota_resp"
assert_json_valid "$quota_resp" "distill/quota 返回有效 JSON"
}
test_distill_status() {
header "测试: 蒸馏状态 /api/v1/distill/status"
local status_resp
status_resp=$(api_get "/api/v1/distill/status")
log_info "distill/status 响应: $status_resp"
assert_json_valid "$status_resp" "distill/status 返回有效 JSON"
}
# ── 运行所有测试 ──────────────────────────────────────────────
main() {
echo ""
echo "╔════════════════════════════════════════════════════════╗"
echo "║ 织忆 (MemoryWeave) 端到端集成测试 ║"
echo "║ API: ${API_BASE} ║"
echo "╚════════════════════════════════════════════════════════╝"
setup
# 执行所有测试
test_health
test_stats
test_graph_stats
test_commit_recall_feedback
test_navigate
test_concurrent
test_batch_commit
test_recall_debug
test_pagerank
test_distill_quota
test_distill_status
test_cli_consistency
teardown
# 输出测试总结
echo ""
echo "══════════════════════════════════════"
echo " 测试总结"
echo "══════════════════════════════════════"
echo -e " 运行: ${TESTS_RUN}"
echo -e " 通过: ${GREEN}${TESTS_PASSED}${NC}"
echo -e " 失败: ${RED}${TESTS_FAILED}${NC}"
echo "══════════════════════════════════════"
if [[ ${TESTS_FAILED} -gt 0 ]]; then
echo -e "${RED}测试失败${NC}"
exit 1
else
echo -e "${GREEN}全部测试通过${NC}"
exit 0
fi
}
# 捕获 EXIT 信号
trap 'teardown' EXIT
main "$@"