QA 테스트 체크리스트
← 개발 가이드 📊 결과 보기 기준: 2026-05-16 · Sprint 0~5 완료 · 40/40

🎁 GiftCard Platform — QA 테스트 체크리스트

MVP 출시 전 검수용 전체 시나리오 — 사용자·관리자·보안 역할별 기능 검증 (Sprint S0~S5, 40 tasks 기준)

👤 사용자 45항목 🛡️ 관리자 35항목 🔒 보안 10항목 총 90개 테스트 CRITICAL 우선 검증
💡 사용 방법
각 항목을 클릭하면 완료(✓) 처리됩니다. 마우스를 올리면 실패(✗) 버튼이 표시됩니다. 진행 상황은 브라우저 localStorage에 자동 저장됩니다.
테스트 계정: test@example.com / test1234 (사용자)  |  admin@example.com / admin1234 (관리자)  |  시드: php artisan migrate:fresh --seed
CRITICAL HIGH MEDIUM LOW 완료 실패
진행률 요약
👤

일반 사용자

인증·탐색·결제·보관함·선물·환불

완료 0 / 45 실패 0 0%
🛡️

관리자

상품·발급·환불·감사·배치

완료 0 / 35 실패 0 0%
🔒

보안 체크리스트

PIN·CSRF·Rate Limit·감사 로그

완료 0 / 10 실패 0 0%
👤

일반 사용자 (role: user)

