test: 배포 스모크 테스트 (deploy/smoke-test.sh) — 인프라·설정 회귀 감지
CI가 못 잡는 '설정 회귀'(예: 보안헤더로 문서 iframe 깨짐)를 배포 후 잡는 그물. 살아있는 사이트를 실제 사용자처럼 두드려 11개 확인: - 홈 200, HTTP→HTTPS 리다이렉트, 보안헤더 3종 - 학습 문서 200 + X-Frame-Options=SAMEORIGIN + no-cache + 한국어 본문 (그 회귀를 정확히 재현·감지) - 백엔드 API 401(Caddy→백엔드→DB 경로), SPA 라우트 폴백 하나라도 실패 시 exit 1. CI.md에 사용법·권장 배포 흐름 문서화. 운영 검증: 11/11 통과, exit 0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
edafae9f86
commit
c723cba92c
23
deploy/CI.md
23
deploy/CI.md
@ -50,8 +50,27 @@ sudo docker compose -f deploy/docker-compose.prod.yml up -d runner
|
|||||||
- **actions/checkout은 node 필요**: job 이미지에 node가 있어야 한다(catthehacker 이미지에 포함).
|
- **actions/checkout은 node 필요**: job 이미지에 node가 있어야 한다(catthehacker 이미지에 포함).
|
||||||
그래서 백엔드 job도 이 이미지를 쓰고 Maven만 따로 내려받는다.
|
그래서 백엔드 job도 이 이미지를 쓰고 Maven만 따로 내려받는다.
|
||||||
|
|
||||||
|
## 배포 스모크 테스트 (`deploy/smoke-test.sh`)
|
||||||
|
|
||||||
|
CI는 "코드"를 배포 전에 검사한다. 하지만 실제로 났던 사고(보안헤더로 학습 문서 iframe이
|
||||||
|
안 뜬 것)는 코드가 아니라 **Caddy 설정** 문제라 CI 테스트로는 안 잡혔다.
|
||||||
|
스모크 테스트는 배포된 **살아있는 사이트**를 실제 사용자처럼 두드려, 설정·헤더·인프라까지
|
||||||
|
포함해 "정말 열리는가"를 확인한다. → CI(코드 그물) + 스모크(설정·운영 그물).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 배포 직후 반드시 실행 — 하나라도 실패하면 exit 1
|
||||||
|
bash deploy/smoke-test.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
검사 항목(11): 홈 200 · HTTP→HTTPS 리다이렉트 · 보안헤더 3종 ·
|
||||||
|
**학습 문서 200 + X-Frame-Options=SAMEORIGIN + no-cache + 한국어 본문**(그 회귀를 직접 감지) ·
|
||||||
|
백엔드 API 401(Caddy→백엔드→DB 경로) · SPA 라우트 폴백.
|
||||||
|
|
||||||
|
> **권장 배포 흐름**: 빌드 → 업로드 → `docker compose up` → **`bash deploy/smoke-test.sh`**.
|
||||||
|
> 스모크가 빨간불이면 배포에 문제가 있는 것이니 바로 롤백/수정한다.
|
||||||
|
|
||||||
## 다음 개선 (학생 티켓)
|
## 다음 개선 (학생 티켓)
|
||||||
|
|
||||||
- 프론트 테스트 추가 후 CI에 `npm test` 단계 넣기 (QA-05)
|
- Maven·npm 캐시로 CI 실행 시간 단축
|
||||||
- Maven·npm 캐시로 실행 시간 단축
|
|
||||||
- main 브랜치 보호(테스트 통과해야 merge)
|
- main 브랜치 보호(테스트 통과해야 merge)
|
||||||
|
- 스모크 테스트를 배포 스크립트에 자동 편입(deploy 후 자동 실행·실패 시 알림)
|
||||||
|
|||||||
86
deploy/smoke-test.sh
Normal file
86
deploy/smoke-test.sh
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 이 파일이 하는 일: 배포 "후에" 실서버가 진짜로 잘 도는지 밖에서 두드려 확인한다(스모크 테스트).
|
||||||
|
#
|
||||||
|
# 학습 포인트 — CI 테스트와 무엇이 다른가?
|
||||||
|
# CI(mvn test/npm test)는 "코드"가 맞는지 배포 전에 본다. 하지만 우리가 실제로 겪은 사고
|
||||||
|
# (보안헤더 X-Frame-Options:DENY로 학습 문서가 통째로 안 뜬 것)는 코드가 아니라 Caddy
|
||||||
|
# "설정" 문제라 CI 테스트로는 안 잡혔다. 스모크 테스트는 배포된 "살아있는 사이트"를 실제
|
||||||
|
# 사용자처럼 두드려, 설정·인프라·헤더까지 포함해 "정말 열리는가"를 확인한다.
|
||||||
|
# → 코드 그물(CI) + 설정·운영 그물(스모크). 둘이 겹쳐야 회귀가 빠져나갈 틈이 좁아진다.
|
||||||
|
#
|
||||||
|
# 사용법: bash deploy/smoke-test.sh (기본: 운영 URL 검사)
|
||||||
|
# BASE=https://다른주소 bash deploy/smoke-test.sh
|
||||||
|
# 하나라도 실패하면 exit 1 (배포 스크립트/CI에서 실패로 인지 가능).
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
BASE="${BASE:-https://edu.awesomedevapp.com}"
|
||||||
|
PASS=0
|
||||||
|
FAIL=0
|
||||||
|
|
||||||
|
# check "설명" 실제값 기대값 → 일치하면 통과, 아니면 실패로 기록
|
||||||
|
check() {
|
||||||
|
local desc="$1" actual="$2" expected="$3"
|
||||||
|
if [ "$actual" = "$expected" ]; then
|
||||||
|
echo " ✅ $desc"
|
||||||
|
PASS=$((PASS + 1))
|
||||||
|
else
|
||||||
|
echo " ❌ $desc (기대: $expected, 실제: $actual)"
|
||||||
|
FAIL=$((FAIL + 1))
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
# contains "설명" 본문 기대문자열 → 포함하면 통과
|
||||||
|
contains() {
|
||||||
|
local desc="$1" body="$2" needle="$3"
|
||||||
|
if printf '%s' "$body" | grep -q "$needle"; then
|
||||||
|
echo " ✅ $desc"
|
||||||
|
PASS=$((PASS + 1))
|
||||||
|
else
|
||||||
|
echo " ❌ $desc ('$needle' 없음)"
|
||||||
|
FAIL=$((FAIL + 1))
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "🔎 배포 스모크 테스트 — $BASE"
|
||||||
|
|
||||||
|
# ── 1) 홈(SPA) 200 ──
|
||||||
|
code=$(curl -s -o /dev/null -m 15 -w '%{http_code}' "$BASE/")
|
||||||
|
check "홈페이지 200 OK" "$code" "200"
|
||||||
|
|
||||||
|
# ── 2) HTTP → HTTPS 리다이렉트 (평문 표면 없음) ──
|
||||||
|
rc=$(curl -s -o /dev/null -m 15 -w '%{http_code}' "http://edu.awesomedevapp.com/")
|
||||||
|
case "$rc" in 301|302|308) echo " ✅ HTTP→HTTPS 리다이렉트($rc)"; PASS=$((PASS+1));; *) echo " ❌ HTTP→HTTPS 리다이렉트 (기대 30x, 실제 $rc)"; FAIL=$((FAIL+1));; esac
|
||||||
|
|
||||||
|
# ── 3) 보안 헤더 ──
|
||||||
|
hdr=$(curl -sI -m 15 "$BASE/")
|
||||||
|
contains "HSTS 헤더" "$hdr" "Strict-Transport-Security"
|
||||||
|
contains "X-Frame-Options 헤더" "$hdr" "X-Frame-Options"
|
||||||
|
contains "X-Content-Type-Options" "$hdr" "X-Content-Type-Options"
|
||||||
|
|
||||||
|
# ── 4) 학습 문서 iframe (실제로 났던 그 회귀!) ──
|
||||||
|
# DENY면 문서 iframe이 막힌다 → 반드시 SAMEORIGIN이어야, 200이어야, 한국어 본문이 있어야.
|
||||||
|
dochdr=$(curl -sI -m 15 "$BASE/docs/intern-guide.html")
|
||||||
|
doccode=$(curl -s -o /dev/null -m 15 -w '%{http_code}' "$BASE/docs/intern-guide.html")
|
||||||
|
docbody=$(curl -s -m 15 "$BASE/docs/intern-guide.html")
|
||||||
|
check "학습 문서 200 OK" "$doccode" "200"
|
||||||
|
xfo=$(printf '%s' "$dochdr" | grep -i "x-frame-options" | tr -d '\r' | awk '{print $2}')
|
||||||
|
check "학습 문서 X-Frame-Options=SAMEORIGIN (DENY면 iframe 깨짐)" "$xfo" "SAMEORIGIN"
|
||||||
|
contains "학습 문서 캐시 재검증(no-cache)" "$dochdr" "no-cache"
|
||||||
|
contains "학습 문서 한국어 본문 로딩" "$docbody" "AWESOMEDEV"
|
||||||
|
|
||||||
|
# ── 5) 백엔드 살아있음 (Caddy→백엔드→DB 경로) ──
|
||||||
|
# 없는 계정 로그인 → 401. 이게 나오면 백엔드가 뜨고 DB 조회까지 도는 것.
|
||||||
|
loginc=$(curl -s -o /dev/null -m 15 -w '%{http_code}' -X POST "$BASE/api/auth/login" \
|
||||||
|
-H "Content-Type: application/json" -d '{"username":"__smoke__","password":"__nope__"}')
|
||||||
|
check "백엔드 API 응답(없는 계정 로그인 401)" "$loginc" "401"
|
||||||
|
|
||||||
|
# ── 6) SPA 라우트 폴백 (새로고침해도 라우터 동작) ──
|
||||||
|
spac=$(curl -s -o /dev/null -m 15 -w '%{http_code}' "$BASE/learn")
|
||||||
|
check "SPA 라우트 폴백(/learn 200)" "$spac" "200"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "── 결과: 통과 $PASS · 실패 $FAIL ──"
|
||||||
|
if [ "$FAIL" -gt 0 ]; then
|
||||||
|
echo "🔴 스모크 테스트 실패 — 배포된 사이트에 문제가 있어요. 위 ❌ 항목을 확인하세요."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "🟢 전부 통과 — 배포가 건강합니다."
|
||||||
Loading…
x
Reference in New Issue
Block a user