16
✅ 통과 (PASS)
0
❌ 실패 (FAIL)
0
🔥 미해결 이슈
수행된 케이스 통과율 (전체 85개 중 16개 수행) PASS 16 / 수행 16 · 100%

환경 점검

사용자 랜딩 페이지 200 OK
관리자 웹 (Vue SPA) 200 OK
백엔드 API (Node.js) Port 4000
HTTP → HTTPS 리다이렉트 301 OK
match-api PM2 등록 미등록
토스페이먼츠 Sandbox 키 미설정
소셜 로그인 OAuth 키 미설정
TypeScript 빌드 에러 0건

발견된 이슈

FIXED
ISS-001 · POST /auth/logout → 404 ROUTE_NOT_FOUND ✅ 2026-05-20 수정 완료
auth.store.ts apiClient.postapiClient.delete 변경 후 빌드 재배포.
검증: DELETE /api/v1/auth/logout → 204 · 로그아웃 후 동일 refreshToken 재사용 → REFRESH_TOKEN_REVOKED (JTI 무효화 확인)
관련 TC: TC-012 (로그아웃), TC-081 (JTI 기반 토큰 무효화)
FIXED
ISS-002 · 관리자 회원 목록 API 미구현 — GET /api/v1/users → 404 ✅ 2026-05-20 수정 완료
user.service.jslistUsersAdmin / getAdminUser / updateUserAdmin / deleteUserAdmin 구현.
user.controller.js에 Zod 검증 포함 핸들러 추가, users.routes.jsrequireRole('ADMIN','SUPERADMIN') 가드 적용.
검증: GET /api/v1/users → 200 · items/nextCursor/total 페이지네이션 정상 · GET /api/v1/users/1 → 200 · PATCH (role/status) → 200 · PATCH {} → 422 (Zod refine 검증)
관련 TC: TC-066 (관리자 회원 관리)
FIXED
ISS-003 · 신고 큐 / 분쟁 처리 API 미구현 ✅ 2026-05-20 수정 완료
근본 원인: 백엔드 GET /reports/open 누락 (기존 /reports/admin만 존재), 어드민 웹은 /open 호출.
GET /disputes/open은 이미 구현되어 있었으나 total 필드 미포함으로 대시보드 카운터 미작동.
수정 1 — reports.routes.js: GET /open 라우트 추가 (requireRole ADMIN/SUPERADMIN 가드).
수정 2 — report.service.js: listForAdmin 반환값을 { items, total } 형태로 변경 (Prisma $transaction 배치).
수정 3 — dispute.service.js: listOpen 반환값도 { items, total } 통일.
검증: GET /reports/open → 200 { items:[], total:0 } · GET /disputes/open → 200 { items:[], total:0 } · 브라우저: /admin/reports, /admin/disputes 페이지 에러 없이 렌더링 ✓
관련 TC: TC-071 (분쟁 처리 필수 검증), TC-069 (신고 큐)
FIXED
ISS-004 · CORS 설정 오류 — wildcard origin + credentials 조합 ✅ 2026-05-20 수정 완료
근본 원인: Apache vhost(httpd-ssl.conf, httpd-vhosts.conf)에서 Header set Access-Control-Allow-Origin "*"가 Express 응답 헤더를 덮어쓰고 있었음.
수정 1 — Apache: 두 vhost에서 Header set Access-Control-Allow-Origin "*" 제거 (SameSite 쿠키 헤더는 유지).
수정 2 — Express: 미허용 origin 시 callback(new Error(...))callback(null, false) 변경 (500 → CORS 헤더 미포함 응답).
검증: match.habyapps.com → Access-Control-Allow-Origin: https://match.habyapps.com + credentials: true ✓ · evil.com → 200 (CORS 헤더 없음, 브라우저 차단) · 500 미발생 ✓
관련 TC: TC-078 (CORS allowlist 강화)
FIXED
ISS-005 · match-api PM2 미등록 — 서버 재시작 시 백엔드 자동 실행 불가 ✅ 2026-05-20 수정 완료
기존 ecosystem.config.js 활용: match-api (cluster, 16 instances, DISABLE_JOBS=true) + match-scheduler (fork, 1 instance) 구성.
/var/log/match/ 로그 디렉터리 생성 후 pm2 start ecosystem.config.js 실행.
검증: pm2 save/root/.pm2/dump.pm2 저장 · pm2 startupsystemctl enable pm2-root 완료 · API 응답 정상 확인
관련 TC: TC-085 (PM2 스케줄러 분리)
FIXED
ISS-006 · Rate Limit 동작 — 3회 실패 후 429 (명세 5회) ✅ 2026-05-20 수정 완료
근본 원인: PM2 cluster 16 워커가 각자 in-memory 카운터를 유지 → 워커 간 카운터 비공유로 rate limit 사실상 무력화.
초기 테스트에서 "4번째 차단"은 이전 세션 요청이 카운터에 누적된 dirty-state 아티팩트였음.
수정: PrismaRateLimitStore (MariaDB rate_limits 테이블) 구현 → 모든 워커가 단일 DB 카운터 공유.
검증: 깨끗한 상태에서 시도 1–5 → 401 INVALID_CREDENTIALS · 시도 6 → 429 · 성공 로그인은 카운터 미포함(skipSuccessfulRequests: true) ✓
관련 TC: TC-080 (Rate Limit)

