ci: покрытие и пороги, тесты shared/api, smoke образа и бюджеты, документация
This commit is contained in:
1 parent
22dd6f9937
commit
5cbf4d29d7
15 files changed
+952
-11
No files matched your search
Executable
+96
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env bash
|
||||
# Smoke-тест собранного образа: запуск с боевыми ограничениями (как в compose),
|
||||
# /ready, логин, JWKS, бюджеты RSS/размера, время docker stop, сохранность данных.
|
||||
#
|
||||
# scripts/ci/smoke-image.sh <image> [port]
|
||||
#
|
||||
# Бюджеты (env): MAX_IMAGE_MB (250), MAX_RSS_MB (200), MAX_START_S (15), MAX_STOP_S (5)
|
||||
set -euo pipefail
|
||||
|
||||
IMAGE="${1:?usage: smoke-image.sh <image> [port]}"
|
||||
PORT="${2:-18080}"
|
||||
MAX_IMAGE_MB="${MAX_IMAGE_MB:-250}"
|
||||
MAX_RSS_MB="${MAX_RSS_MB:-200}"
|
||||
MAX_START_S="${MAX_START_S:-15}"
|
||||
MAX_STOP_S="${MAX_STOP_S:-5}"
|
||||
|
||||
NAME="authportal-smoke-$$"
|
||||
VOLUME="authportal-smoke-data-$$"
|
||||
SECRET="smoke-jwt-secret-0123456789"
|
||||
ADMIN_PW="smoke-admin-pass-1"
|
||||
BASE="http://127.0.0.1:${PORT}"
|
||||
|
||||
fail() { echo "SMOKE FAIL: $*" >&2; docker logs --tail 50 "$NAME" >&2 || true; exit 1; }
|
||||
cleanup() {
|
||||
docker rm -f "$NAME" >/dev/null 2>&1 || true
|
||||
docker volume rm -f "$VOLUME" >/dev/null 2>&1 || true
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
run_container() {
|
||||
docker run -d --name "$NAME" --init \
|
||||
--memory 256m --memory-swap 256m --cpus 1.0 --pids-limit 128 \
|
||||
--read-only --tmpfs /tmp --cap-drop ALL --security-opt no-new-privileges:true \
|
||||
-v "$VOLUME:/data" -p "${PORT}:8080" \
|
||||
-e JWT_SECRET="$SECRET" -e ADMIN_PASSWORD="$ADMIN_PW" \
|
||||
-e ADMIN_EMAIL=admin@smoke.local -e ISSUER="$BASE" \
|
||||
"$IMAGE" >/dev/null
|
||||
}
|
||||
|
||||
wait_ready() {
|
||||
local start deadline
|
||||
start=$(date +%s)
|
||||
deadline=$((start + MAX_START_S))
|
||||
until curl -fsS -m 2 "$BASE/ready" >/dev/null 2>&1; do
|
||||
[ "$(date +%s)" -lt "$deadline" ] || fail "/ready не ответил за ${MAX_START_S}s"
|
||||
sleep 0.3
|
||||
done
|
||||
echo "ready за $(( $(date +%s) - start ))s"
|
||||
}
|
||||
|
||||
login() {
|
||||
curl -fsS -m 10 -X POST "$BASE/api/v1/auth/login" \
|
||||
-H 'content-type: application/json' \
|
||||
-d "{\"email\":\"admin@smoke.local\",\"password\":\"${ADMIN_PW}\"}"
|
||||
}
|
||||
|
||||
# 1. размер образа
|
||||
size_mb=$(( $(docker image inspect "$IMAGE" --format '{{.Size}}') / 1024 / 1024 ))
|
||||
echo "image size: ${size_mb} MiB (budget ${MAX_IMAGE_MB})"
|
||||
[ "$size_mb" -le "$MAX_IMAGE_MB" ] || fail "образ ${size_mb} MiB > ${MAX_IMAGE_MB} MiB"
|
||||
|
||||
# 2. старт, ready, логин, JWKS
|
||||
docker volume create "$VOLUME" >/dev/null
|
||||
docker run --rm -v "$VOLUME:/data" --entrypoint sh "$IMAGE" -c 'chown 1000:1000 /data' >/dev/null 2>&1 || true
|
||||
run_container
|
||||
wait_ready
|
||||
[ "$(curl -fsS -m 3 "$BASE/health")" = '{"ok":true}' ] || fail "/health"
|
||||
login | grep -q '"access_token"' || fail "логин администратора"
|
||||
kid1=$(curl -fsS -m 3 "$BASE/.well-known/jwks.json" | sed -E 's/.*"kid":"([^"]+)".*/\1/')
|
||||
[ -n "$kid1" ] || fail "JWKS пуст"
|
||||
code=$(curl -s -m 3 -o /dev/null -w '%{http_code}' "$BASE/oauth/nope")
|
||||
[ "$code" = 404 ] || fail "/oauth/nope вернул $code, ожидался 404"
|
||||
|
||||
# 3. память под нагрузкой логинов
|
||||
for _ in $(seq 1 10); do login >/dev/null; done
|
||||
rss_mb=$(docker stats --no-stream --format '{{.MemUsage}}' "$NAME" | sed -E 's/^([0-9.]+)([KMG]i?B).*/\1 \2/' | awk '{u=$2; v=$1; if(u ~ /^G/) v*=1024; else if(u ~ /^K/) v/=1024; printf "%d", v}')
|
||||
echo "RSS: ${rss_mb} MiB (budget ${MAX_RSS_MB})"
|
||||
[ "$rss_mb" -le "$MAX_RSS_MB" ] || fail "RSS ${rss_mb} MiB > ${MAX_RSS_MB} MiB"
|
||||
|
||||
# 4. быстрый graceful stop
|
||||
t0=$(date +%s.%N)
|
||||
docker stop -t 15 "$NAME" >/dev/null
|
||||
elapsed=$(awk -v a="$t0" -v b="$(date +%s.%N)" 'BEGIN{printf "%.1f", b-a}')
|
||||
echo "docker stop: ${elapsed}s (budget ${MAX_STOP_S}s)"
|
||||
awk -v e="$elapsed" -v m="$MAX_STOP_S" 'BEGIN{exit !(e<=m)}' || fail "docker stop ${elapsed}s > ${MAX_STOP_S}s"
|
||||
[ "$(docker inspect "$NAME" --format '{{.State.ExitCode}}')" = 0 ] || fail "код выхода после SIGTERM не 0"
|
||||
|
||||
# 5. данные переживают рестарт (тот же ключ подписи и тот же админ)
|
||||
docker rm "$NAME" >/dev/null
|
||||
run_container
|
||||
wait_ready
|
||||
login | grep -q '"access_token"' || fail "логин после рестарта"
|
||||
kid2=$(curl -fsS -m 3 "$BASE/.well-known/jwks.json" | sed -E 's/.*"kid":"([^"]+)".*/\1/')
|
||||
[ "$kid1" = "$kid2" ] || fail "kid изменился после рестарта ($kid1 → $kid2)"
|
||||
|
||||
echo "SMOKE OK"
|
||||
Reference in new issue
Block a user