# SAM E-Sign 테스트 계획서

> **프로젝트명**: SAM E-Sign (전자계약 서명 솔루션)
> **작성일**: 2026-02-12
> **버전**: v1.0
> **작성자**: DX 추진팀
> **상태**: 구현 완료 / 테스트 대기

---

## 1. 테스트 개요

### 1.1 목적

SAM E-Sign v1.0의 모든 기능이 요구사항 정의서(FR-001~FR-012, NFR-001~NFR-007)에 부합하는지 검증한다.

### 1.2 테스트 범위

| 범위 | 대상 | 테스트 유형 |
|------|------|------------|
| API 엔드포인트 | 16개 (인증 10 + 공개 6) | 단위, 통합 |
| MNG 화면 | 8개 (인증 5 + 공개 3) | E2E, UI |
| 핵심 플로우 | 계약 생성 → 서명 완료 | 통합, 시나리오 |
| 보안 | OTP, 토큰, 해시, 접근제어 | 보안 |
| 비기능 | 성능, 호환성, 다중 테넌트 | 비기능 |

### 1.3 테스트 환경

| 항목 | 설정 |
|------|------|
| API 서버 | `docker exec sam-api-1` (Laravel 11, PHP 8.3) |
| MNG 서버 | `docker exec sam-mng-1` (Laravel 11, PHP 8.3) |
| DB | `sam-mysql-1` (MySQL 8.0) |
| 브라우저 | Chrome 최신, Safari 최신, Firefox 최신 |
| 모바일 | Chrome Mobile, Safari iOS |
| 테스트 도구 | Postman (API), 브라우저 DevTools (UI) |

### 1.4 테스트 데이터

| 항목 | 값 |
|------|-----|
| 테스트 테넌트 | tenant_id = 1 |
| 테스트 사용자 (갑) | 기존 로그인 계정 사용 |
| 테스트 상대방 (을) | test-signer@example.com |
| 테스트 PDF | 1~3페이지, 5MB 이하 |
| 대용량 PDF | 정확히 20MB |
| 초과 PDF | 21MB (거부 확인용) |

---

## 2. API 테스트 케이스 - 계약 관리 (인증 필요)

### TC-API-001: 계약 목록 조회

**엔드포인트**: `GET /api/v1/esign/contracts`

| # | 시나리오 | 입력 | 기대 결과 | 우선순위 |
|---|---------|------|----------|---------|
| 1 | 기본 목록 조회 | 파라미터 없음 | 200, 페이지네이션 응답 (기본 20건) | P1 |
| 2 | 상태 필터 | `?status=draft` | 200, draft 상태만 반환 | P1 |
| 3 | 검색 | `?search=테스트` | 200, 제목에 "테스트" 포함된 건만 반환 | P1 |
| 4 | 날짜 범위 | `?date_from=2026-02-01&date_to=2026-02-28` | 200, 해당 기간 내 계약만 반환 | P2 |
| 5 | 페이지네이션 | `?page=2&size=5` | 200, 5건씩 2페이지 반환 | P2 |
| 6 | 비인증 접근 | Authorization 헤더 없음 | 401 Unauthorized | P1 |
| 7 | 다른 테넌트 데이터 격리 | 다른 tenant_id 사용자로 접근 | 200, 해당 테넌트 데이터만 반환 | P1 |

---

### TC-API-002: 계약 생성

**엔드포인트**: `POST /api/v1/esign/contracts`

| # | 시나리오 | 입력 | 기대 결과 | 우선순위 |
|---|---------|------|----------|---------|
| 1 | 정상 생성 | 모든 필수 필드 + PDF | 201, contract_code 자동 생성, status=draft | P1 |
| 2 | 제목 누락 | title 없음 | 422, validation 에러 | P1 |
| 3 | PDF 누락 | file 없음 | 422, validation 에러 | P1 |
| 4 | PDF 형식 아님 | .docx 파일 업로드 | 422, "mimes:pdf" 에러 | P1 |
| 5 | PDF 20MB 초과 | 21MB 파일 | 422, "max:20480" 에러 | P1 |
| 6 | 상대방 이름 누락 | counterpart_name 없음 | 422, validation 에러 | P1 |
| 7 | 상대방 이메일 형식 오류 | "invalid-email" | 422, email validation 에러 | P1 |
| 8 | 서명 순서 지정 | sign_order_type=creator_first | 201, 작성자 먼저 서명 순서 | P2 |
| 9 | 만료일 과거 | expires_at=2026-01-01 | 422, "after:now" 에러 | P2 |
| 10 | 제목 200자 초과 | 201자 제목 | 422, "max:200" 에러 | P3 |
| 11 | SHA-256 해시 생성 확인 | 정상 PDF | 201, original_file_hash 64자 | P1 |
| 12 | 서명자 2인 자동 생성 | 정상 생성 | 201, signers 배열 2개 (creator, counterpart) | P1 |
| 13 | access_token 유일성 | 연속 2건 생성 | 각 서명자의 access_token이 모두 다름 | P1 |