섹션별 테스트 결과

🔐 회원 / 인증 (16 케이스) 7 PASS · 0 FAIL · 9 N/A
TC우선순위항목결과비고
TC-001HIGH정상 회원가입N/ARate limit 초과로 테스트 불가 (신규 계정 생성 필요)
TC-002MEDIUM중복 이메일 가입 차단N/A신규 계정 생성 필요
TC-003MEDIUM비밀번호 8자 미만 차단PASS422 VALIDATION_ERROR · "비밀번호는 최소 8자 이상이어야 합니다."
TC-004MEDIUM닉네임 2자 미만 차단PASS422 VALIDATION_ERROR · "닉네임은 2자 이상"
TC-005MEDIUM닉네임 50자 초과 차단PASS422 VALIDATION_ERROR · "닉네임은 50자 이내"
TC-006MEDIUM이메일 형식 불일치 차단PASS422 VALIDATION_ERROR · "올바른 이메일 형식이 아닙니다."
TC-007HIGH카카오 소셜 로그인N/AKAKAO_CLIENT_ID 미설정
TC-008HIGH구글 소셜 로그인N/AGOOGLE_CLIENT_ID 미설정
TC-009HIGH동일 소셜 계정 재로그인N/A소셜 로그인 키 미설정
TC-010HIGHAccess Token → Refresh 갱신PASSPOST /auth/refresh → 200 · 신규 accessToken/refreshToken 발급 확인
TC-011HIGH무효 Refresh Token → 차단PASS401 INVALID_REFRESH_TOKEN · "유효하지 않은 Refresh Token입니다."
TC-012MEDIUM로그아웃 — JTI 무효화PASSDELETE /api/v1/auth/logout → 204 · ISS-001 수정 완료 (2026-05-20)
TC-013HIGH만 19세 미만 가입 차단N/A별도 age 검증 테스트 필요
TC-014HIGH만 19세 이상 가입 허용N/A별도 age 검증 테스트 필요
TC-015MEDIUM회원 탈퇴 soft deleteN/A탈퇴 전용 계정 필요
TC-016LOW탈퇴 후 재가입 정책N/APRD 미결 항목
TC우선순위항목결과비고
TC-017~029-매칭 등록·신청·필터·페이지네이션N/AFlutter 앱 기능 — 테스트 계정 및 HOST 서브몰 등록 필요. API GET /matchings → 200 (데이터 없음) 확인
TC우선순위항목결과비고
TC-030~040-Socket.IO 연결·메시지·확정·이미지N/ASocket.IO 전용 클라이언트 및 매칭 플로우 선행 필요
TC우선순위항목결과비고
TC-041~046-Sandbox 결제·Webhook·에스크로N/ATOSS_PAYMENTS_TEST_SECRET 미설정. 키 발급 후 재테스트 필요
TC우선순위항목결과비고
TC-047~055-노쇼·취소·분쟁·정산N/A결제 완료 데이터 및 에스크로 HELD 상태 선행 필요
TC우선순위항목결과비고
TC-056MEDIUM일일 신고 10건 초과 차단N/A테스트 신고 데이터 필요
TC-057MEDIUM동일 대상 중복 신고 차단N/A테스트 신고 데이터 필요
TC-058HIGH관리자 행위 audit_logs 자동 기록N/A관리자 액션 실행 데이터 필요
TC-059HIGHSUPERADMIN 전용 audit-logs 조회PASSGET /api/v1/audit-logs + SUPERADMIN 토큰 → 200 · 빈 목록 정상 반환
TC우선순위항목결과비고
TC-060~065-COMPLETED 매칭 리뷰 전체N/ACOMPLETED 상태 매칭 데이터 선행 필요
🛡️ 관리자 웹 E2E (10 케이스) 4 PASS · 0 FAIL · 6 N/A
TC우선순위항목결과비고
TC-066HIGH관리자 로그인 → 대시보드PASSsuperadmin@match.local 로그인 성공 · /admin/dashboard 렌더링 · 4개 통계 카드 표시 · 기간 필터 버튼 동작 확인
TC-067HIGH미로그인 URL 직접 접근 → 로그인 리다이렉트PASS/admin/members 직접 접근 → /admin/login 리다이렉트 확인 (Vue Router 가드 정상 동작)
TC-068HIGH회원 정지 → Flutter 앱 401N/AFlutter 앱 테스트 환경 필요 (API 구현 완료 — ISS-002 FIXED)
TC-069HIGH매칭 강제 취소N/A테스트 매칭 데이터 없음
TC-070MEDIUM장소 승인/반려N/APENDING 장소 데이터 없음
TC-071HIGH분쟁 처리 필수 항목 검증N/AAPI 블로커 해소 (ISS-003 FIXED) · /admin/disputes 페이지 렌더링 ✓ · 실제 분쟁 해결 플로우는 결제 데이터 필요
TC-072HIGHSUPERADMIN 전용 메뉴 (ADMIN 미표시)N/AADMIN 역할 테스트 계정 필요
TC-073HIGHSUPERADMIN 감사 로그·관리자 계정 접근PASS감사 로그(/admin/audit-logs) + 관리자 계정(/admin/admins) 페이지 모두 렌더링 정상 · API 200
TC-074LOWURL 쿼리 파라미터 ↔ 필터 동기화N/A테스트 데이터 없어 필터 동작 미검증
TC-075MEDIUMTypeScript strict 빌드 통과PASSnpm run type-check 에러 0건 · npm run build ✓ built in 1.46s
🔒 보안 패치 검증 v0.2.5 (10 케이스) 4 PASS · 0 FAIL · 6 N/A
TC우선순위항목결과비고
TC-076CRITICAL[P0-1] Webhook HMAC 타이밍 공격 방어N/AToss Secret 미설정 · 코드 구현 확인 필요
TC-077CRITICAL[P0-2] POST_SETTLEMENT 분쟁 상태 범위N/A결제 플로우 선행 필요 · 코드 리뷰로 확인 권장
TC-078CRITICAL[P0-3] CORS allowlist 강화PASSApache wildcard 제거 + Express callback(null,false) 적용 · match.habyapps.com → specific origin header ✓ · evil.com → no CORS headers (브라우저 차단) · 500 없음 ✓ (ISS-004 FIXED)
TC-079CRITICAL[P0-4] 분쟁+에스크로 원자적 처리N/A분쟁 API 404 (ISS-003) · 코드 리뷰로 확인
TC-080HIGH[P1-1] 로그인 Rate Limit 5회/15분PASSPrismaRateLimitStore 도입으로 클러스터 공유 카운터 구현 · 시도 1–5 → 401 · 시도 6 → 429 ✓ (ISS-006 FIXED)
TC-081HIGH[P1-2] JTI 기반 토큰 무효화PASS로그아웃 후 동일 refreshToken 재사용 → 401 REFRESH_TOKEN_REVOKED · ISS-001 수정 완료
TC-082HIGH[P1-3] 이미지 URL 도메인 화이트리스트N/A채팅방 데이터 필요
TC-083CRITICAL[P1-4] 부분 환불 PG 선행 호출N/AToss 결제 데이터 필요
TC-084HIGH[P2-1] WebView XSS 방어N/AFlutter WebView 환경 필요
TC-085HIGH[P2-2] PM2 스케줄러 분리PASSmatch-api (cluster×16, DISABLE_JOBS=true) + match-scheduler (fork×1) · pm2 save + pm2 startup 등록 완료 (ISS-005 FIXED)