0 / 45
🔐 T1. 인증 — 회원가입·로그인·로그아웃·마이페이지 (F010·F011)
회원가입 — 이메일·비밀번호 유효성 검증 (8자 이상 영문+숫자)
/register → 이메일 형식·중복 검증, 비밀번호 8자 미만 시 422, 정상 가입 후 홈 자동 리디렉트
CRITICALPOST /api/auth/register
로그인 → /api/auth/me 200 (Sanctum 세션 유지)
test@example.com / test1234 로그인 → 홈 리디렉트, 새로고침 후에도 세션 유지
CRITICALPOST /api/auth/login
잘못된 비밀번호 → 422 에러 메시지 표시
존재하지 않는 이메일 또는 비밀번호 오류 → 사용자에게 명확한 안내 메시지
HIGHPOST /api/auth/login
로그아웃 후 보호 라우트(/vault) 접근 → /login 리디렉트
로그아웃 → /api/auth/me 401 → Vue Router 가드 동작 확인
HIGHPOST /api/auth/logout
이미 로그인 상태에서 /login 직접 접근 → 홈 리디렉트 (requiresGuest)
로그인 중 /login 또는 /register URL 직접 진입 시 홈(/)으로 리디렉트
MEDIUM
마이페이지 — 이름·이메일·계정유형(user) 표시 + 로그아웃 버튼 동작
/my-page 접근 → GET /api/auth/me 로 사용자 정보 렌더링, 로그아웃 후 홈 이동
MEDIUMGET /api/auth/me
🛍️ T2. 상품 탐색 — 목록·상세 (F001·F002)
비로그인 홈(/) 접근 → 활성 상품만 표시 (비활성·만료 미노출)
owner_id=NULL AND status=active 상품만 카드 그리드에 렌더링, 비활성화 상품 미노출 확인
HIGHGET /api/gift-cards
커서 페이지네이션 — 더보기/무한 스크롤 동작
상품 N개 초과 시 next_cursor 반환 → 추가 로드 버튼/스크롤 동작
MEDIUMGET /api/gift-cards?cursor=
상품 상세 페이지 — 모든 필드 + 환불 법령 고지 문구 표시
/gift-cards/{id} → 금액·유효기간·발행사 + "유효기간 경과 후 5년 이내 90% 환불 가능" 문구
HIGHGET /api/gift-cards/{id}
비활성 상품 ID 직접 URL 진입 → 404 처리
관리자에서 비활성화한 상품 ID로 /gift-cards/{id} 진입 시 404 페이지 표시
MEDIUMGET /api/gift-cards/{id}
상세 페이지 — 구매하기/선물하기 버튼 비로그인 시 /login 리디렉트
비로그인 사용자가 구매하기 클릭 → 로그인 페이지로 이동, 로그인 후 복귀
HIGH
💳 T3. 결제·구매·발급 (F003)
결제 페이지 약관 미동의 시 결제 버튼 비활성화
이용약관·환불정책·개인정보처리방침 3개 체크박스 모두 필수, 미체크 시 결제 진행 차단
CRITICAL
결제 완료 → 기프트카드 발급 + 보관함 표시
상세 → 구매하기 → 결제(mock) → 결제 완료 페이지 → 보관함에 카드 추가 확인
CRITICALPOST /api/orders
멱등성 — 동일 Idempotency-Key 재요청 시 동일 응답 반환
동일 주문 요청을 2회 전송 → 두 번째 요청에서 동일 OrderId 반환, 중복 카드 미발급
CRITICALPOST /api/orders
결제 완료 페이지 — 핀번호 미노출 확인 (네트워크 탭)
CheckoutSuccessView 응답에 pin_encrypted·pin_hash 미포함 확인 (개발자 도구 Network 검사)
CRITICAL
Idempotency-Key 자동 주입 — Axios 인터셉터 동작 확인
POST 요청 헤더에 Idempotency-Key: UUID 형식 자동 주입 (Network 탭 → Request Headers 확인)
HIGH
CSRF 쿠키 없이 POST 요청 → 419 Mismatch
XSRF-TOKEN 쿠키 제거 후 POST /api/orders 재시도 → 419 오류
HIGH
🗃️ T4. 보관함 (F005)
보관함 목록 — 본인 카드만 표시 (타인 카드 미노출)
owner_id = 로그인 사용자 ID 기준 필터, 다른 사용자의 카드는 절대 노출 안됨
CRITICALGET /api/vault
상태 필터 — active·used·expired·refunded 필터 동작
?status=active 등 쿼리 파라미터로 필터링 → 해당 상태 카드만 표시
MEDIUMGET /api/vault?status=
만료 임박(7일 이내) 카드 — 배지 표시
expires_at이 7일 이내인 카드에 경고 배지 표시 (주황색 또는 빨간색)
MEDIUM
보관함 커서 페이지네이션 — 카드 다수 시 정상 동작
카드 N개 이상 보유 시 cursorPaginate 동작, 다음 페이지 로드 확인
LOW
보관함 상세 — 카드 상태 레이블 (active/used/expired/refunded) 표시
각 카드 상태에 맞는 레이블·색상 배지 표시, is_available 필드 기준 구매 가능 여부 표시
MEDIUM
🔑 T5. PIN 노출·사용 (F006·F012)
재인증(ReauthDialog) — 비밀번호 입력 후 PIN 노출
사용하기 버튼 → ReauthDialog 팝업 → 비밀번호 입력 → 5분간 reauth 유효 → PIN 표시
CRITICALPOST /api/auth/reauth
재인증 없이 reveal-pin 직접 호출 → 401 반환
RequireRecentReauth 미들웨어 — reauth_until 세션 없이 POST /api/gift-cards/{id}/reveal-pin → 401
CRITICALPOST /api/gift-cards/{id}/reveal-pin
PIN 노출 — 30초 후 자동 마스킹
PIN 표시 후 30초 타이머 → 자동으로 ****-****-XXXX 마스킹, 타이머 카운트다운 UI 표시
CRITICAL
PIN 분당 3회 초과 요청 → 429 Too Many Requests
reveal-pin API 1분 내 4회 호출 시 throttle:3,1 Rate Limit → 429 에러
HIGHPOST /api/gift-cards/{id}/reveal-pin
사용 완료 처리 → 카드 status=used 전이
PIN 노출 후 사용 완료 버튼 클릭 → 카드 상태 used → 보관함에서 used 배지 표시
CRITICALPOST /api/gift-cards/{id}/use
낙관적 잠금 — 동시 사용 요청 중 1개만 성공 (409 Conflict)
동일 카드에 use 요청 2개 동시 전송 → 1개 200, 1개 409 Conflict 반환
HIGHPOST /api/gift-cards/{id}/use
이미 사용된(used) 카드 재사용 시도 → 422 에러
status=used 카드에 POST /api/gift-cards/{id}/use 재시도 → 422 Unprocessable Content
HIGH
🎁 T6. 선물·클레임 (F004·F009)
선물하기 — 수신자 이메일·이름·메시지(200자 제한) 입력 후 발송
GiftFormView → 200자 카운터 동작, 201자 입력 시 차단
HIGH
회원 이메일 선물 → 수신자 보관함 자동 적립
수신자가 가입 회원인 경우 → 카드 owner_id=수신자, status=active → 보관함 직접 적립
CRITICALPOST /api/orders
비회원 이메일 선물 → claim_pending 상태 + 클레임 메일 발송
미가입 이메일 → 카드 status=claim_pending, 클레임 URL 포함 메일 큐 적재
HIGH
클레임 URL → 회원가입 → 자동 클레임 → 보관함 적립
메일 내 클레임 URL 접근 → 회원가입 후 POST /api/claim/{token} → 수신자 보관함에 카드 표시
CRITICALPOST /api/claim/{token}
클레임 토큰 1회 사용 후 재사용 → 410 Gone
클레임 완료 후 동일 URL 재방문 → 410 응답 (claim_token=null 처리 확인)
HIGHGET /api/claim/{token}
30일 만료 클레임 URL → 410 Gone + 발신자 보관함 자동 환원
claim_expires_at 초과 토큰 → 410, 카드 owner_id=purchaser, status=active 환원 확인
HIGH
📋 T7. 거래 내역 (F007)
거래 내역 — 구매·사용·선물 발송·선물 수신·환불 유형 모두 표시
TransactionListView → TransactionType 별 아이콘·레이블 표시, 날짜 역순 정렬
HIGHGET /api/transactions
거래 내역 — 타 사용자 거래 미노출
from_user_id OR to_user_id = 본인 ID 기준 필터 → 관련 없는 거래 완전 미노출
CRITICAL
거래 유형 필터 동작
?type=purchase 등 필터 파라미터 → 해당 유형만 표시
MEDIUMGET /api/transactions?type=
날짜 범위 필터 동작
?from=2026-05-01&to=2026-05-31 파라미터 → 해당 기간 거래만 표시
MEDIUM
커서 페이지네이션 — 거래 다수 시 다음 페이지 로드
거래 내역 N건 이상 → next_cursor 반환, 추가 로드 동작
LOW
↩️ T8. 환불 신청 (F008)
사용 전(active) 카드 환불 → 100% 금액 계산 표시
RefundFormView → active 카드 선택 시 환불 금액 100% 자동 계산 표시
CRITICALPOST /api/refunds
만료(expired) 카드 환불 → 90% 금액 계산 + 법령 고지 문구
expired 카드 선택 → 90% 계산, "유효기간 경과 후 5년 이내 90% 환불" 문구 표시
CRITICAL
사용 완료(used) 카드 환불 시도 → 422 거부
status=used 카드로 환불 신청 시 422 Unprocessable Content 반환
HIGH
발행 후 5년 초과 만료 카드 환불 → 422 거부
issued_at + 5년 초과 카드 환불 시도 → 422 (법령상 청구권 소멸)
HIGH
환불 신청 목록 — 신청 내역 표시 + 상태 확인
GET /api/refunds → 신청 내역 목록(pending/approved/rejected 상태) 표시
MEDIUMGET /api/refunds
🛡️