---

### TC-API-003: 계약 상세 조회

**엔드포인트**: `GET /api/v1/esign/contracts/{id}`

| # | 시나리오 | 입력 | 기대 결과 | 우선순위 |
|---|---------|------|----------|---------|
| 1 | 정상 조회 | 존재하는 id | 200, signers + signFields + auditLogs 포함 | P1 |
| 2 | 존재하지 않는 id | id=99999 | 404, "계약을 찾을 수 없습니다" | P1 |
| 3 | 다른 테넌트 계약 | 다른 테넌트의 계약 id | 404 (테넌트 격리) | P1 |
| 4 | 삭제된 계약 | soft deleted 계약 id | 404 | P2 |
| 5 | access_token 미노출 | 정상 조회 | signers의 access_token, otp_code 필드 없음 | P1 |

---

### TC-API-004: 계약 취소

**엔드포인트**: `POST /api/v1/esign/contracts/{id}/cancel`

| # | 시나리오 | 입력 | 기대 결과 | 우선순위 |
|---|---------|------|----------|---------|
| 1 | draft 상태 취소 | status=draft | 200, status→cancelled | P1 |
| 2 | pending 상태 취소 | status=pending | 200, status→cancelled | P1 |
| 3 | completed 상태 취소 시도 | status=completed | 400/422, "현재 상태에서 취소 불가" | P1 |
| 4 | expired 상태 취소 시도 | status=expired | 400/422, 상태 변경 불가 | P2 |
| 5 | 이미 cancelled 상태 | status=cancelled | 400/422, 중복 취소 방지 | P2 |
| 6 | 감사 로그 생성 확인 | 정상 취소 | audit_logs에 'cancelled' 액션 기록 | P1 |

---

### TC-API-005: 서명 필드 설정

**엔드포인트**: `POST /api/v1/esign/contracts/{id}/fields`

| # | 시나리오 | 입력 | 기대 결과 | 우선순위 |
|---|---------|------|----------|---------|
| 1 | 정상 설정 (서명 2개) | 갑/을 서명 필드 각 1개 | 200, fields 2개 저장 | P1 |
| 2 | 다양한 필드 타입 | signature + date + text | 200, 3개 저장 | P1 |
| 3 | 빈 배열 | fields=[] | 422, "min:1" 에러 | P1 |
| 4 | 좌표 범위 초과 | position_x=150 | 422, "between:0,100" 에러 | P1 |
| 5 | 음수 좌표 | position_x=-10 | 422, validation 에러 | P2 |
| 6 | 유효하지 않은 signer_id | signer_id=99999 | 422, "exists" 에러 | P1 |
| 7 | 유효하지 않은 field_type | field_type="invalid" | 422, enum 에러 | P2 |
| 8 | pending 상태에서 설정 시도 | status=pending | 400/422, "draft 상태에서만 가능" | P1 |
| 9 | 기존 필드 교체 확인 | 2번째 설정 요청 | 기존 필드 삭제 + 새 필드 생성 | P1 |
| 10 | 페이지 번호 0 | page_number=0 | 422, "min:1" 에러 | P2 |

---

### TC-API-006: 서명 요청 발송

**엔드포인트**: `POST /api/v1/esign/contracts/{id}/send`

| # | 시나리오 | 입력 | 기대 결과 | 우선순위 |
|---|---------|------|----------|---------|
| 1 | 정상 발송 | draft 상태 + 필드 설정 완료 | 200, status→pending, 이메일 발송 | P1 |
| 2 | 필드 미설정 상태 발송 | 필드 0개 | 400, "서명 필드를 먼저 설정해주세요" | P1 |
| 3 | pending 상태에서 재발송 | status=pending | 400, "이미 발송된 계약" | P1 |
| 4 | 첫 서명자 상태 변경 확인 | counterpart_first | 상대방 signer status→notified | P1 |
| 5 | 이메일 내용 확인 | 정상 발송 | 계약 제목, 서명 링크, 만료일 포함 | P2 |
| 6 | 감사 로그 확인 | 정상 발송 | 'sent' 액션 기록 | P1 |