API 엔드포인트 상태

POST /auth/login200 OK
POST /auth/logout404 (DELETE만 존재)
POST /auth/refresh200 OK
GET /users/me200 OK
GET /users (어드민 목록)404 미구현
GET /matchings200 OK
GET /payments200 OK
GET /places200 OK
GET /audit-logs200 OK
GET /reports/open404 미구현
GET /disputes404 미구현
401 미인증 차단정상
422 입력 유효성 검사정상
429 Rate Limit동작
SQL Injection 방어차단
CORS allowlistspecific origin + credentials ✓

다음 단계 (우선순위 순)

완료
1. 로그아웃 API 메서드 불일치 수정 (ISS-001) ✅
auth.store.ts: apiClient.post → apiClient.delete 변경 후 빌드 재배포 완료
완료
2. match-api PM2 등록 및 자동 시작 설정 (ISS-005) ✅
ecosystem.config.js → pm2 start → pm2 save → systemctl enable pm2-root 완료 · match-api (cluster×16) + match-scheduler (fork×1)
완료
3. CORS 설정 수정 (ISS-004) ✅
Apache wildcard 제거 (httpd-ssl.conf + httpd-vhosts.conf) + Express callback(null,false) 적용 — specific origin header + 500 해소
완료
4. 어드민 회원 관리 API 구현 (ISS-002) ✅
GET /users · GET /users/:id · PATCH /users/:id · DELETE /users/:id — 모두 구현 및 검증 완료
완료
5. 신고·분쟁 어드민 API 구현 (ISS-003) ✅
GET /reports/open 추가 · dispute.listOpen + report.listForAdmin { items, total } 통일 · /admin/reports, /admin/disputes 브라우저 렌더링 검증 완료
완료
6. Rate Limit 클러스터 공유 (ISS-006) ✅
PrismaRateLimitStore (MariaDB rate_limits) 구현 · rate-limit-cleanup.job 추가 · 시도 1–5 허용 / 6번째 차단 검증 완료
중기
7. 토스페이먼츠 Sandbox 키 설정 및 결제 플로우 E2E 테스트
TC-041~055 (결제/에스크로/정산/환불/분쟁 21케이스) 수행을 위한 선행 조건