mirror of
https://github.com/RaisFast/raisfast.git
synced 2026-09-23 08:02:36 +00:00
262 lines
11 KiB
Bash
Executable File
262 lines
11 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# CMS API 测试脚本:覆盖 content type 记录里 blob / media / media_set 字段的
|
|
# 添加、修改、删除等所有场景。每个场景是一个独立函数,可单独运行。
|
|
#
|
|
# 用法:
|
|
# scripts/test-cms-api.sh # 运行全部场景
|
|
# scripts/test-cms-api.sh create # 只跑 create
|
|
# scripts/test-cms-api.sh update # 只跑 update
|
|
# scripts/test-cms-api.sh list # 列出所有场景
|
|
#
|
|
# 环境变量:
|
|
# BASE_URL API 地址(默认 http://localhost:3000/api/v1)
|
|
# TEST_EMAIL 登录邮箱(默认 api@test.com)
|
|
# TEST_PASS 密码(默认 ApiPass123!)
|
|
# PLURAL 内容类型复数(默认 some_tests)
|
|
# BLOB_FIELD blob 字段名(默认 bb)
|
|
# MEDIA_SET_FIELD media_set 字段名(默认 gallery_test)
|
|
#
|
|
set -uo pipefail
|
|
|
|
BASE_URL="${BASE_URL:-http://localhost:3000/api/v1}"
|
|
EMAIL="${TEST_EMAIL:-api@test.com}"
|
|
PASS="${TEST_PASS:-ApiPass123!}"
|
|
PLURAL="${PLURAL:-some_tests}"
|
|
BLOB_FIELD="${BLOB_FIELD:-bb}"
|
|
MEDIA_SET_FIELD="${MEDIA_SET_FIELD:-gallery_test}"
|
|
|
|
TOKEN=""
|
|
|
|
# ── 公共辅助 ─────────────────────────────────────────────────────────
|
|
|
|
auth() {
|
|
[ -n "$TOKEN" ] && return 0
|
|
TOKEN=$(curl -s -X POST "$BASE_URL/auth/login" \
|
|
-H "Content-Type: application/json" \
|
|
-d "{\"email\":\"$EMAIL\",\"password\":\"$PASS\"}" \
|
|
| python3 -c "import sys,json;d=json.load(sys.stdin);print(d['data']['access_token'])" 2>/dev/null)
|
|
if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then
|
|
echo "✗ 登录失败(EMAIL=$EMAIL)。请先注册该用户,或设置 TEST_EMAIL/TEST_PASS"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# 上传一个媒体文件,返回 media id
|
|
upload_media() {
|
|
local file="$1"
|
|
curl -s -X POST "$BASE_URL/admin/media/upload" \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
-F "file=@$file" \
|
|
| python3 -c "import sys,json;print(json.load(sys.stdin)['data']['id'])"
|
|
}
|
|
|
|
# 发起请求,返回 "HTTP_STATUS" 单独一行,前面是 body
|
|
api() {
|
|
local method="$1" path="$2" data="${3:-}"
|
|
local args=(-s -w '\n%{http_code}' -X "$method" "$BASE_URL$path" -H "Authorization: Bearer $TOKEN")
|
|
if [ -n "$data" ]; then
|
|
args+=(-H "Content-Type: application/json" -d "$data")
|
|
fi
|
|
curl "${args[@]}"
|
|
}
|
|
|
|
status_of() { echo "$1" | tail -n1; }
|
|
body_of() { echo "$1" | sed '$d'; }
|
|
|
|
b64_file() { base64 -i "$1" 2>/dev/null | tr -d '\n'; }
|
|
|
|
# 创建一条带 blob + media_set 的记录,输出 id
|
|
create_record() {
|
|
local f="/tmp/cms-api-blob-$$.json"
|
|
printf '{"hello":"api test"}' > "$f"
|
|
local b64; b64=$(b64_file "$f"); rm -f "$f"
|
|
local resp
|
|
resp=$(api POST "/cms/$PLURAL" "{\"title\":\"api-scenario\",\"$BLOB_FIELD\":{\"data\":\"$b64\",\"filename\":\"a.json\",\"mimetype\":\"application/json\"},\"$MEDIA_SET_FIELD\":[]}")
|
|
local st; st=$(status_of "$resp")
|
|
if [ "$st" = "201" ] || [ "$st" = "200" ]; then
|
|
body_of "$resp" | python3 -c "import sys,json;print(json.load(sys.stdin)['data']['id'])"
|
|
else
|
|
echo "" >&2
|
|
echo " ✗ 创建记录失败 HTTP=$st: $(body_of "$resp")" >&2
|
|
fi
|
|
}
|
|
|
|
check() {
|
|
local name="$1" ok="$2" detail="$3"
|
|
if [ "$ok" = "0" ]; then
|
|
echo " ✓ $name"
|
|
else
|
|
echo " ✗ $name $detail"
|
|
fi
|
|
}
|
|
|
|
# ── 场景 ─────────────────────────────────────────────────────────────
|
|
|
|
scenario_create() {
|
|
echo "▶ 添加:创建记录(blob 对象 + media_set 数组)"
|
|
local f="/tmp/cms-api-create-$$.json"
|
|
printf '{"x":1}' > "$f"
|
|
local b64; b64=$(b64_file "$f"); rm -f "$f"
|
|
local resp
|
|
resp=$(api POST "/cms/$PLURAL" "{\"title\":\"create\",\"$BLOB_FIELD\":{\"data\":\"$b64\",\"filename\":\"a.json\",\"mimetype\":\"application/json\"},\"$MEDIA_SET_FIELD\":[\"10001\",\"10002\"]}")
|
|
local st; st=$(status_of "$resp")
|
|
local b; b=$(body_of "$resp")
|
|
check "创建返回 2xx" "$([ "$st" = "201" ] || [ "$st" = "200" ]; echo $?)" "(HTTP=$st $b)"
|
|
echo "$b" | python3 -c "import sys,json;d=json.load(sys.stdin)['data'];assert d['$BLOB_FIELD']['filename']=='a.json', 'blob filename mismatch';assert len(d['$MEDIA_SET_FIELD'])==2, 'media_set count mismatch';assert '_meta' not in json.dumps(d), 'companion meta leaked'" \
|
|
&& echo " ✓ blob/media_set 值 + 伴生列不泄漏" \
|
|
|| echo " ✗ 响应结构不正确"
|
|
}
|
|
|
|
scenario_read() {
|
|
echo "▶ 读取:创建后按 id 读取"
|
|
local id; id=$(create_record)
|
|
[ -z "$id" ] && { echo " ✗ 前置创建失败"; return; }
|
|
local resp; resp=$(api GET "/cms/$PLURAL/$id")
|
|
local st; st=$(status_of "$resp")
|
|
check "读取返回 200" "$([ "$st" = "200" ]; echo $?)" "(HTTP=$st)"
|
|
body_of "$resp" | python3 -c "import sys,json;d=json.load(sys.stdin)['data'];assert d['$BLOB_FIELD']['filename']=='a.json', 'blob mismatch'" \
|
|
&& echo " ✓ blob 值往返正确" \
|
|
|| echo " ✗ 读取内容不正确"
|
|
}
|
|
|
|
scenario_update() {
|
|
echo "▶ 修改:替换 blob(data/filename/mimetype)+ media_set"
|
|
local id; id=$(create_record)
|
|
[ -z "$id" ] && { echo " ✗ 前置创建失败"; return; }
|
|
local f="/tmp/cms-api-update-$$.bin"
|
|
printf 'updated-bytes' > "$f"
|
|
local b64; b64=$(b64_file "$f"); rm -f "$f"
|
|
local resp
|
|
resp=$(api PUT "/cms/$PLURAL/$id" "{\"$BLOB_FIELD\":{\"data\":\"$b64\",\"filename\":\"b.bin\",\"mimetype\":\"application/octet-stream\"},\"$MEDIA_SET_FIELD\":[\"10009\"]}")
|
|
local st; st=$(status_of "$resp")
|
|
local b; b=$(body_of "$resp")
|
|
check "更新返回 2xx" "$([ "$st" = "200" ] || [ "$st" = "201" ]; echo $?)" "(HTTP=$st $b)"
|
|
echo "$b" | python3 -c "import sys,json;d=json.load(sys.stdin)['data'];assert d['$BLOB_FIELD']['filename']=='b.bin', 'blob not updated';assert len(d['$MEDIA_SET_FIELD'])==1, 'media_set not updated'" \
|
|
&& echo " ✓ blob/media_set 均已更新" \
|
|
|| echo " ✗ 更新内容不正确"
|
|
}
|
|
|
|
scenario_media_set_clear() {
|
|
echo "▶ media_set 清空:设为 []"
|
|
local id; id=$(create_record)
|
|
[ -z "$id" ] && { echo " ✗ 前置创建失败"; return; }
|
|
local resp; resp=$(api PUT "/cms/$PLURAL/$id" "{\"$MEDIA_SET_FIELD\":[]}")
|
|
local st; st=$(status_of "$resp")
|
|
check "清空返回 2xx" "$([ "$st" = "200" ] || [ "$st" = "201" ]; echo $?)" "(HTTP=$st)"
|
|
body_of "$resp" | python3 -c "import sys,json;assert json.load(sys.stdin)['data']['$MEDIA_SET_FIELD']==[], 'not cleared'" \
|
|
&& echo " ✓ media_set 已清空" \
|
|
|| echo " ✗ 清空失败"
|
|
}
|
|
|
|
scenario_media_set_keep_add() {
|
|
echo "▶ media_set 保留原有 + 追加新文件"
|
|
local id; id=$(create_record)
|
|
[ -z "$id" ] && { echo " ✗ 前置创建失败"; return; }
|
|
local f="/tmp/cms-api-new-$$.png"
|
|
printf 'newfile' > "$f"
|
|
local mid; mid=$(upload_media "$f"); rm -f "$f"
|
|
[ -z "$mid" ] && { echo " ✗ 上传媒体失败"; return; }
|
|
local resp; resp=$(api PUT "/cms/$PLURAL/$id" "{\"$MEDIA_SET_FIELD\":[\"10001\",\"$mid\"]}")
|
|
local st; st=$(status_of "$resp")
|
|
check "追加返回 2xx" "$([ "$st" = "200" ] || [ "$st" = "201" ]; echo $?)" "(HTTP=$st)"
|
|
body_of "$resp" | python3 -c "import sys,json;assert '$mid' in json.load(sys.stdin)['data']['$MEDIA_SET_FIELD'], 'new id missing'" \
|
|
&& echo " ✓ 原 ID + 新 ID 都在" \
|
|
|| echo " ✗ 追加后缺少新 ID"
|
|
}
|
|
|
|
scenario_media_set_delete_some() {
|
|
echo "▶ media_set 删除其中几个"
|
|
local id; id=$(create_record)
|
|
[ -z "$id" ] && { echo " ✗ 前置创建失败"; return; }
|
|
local resp; resp=$(api PUT "/cms/$PLURAL/$id" "{\"$MEDIA_SET_FIELD\":[\"10001\",\"10002\",\"10003\"]}")
|
|
local st; st=$(status_of "$resp")
|
|
[ "$st" = "200" ] || [ "$st" = "201" ] || { echo " ✗ 前置设置失败 HTTP=$st"; return; }
|
|
resp=$(api PUT "/cms/$PLURAL/$id" "{\"$MEDIA_SET_FIELD\":[\"10002\"]}")
|
|
st=$(status_of "$resp")
|
|
check "删除部分返回 2xx" "$([ "$st" = "200" ] || [ "$st" = "201" ]; echo $?)" "(HTTP=$st)"
|
|
body_of "$resp" | python3 -c "import sys,json;assert json.load(sys.stdin)['data']['$MEDIA_SET_FIELD']==['10002'], 'wrong remaining'" \
|
|
&& echo " ✓ 只剩保留的 ID" \
|
|
|| echo " ✗ 删除部分后结果不对"
|
|
}
|
|
|
|
scenario_blob_null() {
|
|
echo "▶ blob 清空:设为 null"
|
|
local id; id=$(create_record)
|
|
[ -z "$id" ] && { echo " ✗ 前置创建失败"; return; }
|
|
local resp; resp=$(api PUT "/cms/$PLURAL/$id" "{\"$BLOB_FIELD\":null}")
|
|
local st; st=$(status_of "$resp")
|
|
check "blob null 返回 2xx" "$([ "$st" = "200" ] || [ "$st" = "201" ]; echo $?)" "(HTTP=$st)"
|
|
body_of "$resp" | python3 -c "import sys,json;assert json.load(sys.stdin)['data']['$BLOB_FIELD'] is None, 'not null'" \
|
|
&& echo " ✓ blob 已清空" \
|
|
|| echo " ✗ 清空失败"
|
|
}
|
|
|
|
scenario_blob_oversize() {
|
|
echo "▶ blob 超限:>512KB 应 400"
|
|
local f="/tmp/cms-api-big-$$.bin"
|
|
dd if=/dev/zero bs=1024 count=600 of="$f" 2>/dev/null
|
|
local b64; b64=$(b64_file "$f"); rm -f "$f"
|
|
local resp; resp=$(api POST "/cms/$PLURAL" "{\"title\":\"big\",\"$BLOB_FIELD\":{\"data\":\"$b64\",\"filename\":\"big.bin\",\"mimetype\":\"application/octet-stream\"}}")
|
|
local st; st=$(status_of "$resp")
|
|
check "超限返回 400" "$([ "$st" = "400" ]; echo $?)" "(HTTP=$st)"
|
|
}
|
|
|
|
scenario_blob_invalid() {
|
|
echo "▶ blob 非法 base64 应 400"
|
|
local resp; resp=$(api POST "/cms/$PLURAL" "{\"title\":\"bad\",\"$BLOB_FIELD\":{\"data\":\"!!!not-base64!!!\",\"filename\":\"x\",\"mimetype\":\"application/json\"}}")
|
|
local st; st=$(status_of "$resp")
|
|
check "非法 base64 返回 400" "$([ "$st" = "400" ]; echo $?)" "(HTTP=$st)"
|
|
}
|
|
|
|
scenario_media_set_invalid() {
|
|
echo "▶ media_set 非法值(非数组/含非字符串)应 400"
|
|
local resp; resp=$(api POST "/cms/$PLURAL" "{\"title\":\"bad\",\"$MEDIA_SET_FIELD\":\"oops\"}")
|
|
local st; st=$(status_of "$resp")
|
|
check "非数组返回 400" "$([ "$st" = "400" ]; echo $?)" "(HTTP=$st)"
|
|
}
|
|
|
|
scenario_delete() {
|
|
echo "▶ 删除:删除后读取应 404"
|
|
local id; id=$(create_record)
|
|
[ -z "$id" ] && { echo " ✗ 前置创建失败"; return; }
|
|
local resp; resp=$(api DELETE "/cms/$PLURAL/$id")
|
|
local st; st=$(status_of "$resp")
|
|
check "删除返回 2xx" "$([ "$st" = "200" ] || [ "$st" = "204" ]; echo $?)" "(HTTP=$st)"
|
|
resp=$(api GET "/cms/$PLURAL/$id")
|
|
st=$(status_of "$resp")
|
|
check "删除后读取 404" "$([ "$st" = "404" ]; echo $?)" "(HTTP=$st)"
|
|
}
|
|
|
|
# ── 调度 ─────────────────────────────────────────────────────────────
|
|
|
|
ALL="create read update media_set_clear media_set_keep_add media_set_delete_some blob_null blob_oversize blob_invalid media_set_invalid delete"
|
|
|
|
usage() {
|
|
echo "用法: $0 [场景名]"
|
|
echo "场景:"
|
|
echo " $ALL" | tr ' ' '\n' | sed 's/^/ /'
|
|
echo "不传参数 = 运行全部"
|
|
}
|
|
|
|
if [ "${1:-}" = "list" ]; then usage; exit 0; fi
|
|
|
|
auth
|
|
|
|
if [ $# -ge 1 ]; then
|
|
case " $ALL " in
|
|
*" $1 "*)
|
|
echo "==> 场景: $1"
|
|
"scenario_$1"
|
|
;;
|
|
*)
|
|
echo "未知场景: $1"; usage; exit 1
|
|
;;
|
|
esac
|
|
else
|
|
for s in $ALL; do
|
|
echo "==> 场景: $s"
|
|
"scenario_$s"
|
|
done
|
|
fi
|