189 lines
6.4 KiB
Bash
189 lines
6.4 KiB
Bash
#!/usr/bin/env bash
|
|
|
|
set -euo pipefail
|
|
|
|
# 先编译并拉起临时 api-server 进程,再校验 /healthz 的裸响应、envelope 和关键响应头契约。
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
用法:
|
|
./server-rs/scripts/smoke.sh
|
|
GENARRATIVE_SMOKE_PORT=3201 ./server-rs/scripts/smoke.sh
|
|
|
|
说明:
|
|
1. 先执行 `cargo build -p api-server`
|
|
2. 启动一个临时 `api-server` 进程并等待 `/healthz` 就绪
|
|
3. 校验 raw `/healthz`、envelope `/healthz` 以及 `x-request-id / x-api-version / x-route-version / x-response-time-ms`
|
|
4. 冒烟结束后自动回收临时进程
|
|
EOF
|
|
}
|
|
|
|
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
|
usage
|
|
exit 0
|
|
fi
|
|
|
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
|
SERVER_RS_DIR="$(cd -- "${SCRIPT_DIR}/.." && pwd)"
|
|
MANIFEST_PATH="${SERVER_RS_DIR}/Cargo.toml"
|
|
SERVER_BIN="${SERVER_RS_DIR}/target/debug/api-server"
|
|
API_HOST="${GENARRATIVE_SMOKE_HOST:-127.0.0.1}"
|
|
API_PORT="${GENARRATIVE_SMOKE_PORT:-3101}"
|
|
API_LOG="${GENARRATIVE_SMOKE_LOG:-warn,tower_http=warn}"
|
|
STARTUP_TIMEOUT_SECONDS="${GENARRATIVE_SMOKE_TIMEOUT_SECONDS:-30}"
|
|
BASE_URL="http://${API_HOST}:${API_PORT}"
|
|
HEALTHZ_URL="${BASE_URL}/healthz"
|
|
RUN_ID="$(date +%s)-$$"
|
|
STDOUT_LOG="${TMPDIR:-/tmp}/genarrative-server-rs-smoke-${RUN_ID}.stdout.log"
|
|
STDERR_LOG="${TMPDIR:-/tmp}/genarrative-server-rs-smoke-${RUN_ID}.stderr.log"
|
|
KEEP_LOGS=0
|
|
SERVER_PID=""
|
|
|
|
assert_contains() {
|
|
local haystack="$1"
|
|
local needle="$2"
|
|
local message="$3"
|
|
|
|
if [[ "${haystack}" != *"${needle}"* ]]; then
|
|
echo "[server-rs:smoke] ${message}" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
print_logs() {
|
|
if [[ -f "${STDOUT_LOG}" ]]; then
|
|
echo "[server-rs:smoke] stdout:"
|
|
cat "${STDOUT_LOG}"
|
|
fi
|
|
|
|
if [[ -f "${STDERR_LOG}" ]]; then
|
|
echo "[server-rs:smoke] stderr:"
|
|
cat "${STDERR_LOG}"
|
|
fi
|
|
}
|
|
|
|
cleanup() {
|
|
if [[ -n "${SERVER_PID}" ]] && kill -0 "${SERVER_PID}" 2>/dev/null; then
|
|
kill "${SERVER_PID}" 2>/dev/null || true
|
|
wait "${SERVER_PID}" 2>/dev/null || true
|
|
fi
|
|
|
|
if [[ "${KEEP_LOGS}" -eq 0 ]]; then
|
|
rm -f "${STDOUT_LOG}" "${STDERR_LOG}"
|
|
fi
|
|
}
|
|
|
|
trap cleanup EXIT
|
|
|
|
if [[ ! -f "${MANIFEST_PATH}" ]]; then
|
|
echo "[server-rs:smoke] 未找到 ${MANIFEST_PATH},无法启动冒烟脚本。" >&2
|
|
exit 1
|
|
fi
|
|
|
|
cd "${SERVER_RS_DIR}"
|
|
|
|
echo "[server-rs:smoke] 步骤: cargo build -p api-server"
|
|
cargo build -p api-server --manifest-path "${MANIFEST_PATH}"
|
|
|
|
if [[ ! -x "${SERVER_BIN}" ]]; then
|
|
echo "[server-rs:smoke] 未找到 ${SERVER_BIN},构建结果异常。" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "[server-rs:smoke] 步骤: start api-server binary"
|
|
GENARRATIVE_API_HOST="${API_HOST}" \
|
|
GENARRATIVE_API_PORT="${API_PORT}" \
|
|
GENARRATIVE_API_LOG="${API_LOG}" \
|
|
"${SERVER_BIN}" >"${STDOUT_LOG}" 2>"${STDERR_LOG}" &
|
|
SERVER_PID=$!
|
|
|
|
echo "[server-rs:smoke] 步骤: wait for /healthz readiness"
|
|
READY=0
|
|
for ((i = 0; i < STARTUP_TIMEOUT_SECONDS * 10; i++)); do
|
|
if ! kill -0 "${SERVER_PID}" 2>/dev/null; then
|
|
KEEP_LOGS=1
|
|
print_logs
|
|
echo "[server-rs:smoke] api-server exited before /healthz became ready." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if curl -fsS "${HEALTHZ_URL}" >/dev/null 2>&1; then
|
|
READY=1
|
|
break
|
|
fi
|
|
|
|
sleep 0.1
|
|
done
|
|
|
|
if [[ "${READY}" -ne 1 ]]; then
|
|
KEEP_LOGS=1
|
|
print_logs
|
|
echo "[server-rs:smoke] /healthz readiness timed out." >&2
|
|
exit 1
|
|
fi
|
|
|
|
RAW_BODY_FILE="${TMPDIR:-/tmp}/genarrative-server-rs-smoke-${RUN_ID}.raw.body.json"
|
|
RAW_HEADER_FILE="${TMPDIR:-/tmp}/genarrative-server-rs-smoke-${RUN_ID}.raw.headers.txt"
|
|
ENVELOPE_BODY_FILE="${TMPDIR:-/tmp}/genarrative-server-rs-smoke-${RUN_ID}.envelope.body.json"
|
|
ENVELOPE_HEADER_FILE="${TMPDIR:-/tmp}/genarrative-server-rs-smoke-${RUN_ID}.envelope.headers.txt"
|
|
|
|
trap 'rm -f "${RAW_BODY_FILE}" "${RAW_HEADER_FILE}" "${ENVELOPE_BODY_FILE}" "${ENVELOPE_HEADER_FILE}"; cleanup' EXIT
|
|
|
|
RAW_REQUEST_ID="smoke-healthz-raw"
|
|
echo "[server-rs:smoke] 步骤: verify raw /healthz contract"
|
|
curl -fsS \
|
|
-H "x-request-id: ${RAW_REQUEST_ID}" \
|
|
-D "${RAW_HEADER_FILE}" \
|
|
-o "${RAW_BODY_FILE}" \
|
|
"${HEALTHZ_URL}"
|
|
|
|
RAW_BODY="$(tr -d '\n' <"${RAW_BODY_FILE}")"
|
|
RAW_REQUEST_ID_HEADER="$(grep -i '^x-request-id:' "${RAW_HEADER_FILE}" | tail -n 1 | cut -d':' -f2- | tr -d '\r' | xargs)"
|
|
API_VERSION_HEADER="$(grep -i '^x-api-version:' "${RAW_HEADER_FILE}" | tail -n 1 | cut -d':' -f2- | tr -d '\r' | xargs)"
|
|
ROUTE_VERSION_HEADER="$(grep -i '^x-route-version:' "${RAW_HEADER_FILE}" | tail -n 1 | cut -d':' -f2- | tr -d '\r' | xargs)"
|
|
RESPONSE_TIME_HEADER="$(grep -i '^x-response-time-ms:' "${RAW_HEADER_FILE}" | tail -n 1 | cut -d':' -f2- | tr -d '\r' | xargs)"
|
|
|
|
assert_contains "${RAW_BODY}" '"ok":true' "raw /healthz body 缺少 ok=true。"
|
|
assert_contains "${RAW_BODY}" '"service":"genarrative-node-server"' "raw /healthz body 返回了意外的 service。"
|
|
|
|
if [[ "${RAW_REQUEST_ID_HEADER}" != "${RAW_REQUEST_ID}" ]]; then
|
|
echo "[server-rs:smoke] raw /healthz 没有正确回写 x-request-id。" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ -z "${API_VERSION_HEADER}" ]]; then
|
|
echo "[server-rs:smoke] raw /healthz 缺少 x-api-version。" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "${ROUTE_VERSION_HEADER}" != "${API_VERSION_HEADER}" ]]; then
|
|
echo "[server-rs:smoke] raw /healthz 的 x-route-version 与 x-api-version 不一致。" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! "${RESPONSE_TIME_HEADER}" =~ ^[0-9]+$ ]]; then
|
|
echo "[server-rs:smoke] raw /healthz 的 x-response-time-ms 不是合法整数。" >&2
|
|
exit 1
|
|
fi
|
|
|
|
ENVELOPE_REQUEST_ID="smoke-healthz-envelope"
|
|
echo "[server-rs:smoke] 步骤: verify envelope /healthz contract"
|
|
curl -fsS \
|
|
-H "x-request-id: ${ENVELOPE_REQUEST_ID}" \
|
|
-H "x-genarrative-response-envelope: 1" \
|
|
-D "${ENVELOPE_HEADER_FILE}" \
|
|
-o "${ENVELOPE_BODY_FILE}" \
|
|
"${HEALTHZ_URL}"
|
|
|
|
ENVELOPE_BODY="$(tr -d '\n' <"${ENVELOPE_BODY_FILE}")"
|
|
|
|
assert_contains "${ENVELOPE_BODY}" '"ok":true' "envelope /healthz body 缺少 ok=true。"
|
|
assert_contains "${ENVELOPE_BODY}" '"error":null' "envelope /healthz body 缺少 error=null。"
|
|
assert_contains "${ENVELOPE_BODY}" '"data":{"ok":true,"service":"genarrative-node-server"}' "envelope /healthz data 返回异常。"
|
|
assert_contains "${ENVELOPE_BODY}" "\"requestId\":\"${ENVELOPE_REQUEST_ID}\"" "envelope /healthz meta.requestId 没有回写请求 ID。"
|
|
assert_contains "${ENVELOPE_BODY}" "\"apiVersion\":\"${API_VERSION_HEADER}\"" "envelope /healthz meta.apiVersion 与响应头不一致。"
|
|
assert_contains "${ENVELOPE_BODY}" "\"routeVersion\":\"${ROUTE_VERSION_HEADER}\"" "envelope /healthz meta.routeVersion 与响应头不一致。"
|
|
assert_contains "${ENVELOPE_BODY}" '"operation":"GET /healthz"' "envelope /healthz meta.operation 异常。"
|
|
|
|
echo "[server-rs:smoke] all checks passed"
|