---

### TC-API-007: 리마인더 발송

**엔드포인트**: `POST /api/v1/esign/contracts/{id}/remind`

| # | 시나리오 | 입력 | 기대 결과 | 우선순위 |
|---|---------|------|----------|---------|
| 1 | 정상 리마인더 | pending 상태 | 200, 리마인더 이메일 발송 | P1 |
| 2 | draft 상태 리마인더 | status=draft | 400, 발송 전 상태 | P2 |
| 3 | completed 상태 리마인더 | status=completed | 400, 완료 상태에서 불가 | P2 |
| 4 | 감사 로그 확인 | 정상 발송 | 'reminded' 액션 기록 | P2 |

---

### TC-API-008: 계약 통계

**엔드포인트**: `GET /api/v1/esign/contracts/stats`

| # | 시나리오 | 입력 | 기대 결과 | 우선순위 |
|---|---------|------|----------|---------|
| 1 | 정상 통계 조회 | 없음 | 200, 상태별 카운트 7개 (total, draft, pending 등) | P1 |
| 2 | 데이터 없을 때 | 계약 0건 | 200, 모든 값 0 | P2 |
| 3 | 테넌트 격리 | 다른 테넌트 | 해당 테넌트 데이터만 집계 | P1 |

---

### TC-API-009: 완료 PDF 다운로드

**엔드포인트**: `GET /api/v1/esign/contracts/{id}/download`

| # | 시나리오 | 입력 | 기대 결과 | 우선순위 |
|---|---------|------|----------|---------|
| 1 | 정상 다운로드 | completed 상태 | 200, Content-Type: application/pdf | P1 |
| 2 | 미완료 상태 | status=pending | 400, "완료된 계약만 다운로드 가능" | P1 |
| 3 | 비인증 접근 | Authorization 없음 | 401 | P1 |
| 4 | 감사 로그 확인 | 정상 다운로드 | 'downloaded' 액션 기록 | P2 |

---

### TC-API-010: 문서 무결성 검증

**엔드포인트**: `GET /api/v1/esign/contracts/{id}/verify`

| # | 시나리오 | 입력 | 기대 결과 | 우선순위 |
|---|---------|------|----------|---------|
| 1 | 정상 검증 (무결) | 변경 없는 파일 | 200, original_integrity=true | P1 |
| 2 | 위변조된 파일 | 파일 직접 변경 | 200, original_integrity=false | P1 |
| 3 | 해시값 포함 확인 | 정상 검증 | original_hash, signed_hash 반환 | P1 |

---

## 3. API 테스트 케이스 - 서명 프로세스 (토큰 기반)

### TC-API-011: 토큰으로 계약 조회

**엔드포인트**: `GET /api/v1/esign/sign/{token}`

| # | 시나리오 | 입력 | 기대 결과 | 우선순위 |
|---|---------|------|----------|---------|
| 1 | 유효한 토큰 접속 | 정상 access_token | 200, 계약 정보 + 서명자 정보 반환 | P1 |
| 2 | 유효하지 않은 토큰 | "invalid-token-xxx" | 404, "유효하지 않은 링크" | P1 |
| 3 | 만료된 토큰 | token_expires_at 경과 | 400, "서명 링크가 만료되었습니다" | P1 |
| 4 | 취소된 계약 토큰 | status=cancelled | 400, "취소된 계약" | P1 |
| 5 | 이미 서명 완료된 토큰 | signer status=signed | 400, "이미 서명 완료" | P1 |
| 6 | 서명 순서 아닌 서명자 | 두 번째 순서 서명자 | 400, "아직 차례가 아닙니다" 또는 대기 안내 | P1 |

---

### TC-API-012: OTP 발송

**엔드포인트**: `POST /api/v1/esign/sign/{token}/otp/send`

| # | 시나리오 | 입력 | 기대 결과 | 우선순위 |
|---|---------|------|----------|---------|
| 1 | 정상 OTP 발송 | 유효한 토큰 | 200, "인증코드가 발송되었습니다", expires_in=300 | P1 |
| 2 | OTP 재발송 | 이전 OTP 만료 후 | 200, 새 OTP 발송 | P1 |
| 3 | 유효하지 않은 토큰 | 잘못된 토큰 | 404 | P1 |
| 4 | OTP 시도 횟수 초과 후 | 5회 실패 이후 발송 | 400, "인증 횟수 초과" | P1 |

