Files
voiceon-character-admin/docs/20260725_AI캐릭터관리자웹/reviews/review-phase-6-20260729.md

130 lines
5.4 KiB
Markdown

# Phase 6 코드 리뷰·QA — 2026-07-29
## 1. 리뷰 정보
| 항목 | 내용 |
|---|---|
| 리뷰 대상 | Phase 6 / Community vertical slice와 `P10-T4` |
| 기준 commit 또는 working tree | `dd30e36323543e8f60e9983326503653e8001f12`; 리뷰 시작 시 tracked diff 36개, 전체 변경 169개 |
| 리뷰 일자 | 2026-07-29 |
| 리뷰어 | Codex |
| 기준 문서 | `prd.md`, `api-contract.openapi.json`, `plan-task.md` |
| 리뷰 상태 | 판정 완료 |
## 2. 리뷰 목적과 범위
### 목적
- `COMMUNITY-001~015`, Community pagination/media/price와 Sheet mutation을 대조한다.
### 포함 범위
- 코드: `src/features/community-posts`
- 테스트: Community contract/form/list/sheet
- 문서: Phase 6, `P10-T4`, OpenAPI Community request
- 수동 검증: price raw input과 payload 계산
### 제외 범위
- Comments 내부 pagination은 Phase 8, inactive comment mutation은 Phase 9
## 3. 판정 기준
심각도는 `Blocker/High/Medium/Low`, 상태는 `확정/오탐/보류/수정 완료`를 사용했다.
## 4. 검토한 근거
### 문서와 코드
- 요구사항: `COMMUNITY-007`, `COMMUNITY-012`, PRD `14.1`
- 코드: `community-post-form-helpers.ts:11`, `community-post-form-helpers.ts:12`, `CommunityPostForm.tsx:99`, `CommunityPostForm.tsx:122`, `CommunityPostForm.tsx:142`
- 테스트: `community-contract.test.ts`, `community-form.test.tsx`, `community-list.test.tsx`, `community-sheet.test.tsx`
### 실행 환경
```text
OS: macOS 26.0
Node: v24.12.0
npm: 11.7.0
API mode: injected client/MSW
```
### 실행한 검증
| 명령 또는 검증 | 결과 | 핵심 증거 |
|---|---|---|
| `npm run test:run -- src/features/community-posts src/features/fan-talks src/features/comments` | 성공 | 합계 9 files / 52 tests passed |
| 가격 parser `node` 계산 | 실패 재현 | `-1 → 1`, `1.5 → 15` |
| pagination contract 대조 | 성공 | server `totalCount/page/size/hasNext/items` 사용 |
## 5. 발견 사항 요약
| ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal |
|---|---|---|---|---|---|
| `REV-P6-001` | High | 수정 완료 | Community 가격의 음수·소수를 거부하지 않고 다른 값으로 저장한다 | `P10-T7` | `P6-R1` |
## 6. 발견 사항 상세
### REV-P6-001 — Community 금지 가격 입력이 유효한 다른 값으로 변환된다
- **심각도:** High
- **상태:** 확정
- **관련 요구사항:** `COMMUNITY-007`, PRD `14.1`
- **관련 계약:** create `price` integer `0..99999`
- **소유 Task:** 신규 `P6-R1`
**관찰 내용**
입력 parser가 숫자가 아닌 문자를 전부 제거한다. `-1``1캔`, `1.5``15캔`으로 바뀌고 form validation을 통과한다.
**재현 또는 검증 절차**
1. Community create form 가격에 `-1` 또는 `1.5`를 입력한다.
2. input은 각각 `1캔`, `15캔`으로 바뀐다.
3. 필수 필드를 채워 제출하면 거부 대신 해당 양수 정수가 전송된다.
4. 요구 결과는 음수·소수 제출 전 거부다.
**영향**
운영자 의도와 다른 유료 가격이 저장될 수 있다.
**권장 조치**
Audio와 동일한 raw-price validation primitive를 사용해 부호·소수점 입력을 invalid로 유지하고 form-level `-1`, `1.5` request 0건 test를 추가한다.
**판정 기록**
- 2026-07-29 — parser 계산과 submit serializer를 대조해 확정.
- 2026-07-30 — `P6-R1`에서 raw CAN price parser를 shared로 분리하고 Community form이 `-1`, `1.5`를 원문 유지 오류로 차단하도록 수정 완료.
## 7. 확정 항목의 plan·goal 전환
- `REV-P6-001``P6-R1`
## 8. 리뷰 종료 판정
| 판정 항목 | 결과 | 근거 |
|---|---|---|
| 리뷰 범위 전체 확인 | 충족 | Community code/test/contract 대조 |
| 후보 항목 판정 완료 | 충족 | 1건 확정 |
| 확정 항목 plan 반영 | 충족 | `P6-R1` |
| 보류 항목 담당·재개 조건 | 해당 없음 | 보류 없음 |
| 검증 명령과 결과 기록 | 충족 | §4 |
**최종 결론:** 수정 goal 필요.
**남은 항목:** Phase 8 reply pagination, Phase 9 inactive/accessibility.
## 9. 수정 후 검증 기록
### P6-R1 Community raw price validation 복구, 2026-07-30
- 무엇을: Community create 가격 입력에서 음수·소수 raw input을 다른 숫자로 변형하지 않고 제출 전에 차단했다.
- 왜: `-1 → 1캔`, `1.5 → 15캔`처럼 운영자 의도와 다른 유료 가격이 저장될 수 있었기 때문이다.
- 어떻게:
- RED: `npm run test:run -- src/features/community-posts/tests/community-price-validation.test.tsx`는 3 failed로, `-1`/`1.5`가 오류 없이 양수 캔 가격으로 변형됨을 확인했다.
- GREEN: `parseCanPriceInput`/`formatCanPriceInput``src/shared/validation/can-price.ts`에 추가하고 Audio/Community helper가 같은 raw price 규칙을 쓰게 했다. Community form은 빈 가격만 optional로 허용하고, 입력이 있는데 parse 실패하면 inline 오류와 POST 0건으로 막는다.
- 검증: focused `npm run test:run -- src/shared/validation/can-price.test.ts src/features/community-posts/tests/community-price-validation.test.tsx`는 2 files / 15 tests 통과했다. `npm run test:run -- src/shared/validation/can-price.test.ts src/features/community-posts`는 6 files / 51 tests 통과했고, `npm run typecheck`, `npm run lint`, `npm run build`도 모두 exit 0이었다.
남은 항목: Phase 8 reply pagination과 Phase 9 inactive/accessibility는 후속 범위로 유지한다.