#!/usr/bin/env bash # 构建冒烟测试(设计 §8.3-6): # 启动二进制 → 创建公开/私有笔记 → 断言首页/深链 HTML 注入 meta、 # script/link 以 200 + 正确 MIME 加载(防「深链白屏」类回归)→ 可见性抽查。 set -euo pipefail ROOT="$(cd "$(dirname "$0")/.." && pwd)" BIN="$ROOT/pn" TMP="$(mktemp -d)" PORT="${SMOKE_PORT:-18099}" BASE="http://127.0.0.1:$PORT" FAIL=0 cleanup() { kill "$SRV_PID" 2>/dev/null || true; rm -rf "$TMP"; } trap cleanup EXIT ok() { echo " ✓ $1"; } bad() { echo " ✗ $1"; FAIL=1; } check() { if [ "$2" -eq 0 ]; then ok "$1"; else bad "$1"; fi } echo "[smoke] 初始化数据目录" PN_ADMIN_PASSWORD=smoke-password-123 PN_SITE_TITLE="Smoke 站" "$BIN" init --data-dir "$TMP/data" echo "[smoke] 启动服务 :$PORT" "$BIN" start --data-dir "$TMP/data" --addr "127.0.0.1:$PORT" --dev >"$TMP/serve.log" 2>&1 & SRV_PID=$! for _ in $(seq 1 50); do curl -sf "$BASE/api/health" >/dev/null 2>&1 && break sleep 0.2 done echo "[smoke] 健康检查" curl -sf "$BASE/api/health" | grep -q '"ok"'; check "GET /api/health 200 含 ok" $? echo "[smoke] 登录并创建笔记" CSRF=$(curl -sf -X POST "$BASE/api/auth/login" -H "Origin: $BASE" \ -d '{"password":"smoke-password-123"}' -c "$TMP/cookies" \ | sed 's/.*"csrf_token":"\([^"]*\)".*/\1/') [ -n "$CSRF" ]; check "登录获得 CSRF token" $? CREATE=$(curl -sf -X POST "$BASE/api/admin/notes" -H "Origin: $BASE" -H "X-CSRF-Token: $CSRF" \ -H 'Content-Type: application/json' -b "$TMP/cookies" \ -d '{"title":"冒烟笔记 Smoke","content":"# Hello\n\n正文","status":"public"}') echo "$CREATE" | grep -q '"slug"'; check "创建公开笔记" $? SLUG=$(echo "$CREATE" | sed 's/.*"slug":"\([^"]*\)".*/\1/') curl -sf -X POST "$BASE/api/admin/notes" -H "Origin: $BASE" -H "X-CSRF-Token: $CSRF" \ -H 'Content-Type: application/json' -b "$TMP/cookies" \ -d '{"title":"私有机密","content":"秘密","status":"private"}' >/dev/null check "创建私有笔记" $? echo "[smoke] SPA 与 meta 注入" HOME_HTML=$(curl -sf "$BASE/") echo "$HOME_HTML" | grep -q 'Smoke 站'; check "首页注入站点标题" $? DEEP_HTML=$(curl -sf "$BASE/notes/$SLUG") echo "$DEEP_HTML" | grep -q '冒烟笔记 Smoke - Smoke 站' check "深链注入笔记标题(防白屏回归)" $? JS=$(echo "$DEEP_HTML" | grep -o '/assets/index-[^"]*\.js' | head -1) [ -n "$JS" ]; check "HTML 含打包 script 引用" $? CODE=$(curl -s -o /dev/null -w '%{http_code}' "$BASE$JS" || true) [ "$CODE" = "200" ]; check "script 资源 200(实际 $CODE)" $? MIME=$(curl -s -o /dev/null -w '%{content_type}' "$BASE$JS" || true) echo "$MIME" | grep -q 'javascript'; check "script MIME 为 javascript(实际 $MIME)" $? CSS=$(echo "$DEEP_HTML" | grep -o '/assets/index-[^"]*\.css' | head -1) CODE=$(curl -s -o /dev/null -w '%{http_code}' "$BASE$CSS" || true) [ "$CODE" = "200" ]; check "link 样式资源 200(实际 $CODE)" $? MIME=$(curl -s -o /dev/null -w '%{content_type}' "$BASE$CSS" || true) echo "$MIME" | grep -q 'text/css'; check "link MIME 为 css(实际 $MIME)" $? echo "[smoke] 安全头抽查" H=$(curl -sI "$BASE/") echo "$H" | grep -qi "Strict-Transport-Security"; check "HSTS" $? echo "$H" | grep -qi "X-Frame-Options: DENY"; check "X-Frame-Options DENY" $? echo "$H" | grep -qi "X-Content-Type-Options: nosniff"; check "nosniff" $? echo "$H" | grep -qi "Content-Security-Policy.*script-src 'self'"; check "CSP script-src 'self'" $? echo "[smoke] 可见性抽查(私有不可见)" if curl -sf "$BASE/api/notes" | grep -q '私有机密'; then bad "匿名列表不含私有笔记"; else ok "匿名列表不含私有笔记"; fi if curl -sf "$BASE/feed.xml" | grep -q '私有机密'; then bad "RSS 不含私有笔记"; else ok "RSS 不含私有笔记"; fi CODE=$(curl -s -o /dev/null -w '%{http_code}' "$BASE/api/notes/$SLUG") [ "$CODE" = "200" ]; check "公开笔记详情匿名 200(实际 $CODE)" $? if [ "$FAIL" -eq 0 ]; then echo "[smoke] 全部通过 ✓" else echo "[smoke] 存在失败项 ✗" exit 1 fi