Merge pull request 'Complete scenario-2 name-filter assertion and refresh the harness for v0.4.0' (#529) from feature/rdm-increment-settings-duties into main
This commit was merged in pull request #529.
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -1,7 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
# P8 第八节十场景验收(提案 rdm-settings-members-duties-perms-increment §8)。
|
||||
# 走真实 dev 服务 HTTP(--auth 会话),多账号多角色;输出逐场景 PASS/FAIL。
|
||||
# 用法:BASE=http://127.0.0.1:8788 ADMIN_USER=weichen ADMIN_PASS=<密码> ./acceptance_scenarios.sh
|
||||
# 走真实服务 HTTP(--auth 会话),多账号多角色;输出逐场景 PASS/FAIL。
|
||||
# v0.4.0 版:RDM 命令面用 rdm.project.create 的 RID(不再直用基座项目);
|
||||
# 场景 2 姓名过滤全量断言(基座 v0.4.0+ AccountSearcher);setRole→set-role(#350)。
|
||||
# 前置:ADMIN 账号须为基座管理员(改密门已过)且已登记 RDM 域管理员
|
||||
# (组合二进制 -bootstrap-admin <owner>,仅空台账生效)。
|
||||
# 用法:BASE=http://127.0.0.1:8799 ADMIN_USER=rootadmin ADMIN_PASS=<密码> ./acceptance_scenarios.sh
|
||||
set -u
|
||||
BASE="${BASE:-http://127.0.0.1:8788}"
|
||||
ADMIN_USER="${ADMIN_USER:?需要管理员账号}"
|
||||
@@ -33,19 +37,27 @@ JA="$T/admin"; login "$ADMIN_USER" "$ADMIN_PASS" "$JA"
|
||||
|
||||
say "== 准备:账号与两项目 =="
|
||||
TS=$(date +%s)
|
||||
# 建号:非管理员负责人 owner2 / 普通成员 mem1 / 三权三人 w1 a1 r1(密码同式)
|
||||
# 建号:非管理员负责人 owner2 / 普通成员 mem1 / 三权三人 w1 a1 r1(密码同式;name=角色-<u>)
|
||||
for u in owner2 mem1 w1 a1 r1; do
|
||||
resp=$(api "$JA" POST /api/admin/users "{\"owner\":\"$u\",\"name\":\"角色-$u\",\"password\":\"Pass-$TS-x8z\",\"isAdmin\":false}")
|
||||
[ "$(echo "$resp" | code)" = "200" ] || say " ($u 已存在或其他:$(echo "$resp" | code))"
|
||||
done
|
||||
# 各自完成改密门(登录→改密)
|
||||
# 各自完成改密门(登录→改密);基座 #195 起改密撤销旧会话 → 用新密码重登刷新 jar
|
||||
for u in owner2 mem1 w1 a1 r1; do
|
||||
J="$T/$u"; login "$u" "Pass-$TS-x8z" "$J"
|
||||
api "$J" POST /api/auth/password "{\"current\":\"Pass-$TS-x8z\",\"next\":\"Pass2-$TS\"}" >/dev/null
|
||||
login "$u" "Pass2-$TS" "$J"
|
||||
done
|
||||
JPASS="Pass2-$TS"
|
||||
|
||||
# 场景 10 前置数据 + 项目创建走 owner2(非管理员)
|
||||
RDM() { # jar cmd json
|
||||
api "$1" POST "/api/commands/$2" "{\"params\":$3}" | body
|
||||
}
|
||||
PAYLOAD() { # stdin json → payload 子对象(命令应答信封 {payload,receipt})
|
||||
python3 -c 'import json,sys; print(json.dumps(json.load(sys.stdin)["payload"], ensure_ascii=False))'
|
||||
}
|
||||
|
||||
# 基座项目(场景 1/10 用):owner2(非管理员)创建
|
||||
JO="$T/owner2"
|
||||
P1=$(api "$JO" POST /api/projects "{\"name\":\"验收甲-$TS\"}")
|
||||
PID1=$(echo "$P1" | body | python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])' 2>/dev/null)
|
||||
@@ -53,9 +65,15 @@ P2=$(api "$JO" POST /api/projects "{\"name\":\"验收乙-$TS\"}")
|
||||
PID2=$(echo "$P2" | body | python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])' 2>/dev/null)
|
||||
[ -n "${PID1:-}" ] && [ -n "${PID2:-}" ] && ok "准备:两项目创建($PID1 / $PID2)" || { bad "准备:项目创建" "$P1"; exit 1; }
|
||||
|
||||
RDM() { # jar cmd json
|
||||
api "$1" POST "/api/commands/$2" "{\"params\":$3}" | body
|
||||
}
|
||||
# RDM 项目(场景 2-8 用):域管理员建项(创建者自动为 owner)+ 绑定基座项目;
|
||||
# owner2 入项为 owner 后域管理员自移出——owner2 成为唯一 owner(场景 3b 前提)
|
||||
RID=$(RDM "$JA" rdm.project.create "{\"name\":\"验收RDM甲-$TS\"}" | python3 -c 'import json,sys; print(json.load(sys.stdin)["payload"]["projectId"])' 2>/dev/null)
|
||||
[ -n "${RID:-}" ] && ok "准备:RDM 项目创建($RID)" || { bad "准备:RDM 项目创建" ""; exit 1; }
|
||||
RDM "$JA" rdm.project.bind "{\"hostProjectId\":\"$PID1\",\"projectId\":\"$RID\"}" >/dev/null
|
||||
out=$(RDM "$JA" rdm.project.member.add "{\"projectId\":\"$RID\",\"member\":\"owner2\",\"role\":\"owner\"}")
|
||||
echo "$out" | grep -q '"owner"' || { bad "准备:owner2 入项" "$out"; exit 1; }
|
||||
out=$(RDM "$JA" rdm.project.member.remove "{\"projectId\":\"$RID\",\"member\":\"$ADMIN_USER\"}")
|
||||
echo "$out" | grep -q '"removed":true' && ok "准备:owner2 为 RDM 项目唯一 owner(管理员自移出)" || { bad "准备:管理员自移出" "$out"; exit 1; }
|
||||
|
||||
say "== 场景 1:受管项写入拒绝(user/project 层,含手造请求)=="
|
||||
r=$(api "$JO" PUT /api/settings/user '{"models":{"chat":{"model":"m-hack"}}}' | code)
|
||||
@@ -66,51 +84,68 @@ r=$(api "$JO" PUT "/api/settings/project/$PID1" '{"models":{"chat":{"apiKey":"sk
|
||||
r=$(api "$JO" PUT /api/settings/system '{"models":{"chat":{"model":"x"}}}' | code)
|
||||
[ "$r" = "403" ] && ok "1c 非管理员写系统面 403" || bad "1c" "$r"
|
||||
|
||||
say "== 场景 2:目录查找成员(账号/分页;姓名过滤待发版 tag 后全量)=="
|
||||
out=$(RDM "$JO" rdm.account.list "{\"projectId\":\"$PID1\",\"limit\":5}")
|
||||
n=$(echo "$out" | python3 -c 'import json,sys; d=json.load(sys.stdin); print(len(d.get("accounts",[])))' 2>/dev/null)
|
||||
say "== 场景 2:目录查找成员(账号/分页 + 姓名过滤 v0.4.0 全量)=="
|
||||
out=$(RDM "$JO" rdm.account.list "{\"projectId\":\"$RID\",\"limit\":5}")
|
||||
n=$(echo "$out" | python3 -c 'import json,sys; d=json.load(sys.stdin)["payload"]; print(len(d.get("accounts",[])))' 2>/dev/null)
|
||||
[ "${n:-0}" -gt 0 ] && ok "2a 目录分页浏览($n 条)" || bad "2a" "$out"
|
||||
# 2b 姓名子串「角色-w」→ 仅 w1(角色-w1),姓名随行、检索无游标
|
||||
out=$(RDM "$JO" rdm.account.list "{\"projectId\":\"$RID\",\"query\":\"角色-w\",\"limit\":20}")
|
||||
r=$(echo "$out" | python3 -c 'import json,sys
|
||||
d=json.load(sys.stdin)["payload"]; a=d.get("accounts",[])
|
||||
print("ok" if len(a)==1 and a[0]["owner"]=="w1" and a[0].get("name")=="角色-w1" and d.get("next","")=="" else json.dumps(d,ensure_ascii=False))' 2>/dev/null)
|
||||
[ "$r" = "ok" ] && ok "2b 姓名过滤「角色-w」仅 w1 且姓名随行无游标" || bad "2b" "$r"
|
||||
# 2c 账号子串大小写不敏感「MEM」→ 仅 mem1(角色-mem1)
|
||||
out=$(RDM "$JO" rdm.account.list "{\"projectId\":\"$RID\",\"query\":\"MEM\",\"limit\":20}")
|
||||
r=$(echo "$out" | python3 -c 'import json,sys
|
||||
d=json.load(sys.stdin)["payload"]; a=d.get("accounts",[])
|
||||
print("ok" if len(a)==1 and a[0]["owner"]=="mem1" and a[0].get("name")=="角色-mem1" else json.dumps(d,ensure_ascii=False))' 2>/dev/null)
|
||||
[ "$r" = "ok" ] && ok "2c 账号过滤「MEM」(不区分大小写)仅 mem1" || bad "2c" "$r"
|
||||
# 2d 多命中(角色- 前缀 5 人)+ limit 截断
|
||||
out=$(RDM "$JO" rdm.account.list "{\"projectId\":\"$RID\",\"query\":\"角色\",\"limit\":3}")
|
||||
r=$(echo "$out" | python3 -c 'import json,sys
|
||||
d=json.load(sys.stdin)["payload"]; a=d.get("accounts",[])
|
||||
print("ok" if len(a)==3 and all(str(e.get("name","")).startswith("角色-") for e in a) else json.dumps(d,ensure_ascii=False))' 2>/dev/null)
|
||||
[ "$r" = "ok" ] && ok "2d 多命中姓名检索按 limit 截断(3/5)" || bad "2d" "$r"
|
||||
|
||||
say "== 场景 3:重复添加与最后负责人保护 =="
|
||||
RDM "$JO" rdm.project.member.add "{\"projectId\":\"$PID1\",\"member\":\"mem1\",\"role\":\"member\"}" >/dev/null
|
||||
out=$(RDM "$JO" rdm.project.member.add "{\"projectId\":\"$PID1\",\"member\":\"mem1\",\"role\":\"owner\"}")
|
||||
RDM "$JO" rdm.project.member.add "{\"projectId\":\"$RID\",\"member\":\"mem1\",\"role\":\"member\"}" >/dev/null
|
||||
out=$(RDM "$JO" rdm.project.member.add "{\"projectId\":\"$RID\",\"member\":\"mem1\",\"role\":\"owner\"}")
|
||||
echo "$out" | grep -q "已是成员" && ok "3a 重复添加拒绝且不隐式变更" || bad "3a" "$out"
|
||||
out=$(RDM "$JO" rdm.project.member.setRole "{\"projectId\":\"$PID1\",\"member\":\"owner2\",\"role\":\"member\"}")
|
||||
out=$(RDM "$JO" rdm.project.member.set-role "{\"projectId\":\"$RID\",\"member\":\"owner2\",\"role\":\"member\"}")
|
||||
echo "$out" | grep -q "最后一位负责人" && ok "3b 降级唯一负责人被拒" || bad "3b" "$out"
|
||||
out=$(RDM "$JO" rdm.project.member.setRole "{\"projectId\":\"$PID1\",\"member\":\"mem1\",\"role\":\"owner\"}")
|
||||
out=$(RDM "$JO" rdm.project.member.set-role "{\"projectId\":\"$RID\",\"member\":\"mem1\",\"role\":\"owner\"}")
|
||||
echo "$out" | grep -q '"role":"owner"' && ok "3c 显式升级成功" || bad "3c" "$out"
|
||||
RDM "$JO" rdm.project.member.setRole "{\"projectId\":\"$PID1\",\"member\":\"mem1\",\"role\":\"member\"}" >/dev/null
|
||||
RDM "$JO" rdm.project.member.set-role "{\"projectId\":\"$RID\",\"member\":\"mem1\",\"role\":\"member\"}" >/dev/null
|
||||
# 成员补充:三权三人入项目
|
||||
for u in w1 a1 r1; do RDM "$JO" rdm.project.member.add "{\"projectId\":\"$PID1\",\"member\":\"$u\"}" >/dev/null; done
|
||||
for u in w1 a1 r1; do RDM "$JO" rdm.project.member.add "{\"projectId\":\"$RID\",\"member\":\"$u\"}" >/dev/null; done
|
||||
|
||||
say "== 场景 4:文档目录与职责分配对象校验 =="
|
||||
# 建文档(经 RDM 世界初始化:用 rdm.init 或模板?最小:rdm.duty.catalog 应答;docId 误提交由场景 5 的 assign 覆盖
|
||||
cat_out=$(RDM "$JO" rdm.duty.catalog "{\"projectId\":\"$PID1\"}")
|
||||
echo "$cat_out" | python3 -c 'import json,sys; d=json.load(sys.stdin); assert "docs" in d' 2>/dev/null && ok "4a duty.catalog 目录可读" || bad "4a" "$cat_out"
|
||||
cat_out=$(RDM "$JO" rdm.duty.catalog "{\"projectId\":\"$RID\"}")
|
||||
echo "$cat_out" | python3 -c 'import json,sys; assert "docs" in json.load(sys.stdin)["payload"]' 2>/dev/null && ok "4a duty.catalog 目录可读" || bad "4a" "$cat_out"
|
||||
# 章节误提交:构造不存在的 docId → assign 拒绝
|
||||
out=$(RDM "$JO" rdm.review.assign.doc "{\"projectId\":\"$PID1\",\"docId\":\"section-not-doc\",\"author\":\"w1\",\"reviewers\":[\"a1\"],\"approver\":\"r1\"}")
|
||||
out=$(RDM "$JO" rdm.review.assign.doc "{\"projectId\":\"$RID\",\"docId\":\"section-not-doc\",\"author\":\"w1\",\"reviewers\":[\"a1\"],\"approver\":\"r1\"}")
|
||||
echo "$out" | grep -qE "不存在|失败|错误" && ok "4b 不存在对象整笔拒绝" || bad "4b" "$out"
|
||||
|
||||
say "== 场景 5:非成员职责目标被拒;移出成员保存被拒 =="
|
||||
out=$(RDM "$JO" rdm.review.assign.doc "{\"projectId\":\"$PID1\",\"docId\":\"any\",\"author\":\"zombie-$TS\",\"reviewers\":[\"a1\"],\"approver\":\"r1\"}")
|
||||
out=$(RDM "$JO" rdm.review.assign.doc "{\"projectId\":\"$RID\",\"docId\":\"any\",\"author\":\"zombie-$TS\",\"reviewers\":[\"a1\"],\"approver\":\"r1\"}")
|
||||
echo "$out" | grep -q "成员" && ok "5a 非成员职责目标被拒" || bad "5a" "$out"
|
||||
# mem1 移出后 setRole 保存拒绝
|
||||
RDM "$JO" rdm.project.member.remove "{\"projectId\":\"$PID1\",\"member\":\"mem1\"}" >/dev/null
|
||||
out=$(RDM "$JO" rdm.project.member.setRole "{\"projectId\":\"$PID1\",\"member\":\"mem1\",\"role\":\"owner\"}")
|
||||
# mem1 移出后 set-role 保存拒绝
|
||||
RDM "$JO" rdm.project.member.remove "{\"projectId\":\"$RID\",\"member\":\"mem1\"}" >/dev/null
|
||||
out=$(RDM "$JO" rdm.project.member.set-role "{\"projectId\":\"$RID\",\"member\":\"mem1\",\"role\":\"owner\"}")
|
||||
echo "$out" | grep -q "不是" && ok "5b 移出成员的成员面操作被拒" || bad "5b" "$out"
|
||||
|
||||
say "== 场景 6:批量整批拒绝(预检列原因)=="
|
||||
out=$(RDM "$JO" rdm.review.assign.doc.batch "{\"projectId\":\"$PID1\",\"docs\":[{\"docId\":\"x1\",\"author\":\"w1\",\"reviewers\":[\"a1\"],\"approver\":\"r1\"},{\"docId\":\"x2\",\"author\":\"zombie-$TS\",\"reviewers\":[\"a1\"],\"approver\":\"r1\"}]}")
|
||||
out=$(RDM "$JO" rdm.review.assign.doc.batch "{\"projectId\":\"$RID\",\"docs\":[{\"docId\":\"x1\",\"author\":\"w1\",\"reviewers\":[\"a1\"],\"approver\":\"r1\"},{\"docId\":\"x2\",\"author\":\"zombie-$TS\",\"reviewers\":[\"a1\"],\"approver\":\"r1\"}]}")
|
||||
echo "$out" | grep -q "整批拒绝" && ok "6 批量含非成员整批拒绝并列原因" || bad "6" "$out"
|
||||
|
||||
say "== 场景 7:单成员解释与矩阵同源 =="
|
||||
chk=$(RDM "$JO" rdm.perm.check "{\"projectId\":\"$PID1\",\"member\":\"owner2\"}")
|
||||
mtx=$(RDM "$JO" rdm.perm.matrix "{\"projectId\":\"$PID1\"}")
|
||||
chk=$(RDM "$JO" rdm.perm.check "{\"projectId\":\"$RID\",\"member\":\"owner2\"}")
|
||||
mtx=$(RDM "$JO" rdm.perm.matrix "{\"projectId\":\"$RID\"}")
|
||||
same=$(python3 - "$chk" "$mtx" <<'PY'
|
||||
import json,sys
|
||||
c=json.loads(sys.argv[1]); m=json.loads(sys.argv[2])
|
||||
c=json.loads(sys.argv[1])["payload"]; m=json.loads(sys.argv[2])["payload"]
|
||||
row=[r for r in m.get("rows",[]) if r["member"]==c["member"]]
|
||||
if not row: print("no-row"); sys.exit(1)
|
||||
if not row: print("no-row"); sys.exit(0)
|
||||
ca={a["action"]:(a["allowed"],a.get("label"),a.get("scope")) for a in c["actions"]}
|
||||
ra={a["action"]:(a["allowed"],a.get("label"),a.get("scope")) for a in row[0]["actions"]}
|
||||
print("same" if ca==ra else "diff")
|
||||
@@ -119,15 +154,15 @@ PY
|
||||
[ "$same" = "same" ] && ok "7 单成员详情与矩阵单元格一致" || bad "7" "$same"
|
||||
# 普通成员自省:a1 查自己
|
||||
ja="$T/a1"
|
||||
out=$(RDM "$ja" rdm.perm.check "{\"projectId\":\"$PID1\",\"member\":\"a1\"}")
|
||||
out=$(RDM "$ja" rdm.perm.check "{\"projectId\":\"$RID\",\"member\":\"a1\"}")
|
||||
echo "$out" | grep -q '"actions"' && ok "7b 普通成员自省可用" || bad "7b" "$out"
|
||||
|
||||
say "== 场景 8:系统动作不可项目覆盖 =="
|
||||
out=$(RDM "$JO" rdm.perm.set "{\"projectId\":\"$PID1\",\"member\":\"a1\",\"action\":\"project.create\",\"allow\":true}")
|
||||
out=$(RDM "$JO" rdm.perm.set "{\"projectId\":\"$RID\",\"member\":\"a1\",\"action\":\"project.create\",\"allow\":true}")
|
||||
echo "$out" | grep -q "系统范围" && ok "8a project.create 覆盖被服务端拒绝" || bad "8a" "$out"
|
||||
out=$(RDM "$JO" rdm.perm.set "{\"projectId\":\"$PID1\",\"member\":\"a1\",\"action\":\"doc.write\",\"allow\":true}")
|
||||
echo "$out" | grep -q '"ok"' || echo "$out" | grep -q 'ok' && ok "8b 项目范围动作覆盖可用" || bad "8b" "$out"
|
||||
out=$(RDM "$JO" rdm.perm.unset "{\"projectId\":\"$PID1\",\"member\":\"a1\",\"action\":\"doc.write\"}")
|
||||
out=$(RDM "$JO" rdm.perm.set "{\"projectId\":\"$RID\",\"member\":\"a1\",\"action\":\"doc.write\",\"allow\":true}")
|
||||
echo "$out" | grep -q '"allow":true' && ok "8b 项目范围动作覆盖可用" || bad "8b" "$out"
|
||||
out=$(RDM "$JO" rdm.perm.unset "{\"projectId\":\"$RID\",\"member\":\"a1\",\"action\":\"doc.write\"}")
|
||||
echo "$out" | grep -q '"restored":true' && ok "8c 恢复默认(实际撤销)" || bad "8c" "$out"
|
||||
|
||||
say "== 场景 9:迟到响应/断网(UI 行为)=="
|
||||
|
||||
Reference in New Issue
Block a user