games-development-ai/deploy/tests/test-collect-dogfood-account-report.sh
lili 8a81b37348
Some checks failed
contract-gates / contract-gates (push) Has been cancelled
docs-gate / docs-gate (push) Has been cancelled
test(release): make account report mode check portable
2026-07-23 03:07:37 -07:00

310 lines
14 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# 账号报告采集器回归:使用假 SSH/MySQL 输出,不连接真实内网主机。
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
SCRIPT="$ROOT_DIR/deploy/collect-dogfood-account-report.sh"
TMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_DIR"' EXIT
fail() {
printf 'TEST_FAIL %s\n' "$*" >&2
exit 1
}
openssl genpkey -algorithm ED25519 -out "$TMP_DIR/private.pem" >/dev/null 2>&1
openssl pkey -in "$TMP_DIR/private.pem" -pubout -out "$TMP_DIR/public.pem" >/dev/null 2>&1
{
for index in $(seq 1 10); do
printf 'ACCOUNT\t%d\tdogfoodc%02d\tcreator\tACTIVE\t%d\tCLAIMED\t6849315\t%d\t%d\n' \
"$((1000 + index))" "$index" "$((2000 + index))" "$((4000 + index))" "$((5000 + index))"
done
for index in $(seq 1 30); do
printf 'ACCOUNT\t%d\tdogfoodp%02d\tplayer\tACTIVE\t%d\tCLAIMED\t6849315\t%d\t%d\n' \
"$((1100 + index))" "$index" "$((2100 + index))" "$((4100 + index))" "$((5100 + index))"
done
for index in $(seq 1 12); do
printf 'RESERVE\t%d\t%d\t%d\tFREE\t6849315\t\t\t\t\n' \
"$((3000 + index))" "$((4200 + index))" "$((5200 + index))"
done
} >"$TMP_DIR/mysql.tsv"
{
for index in $(seq 1 10); do
printf 'TOKEN|%d|neice_%03d|%d|1|0|-1|6849315\n' \
"$((4000 + index))" "$index" "$((5000 + index))"
done
for index in $(seq 1 30); do
printf 'TOKEN|%d|neice_%03d|%d|1|0|-1|6849315\n' \
"$((4100 + index))" "$((10 + index))" "$((5100 + index))"
done
for index in $(seq 1 12); do
printf 'TOKEN|%d|neice_%03d|%d|1|0|-1|6849315\n' \
"$((4200 + index))" "$((40 + index))" "$((5200 + index))"
done
} >"$TMP_DIR/newapi.tsv"
cat >"$TMP_DIR/fake-ssh" <<'SH'
#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' "$*" >>"$DOGFOOD_TEST_SSH_ARGS"
remote_command="${!#}"
if [[ "$remote_command" == *DOGFOOD_RUNTIME_STATE_V1* ]]; then
state_call_count=0
if [ -f "$DOGFOOD_TEST_STATE_CALL_COUNT" ]; then
state_call_count="$(cat "$DOGFOOD_TEST_STATE_CALL_COUNT")"
fi
state_call_count="$((state_call_count + 1))"
printf '%s\n' "$state_call_count" >"$DOGFOOD_TEST_STATE_CALL_COUNT"
if [ "$state_call_count" -eq 2 ] && [ -n "${DOGFOOD_TEST_DRIFT_TARGET:-}" ]; then
ln -sfn "$DOGFOOD_TEST_DRIFT_TARGET" "$DOGFOOD_COLLECTOR_TEST_RELEASES_ROOT/active"
fi
bash -c "$remote_command"
else
cat >"$DOGFOOD_TEST_SQL_CAPTURE"
cat "$DOGFOOD_TEST_MYSQL_ROWS"
fi
SH
chmod +x "$TMP_DIR/fake-ssh"
cat >"$TMP_DIR/fake-docker" <<'SH'
#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' "$*" >"$DOGFOOD_TEST_DOCKER_ARGS"
cat >"$DOGFOOD_TEST_PG_SQL_CAPTURE"
cat "$DOGFOOD_TEST_NEWAPI_ROWS"
SH
chmod +x "$TMP_DIR/fake-docker"
export DOGFOOD_COLLECTOR_TEST_MODE=1
export DOGFOOD_COLLECTOR_TEST_SSH_BIN="$TMP_DIR/fake-ssh"
export DOGFOOD_COLLECTOR_TEST_SIGNER_KEY="$TMP_DIR/private.pem"
export DOGFOOD_COLLECTOR_TEST_OPENSSL_BIN="$(command -v openssl)"
export DOGFOOD_COLLECTOR_TEST_DOCKER_BIN="$TMP_DIR/fake-docker"
export DOGFOOD_TEST_SSH_ARGS="$TMP_DIR/ssh-args"
export DOGFOOD_TEST_SQL_CAPTURE="$TMP_DIR/mysql.sql"
export DOGFOOD_TEST_MYSQL_ROWS="$TMP_DIR/mysql.tsv"
export DOGFOOD_TEST_DOCKER_ARGS="$TMP_DIR/docker-args"
export DOGFOOD_TEST_PG_SQL_CAPTURE="$TMP_DIR/newapi.sql"
export DOGFOOD_TEST_NEWAPI_ROWS="$TMP_DIR/newapi.tsv"
export DOGFOOD_TEST_STATE_CALL_COUNT="$TMP_DIR/state-call-count"
export DOGFOOD_COLLECTOR_TEST_RELEASES_ROOT="$TMP_DIR/releases"
mkdir -p "$DOGFOOD_COLLECTOR_TEST_RELEASES_ROOT"
export DOGFOOD_COLLECTOR_TEST_RELEASES_ROOT="$(cd "$DOGFOOD_COLLECTOR_TEST_RELEASES_ROOT" && pwd -P)"
active_commit=1111111111111111111111111111111111111111
active_run_id=20260723T010203Z
active_release="$DOGFOOD_COLLECTOR_TEST_RELEASES_ROOT/$active_commit"
active_evidence="$active_release/evidence/activation-$active_run_id"
mkdir -p "$active_evidence"
ln -s "$active_release" "$DOGFOOD_COLLECTOR_TEST_RELEASES_ROOT/active"
printf 'active commit=%s at=2026-07-23T01:02:03Z evidence=%s\n' \
"$active_commit" "$active_evidence" >"$active_release/RC_STATUS"
cat >"$active_evidence/activation.log" <<EOF
[2026-07-23T01:02:03Z] RC_ACTIVATION_PASS commit=$active_commit active=$active_release
[2026-07-23T01:02:03Z] ACTIVATION_EVIDENCE=$active_evidence
EOF
chmod 600 "$active_release/RC_STATUS" "$active_evidence/activation.log"
reset_state_probe() {
rm -f "$DOGFOOD_TEST_STATE_CALL_COUNT"
unset DOGFOOD_TEST_DRIFT_TARGET
}
assert_refused_without_output() {
local name="$1"
shift
reset_state_probe
if "$SCRIPT" "$@" --output "$TMP_DIR/$name.json" >/dev/null 2>&1; then
fail "$name 未被拒绝"
fi
[ ! -e "$TMP_DIR/$name.json" ] && [ ! -e "$TMP_DIR/$name.json.sig" ] || \
fail "$name 失败后遗留半成品"
}
report="$TMP_DIR/account-report.json"
reset_state_probe
"$SCRIPT" \
--activation-run-id "$active_run_id" \
--rc-commit "$active_commit" \
--output "$report"
[ -f "$report" ] && [ -f "$report.sig" ] || fail '没有生成报告及 detached signature'
[ "$(stat -c '%a' "$report" 2>/dev/null || stat -f '%Lp' "$report")" = 600 ] || fail '报告权限不是 0600'
[ "$(stat -c '%a' "$report.sig" 2>/dev/null || stat -f '%Lp' "$report.sig")" = 600 ] || fail '签名权限不是 0600'
openssl pkeyutl -verify -pubin -inkey "$TMP_DIR/public.pem" -rawin \
-in "$report" -sigfile "$report.sig" >/dev/null 2>&1 || fail 'detached signature 无法验证'
grep -q 'root@100.64.0.7' "$TMP_DIR/ssh-args" || fail 'SSH 目标不是固定 mini-desktop'
grep -q '^START TRANSACTION READ ONLY;' "$TMP_DIR/mysql.sql" || fail 'MySQL 查询未进入服务端只读事务'
grep -q 'FROM game_player p' "$TMP_DIR/mysql.sql" || fail '固定账号查询缺失'
grep -q 'FROM newapi_quota_pool q' "$TMP_DIR/mysql.sql" || fail '固定额度池查询缺失'
if grep -Eqi 'newapi_token_key|password|access_token|refresh_token' "$TMP_DIR/mysql.sql"; then
fail '固定查询选择了敏感列'
fi
grep -q 'infra-postgres' "$TMP_DIR/docker-args" || fail 'new-api PostgreSQL 容器不是固定值'
grep -q 'new-api' "$TMP_DIR/docker-args" || fail 'new-api PostgreSQL 数据库不是固定值'
grep -q '^BEGIN TRANSACTION READ ONLY;' "$TMP_DIR/newapi.sql" || fail 'new-api 查询未进入只读事务'
grep -q 'FROM users u' "$TMP_DIR/newapi.sql" || fail 'new-api users 固定查询缺失'
grep -q 'JOIN tokens t' "$TMP_DIR/newapi.sql" || fail 'new-api tokens 固定查询缺失'
if grep -Eqi 'SELECT[^;]*(key|access_token|password)' "$TMP_DIR/newapi.sql"; then
fail 'new-api 固定查询选择了敏感凭据'
fi
grep -q "SIGNER_PRIVATE_KEY='/root/dogfood-r1-signer/r1-ed25519-private.pem'" "$SCRIPT" || \
fail '生产 signer 私钥路径不是固定值'
grep -q "STAGING_RELEASES_ROOT='/root/game-staging/releases'" "$SCRIPT" || \
fail '生产 staging releases 根不是固定值'
grep -q 'realpath.*signer_key' "$SCRIPT" || fail '生产 signer 私钥未校验 canonical 路径'
grep -q "stat -c '%u'.*signer_key" "$SCRIPT" || fail '生产 signer 私钥未校验 root 所有权'
python3 - "$report" <<'PY'
import json
import sys
report = json.load(open(sys.argv[1], encoding="utf-8"))
assert report["schema"] == "dogfood-account-database-report/2"
assert report["issuer"] == "r1-mini-infra"
assert report["queryId"] == "dogfood-account-readiness-v2"
assert report["querySchema"] == "dogfood-account-readiness-row/2"
assert report["rcCommit"] == "1" * 40
assert report["activationRunId"] == "20260723T010203Z"
assert report["activationStatus"] == "PASS"
assert report["activationCompletedAt"] == "2026-07-23T01:02:03Z"
assert report["activeReleaseRef"].endswith("/" + "1" * 40)
assert report["activationEvidenceRef"].endswith(
"/1111111111111111111111111111111111111111/evidence/activation-20260723T010203Z"
)
assert report["activationEvidenceRef"].startswith(report["activeReleaseRef"] + "/evidence/")
assert len(report["accounts"]) == 40
assert len(report["reserves"]) == 12
for item in report["accounts"] + report["reserves"]:
assert item["newapiUsername"].startswith("neice_")
assert item["tokenStatus"] == "ENABLED"
assert item["tokenRemainQuota"] > 0
assert item["tokenUnlimitedQuota"] is False
assert item["tokenExpiredTime"] == -1
serialized = json.dumps(report)
for secret_name in ("tokenKey", "password", "accessToken", "refreshToken"):
assert secret_name not in serialized
PY
# 参数只是 expected:与 mini-desktop 当前 active 事实不一致时,签名器必须拒绝。
assert_refused_without_output forged-commit \
--activation-run-id "$active_run_id" \
--rc-commit 2222222222222222222222222222222222222222
assert_refused_without_output forged-run-id \
--activation-run-id 20260723T111111Z \
--rc-commit "$active_commit"
# active、RC_STATUS、activation evidence 任一缺失都不能退回使用命令参数。
rm "$DOGFOOD_COLLECTOR_TEST_RELEASES_ROOT/active"
assert_refused_without_output missing-active \
--activation-run-id "$active_run_id" --rc-commit "$active_commit"
ln -s "$active_release" "$DOGFOOD_COLLECTOR_TEST_RELEASES_ROOT/active"
mv "$active_release/RC_STATUS" "$active_release/RC_STATUS.missing"
assert_refused_without_output missing-rc-status \
--activation-run-id "$active_run_id" --rc-commit "$active_commit"
mv "$active_release/RC_STATUS.missing" "$active_release/RC_STATUS"
mv "$active_evidence" "$active_evidence.missing"
assert_refused_without_output missing-activation-evidence \
--activation-run-id "$active_run_id" --rc-commit "$active_commit"
mv "$active_evidence.missing" "$active_evidence"
# RC_STATUS 与 evidence 自身必须绑定 active commit、规范 run-id 和成功终态。
cp "$active_release/RC_STATUS" "$TMP_DIR/RC_STATUS.valid"
sed "s/commit=$active_commit/commit=2222222222222222222222222222222222222222/" \
"$TMP_DIR/RC_STATUS.valid" >"$active_release/RC_STATUS"
assert_refused_without_output rc-status-commit-drift \
--activation-run-id "$active_run_id" --rc-commit "$active_commit"
cp "$TMP_DIR/RC_STATUS.valid" "$active_release/RC_STATUS"
malformed_evidence="$active_release/evidence/activation-manual"
mv "$active_evidence" "$malformed_evidence"
sed "s#evidence=$active_evidence#evidence=$malformed_evidence#" \
"$TMP_DIR/RC_STATUS.valid" >"$active_release/RC_STATUS"
sed "s#ACTIVATION_EVIDENCE=$active_evidence#ACTIVATION_EVIDENCE=$malformed_evidence#" \
"$malformed_evidence/activation.log" >"$TMP_DIR/activation.log.malformed"
mv "$TMP_DIR/activation.log.malformed" "$malformed_evidence/activation.log"
chmod 600 "$malformed_evidence/activation.log"
assert_refused_without_output malformed-evidence-run-id \
--activation-run-id "$active_run_id" --rc-commit "$active_commit"
mv "$malformed_evidence" "$active_evidence"
cp "$TMP_DIR/RC_STATUS.valid" "$active_release/RC_STATUS"
cat >"$active_evidence/activation.log" <<EOF
[2026-07-23T01:02:03Z] ACTIVATION FAIL rc=1
[2026-07-23T01:02:03Z] ACTIVATION_EVIDENCE=$active_evidence
EOF
chmod 600 "$active_evidence/activation.log"
assert_refused_without_output failed-activation-terminal \
--activation-run-id "$active_run_id" --rc-commit "$active_commit"
cat >"$active_evidence/activation.log" <<EOF
[2026-07-23T01:02:03Z] RC_ACTIVATION_PASS commit=$active_commit active=$active_release
[2026-07-23T01:02:03Z] ACTIVATION_EVIDENCE=$active_evidence
EOF
chmod 600 "$active_evidence/activation.log"
# 数据库采集前后必须观测同一 active release,防止切版窗口产生错绑报告。
drift_commit=2222222222222222222222222222222222222222
drift_run_id=20260723T020304Z
drift_release="$DOGFOOD_COLLECTOR_TEST_RELEASES_ROOT/$drift_commit"
drift_evidence="$drift_release/evidence/activation-$drift_run_id"
mkdir -p "$drift_evidence"
printf 'active commit=%s at=2026-07-23T02:03:04Z evidence=%s\n' \
"$drift_commit" "$drift_evidence" >"$drift_release/RC_STATUS"
cat >"$drift_evidence/activation.log" <<EOF
[2026-07-23T02:03:04Z] RC_ACTIVATION_PASS commit=$drift_commit active=$drift_release
[2026-07-23T02:03:04Z] ACTIVATION_EVIDENCE=$drift_evidence
EOF
chmod 600 "$drift_release/RC_STATUS" "$drift_evidence/activation.log"
reset_state_probe
export DOGFOOD_TEST_DRIFT_TARGET="$drift_release"
if "$SCRIPT" --activation-run-id "$active_run_id" --rc-commit "$active_commit" \
--output "$TMP_DIR/pointer-drift.json" >/dev/null 2>&1; then
fail '采集期间 active 指针漂移仍被签发'
fi
[ ! -e "$TMP_DIR/pointer-drift.json" ] && [ ! -e "$TMP_DIR/pointer-drift.json.sig" ] || \
fail '指针漂移失败后遗留半成品'
ln -sfn "$active_release" "$DOGFOOD_COLLECTOR_TEST_RELEASES_ROOT/active"
unset DOGFOOD_TEST_DRIFT_TARGET
# 精确规模是签发前置条件,39 个 CLAIMED 账号必须失败且不得留下半成品。
head -n 51 "$TMP_DIR/mysql.tsv" >"$TMP_DIR/incomplete.tsv"
export DOGFOOD_TEST_MYSQL_ROWS="$TMP_DIR/incomplete.tsv"
if "$SCRIPT" --activation-run-id "$active_run_id" \
--rc-commit "$active_commit" \
--output "$TMP_DIR/incomplete.json" >/dev/null 2>&1; then
fail '不完整 40+12 数据仍被签发'
fi
[ ! -e "$TMP_DIR/incomplete.json" ] && [ ! -e "$TMP_DIR/incomplete.json.sig" ] || fail '失败后遗留半成品'
# new-api 权威状态异常、ID 关联漂移或一名用户匹配多行时都不得签发。
for mutation in disabled zero id-drift multi-row; do
case "$mutation" in
disabled)
awk -F'|' 'BEGIN{OFS="|"} NR==1{$5=0} {print}' "$TMP_DIR/newapi.tsv" >"$TMP_DIR/newapi-$mutation.tsv"
;;
zero)
awk -F'|' 'BEGIN{OFS="|"} NR==1{$8=0} {print}' "$TMP_DIR/newapi.tsv" >"$TMP_DIR/newapi-$mutation.tsv"
;;
id-drift)
awk -F'|' 'BEGIN{OFS="|"} NR==1{$4=999999} {print}' "$TMP_DIR/newapi.tsv" >"$TMP_DIR/newapi-$mutation.tsv"
;;
multi-row)
cp "$TMP_DIR/newapi.tsv" "$TMP_DIR/newapi-$mutation.tsv"
head -n 1 "$TMP_DIR/newapi.tsv" >>"$TMP_DIR/newapi-$mutation.tsv"
;;
esac
export DOGFOOD_TEST_NEWAPI_ROWS="$TMP_DIR/newapi-$mutation.tsv"
if "$SCRIPT" --activation-run-id "$active_run_id" \
--rc-commit "$active_commit" \
--output "$TMP_DIR/$mutation.json" >/dev/null 2>&1; then
fail "new-api 异常仍被签发 mutation=$mutation"
fi
[ ! -e "$TMP_DIR/$mutation.json" ] && [ ! -e "$TMP_DIR/$mutation.json.sig" ] || \
fail "new-api 异常后遗留半成品 mutation=$mutation"
done
printf 'TEST_PASS 账号报告固定 SSH/MySQL、40+12、签名、权限与脱敏校验通过\n'