From fc457a0651b0c05b762a32938afbb1d4b8ac4342 Mon Sep 17 00:00:00 2001 From: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com> Date: Tue, 4 Aug 2026 19:20:34 +0800 Subject: [PATCH] test(smoke): decouple admin checks from bootstrap credentials (#686) Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com> --- .github/workflows/pr-scripts.yml | 1 + Makefile | 2 +- README.md | 12 ++ README_zh.md | 10 ++ docs/dev-workflow.md | 6 + scripts/smoke-test.sh | 40 ++++- scripts/tests/smoke-test-admin-mode-test.sh | 156 ++++++++++++++++++++ scripts/tests/workflow-security-test.sh | 2 + 8 files changed, 220 insertions(+), 9 deletions(-) create mode 100755 scripts/tests/smoke-test-admin-mode-test.sh diff --git a/.github/workflows/pr-scripts.yml b/.github/workflows/pr-scripts.yml index 6b46fde7..d49466e7 100644 --- a/.github/workflows/pr-scripts.yml +++ b/.github/workflows/pr-scripts.yml @@ -38,5 +38,6 @@ jobs: - run: bash scripts/tests/runtime-secret-test.sh - run: bash scripts/tests/validate-release-config-test.sh - run: bash scripts/tests/nginx-forwarded-proto-test.sh + - run: bash scripts/tests/smoke-test-admin-mode-test.sh - run: bash scripts/tests/dev-web-host-test.sh - run: bash scripts/tests/workflow-security-test.sh diff --git a/Makefile b/Makefile index 95d62b57..aa52cfb1 100644 --- a/Makefile +++ b/Makefile @@ -313,7 +313,7 @@ staging: ## 构建并启动 staging 环境,运行 smoke test(混合模式: @echo "=== [4/5] Starting staging services ===" $(STAGING_COMPOSE) up -d --wait server web @echo "=== [5/5] Running smoke tests ===" - @if BOOTSTRAP_ADMIN_USERNAME=admin BOOTSTRAP_ADMIN_PASSWORD='Admin@staging2026' \ + @if SMOKE_ADMIN_USERNAME=admin SMOKE_ADMIN_PASSWORD='Admin@staging2026' \ bash scripts/smoke-test.sh $(STAGING_API_URL); then \ echo ""; \ echo "Staging passed. Environment is running:"; \ diff --git a/README.md b/README.md index dda1f3ef..34555dda 100644 --- a/README.md +++ b/README.md @@ -400,6 +400,18 @@ Run it against a local backend: ./scripts/smoke-test.sh http://localhost:8080 ``` +Admin label-management smoke checks run only when current admin credentials are +supplied explicitly: + +```bash +SMOKE_ADMIN_USERNAME=admin SMOKE_ADMIN_PASSWORD='current-password' \ + ./scripts/smoke-test.sh http://localhost:8080 +``` + +Use `SMOKE_ADMIN_CHECKS=false` for persistent environments where only non-admin +smoke checks should run. The script no longer falls back to bootstrap admin +password defaults. + ## Architecture ``` diff --git a/README_zh.md b/README_zh.md index 2e7da740..c8132525 100644 --- a/README_zh.md +++ b/README_zh.md @@ -193,6 +193,16 @@ make generate-api # 重新生成 OpenAPI 类型 ./scripts/smoke-test.sh http://localhost:8080 # 运行冒烟测试 ``` +管理员标签管理冒烟测试只会在显式提供当前管理员凭证时运行: + +```bash +SMOKE_ADMIN_USERNAME=admin SMOKE_ADMIN_PASSWORD='current-password' \ + ./scripts/smoke-test.sh http://localhost:8080 +``` + +持久化环境只跑非管理员冒烟检查时,可设置 `SMOKE_ADMIN_CHECKS=false`。 +脚本不再回退使用 bootstrap 管理员默认密码。 + 说明:不要在 `server/` 下直接执行 `./mvnw -pl skillhub-app clean test`。`skillhub-app` 依赖同仓库的 sibling modules,单独 clean 构建时会回退到本地 Maven 仓库里的旧产物并出现大量 `cannot find symbol` / 签名不匹配错误。需要使用 `-am`,或者直接使用上面的 `make test-backend-app` / `make build-backend-app`。 ### 项目结构 diff --git a/docs/dev-workflow.md b/docs/dev-workflow.md index d79e62a8..de332c16 100644 --- a/docs/dev-workflow.md +++ b/docs/dev-workflow.md @@ -129,6 +129,12 @@ This will: 4. Run smoke tests against the API 5. Print pass/fail summary +`scripts/smoke-test.sh` separates bootstrap creation from smoke credentials. +Admin smoke checks run only when `SMOKE_ADMIN_USERNAME` and +`SMOKE_ADMIN_PASSWORD` are provided. Use `SMOKE_ADMIN_CHECKS=false` when testing +a persistent environment where the current admin password is not part of the +smoke run. + If all tests pass, the environment stays running at: - Web UI: http://localhost - Backend API: http://localhost:8080 diff --git a/scripts/smoke-test.sh b/scripts/smoke-test.sh index e1e46289..a523aafe 100755 --- a/scripts/smoke-test.sh +++ b/scripts/smoke-test.sh @@ -31,6 +31,12 @@ check() { fi } +finish() { + echo + echo "Results: $PASS passed, $FAIL failed" + [[ "$FAIL" -eq 0 ]] +} + echo "=== SkillHub Smoke Test ===" echo "Target: $BASE_URL" echo @@ -113,8 +119,30 @@ else fi # ---- Label Management (requires admin) ---- -ADMIN_USERNAME="${BOOTSTRAP_ADMIN_USERNAME:-admin}" -ADMIN_PASSWORD="${BOOTSTRAP_ADMIN_PASSWORD:-ChangeMe!2026}" +SMOKE_ADMIN_CHECKS="${SMOKE_ADMIN_CHECKS:-auto}" +SMOKE_ADMIN_USERNAME="${SMOKE_ADMIN_USERNAME:-}" +SMOKE_ADMIN_PASSWORD="${SMOKE_ADMIN_PASSWORD:-}" +ADMIN_CONFIG_FAILED=0 +if [[ "$SMOKE_ADMIN_CHECKS" != "auto" && "$SMOKE_ADMIN_CHECKS" != "true" && "$SMOKE_ADMIN_CHECKS" != "false" ]]; then + echo "FAIL: SMOKE_ADMIN_CHECKS must be auto, true, or false" + FAIL=$((FAIL + 1)) + ADMIN_CONFIG_FAILED=1 +fi +if [[ "$SMOKE_ADMIN_CHECKS" == "true" && ( -z "$SMOKE_ADMIN_USERNAME" || -z "$SMOKE_ADMIN_PASSWORD" ) ]]; then + echo "FAIL: SMOKE_ADMIN_CHECKS=true requires SMOKE_ADMIN_USERNAME and SMOKE_ADMIN_PASSWORD" + FAIL=$((FAIL + 1)) + ADMIN_CONFIG_FAILED=1 +fi +if [[ "$ADMIN_CONFIG_FAILED" -ne 0 ]]; then + finish + exit $? +fi +if [[ "$SMOKE_ADMIN_CHECKS" == "false" || ( "$SMOKE_ADMIN_CHECKS" == "auto" && ( -z "$SMOKE_ADMIN_USERNAME" || -z "$SMOKE_ADMIN_PASSWORD" ) ) ]]; then + echo "SKIP: Admin label management (set SMOKE_ADMIN_USERNAME and SMOKE_ADMIN_PASSWORD to enable)" + finish + exit $? +fi + ADMIN_COOKIE_JAR="$(mktemp)" LABEL_SLUG="smoke-label-$(date +%s)" @@ -134,7 +162,7 @@ ADMIN_LOGIN_STATUS="$(curl --max-time 10 -s -o /dev/null -w "%{http_code}" \ -c "$ADMIN_COOKIE_JAR" \ -H "X-XSRF-TOKEN: $ADMIN_CSRF" \ -H "Content-Type: application/json" \ - -d "{\"username\":\"$ADMIN_USERNAME\",\"password\":\"$ADMIN_PASSWORD\"}" || true)" + -d "{\"username\":\"$SMOKE_ADMIN_USERNAME\",\"password\":\"$SMOKE_ADMIN_PASSWORD\"}" || true)" if [[ "$ADMIN_LOGIN_STATUS" == "200" ]]; then echo "PASS: Admin login (HTTP $ADMIN_LOGIN_STATUS)" PASS=$((PASS + 1)) @@ -196,8 +224,4 @@ else FAIL=$((FAIL + 1)) fi -echo -echo "Results: $PASS passed, $FAIL failed" -if [[ "$FAIL" -ne 0 ]]; then - exit 1 -fi +finish diff --git a/scripts/tests/smoke-test-admin-mode-test.sh b/scripts/tests/smoke-test-admin-mode-test.sh new file mode 100755 index 00000000..aa0c6d9e --- /dev/null +++ b/scripts/tests/smoke-test-admin-mode-test.sh @@ -0,0 +1,156 @@ +#!/usr/bin/env bash +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +SMOKE_SCRIPT="$REPO_ROOT/scripts/smoke-test.sh" + +TMP_DIR="$(mktemp -d)" +cleanup() { + rm -rf "$TMP_DIR" +} +trap cleanup EXIT + +fail() { + echo "FAIL: $*" >&2 + exit 1 +} + +mkdir -p "$TMP_DIR/bin" +cat >"$TMP_DIR/bin/curl" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail + +url="" +method="GET" +data="" +cookie_in="" +cookie_out="" +write_code=false +output_file="" +while (($#)); do + case "$1" in + -X) + method="$2" + shift 2 + ;; + -d) + data="$2" + shift 2 + ;; + -b) + cookie_in="$2" + shift 2 + ;; + -c) + cookie_out="$2" + shift 2 + ;; + -w) + write_code=true + shift 2 + ;; + -o) + output_file="$2" + shift 2 + ;; + -H|--max-time|--retry|--retry-delay) + shift 2 + ;; + -s|-sS) + shift + ;; + http://*|https://*) + url="$1" + shift + ;; + *) + shift + ;; + esac +done + +if [[ -n "$cookie_out" ]]; then + printf '%s\n' "localhost FALSE / FALSE 0 XSRF-TOKEN csrf-token" >>"$cookie_out" +fi + +printf '%s\n' "$method $url $data" >>"${SMOKE_CURL_LOG:?SMOKE_CURL_LOG is required}" + +status=200 +case "$url" in + */actuator/health) status=200 ;; + */actuator/prometheus) status=401 ;; + */api/v1/namespaces) + if [[ -n "$cookie_in" && -f "$cookie_in.session" ]]; then status=200; else status=401; fi + ;; + */api/v1/auth/me) + if [[ -n "$cookie_in" && -f "$cookie_in.session" && ! -f "$cookie_in.logged-out" ]]; then status=200; else status=401; fi + ;; + */api/v1/auth/local/register) + [[ -n "$cookie_out" ]] && touch "$cookie_out.session" + status=200 + ;; + */api/v1/auth/local/change-password) status=200 ;; + */api/v1/auth/logout) + [[ -n "$cookie_out" ]] && touch "$cookie_out.logged-out" + status=200 + ;; + */api/v1/auth/local/login) + if [[ "$data" == *'"username":"current-admin"'* && "$data" == *'"password":"current-secret"'* ]]; then + [[ -n "$cookie_out" ]] && touch "$cookie_out.session" + status=200 + else + status=401 + fi + ;; + */api/v1/admin/labels|*/api/v1/labels|*/api/v1/admin/labels/*) + status=200 + ;; +esac + +if [[ "$output_file" != "/dev/null" && -n "$output_file" ]]; then + printf '{}\n' >"$output_file" +fi +if [[ "$write_code" == true ]]; then + printf '%s' "$status" +fi +EOF +chmod +x "$TMP_DIR/bin/curl" + +run_smoke() { + local name="$1" + shift + local log="$TMP_DIR/$name.curl.log" + local out="$TMP_DIR/$name.out" + local status=0 + env PATH="$TMP_DIR/bin:$PATH" SMOKE_CURL_LOG="$log" "$@" "$SMOKE_SCRIPT" http://skillhub.test >"$out" 2>&1 || status=$? + printf '%s\n' "$status" +} + +status="$(run_smoke skip-admin env)" +[[ "$status" == "0" ]] || fail "default smoke without admin credentials should pass" +grep -Fq "SKIP: Admin label management" "$TMP_DIR/skip-admin.out" \ + || fail "default smoke should skip admin section without explicit credentials" +if grep -Fq "/api/v1/auth/local/login" "$TMP_DIR/skip-admin.curl.log"; then + fail "default smoke must not attempt admin login without explicit credentials" +fi + +status="$(run_smoke missing-admin env SMOKE_ADMIN_CHECKS=true)" +[[ "$status" != "0" ]] || fail "SMOKE_ADMIN_CHECKS=true without credentials should fail" +grep -Fq "SMOKE_ADMIN_CHECKS=true requires SMOKE_ADMIN_USERNAME and SMOKE_ADMIN_PASSWORD" "$TMP_DIR/missing-admin.out" \ + || fail "missing admin credentials should produce an actionable error" +if grep -Fq "/api/v1/auth/local/login" "$TMP_DIR/missing-admin.curl.log"; then + fail "missing explicit admin credentials must stop before admin login" +fi + +status="$(run_smoke explicit-admin env SMOKE_ADMIN_USERNAME=current-admin SMOKE_ADMIN_PASSWORD=current-secret BOOTSTRAP_ADMIN_PASSWORD=wrong-bootstrap)" +[[ "$status" == "0" ]] || fail "explicit admin credentials should run full smoke successfully" +grep -Fq '"username":"current-admin","password":"current-secret"' "$TMP_DIR/explicit-admin.curl.log" \ + || fail "admin login should use SMOKE_ADMIN credentials" +if grep -Fq 'wrong-bootstrap' "$TMP_DIR/explicit-admin.curl.log"; then + fail "admin login must not use BOOTSTRAP_ADMIN_PASSWORD" +fi +if grep -Fq 'ChangeMe!2026' "$TMP_DIR/explicit-admin.curl.log"; then + fail "admin login must not fall back to the bootstrap default password" +fi + +echo "smoke-test-admin-mode-test passed" diff --git a/scripts/tests/workflow-security-test.sh b/scripts/tests/workflow-security-test.sh index a13c7c62..62002ab5 100755 --- a/scripts/tests/workflow-security-test.sh +++ b/scripts/tests/workflow-security-test.sh @@ -77,6 +77,8 @@ grep -Fq 'bash scripts/tests/validate-release-config-test.sh' "$PR_SCRIPTS_WORKF || fail "pr-scripts must run validate-release-config-test" grep -Fq 'bash scripts/tests/nginx-forwarded-proto-test.sh' "$PR_SCRIPTS_WORKFLOW" \ || fail "pr-scripts must run nginx-forwarded-proto-test" +grep -Fq 'bash scripts/tests/smoke-test-admin-mode-test.sh' "$PR_SCRIPTS_WORKFLOW" \ + || fail "pr-scripts must run smoke-test-admin-mode-test" grep -Fq 'bash scripts/tests/runtime-secret-test.sh' "$PR_SCRIPTS_WORKFLOW" \ || fail "pr-scripts must run runtime-secret-test" grep -Fq 'bash scripts/tests/dev-web-host-test.sh' "$PR_SCRIPTS_WORKFLOW" \