template-monorepo/scripts/e2e-list.sh

127 lines
5.6 KiB
Bash
Raw Normal View History

test(e2e): 加 banner / e2e-list / k6 風格 user journey 讓「我有哪些測試、現在在測什麼」一眼看得到,並補上跨 endpoint 的狀態流測試: 每個測試開頭印中文 banner - 新增 e2eStep(t, id, method, path, desc) helper(test/e2e/setup_test.go) - 17 個 contract test 開頭加 banner,go test -v 會逐個顯示 ▶ [M-01] GET /api/v1/members/me — 讀 profile(tenant/uid/status) - 對外 ID 與 docs/e2e-testing.md 的測試覆蓋矩陣對齊 新增 make e2e-list - scripts/e2e-list.sh 掃 _test.go,分兩節印 contract tests + journeys; 每個 journey 列出所有 step ID + 描述(Step 用 ▶、SkipStep 用 ⊘) scripts 彩色 step banner + optional MailHog - scripts/e2e-lib.sh 抽共用 helpers(e2e_step/info/ok/warn、e2e_print_services) - e2e-run.sh / e2e-up.sh 改用 step banner + 服務面板(執行完印出 Mongo/Redis/ Gateway/MailHog 的 URL) - E2E_WITH_SMTP=1 會額外起 MailHog(http://localhost:8025),方便肉眼確認流程 k6 風格 user journey - 新增 test/e2e/journey.go:NewJourney + Step + SkipStep + Summary, 任一步 fail 自動 skip 後續,輸出 ▶ [J-x.y] 階層 banner - J-1 Tenant Owner 入職第一天(12 steps):/me → PATCH → email verify → phone verify → TOTP enroll/verify/replay/disable - J-2 Tenant Admin 建 qa_engineer 角色 → 指派 → 二人視角驗證 → 撤銷(8 steps) - J-3 Session 生命週期 refresh → /me → logout → 舊 token 401(4 steps,ZZZ 排最後) - J-4 完整註冊 → 登入(5 steps stub,標 SkipStep;接 ZITADEL container 後改 Step 即可) - make e2e-journey / make test-e2e-journey 拆獨立 target;e2e-run.sh 透過 E2E_MODE=journey + E2E_TEST_PATTERN_ZZZ 切換 docs/e2e-testing.md - 首節改為「我現在有哪些測試?make e2e-list」並附 banner 範例輸出 - 加 Journeys 章節:journey 列表、執行範例、失敗時的輸出、寫新 journey 範本 - 補 e2e-journey / test-e2e-journey / E2E_WITH_SMTP 環境變數 Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 09:18:36 +00:00
#!/usr/bin/env bash
# 列出所有 E2E 測試(從 _test.go 的 e2eStep(...) 呼叫撈)。
# 對齊 docs/e2e-testing.md 的「測試覆蓋矩陣」;新增 / 修改 e2eStep 後重跑即可。
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
TESTS_DIR="${ROOT}/test/e2e"
if ! command -v rg >/dev/null 2>&1; then
echo "需要 ripgrepbrew install ripgrep" >&2
exit 1
fi
# Colors非 TTY 時關掉)
if [[ -t 1 ]]; then
BOLD=$'\033[1m'; DIM=$'\033[2m'; CYAN=$'\033[36m'; YELLOW=$'\033[33m'; RESET=$'\033[0m'
else
BOLD=""; DIM=""; CYAN=""; YELLOW=""; RESET=""
fi
echo "${BOLD}Gateway E2E — 自動測試清單${RESET}"
echo "${DIM}執行make e2e-full / make e2e-journey / make test-e2e單測go test -tags=e2e -run TestXxx${RESET}"
# ─────────────────────────────────────────────────────────────
# Section A: Contract tests單 endpoint由 e2eStep banner 撈)
# ─────────────────────────────────────────────────────────────
echo
echo "${BOLD}${CYAN}═══ Contract testsmake e2e-full═══${RESET}"
echo "${DIM}單一 endpoint 驗 HTTP contract可平行每個 func 一個測試。${RESET}"
# 模組分組
contract_module() {
case "$(basename "$1")" in
health_test.go) echo "Health" ;;
auth_test.go) echo "Auth" ;;
member_test.go) echo "Member" ;;
permission_test.go) echo "Permission" ;;
*) echo "Other" ;;
esac
}
contract_count=0
current_module=""
while IFS= read -r line; do
file="${line%%:*}"; rest="${line#*:}"
lineno="${rest%%:*}"
sig="${rest#*:}"
fname="$(printf '%s' "$sig" | sed -E 's/^func (Test[A-Za-z0-9_]+).*/\1/')"
# 只看 contract test 檔(不含 journey_*
case "$(basename "$file")" in
journey_*.go|journey.go) continue ;;
esac
mod="$(contract_module "$file")"
if [[ "$mod" != "$current_module" ]]; then
echo
echo " ${BOLD}── $mod ──${RESET}"
current_module="$mod"
fi
step=$(awk -v start="$lineno" 'NR>=start && NR<=start+5 && /e2eStep\(t,/ { print; exit }' "$file")
if [[ -z "$step" ]]; then
printf " ${YELLOW}? %-40s${RESET} ${DIM}(no e2eStep banner)${RESET}\n" "$fname"
continue
fi
id="$(printf '%s' "$step" | sed -nE 's/.*e2eStep\(t, "([^"]*)", *"([^"]*)", *"([^"]*)", *"([^"]*)"\).*/\1/p')"
method="$(printf '%s' "$step" | sed -nE 's/.*e2eStep\(t, "([^"]*)", *"([^"]*)", *"([^"]*)", *"([^"]*)"\).*/\2/p')"
path="$(printf '%s' "$step" | sed -nE 's/.*e2eStep\(t, "([^"]*)", *"([^"]*)", *"([^"]*)", *"([^"]*)"\).*/\3/p')"
desc="$(printf '%s' "$step" | sed -nE 's/.*e2eStep\(t, "([^"]*)", *"([^"]*)", *"([^"]*)", *"([^"]*)"\).*/\4/p')"
printf " ${BOLD}[%-9s]${RESET} %-7s %-50s %s\n" "$id" "$method" "$path" "$desc"
printf " ${DIM}└─ %s${RESET}\n" "$fname"
contract_count=$((contract_count+1))
done < <(rg -n --no-heading '^func Test[A-Za-z0-9_]+\(t \*testing\.T\)' "${TESTS_DIR}" -t go)
# ─────────────────────────────────────────────────────────────
# Section B: Journeysk6 風格多步驟)
# ─────────────────────────────────────────────────────────────
echo
echo "${BOLD}${CYAN}═══ Journeysmake e2e-journey═══${RESET}"
echo "${DIM}多步驟 user flow共享狀態任一步 fail 自動 skip 後續;用 NewJourney() + j.Step()。${RESET}"
journey_count=0
journey_step_total=0
while IFS= read -r line; do
file="${line%%:*}"; rest="${line#*:}"
lineno="${rest%%:*}"
sig="${rest#*:}"
fname="$(printf '%s' "$sig" | sed -E 's/^func (Test[A-Za-z0-9_]+).*/\1/')"
case "$(basename "$file")" in
journey_*.go) : ;;
*) continue ;;
esac
# 抓 NewJourney(t, "J-1", "title")
jline=$(awk -v start="$lineno" 'NR>=start && NR<=start+3 && /NewJourney\(t,/ { print; exit }' "$file")
jid="$(printf '%s' "$jline" | sed -nE 's/.*NewJourney\(t, "([^"]*)", *"([^"]*)"\).*/\1/p')"
jtitle="$(printf '%s' "$jline" | sed -nE 's/.*NewJourney\(t, "([^"]*)", *"([^"]*)"\).*/\2/p')"
if [[ -z "$jid" ]]; then
jid="?"; jtitle="(no NewJourney call)"
fi
steps="$(rg -n '\s+j\.(Step|SkipStep)\(' "$file" 2>/dev/null | wc -l | tr -d ' ')"
echo
printf " ${BOLD}[%s] %s${RESET} ${DIM}(%d steps · %s)${RESET}\n" "$jid" "$jtitle" "$steps" "$fname"
# 列出每個 step 的 id + desc
rg -oN 'j\.(Step|SkipStep)\("[^"]+",\s*"[^"]+"' "$file" 2>/dev/null \
| sed -nE 's/.*j\.(Step|SkipStep)\("([^"]+)", *"([^"]+)".*/\1|\2|\3/p' \
| awk -F'|' -v jid="$jid" -v reset="$RESET" -v yellow="$YELLOW" '
{
kind=$1; sid=$2; desc=$3
marker = (kind == "SkipStep") ? "⊘" : "▶"
color = (kind == "SkipStep") ? yellow : ""
printf " %s%s [%s.%s]%s %s\n", color, marker, jid, sid, reset, desc
}
'
journey_count=$((journey_count+1))
journey_step_total=$((journey_step_total+steps))
done < <(rg -n --no-heading '^func Test[A-Za-z0-9_]+\(t \*testing\.T\)' "${TESTS_DIR}" -t go)
# ─────────────────────────────────────────────────────────────
echo
echo "${DIM}合計:${contract_count} 個 contract tests · ${journey_count} 個 journeys (${journey_step_total} steps)${RESET}"