---

### TC-API-013: OTP 인증

**엔드포인트**: `POST /api/v1/esign/sign/{token}/otp/verify`

| # | 시나리오 | 입력 | 기대 결과 | 우선순위 |
|---|---------|------|----------|---------|
| 1 | 정상 인증 | 올바른 OTP 코드 | 200, verified=true, sign_session_token 발급 | P1 |
| 2 | 잘못된 OTP | 틀린 코드 | 401, verified=false, remaining_attempts 감소 | P1 |
| 3 | OTP 만료 | 5분 경과 후 입력 | 401, "인증코드가 만료되었습니다" | P1 |
| 4 | 5회 시도 초과 | 6번째 시도 | 400, "OTP 입력 횟수를 초과했습니다" | P1 |
| 5 | OTP 없이 인증 | otp_code 미전송 | 422, validation 에러 | P2 |
| 6 | 인증 후 auth_verified_at 기록 | 정상 인증 | DB에 인증 시각 저장 | P1 |
| 7 | 감사 로그 확인 (성공) | 정상 인증 | 'authenticated' 액션 기록 | P1 |
| 8 | 감사 로그 확인 (실패) | 잘못된 OTP | 'otp_failed' 또는 시도 횟수 기록 | P2 |

---

### TC-API-014: PDF 문서 조회

**엔드포인트**: `GET /api/v1/esign/sign/{token}/document`

| # | 시나리오 | 입력 | 기대 결과 | 우선순위 |
|---|---------|------|----------|---------|
| 1 | 인증 후 문서 조회 | 유효한 토큰 + sign_session_token | 200, application/pdf 스트리밍 | P1 |
| 2 | 인증 전 문서 접근 | sign_session_token 없음 | 401 또는 403 | P1 |
| 3 | 감사 로그 확인 | 정상 조회 | 'viewed' 액션 기록 | P2 |

---

### TC-API-015: 서명 제출

**엔드포인트**: `POST /api/v1/esign/sign/{token}/submit`

| # | 시나리오 | 입력 | 기대 결과 | 우선순위 |
|---|---------|------|----------|---------|
| 1 | 정상 서명 제출 | base64 서명 이미지 | 200, signer status→signed, signed_at 기록 | P1 |
| 2 | 서명 이미지 누락 | signature_image 없음 | 422, validation 에러 | P1 |
| 3 | 인증 전 서명 시도 | sign_session_token 없음 | 401 또는 403 | P1 |
| 4 | 이미 서명한 서명자 | status=signed | 400, "이미 서명 완료" | P1 |
| 5 | IP/UserAgent 기록 확인 | 정상 제출 | sign_ip_address, sign_user_agent 저장 | P1 |
| 6 | 첫 서명 → partially_signed | 첫 번째 서명자 서명 | contract status→partially_signed | P1 |
| 7 | 두 번째 서명 → completed | 두 번째 서명자 서명 | contract status→completed, completed_at 기록 | P1 |
| 8 | 다음 서명자 알림 | 첫 서명 완료 | 다음 서명자에게 이메일 발송 | P1 |
| 9 | 감사 로그 확인 | 정상 서명 | 'signed' 액션 + IP/UA 기록 | P1 |

---

### TC-API-016: 서명 거절

**엔드포인트**: `POST /api/v1/esign/sign/{token}/reject`

| # | 시나리오 | 입력 | 기대 결과 | 우선순위 |
|---|---------|------|----------|---------|
| 1 | 정상 거절 | reason="조건 수정 필요" | 200, signer status→rejected, contract status→rejected | P1 |
| 2 | 사유 누락 | reason 없음 | 422, validation 에러 | P1 |
| 3 | 사유 1000자 초과 | 1001자 텍스트 | 422, "max:1000" 에러 | P2 |
| 4 | 이미 서명한 서명자 거절 | status=signed | 400, "이미 서명 완료" | P1 |
| 5 | 감사 로그 확인 | 정상 거절 | 'rejected' 액션 + 거절 사유 metadata | P1 |

---

## 4. E2E 시나리오 테스트

### TC-E2E-001: 정상 계약 플로우 (상대방 먼저)