관리자 (role: admin)

0 / 35
🔐 A1. 관리자 인증·접근 권한
관리자 로그인 후 /admin/gift-cards 접근 정상
admin@example.com / admin1234 → 관리자 대시보드 렌더링, 사이드바 메뉴 표시
CRITICAL
일반 사용자 계정으로 /api/admin/* 접근 → 403 Forbidden
test@example.com 토큰으로 GET /api/admin/gift-cards → 403 (EnsureUserIsAdmin 미들웨어)
CRITICALGET /api/admin/gift-cards
비인증 상태에서 관리자 API 호출 → 401 Unauthorized
인증 쿠키 없이 /api/admin/* → 401 반환
HIGH
일반 사용자가 /admin/* 라우트 직접 진입 → 리디렉트 또는 403
requiresAdmin 라우트 가드 동작 — 일반 사용자는 관리자 UI 페이지 접근 불가
CRITICAL
📊 A2. 관리자 대시보드
대시보드 — 총 발급·사용·환불 대기 카운트 표시
AdminDashboardView → DashboardController 집계 카운트 정상 표시
HIGHGET /api/admin/dashboard
최근 5건 거래 목록 표시
대시보드 하단 최근 거래 5건 → 유형·금액·날짜 표시
MEDIUM
대시보드 새로고침 시 최신 데이터 반영
페이지 새로고침 → API 재호출 → 최신 카운트 표시
LOW
🎴 A3. 상품(기프트카드) 관리 (F020)
상품 등록 — 정상 등록 (금액·유효기간·이름 입력)
AdminGiftCardFormView → 10,000원·365일 상품 등록 → 목록에 신규 상품 표시
CRITICALPOST /api/admin/gift-cards
5만원 초과 상품 등록 시도 → 422 거부
amount=50001 입력 → 422 (법령 §5만원 제한), 프론트 유효성 검사도 차단
CRITICAL
유효기간 364일 이하 설정 시도 → 422 거부
validity_days=364 입력 → 422 (법령 §1년 이상 유효기간 필수)
CRITICAL
상품 수정 — 이름·설명 수정 후 저장 확인
상품 수정 폼 → 변경 사항 저장 → 목록에 수정된 내용 반영
HIGHPUT /api/admin/gift-cards/{id}
활성 토글 — 비활성화 후 공개 목록 미노출 확인
is_active 토글 OFF → 사용자 홈 페이지에서 해당 상품 즉시 미노출
HIGHPATCH /api/admin/gift-cards/{id}/toggle
상품 목록 — 페이지네이션·검색 동작
상품 목록 검색 필터 동작, 목록 페이지네이션 정상
MEDIUMGET /api/admin/gift-cards
상품 검색 — LIKE 인젝션 특수문자(%, _) 안전 처리
검색어에 %·_ 입력 시 SQL 에러 없이 정상 검색 결과 (이스케이프 처리 확인)
HIGH
🔍 A4. 발급 내역·PIN reveal (F021)
발급 내역 목록 — PIN 마스킹 표시 (****-****-XXXX)
관리자 발급 내역 목록에서 PIN은 항상 마스킹, pin_encrypted 응답 미포함 확인
CRITICALGET /api/admin/gift-cards
PIN reveal — 사유 입력 없이 요청 → 422 거부
POST /api/admin/gift-cards/{id}/reveal-pin → reason 필드 미입력 시 422
HIGHPOST /api/admin/gift-cards/{id}/reveal-pin
PIN reveal — 사유 입력 후 핀 노출 + 감사 로그 기록
reason 입력 후 reveal → PIN 노출, GiftCardStatusLog에 pin_revealed + actor_type=admin 기록
CRITICAL
발급 내역 검색 — 이메일·상태·날짜 필터 동작
구매자 이메일·카드 상태·날짜 범위 필터 → 해당 조건 카드만 표시
MEDIUM
카드별 감사 로그 페이지 — 전 생명주기 이벤트 시간순 표시
AdminGiftCardAuditView → 발급·사용·선물·환불·만료·PIN reveal 이벤트 순서 확인
HIGHGET /api/admin/gift-cards/{id}/audit-logs
Eager Loading — N+1 없이 발급 내역 목록 로드
with(['product', 'purchaser', 'owner']) 확인 — DB 쿼리 수가 카드 수에 비례하지 않음
MEDIUM
📈 A5. 관리자 거래 내역 (F022)
전체 거래 내역 조회 — 모든 사용자 거래 표시
AdminTransactionListView → 시스템 전체 거래 목록, 사용자 이메일·카드 정보 포함
HIGHGET /api/admin/transactions
거래 유형·날짜 필터 + 커서 페이지네이션
관리자 거래 내역 필터·검색·다음 페이지 로드 정상 동작
MEDIUM
거래 내역 — AppendOnly 보장 (UPDATE/DELETE 불가)
GiftCardTransaction 모델 update()/delete() 호출 시 500 abort (AppendOnly 트레이트)
HIGHphp artisan tinker
💸 A6. 환불 처리 (F023)
환불 신청 목록 — pending 상태 신청 표시
AdminRefundListView → 대기 중 환불 신청 목록, 신청자·카드·금액 정보
HIGHGET /api/admin/refunds
환불 승인 → 카드 status=refunded + 감사 로그 기록
승인 버튼 클릭 → Refund.status=completed, GiftCard.status=refunded, 거래 내역 refund 기록
CRITICALPOST /api/admin/refunds/{id}/approve
환불 거절 — 사유 미입력 시 422 거부
거절 모달에서 reason 미입력 → 422 (RejectRefundRequest 검증)
HIGHPOST /api/admin/refunds/{id}/reject
환불 승인 멱등성 — 동일 Idempotency-Key 재요청 → 중복 처리 방지
approve 요청 2회 전송 → 두 번째는 저장된 응답 반환, 이중 환불 없음
HIGH
환불 거절 후 카드 상태 유지 + 사용자에게 거절 표시
거절 시 카드 원래 상태 유지, 사용자 환불 목록에 rejected 상태 표시
MEDIUM
A7. 만료 배치·감사 로그·Idempotency 정리 (F024)
만료 배치 — expires_at 초과 카드 expired 전이
php artisan app:expire-gift-cards 실행 → 만료 카드만 expired, 감사 로그 actor_type=system 기록
CRITICALphp artisan app:expire-gift-cards
만료 배치 — 비만료 카드 미영향 확인
배치 실행 후 expires_at 미초과 카드는 status 변경 없음
HIGH
Idempotency 키 정리 배치 — 24시간 초과 키 삭제
php artisan app:clean-expired-idempotency-keys → TTL 초과 키만 삭제, 활성 키 보존
MEDIUMphp artisan app:clean-expired-idempotency-keys
감사 로그 불변성 — update()/delete() 호출 시 abort(500)
GiftCardStatusLog 모델에서 update() 시도 → AppendOnly 트레이트가 500 abort 발생
CRITICALphp artisan tinker
📡 A8. Webhook·PG 처리
🔒

보안 체크리스트 (T502)

0 / 10
🛡️ S1. 보안 필수 항목 — MVP 출시 전 전항목 PASS 필수
PIN 평문이 API 응답에 절대 미포함
GET /api/gift-cards, GET /api/vault 응답 JSON에 pin_encrypted·pin_hash 필드 없음 (Network 탭 확인)
CRITICAL
PIN 평문이 localStorage·sessionStorage에 저장되지 않음
PIN 노출 후 Application > Storage 탭 → 평문 PIN 없음 확인
CRITICAL
owner_id·version 필드가 공개 API 응답에 미노출
GET /api/gift-cards/{id} → is_available 필드 있음, owner_id·version 없음
CRITICAL
CSRF 쿠키 없는 POST → 419 반환
XSRF-TOKEN 쿠키 제거 후 POST /api/orders → 419 TokenMismatchException
CRITICAL
Admin 미들웨어 우회 시도 → 403
일반 사용자 세션으로 /api/admin/* 모든 엔드포인트 접근 → 403
CRITICAL
reveal-pin Rate Limit — 분당 3회 초과 → 429
동일 카드 reveal-pin 1분 내 4회 호출 → throttle:3,1 → 429 응답
CRITICAL
비회원 클레임 토큰 재사용 → 410 Gone
클레임 완료 후 동일 토큰 URL 재방문 → 410 (1회용 토큰 claim_token=null 처리)
CRITICAL
감사 로그 update()/delete() 시도 → abort(500)
GiftCardStatusLog·GiftCardTransaction AppendOnly 트레이트 — 수정/삭제 차단
CRITICAL
Idempotency 키 동일 본문 재요청 → 동일 응답 / 다른 본문 → 409
동일 키+동일 본문 → 200 (캐시) / 동일 키+다른 본문 → 409 Conflict
HIGH
5만원 초과 상품 등록 → 422 / 위조 Webhook 서명 → 거부
amount=50001 → 422 (법령), 잘못된 HMAC 서명 webhook → 401/403 거부
HIGH
완료 0 / 90