games-development-ai/deploy/tests/test-dogfood-smoke.sh

243 lines
10 KiB
Bash
Executable File
Raw Permalink 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
# 内部狗粮 smoke 回归:用假 HTTP 服务锁住默认无副作用与写模式 fail-closed 前置门。
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
SMOKE="$ROOT_DIR/deploy/smoke-test.sh"
TMP_DIR="$(mktemp -d)"
REQUEST_LOG="$TMP_DIR/requests.log"
trap 'rm -rf "$TMP_DIR"' EXIT
fail() {
printf 'FAIL: %s\n' "$1" >&2
exit 1
}
assert_contains() {
local file="$1" expected="$2"
grep -Fq "$expected" "$file" || fail "$file 缺少:$expected"
}
assert_exit_code() {
local expected="$1" actual="$2" message="$3"
[ "$actual" -eq "$expected" ] || fail "${message}expected=${expected} actual=${actual}"
}
# 假 curl 实现完整写链状态机,并记录方法与 URL测试不访问任何真实服务。
mkdir -p "$TMP_DIR/bin"
cat >"$TMP_DIR/bin/curl" <<'FAKE_CURL'
#!/usr/bin/env bash
set -euo pipefail
method="GET"
output=""
url=""
auth=""
data=""
while [ "$#" -gt 0 ]; do
case "$1" in
-X) method="$2"; shift 2 ;;
-o) output="$2"; shift 2 ;;
-H)
[[ "$2" == Authorization:* ]] && auth="${2#Authorization: Bearer }"
shift 2 ;;
-w|--connect-timeout|--max-time) shift 2 ;;
--data) data="$2"; shift 2 ;;
-sS) shift ;;
http://*|https://*) url="$1"; shift ;;
*) shift ;;
esac
done
printf '%s %s\n' "$method" "$url" >>"$REQUEST_LOG"
approved_file="$REQUEST_LOG.approved"
telemetry_file="$REQUEST_LOG.telemetry"
case "$method $url" in
"POST "*/app-api/passport/password-login)
body='{"code":0,"data":{"accessToken":"creator-token","userId":101,"creatorFlag":1}}' ;;
"POST "*/admin-api/system/auth/login)
if grep -q reviewer <<<"$data"; then token=reviewer-token; else token=readonly-token; fi
body="{\"code\":0,\"data\":{\"accessToken\":\"$token\"}}" ;;
"POST "*/app-api/passport/password-register)
body='{"code":1002090007,"data":null}' ;;
"POST "*/app-api/passport/send-sms-code)
body='{"code":1002090008,"data":null}' ;;
"GET "*/app-api/ad/slot/list-enabled)
body='{"code":1111002006,"data":null}' ;;
"GET "*/app-api/trade/charge/points-balance|"GET "*/app-api/trade/withdraw/page*)
body='{"code":1106008000,"data":null}' ;;
"POST "*/app-api/studio/create)
body='{"code":0,"data":{"id":31,"aigcTaskId":32,"gameId":11}}' ;;
"GET "*/app-api/studio/task/31)
body='{"code":0,"data":{"status":"2","aigcTaskId":32,"gameId":11,"versionId":22}}' ;;
"GET "*/app-api/runtime/package/22/manifest*)
body='{"entry":"index.html"}' ;;
"GET "*/app-api/runtime/package/22*)
body='{"code":0,"data":{"gameId":11,"versionId":22,"checksum":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}}' ;;
"POST "*/app-api/project/11/publish)
body='{"code":0,"data":{"admitted":true,"gates":[]}}' ;;
"GET "*/admin-api/project/review/page*)
body='{"code":0,"data":{"list":[{"id":11}]}}' ;;
"POST "*/admin-api/project/review)
if [ "$auth" = reviewer-token ]; then : >"$approved_file"; body='{"code":0,"data":true}';
else body='{"code":1004030000,"data":null}'; fi ;;
"GET "*/app-api/project/11)
body='{"code":0,"data":{"status":"4"}}' ;;
"POST "*/app-api/runtime/session/start)
body='{"code":0,"data":{"sessionId":41}}' ;;
"POST "*/app-api/runtime/session/end)
body='{"code":0,"data":true}' ;;
"POST "*/app-api/telemetry/events/batch)
: >"$telemetry_file"; body='{"code":0,"data":{"accepted":2,"rejected":0}}' ;;
"GET "*/admin-api/telemetry/game-stat/11)
if [ -f "$telemetry_file" ]; then body='{"code":0,"data":{"playCount":1,"playEndCount":1}}';
else body='{"code":0,"data":{"playCount":0,"playEndCount":0}}'; fi ;;
"GET "*/actuator/health)
body='{"status":"UP"}' ;;
"GET "*/app-api/feed/zones)
body='{"code":0,"data":[{"id":1}]}' ;;
"GET "*/app-api/feed/stream*)
if [ -f "$approved_file" ]; then
body='{"code":0,"data":{"list":[{"gameId":11,"versionId":22}],"hasMore":false}}'
else
body='{"code":0,"data":{"list":[{"gameId":99,"versionId":98}],"hasMore":false}}'
fi ;;
"GET "*/app-api/runtime/package/98*)
body='{"code":0,"data":{"gameId":99,"versionId":98,"checksum":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"}}' ;;
*)
body='{"code":404,"data":null}' ;;
esac
printf '%s' "$body" >"$output"
printf '200'
FAKE_CURL
chmod +x "$TMP_DIR/bin/curl"
export PATH="$TMP_DIR/bin:$PATH"
export REQUEST_LOG
cat >"$TMP_DIR/bin/hostname" <<'SH'
#!/usr/bin/env bash
printf 'mini-desktop\n'
SH
chmod +x "$TMP_DIR/bin/hostname"
cat >"$TMP_DIR/bin/systemctl" <<'SH'
#!/usr/bin/env bash
set -euo pipefail
if [ "$*" = 'is-active --quiet gda-staging-backend' ]; then
exit 0
fi
if [ "$*" = 'show --property=MainPID --value gda-staging-backend' ]; then
printf '4242\n'
exit 0
fi
exit 2
SH
chmod +x "$TMP_DIR/bin/systemctl"
bash -n "$SMOKE"
# 构建配置只能包含内网地址和模式开关,不能烘焙登录 token。
assert_contains "$ROOT_DIR/game-studio/.env.staging" 'VITE_STUDIO_TOKEN='
assert_contains "$ROOT_DIR/game-studio/.env.staging" 'VITE_DOGFOOD_MODE=true'
if grep -Eq '^VITE_STUDIO_TOKEN=.+$' "$ROOT_DIR/game-studio/.env.staging"; then
fail 'game-studio staging 构建烘焙了 token'
fi
assert_contains "$ROOT_DIR/game-admin/.env.stage" "VITE_BASE_URL='http://100.64.0.7:48080'"
if find "$ROOT_DIR/game-studio/public/mock-manifests" -type f -print -quit 2>/dev/null | grep -q .; then
fail 'staging public 目录仍包含退役 mock/CDP 走查资产'
fi
# 写模式必须显式确认副作用,且在任何 HTTP 请求前 fail-closed。
assert_contains "$SMOKE" 'DOGFOOD_E2E_ACK'
set +e
PATH="$TMP_DIR/bin:$PATH" "$SMOKE" --dogfood-e2e >"$TMP_DIR/missing-env.out" 2>&1
missing_env_rc=$?
set -e
assert_exit_code 2 "$missing_env_rc" '缺凭据时没有 fail-closed'
[ ! -s "$REQUEST_LOG" ] || fail '缺凭据时已发出网络请求'
set +e
DOGFOOD_CREATOR_USERNAME=creator DOGFOOD_CREATOR_PASSWORD=creator-pass \
DOGFOOD_REVIEWER_USERNAME=reviewer DOGFOOD_REVIEWER_PASSWORD=reviewer-pass \
DOGFOOD_READONLY_USERNAME=readonly DOGFOOD_READONLY_PASSWORD=readonly-pass \
PATH="$TMP_DIR/bin:$PATH" "$SMOKE" --dogfood-e2e >"$TMP_DIR/missing-ack.out" 2>&1
missing_ack_rc=$?
set -e
assert_exit_code 2 "$missing_ack_rc" '缺 ACK 时没有 fail-closed'
[ ! -s "$REQUEST_LOG" ] || fail '缺 ACK 时已发出网络请求'
# 写模式固定到 mini-desktop stagingACK 不能把 BASE 重定向到任意远端。
set +e
BASE=http://example.invalid DOGFOOD_CREATOR_USERNAME=creator DOGFOOD_CREATOR_PASSWORD=creator-pass \
DOGFOOD_REVIEWER_USERNAME=reviewer DOGFOOD_REVIEWER_PASSWORD=reviewer-pass \
DOGFOOD_READONLY_USERNAME=readonly DOGFOOD_READONLY_PASSWORD=readonly-pass \
DOGFOOD_E2E_ACK=I_ACKNOWLEDGE_DOGFOOD_WRITES EXPECTED_RC_COMMIT=0000000000000000000000000000000000000000 \
PATH="$TMP_DIR/bin:$PATH" "$SMOKE" --dogfood-e2e >"$TMP_DIR/wrong-target.out" 2>&1
wrong_target_rc=$?
set -e
assert_exit_code 2 "$wrong_target_rc" '写模式允许重定向到非 staging BASE'
# 默认 readiness 只能发送 GET且假服务完整返回时应通过。
PATH="$TMP_DIR/bin:$PATH" "$SMOKE" >"$TMP_DIR/readiness.out" 2>&1
assert_contains "$TMP_DIR/readiness.out" 'SMOKE_VERDICT=PASS'
if grep -Ev '^GET ' "$REQUEST_LOG" | grep -q .; then
fail '默认 readiness 发出了非 GET 请求'
fi
# 未知参数同样必须在发请求前拒绝。
: >"$REQUEST_LOG"
set +e
PATH="$TMP_DIR/bin:$PATH" "$SMOKE" --deep >"$TMP_DIR/unknown.out" 2>&1
unknown_rc=$?
set -e
assert_exit_code 2 "$unknown_rc" '未知参数未拒绝'
[ ! -s "$REQUEST_LOG" ] || fail '未知参数触发了网络请求'
# 完整写模式回归:审核前 scan_main_feed 返回 1 是正确缺席bash -e 不得提前退出。
: >"$REQUEST_LOG"
rm -f "$REQUEST_LOG.approved" "$REQUEST_LOG.telemetry"
active_root="$TMP_DIR/releases"
active_release="$active_root/0000000000000000000000000000000000000000"
mkdir -p "$active_release"
ln -s "$active_release" "$active_root/active"
printf 'active commit=0000000000000000000000000000000000000000 at=test evidence=test\n' >"$active_release/RC_STATUS"
active_release="$(readlink -f "$active_root/active")"
proc_root="$TMP_DIR/proc"
mkdir -p "$proc_root/4242"
printf 'java\0-jar\0%s\0' "$TMP_DIR/releases/wrong/game-cloud/huijing-server/target/huijing-server.jar" \
>"$proc_root/4242/cmdline"
set +e
DOGFOOD_CREATOR_USERNAME=creator DOGFOOD_CREATOR_PASSWORD=creator-pass \
DOGFOOD_REVIEWER_USERNAME=reviewer DOGFOOD_REVIEWER_PASSWORD=reviewer-pass \
DOGFOOD_READONLY_USERNAME=readonly DOGFOOD_READONLY_PASSWORD=readonly-pass \
DOGFOOD_E2E_ACK=I_ACKNOWLEDGE_DOGFOOD_WRITES EXPECTED_RC_COMMIT=0000000000000000000000000000000000000000 \
STAGING_ACTIVE_POINTER="$active_root/active" SYSTEMCTL_BIN="$TMP_DIR/bin/systemctl" PROC_ROOT="$proc_root" \
BASE=http://127.0.0.1:48080 STUDIO_BASE=http://127.0.0.1:4173 \
PATH="$TMP_DIR/bin:$PATH" bash "$SMOKE" --dogfood-e2e >"$TMP_DIR/wrong-process.out" 2>&1
wrong_process_rc=$?
set -e
assert_exit_code 2 "$wrong_process_rc" '写 smoke 未拒绝与 active RC 不一致的 backend 进程'
mkdir -p "$active_release/game-cloud/huijing-server/target"
printf 'java\0-jar\0%s\0' "$active_release/game-cloud/huijing-server/target/huijing-server.jar" \
>"$proc_root/4242/cmdline"
set +e
DOGFOOD_CREATOR_USERNAME=creator DOGFOOD_CREATOR_PASSWORD=creator-pass \
DOGFOOD_REVIEWER_USERNAME=reviewer DOGFOOD_REVIEWER_PASSWORD=reviewer-pass \
DOGFOOD_READONLY_USERNAME=readonly DOGFOOD_READONLY_PASSWORD=readonly-pass \
DOGFOOD_E2E_ACK=I_ACKNOWLEDGE_DOGFOOD_WRITES EXPECTED_RC_COMMIT=0000000000000000000000000000000000000000 \
STAGING_ACTIVE_POINTER="$active_root/active" SYSTEMCTL_BIN="$TMP_DIR/bin/systemctl" PROC_ROOT="$proc_root" \
BASE=http://127.0.0.1:48080 STUDIO_BASE=http://127.0.0.1:4173 \
E2E_TIMEOUT_SEC=5 POLL_INTERVAL_SEC=0 EVIDENCE_OUT="$TMP_DIR/e2e.json" \
PATH="$TMP_DIR/bin:$PATH" bash -e "$SMOKE" --dogfood-e2e >"$TMP_DIR/e2e.out" 2>&1
e2e_rc=$?
set -e
if [ "$e2e_rc" -ne 0 ]; then
sed -n '1,200p' "$TMP_DIR/e2e.out" >&2
fail "完整写模式回归失败 rc=$e2e_rc"
fi
assert_contains "$TMP_DIR/e2e.out" 'pre-approve game absent from main feed'
assert_contains "$TMP_DIR/e2e.out" 'DOGFOOD_E2E_VERDICT=PASS'
assert_contains "$TMP_DIR/e2e.json" '"status": "passed"'
printf 'PASS: dogfood smoke fail-closed regression\n'