**시나리오**: 계약 생성 → 필드 설정 → 발송 → 상대방 서명 → 작성자 서명 → 완료

| 단계 | 행위 | 검증 포인트 |
|------|------|------------|
| 1 | A: 계약 생성 (PDF 업로드 + 정보 입력) | status=draft, 서명자 2인 생성, 해시 생성 |
| 2 | A: 서명 필드 설정 (갑/을 각 1개) | fields 2개 저장 |
| 3 | A: 서명 요청 발송 | status→pending, 을에게 이메일 |
| 4 | B: 서명 링크 접속 | 계약 정보 표시 |
| 5 | B: OTP 발송 요청 | OTP 이메일 발송 |
| 6 | B: OTP 입력 → 인증 | sign_session_token 발급 |
| 7 | B: 계약서 확인 (PDF 렌더링) | PDF 정상 표시, 서명 위치 하이라이트 |
| 8 | B: 서명 수행 | signer status→signed, contract→partially_signed |
| 9 | A: 알림 수신 | "상대방이 서명했습니다" 이메일 |
| 10 | A: 서명 링크 접속 → OTP → 서명 | signer status→signed |
| 11 | 자동 완료 처리 | contract→completed, completed_at 기록 |
| 12 | 양쪽에 완료 알림 | 완료 이메일 + 다운로드 링크 |
| 13 | A: PDF 다운로드 | Content-Type: application/pdf |
| 14 | A: 무결성 검증 | integrity=true |

---

### TC-E2E-002: 정상 계약 플로우 (작성자 먼저)

**시나리오**: sign_order_type=creator_first로 생성

| 단계 | 행위 | 검증 포인트 |
|------|------|------------|
| 1 | A: 계약 생성 (creator_first) | 작성자 sign_order=1 |
| 2 | A: 필드 설정 + 발송 | 작성자에게 먼저 이메일 |
| 3 | A: 서명 수행 | contract→partially_signed |
| 4 | B: 알림 수신 → 서명 수행 | contract→completed |

---

### TC-E2E-003: 서명 거절 플로우

| 단계 | 행위 | 검증 포인트 |
|------|------|------------|
| 1 | A: 계약 생성 → 필드 설정 → 발송 | status=pending |
| 2 | B: 서명 링크 접속 → OTP 인증 | 인증 완료 |
| 3 | B: 서명 거절 (사유 입력) | signer→rejected, contract→rejected |
| 4 | A: 거절 알림 수신 | 거절 사유 확인 가능 |
| 5 | 이후 서명 시도 | 거절된 계약 서명 불가 확인 |

---

### TC-E2E-004: 계약 취소 플로우

| 단계 | 행위 | 검증 포인트 |
|------|------|------------|
| 1 | A: 계약 생성 → 발송 | status=pending |
| 2 | A: 계약 취소 | status→cancelled |
| 3 | B: 기존 서명 링크 접속 | "취소된 계약" 메시지 표시 |
| 4 | A: 대시보드에서 상태 확인 | cancelled 표시 |

---

### TC-E2E-005: 리마인더 발송 플로우

| 단계 | 행위 | 검증 포인트 |
|------|------|------------|
| 1 | A: 계약 발송 (pending 상태) | 서명 대기 중 |
| 2 | A: 리마인더 발송 | 이메일 재발송 |
| 3 | 감사 로그 확인 | 'reminded' 기록 |

---

## 5. 보안 테스트 케이스

### TC-SEC-001: 토큰 보안

| # | 시나리오 | 검증 방법 | 기대 결과 | 우선순위 |
|---|---------|----------|----------|---------|
| 1 | 토큰 추측 공격 | 랜덤 128자 토큰으로 접속 | 404, 접근 차단 | P1 |
| 2 | 토큰 재사용 방지 | 서명 완료 후 같은 토큰 접속 | 400, "이미 서명 완료" | P1 |
| 3 | 토큰 만료 | token_expires_at 이후 접속 | 400, "링크 만료" | P1 |
| 4 | API 응답에 토큰 미포함 | 계약 상세 API 호출 | signers에 access_token 필드 없음 | P1 |

---

### TC-SEC-002: OTP 보안

| # | 시나리오 | 검증 방법 | 기대 결과 | 우선순위 |
|---|---------|----------|----------|---------|
| 1 | 브루트포스 방지 | 6번째 OTP 시도 | 인증 차단 | P1 |
| 2 | OTP 시간 만료 | 5분 경과 후 입력 | "인증코드 만료" | P1 |
| 3 | 이전 OTP 재사용 | 재발송 후 이전 코드 입력 | 인증 실패 | P1 |
| 4 | OTP 탈취 방지 | API 응답에서 OTP 코드 확인 | OTP 코드가 응답에 포함되지 않음 | P1 |

---

### TC-SEC-003: 접근 제어

| # | 시나리오 | 검증 방법 | 기대 결과 | 우선순위 |
|---|---------|----------|----------|---------|
| 1 | 다른 테넌트 계약 접근 | 타 테넌트 contract_id로 API 호출 | 404 (데이터 격리) | P1 |
| 2 | 비인증 사용자 관리 API 접근 | Authorization 없이 계약 관리 API | 401 | P1 |
| 3 | 인증 전 서명 시도 | sign_session_token 없이 submit | 401/403 | P1 |
| 4 | 다른 서명자의 토큰 사용 | A의 토큰으로 B의 서명 시도 | 400/403, 불일치 | P1 |
| 5 | 서명 순서 우회 시도 | 두 번째 순서 서명자가 먼저 접근 | 400, "차례가 아닙니다" | P1 |

---

### TC-SEC-004: 문서 무결성

| # | 시나리오 | 검증 방법 | 기대 결과 | 우선순위 |
|---|---------|----------|----------|---------|
| 1 | 원본 PDF 해시 검증 | verify API 호출 | original_integrity=true | P1 |
| 2 | 위변조 감지 | 스토리지에서 파일 직접 수정 후 검증 | original_integrity=false | P1 |
| 3 | 해시 비교 시 타이밍 공격 방지 | hash_equals() 사용 여부 확인 | 코드 리뷰로 확인 | P2 |

---

### TC-SEC-005: 감사 추적

| # | 시나리오 | 검증 방법 | 기대 결과 | 우선순위 |
|---|---------|----------|----------|---------|
| 1 | 모든 행위 기록 | 전체 플로우 수행 후 로그 확인 | 모든 주요 액션 기록됨 | P1 |
| 2 | IP/UserAgent 기록 | 서명 시 환경 정보 | 정확한 IP/UA 기록 | P1 |
| 3 | 감사 로그 삭제 불가 | DELETE 시도 | SoftDeletes 미적용, 삭제 API 없음 | P1 |
| 4 | 로그 timestamp 정확성 | 서명 시각 기록 | 서버 시간 기준 정확 기록 | P2 |

---

## 6. MNG 화면 테스트 케이스

### TC-UI-001: 대시보드 (/esign)

| # | 시나리오 | 검증 포인트 | 우선순위 |
|---|---------|------------|---------|
| 1 | 화면 로딩 | React 컴포넌트 정상 마운트 (#esign-dashboard-root) | P1 |
| 2 | 통계 카드 표시 | 상태별 건수 (전체, 진행중, 완료 등) 표시 | P1 |
| 3 | 계약 목록 표시 | 테이블 형태 목록 (제목, 상태, 서명자, 날짜) | P1 |
| 4 | 상태 필터 | 상태 클릭 시 해당 상태 계약만 필터링 | P2 |
| 5 | 검색 | 제목 검색 동작 | P2 |
| 6 | "새 계약" 버튼 | /esign/create로 이동 | P1 |
| 7 | 계약 행 클릭 | /esign/{id}로 이동 | P1 |
| 8 | HTMX 네비게이션 | 사이드바에서 클릭 시 HX-Redirect로 전체 페이지 로드 | P1 |

---

### TC-UI-002: 계약 생성 (/esign/create)

| # | 시나리오 | 검증 포인트 | 우선순위 |
|---|---------|------------|---------|
| 1 | 화면 로딩 | React 컴포넌트 정상 마운트 | P1 |
| 2 | PDF 업로드 영역 | 드래그&드롭 또는 파일 선택 | P1 |
| 3 | PDF 미리보기 | 업로드 후 파일명/크기 표시 | P2 |
| 4 | 필수 필드 표시 | 제목, PDF, 이름, 이메일에 필수 마크 | P1 |
| 5 | 유효성 검증 | 빈 필드 제출 시 에러 메시지 | P1 |
| 6 | 이메일 형식 검증 | 잘못된 이메일 입력 시 에러 | P2 |
| 7 | 생성 완료 | 생성 후 필드 설정 화면으로 이동 | P1 |

---

### TC-UI-003: 서명 위치 지정 (/esign/{id}/fields)

| # | 시나리오 | 검증 포인트 | 우선순위 |
|---|---------|------------|---------|
| 1 | PDF.js 렌더링 | 업로드한 PDF 정상 표시 | P1 |
| 2 | 페이지 이동 | 다중 페이지 PDF 페이지 전환 | P1 |
| 3 | 서명 필드 추가 | 클릭으로 서명란 배치 | P1 |
| 4 | 서명자 구분 | 갑(파랑)/을(빨강) 색상 구분 | P2 |
| 5 | 드래그로 위치 이동 | 필드 드래그 이동 | P1 |
| 6 | 필드 크기 조절 | 리사이즈 핸들 동작 | P2 |
| 7 | 필드 삭제 | 필드 선택 후 삭제 | P1 |
| 8 | 저장 | API 호출 후 저장 완료 메시지 | P1 |

---

### TC-UI-004: 서명 요청 발송 (/esign/{id}/send)

| # | 시나리오 | 검증 포인트 | 우선순위 |
|---|---------|------------|---------|
| 1 | 체크리스트 표시 | 서명자 정보, 필드 수, PDF 확인 | P1 |
| 2 | 서명 순서 표시 | 누가 먼저 서명하는지 명확 표시 | P1 |
| 3 | 발송 버튼 | 클릭 시 확인 다이얼로그 → API 호출 | P1 |
| 4 | 발송 완료 | 성공 메시지 + 대시보드로 이동 | P1 |

---

### TC-UI-005: 계약 상세 (/esign/{id})

| # | 시나리오 | 검증 포인트 | 우선순위 |
|---|---------|------------|---------|
| 1 | 기본 정보 표시 | 제목, 코드, 상태, 날짜 | P1 |
| 2 | 서명자 현황 | 갑/을 서명 상태 (대기/완료/거절) | P1 |
| 3 | 감사 로그 타임라인 | 시간순 이벤트 목록 | P1 |
| 4 | 취소 버튼 | draft/pending 상태에서 노출, 취소 확인 | P1 |
| 5 | 다운로드 버튼 | completed 상태에서만 노출 | P1 |
| 6 | 리마인더 버튼 | pending/partially_signed에서 노출 | P2 |

---

### TC-UI-006: 본인인증 OTP (/esign/sign/{token})

| # | 시나리오 | 검증 포인트 | 우선순위 |
|---|---------|------------|---------|
| 1 | 계약 정보 표시 | 제목, 서명 요청자, 기한 | P1 |
| 2 | OTP 발송 버튼 | 클릭 시 이메일 발송 | P1 |
| 3 | OTP 입력 필드 | 6자리 입력 UI | P1 |
| 4 | 타이머 표시 | 남은 시간 카운트다운 (5분) | P2 |
| 5 | 에러 메시지 | 잘못된 OTP 입력 시 에러 표시 | P1 |
| 6 | 횟수 안내 | 남은 시도 횟수 표시 | P2 |
| 7 | 인증 성공 | 서명 화면으로 자동 이동 | P1 |
| 8 | 재발송 버튼 | OTP 재발송 기능 | P2 |

---

### TC-UI-007: 서명 수행 (/esign/sign/{token}/sign)

| # | 시나리오 | 검증 포인트 | 우선순위 |
|---|---------|------------|---------|
| 1 | PDF 렌더링 | 계약서 전체 내용 표시 | P1 |
| 2 | 서명 위치 하이라이트 | 서명해야 할 위치 강조 표시 | P1 |
| 3 | SignaturePad 동작 | 터치/마우스 서명 입력 | P1 |
| 4 | 서명 초기화 | "다시 서명" 버튼으로 캔버스 초기화 | P1 |
| 5 | 동의 체크박스 | 2개 체크박스 (내용 확인 + 법적 효력 동의) | P1 |
| 6 | 제출 버튼 | 모든 체크 완료 후 활성화 | P1 |
| 7 | 제출 완료 | 완료 화면으로 이동 | P1 |
| 8 | 모바일 서명 | 모바일 터치로 서명 가능 | P1 |

---

### TC-UI-008: 서명 완료 (/esign/sign/{token}/done)

| # | 시나리오 | 검증 포인트 | 우선순위 |
|---|---------|------------|---------|
| 1 | 완료 메시지 | "서명이 완료되었습니다" 표시 | P1 |
| 2 | 계약 정보 요약 | 계약 제목, 서명 시각 | P2 |
| 3 | 거절 시 메시지 | "서명이 거절되었습니다" + 사유 표시 | P1 |

---

## 7. 비기능 테스트 케이스

### TC-NFR-001: 성능

| # | 시나리오 | 기준 | 우선순위 |
|---|---------|------|---------|
| 1 | API 응답 시간 | 목록/상세 조회 < 500ms | P2 |
| 2 | PDF 업로드 (20MB) | 업로드 + 해시 생성 < 5초 | P2 |
| 3 | OTP 이메일 발송 | 발송 요청 ~ 이메일 수신 < 30초 | P2 |
| 4 | 동시 접속 | 10명 동시 서명 시 에러 없음 | P3 |

---

### TC-NFR-002: 브라우저 호환성

| # | 브라우저 | 테스트 대상 | 우선순위 |
|---|---------|------------|---------|
| 1 | Chrome 최신 (Desktop) | 전체 기능 | P1 |
| 2 | Safari 최신 (Desktop) | 전체 기능 | P1 |
| 3 | Firefox 최신 (Desktop) | 전체 기능 | P2 |
| 4 | Chrome (Android) | 서명 화면 (터치 서명) | P1 |
| 5 | Safari (iOS) | 서명 화면 (터치 서명) | P1 |
| 6 | Edge 최신 | 전체 기능 | P3 |

---

### TC-NFR-003: 다중 테넌트

| # | 시나리오 | 검증 방법 | 우선순위 |
|---|---------|----------|---------|
| 1 | 데이터 격리 | 테넌트 A에서 테넌트 B 계약 조회 불가 | P1 |
| 2 | 통계 격리 | 각 테넌트 자체 통계만 집계 | P1 |
| 3 | 파일 격리 | 다른 테넌트의 PDF 접근 불가 | P1 |

---

## 8. 테스트 우선순위 정의

| 우선순위 | 의미 | 실행 시점 |
|---------|------|----------|
| **P1** (필수) | 핵심 기능, 실패 시 서비스 불가 | 매 배포 전 |
| **P2** (중요) | 부가 기능, 사용 편의 관련 | 주요 변경 시 |
| **P3** (권장) | 엣지 케이스, 호환성 | 분기별 |

### 우선순위별 테스트 케이스 수

| 우선순위 | API TC | UI TC | 보안 TC | E2E TC | NFR TC | 합계 |
|---------|--------|-------|--------|--------|--------|------|
| P1 | 58 | 32 | 16 | 5 | 5 | **116** |
| P2 | 20 | 12 | 2 | 0 | 5 | **39** |
| P3 | 3 | 0 | 0 | 0 | 1 | **4** |
| **합계** | **81** | **44** | **18** | **5** | **11** | **159** |

---

## 9. 테스트 실행 가이드

### 9.1 API 테스트 (Postman)

```
1. Postman Collection 생성: "SAM E-Sign API Tests"
2. 환경 변수 설정:
   - base_url: http://api.sam.kr (또는 로컬 Docker)
   - auth_token: 로그인 API로 획득
   - test_token: 계약 생성 후 서명자 access_token
3. 순서대로 실행:
   ① 계약 생성 → contract_id, signer_id 저장
   ② 필드 설정 → field_id 저장
   ③ 발송 → access_token 확인
   ④ 토큰 접속 → 서명 프로세스 테스트
```

### 9.2 E2E 테스트 (브라우저)

```
1. MNG 로그인 (https://mng.sam.kr)
2. 사이드바 → 전자계약 → 대시보드
3. "새 계약" → 테스트 PDF 업로드 + 정보 입력
4. 서명 위치 지정 → 갑/을 서명란 배치
5. 서명 요청 발송
6. 이메일 확인 → 서명 링크 클릭 (시크릿 창)
7. OTP 인증 → 서명 수행 → 완료
```

### 9.3 테스트 결과 기록 양식

| TC ID | 시나리오 | 결과 | 비고 | 테스트일 | 테스터 |
|-------|---------|------|------|---------|--------|
| TC-API-001-1 | 기본 목록 조회 | PASS/FAIL | | | |
| TC-API-001-2 | 상태 필터 | PASS/FAIL | | | |
| ... | ... | ... | ... | ... | ... |

---

*이 문서는 SAM E-Sign v1.0 테스트 계획서입니다. 최종 업데이트: 2026-02-12*
