23 KiB
Phase 4 Audio Content 코드 리뷰·QA
1. 리뷰 정보
| 항목 | 내용 |
|---|---|
| 리뷰 대상 | Phase 4 / Audio 목록·상세·생성·수정·soft delete·player |
| 기준 commit 또는 working tree | dd30e36323543e8f60e9983326503653e8001f12, 2026-07-31 종합 재점검 당시 사용자 변경을 포함한 current working tree |
| 리뷰 일자 | 2026-07-31 |
| 리뷰어 | Codex |
| 기준 문서 | prd.md, api-contract.openapi.json 2.3.0, plan-task.md |
| 리뷰 상태 | 판정 완료, 회귀 수정 완료 및 외부 수동 QA 대기 |
2. 리뷰 목적과 범위
목적
- Audio multipart DTO, 예약 UTC, 파일 정책, CRUD와 soft delete 불변식을 검증한다.
- schema·API adapter·contract test가 동일한 request 경계를 강제하는지 확인한다.
포함 범위
- 코드:
src/features/audio-contents - 테스트: Audio unit/contract/UI/E2E
- 문서:
AUDIO-001~AUDIO-033, 관련 OpenAPI operation,P4-* - 수동 검증: update/deactivate payload schema 정적 대조
제외 범위
- 실제 대용량 upload와 외부 CDN 재생 품질
- Comments 동작은 Phase 8에서 판정
3. 판정 기준
심각도는 Blocker, High, Medium, Low, 상태는 후보, 확정, 오탐, 보류, 수정 완료를 사용한다. 데이터 mutation 계약 위반과 경계 test의 잘못된 허용을 우선한다.
4. 검토한 근거
문서와 코드
- 요구사항:
AUDIO-006,AUDIO-007,AUDIO-012,AUDIO-019,AUDIO-023~AUDIO-033 - 계약: Audio POST/PUT multipart request schema
- 계획:
P4-T1~P4-GATE - 코드:
src/features/audio-contents/schemas/audio-content-schema.ts:35-44 - 테스트:
src/features/audio-contents/tests/audio-contract.test.ts:159-202
실행 환경
macOS 26.0 / Node v24.12.0 / npm 11.7.0
Vitest + Playwright 4 projects
실행한 검증
| 명령 또는 수동 검증 | 결과 | 핵심 증거 |
|---|---|---|
npm run test:run |
성공 | 72 files, 354 tests passed |
npm run e2e:mock |
부분 실패 | Audio E2E 흐름은 통과; 전체 origin 실패는 REV-P0-004 |
npm run typecheck / npm run lint / npm run build |
성공 | 모두 exit 0 |
| Audio update schema·test 정적 대조 | 실패 | 일반 update에서 nullable boolean isActive, test는 isActive:true를 허용·기대 |
5. 발견 사항 요약
| ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal |
|---|---|---|---|---|---|
REV-P4-007 |
Medium | 수정 완료 | 일반 Audio update schema와 contract test가 isActive=true를 허용한다 |
P4-R4 |
P4-R4 |
6. 발견 사항 상세
REV-P4-007 — 일반 Audio update schema와 contract test가 isActive=true를 허용한다
- 심각도: Medium
- 상태: 수정 완료
- 관련 요구사항:
AUDIO-007 - 관련 계약: Audio PUT request의 optional
isActive를 제품 정책상 soft delete로 제한 - 소유 Task: 신규
P4-R4
관찰 내용
audioContentUpdateRequestSchema의 isActive가 nullable boolean이라 true, false, null을 모두 허용한다. contract test는 일반 수정 request에 isActive:true를 넣고 그대로 전송되는 것을 성공 조건으로 고정한다. PRD는 일반 수정에서는 필드를 생략하고 soft delete 전용 request에서만 false를 보내며 true는 전송하지 않도록 명시한다.
근거
- 코드:
src/features/audio-contents/schemas/audio-content-schema.ts:35-44 - 테스트:
src/features/audio-contents/tests/audio-contract.test.ts:159-202 - 문서:
prd.md:225의AUDIO-007 - 계약 해석: OpenAPI의 optional boolean 범위보다 제품 mutation 정책이 더 좁으며 별도 deactivate adapter가 이미 존재
재현 또는 검증 절차
audioContentUpdateRequestSchema.parse({ isActive: true })를 호출한다.- parsing이 성공하는 것을 확인한다.
- 기존 contract test가 일반 update body의
isActive:true를 기대하는 것을 확인한다. - 요구 결과는 일반 update에서
isActive자체를 거부하고 deactivate request만{ isActive:false }를 허용하는 것이다.
영향
후속 UI·adapter 변경이 활성 복원 request를 잘못 보내도 schema와 contract test가 차단하지 못해 확정된 soft-delete-only 정책이 회귀할 수 있다.
권장 조치
일반 update schema에서 isActive를 제거하고 {isActive:false} strict schema를 deactivate 전용으로 분리한다. true, null, unknown field 거부와 두 payload의 정확한 직렬화를 test한다.
판정 기록
- 2026-07-30 — PRD, schema, adapter contract test의 상반된 허용 범위를 대조해 확정.
- 2026-07-30 —
P4-R4에서 normal update schema/API/mock parser와 deactivate schema/API/mock parser를 분리해 수정 완료. 일반 update는isActive=true/null/false를 거부하고 deactivate만{ isActive:false }를 허용한다.
7. 확정 항목의 plan·goal 전환
REV-P4-007→plan-task.md신규P4-R4- goal objective:
[P4-R4] Audio 일반 update와 soft delete schema를 분리해 isActive 전송 정책을 강제한다.
8. 리뷰 종료 판정
| 판정 항목 | 결과 | 근거 |
|---|---|---|
| 리뷰 범위 전체 확인 | 충족 | Audio code·test·contract 대조 |
| 후보 항목 판정 완료 | 충족 | 1건 확정 |
| 확정 항목 plan 반영 | 충족 | P4-R4 |
| 보류 항목의 담당·재개 조건 기록 | 해당 없음 | 보류 없음 |
| 검증 명령과 결과 기록 | 충족 | §4 |
최종 결론: 확정 발견 사항 수정 완료
남은 항목: Audio 실제 server integration은 기존 Gate 정책에 따라 mock UI 증거와 분리해 추적한다.
9. 수정 후 검증 기록
- 2026-07-30 — RED:
npm run test:run -- src/features/audio-contents/tests/audio-contract.test.ts에서 normal update의isActive:false가 거부되지 않아 실패하는 것을 확인했다. - 2026-07-30 — GREEN/focused:
npm run test:run -- src/features/audio-contents/tests/audio-contract.test.ts결과 1 file / 10 tests passed. - 2026-07-30 — 회귀:
npm run test:run -- src/features/audio-contents src/shared/mocks결과 14 files / 67 tests passed. 첫 병렬 실행에서audio-list.test.tsx가 loading 상태로 timeout됐으나 단독 재현은 2 passed였고, 같은 전체 focused 명령 재실행은 14 files / 67 tests passed로 통과했다. - 2026-07-30 — E2E/static:
npm run e2e:mock -- tests/e2e/audio-content.spec.ts결과 21 passed / 3 skipped.npm run typecheck,npm run lint,npm run build,git diff --check는 모두 exit 0 또는 no output이었다. - 2026-07-30 — LSP/size:
audio-content-schema.ts,audio-content-api.ts,audio-content-mock-store.ts,audio-content-handlers.ts,handlers.ts,audio-contract.test.tsdiagnostics는 오류 0건이었다. Audio handler 추출 후src/shared/mocks/handlers.ts는 238 lines,src/shared/mocks/audio-content-handlers.ts는 133 lines다.
10. 2차 점검 결과 — 2026-07-30
- Phase 4 request/schema 자체의 신규 계약 위반은 없고
REV-P4-007수정 완료 상태가 유지된다. - Audio 비활성화 pending·실패 복구의 공통 결함은 중복 등록하지 않고
REV-P9-004/P9-R4에서 추적한다.
11. 2026-07-31 재점검
실행·판정 요약
- 범위: Audio 목록 URL·API query, create/edit cover crop lifecycle, upload·deactivate를 PRD
AUDIO-001~033,FILE-006~009와 OpenAPI 2.3.0에 재대조했다. - 검증: 전체 unit 72 files / 360 tests, server allowlist 36 tests, typecheck·lint·build가 통과했다.
- 신규 발견: Medium 2건. 기존
REV-P4-007은 수정 완료 상태를 유지한다.
| ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal |
|---|---|---|---|---|---|
REV-P4-008 |
Medium | 수정 완료 | Audio URL이 계약 query search_word 대신 search를 사용한다 |
P4-R5 |
P4-R5 |
REV-P4-009 |
Medium | 수정 완료 | Audio cover crop 준비·연속 선택 상태가 저장 경계에 없다 | P4-R6 |
P4-R6 |
REV-P4-008 — Audio URL이 계약 query search_word 대신 search를 사용한다
- 심각도: Medium
- 상태: 수정 완료
- 관련 요구사항: PRD §7.1
- 관련 계약: Audio 목록 query
search_word,page,size - 소유 Task: 신규
P4-R5
관찰 내용과 근거
브라우저 URL은 search를 읽고 쓰고 API 호출 직전에만 search_word로 이름을 바꾼다. ?search_word=루나 직접 링크는 UI 검색 상태로 복원되지 않는다.
- 코드:
src/features/audio-contents/pages/AudioContentListPage.tsx:25-43,55-58 - 테스트:
src/features/audio-contents/tests/audio-list.test.tsx:87-128은?search=루를 정상 URL로 사용한다. - 문서:
prd.md:147-150
재현 또는 검증 절차
/ai-characters/101/audio-contents?search_word=루나&page=1&size=20에 직접 진입한다.- 검색 input과 request가 URL 검색어를 복원하지 않는지 확인한다.
- UI 검색 뒤 URL이
search=...가 되는지 확인한다.
영향 및 권장 수정 방향
계약 이름으로 공유한 deep link가 무시되고 URL·API 추적성이 달라진다. URL parser/serializer key만 search_word로 맞추고 직접 진입·검색·pagination 왕복 test를 추가한다.
판정 기록:
- 2026-07-31 — PRD·OpenAPI·page/test 대조로 확정.
- 2026-07-31 —
P4-R5에서 Audio list URL read/write key를search_word로 교체했다.?search_word=루나&page=1&size=20직접 진입은 input과 API request를 복원하고, 검색 변경 URL에는search가 남지 않는다.
REV-P4-009 — Audio cover crop 준비·연속 선택 상태가 저장 경계에 없다
- 심각도: Medium
- 상태: 수정 완료
- 관련 요구사항:
FILE-006~009, PRD §10.5 - 소유 Task: 신규
P4-R6
관찰 내용과 근거
selectCoverImage는 createCropSource를 바로 await하지만 준비 상태, selection token, reject 오류를 관리하지 않는다. submit button은 audio upload 중에만 비활성화되며 source 준비 중에는 저장할 수 있다. create는 필수 cover validation으로 일부 차단되지만 edit는 새 선택 의도와 무관하게 기존 cover 유지 payload를 먼저 저장할 수 있고, 연속 선택은 느린 이전 Promise가 최신 dialog를 덮을 수 있다.
- 코드:
src/features/audio-contents/components/AudioContentForm.tsx:65-96,116-126,233-244 - 문서:
prd.md:353-359,524-538 - 비교 근거: Community form/sheet는 selection ID와
isImagePreparing을 사용해 동일 경계를 처리한다.
재현 또는 검증 절차
- edit form에서 pending
createCropSource를 주입하고 새 cover를 선택한다. - source가 resolve되기 전에 저장하면 새 cover 선택이 확정되지 않은 채 update가 진행된다.
- 느린 첫 파일과 빠른 둘째 파일을 선택해 첫 Promise를 나중에 resolve하면 오래된 dialog가 열릴 수 있다.
- Promise reject 시 사용자 오류 없이 unhandled rejection이 될 수 있다.
영향 및 권장 수정 방향
운영자가 선택한 새 cover가 누락되거나 이전 선택 dialog가 열려 잘못된 파일을 적용할 수 있다. committed crop result와 준비 상태를 분리하고 마지막 선택만 허용하며 준비/crop 중 submit을 막고 오류를 표시한다.
판정 기록:
- 2026-07-31 — 비동기 state 전이와 파일 정책을 대조해 확정.
- 2026-07-31 —
P4-R6에서 Audio cover source 준비 상태와 selection token을 추가하고 준비/crop 중 submit을 차단했다. Promise reject는 inline 오류로 표시하고, crop 적용 결과만coverImage에 commit하며 수정 취소는 기존 서버 cover 유지 계약을 보존한다.
plan·goal 전환 및 종료 판정
REV-P4-008→P4-R5REV-P4-009→P4-R6- 최종 결론: 신규 Medium 2건 수정 완료. Blocker/High 없음.
- 교차 QA: 당시 Mobile Safari Audio 접근성 시나리오의 로그인 input 유실은 focused
--repeat-each=5에서 5 passed였고, 이후 지원 project 축소로 현재 Task에서 제외했다.
12. 최종 Phase별 점검 — 2026-07-31
실행 결과
| 명령 또는 검증 | 결과 | 판정 |
|---|---|---|
npm run test:run -- src/features/audio-contents |
8 files / 53 tests passed | 성공 |
npm run e2e:mock -- --project=chromium |
전체 57 tests passed, Audio 6 scenarios 포함 | 성공 |
npm run typecheck / npm run lint / npm run build |
모두 exit 0 | 성공 |
| PRD file flow와 Audio cover selection 정적 대조 | crop 전 원본 validator 호출 없음 | 실패, 신규 finding 확정 |
병렬로 네 개 Vitest process를 실행한 첫 Audio focused run에서는 audio-list.test.tsx 1건이 로딩 대기에서 timeout됐으나, 해당 파일 단독 2/2와 Audio 전체 단독 53/53이 통과했다. 같은 양상이 기존 §9·P4-R5 기록에도 있어 이번 계약 finding과 분리했고 신규 제품 결함으로 등록하지 않았다.
신규 발견 요약
| ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal |
|---|---|---|---|---|---|
REV-P4-010 |
Medium | 수정 완료 | Audio cover가 원본 파일 정책 검증 전에 crop 준비로 진입한다 | P4-R7 |
P4-R7 |
REV-P4-010 — Audio cover가 원본 파일 정책 검증 전에 crop 준비로 진입한다
- 심각도: Medium
- 상태: 수정 완료
- 관련 요구사항:
FILE-001~003,FILE-006~007, PRDImage crop UI 흐름1~3단계 - 관련 계약: cover JPEG/PNG, exact max
10,485,760 bytes, extension/MIME pair - 소유 Task: 신규
P4-R7
관찰 내용
selectCoverImage는 새 파일을 선택하면 validateAudioCoverFile을 호출하지 않고 바로 createCropSource(file)을 실행한다. 파일 정책 검증은 crop 적용 결과가 coverImage에 저장된 뒤 form submit validation에서만 일어난다. 따라서 10,485,761 bytes 원본, GIF, 확장자/MIME 불일치 파일도 먼저 crop source 생성과 Dialog 경로로 들어간다.
근거
- 문서:
prd.md:350-356,382-389 - 코드:
src/features/audio-contents/components/AudioContentForm.tsx:83-109,112-117 - validator:
src/features/audio-contents/validation/audio-cover-policy.ts:7-27 - 테스트 공백:
audio-contract.test.ts:216-227은 validator 단위 계약만 확인하고, Audio form tests에는 invalid 원본이createCropSource를 호출하지 않는 경계가 없다. - 비교 근거:
SeriesForm은 동일 단계에서validateSeriesImageFile실패 시 crop source 호출 전에 반환한다.
재현 또는 검증 절차
- Audio 생성·수정 form에 호출 횟수를 기록하는
createCropSource를 주입한다. 10,485,761 bytesPNG 또는.png/image/jpeg파일을 cover로 선택한다.- 현재 구현에서
createCropSource가 1회 호출되는 것을 확인한다. - 요구 결과는 호출 0회, crop Dialog 0개, 즉시 inline 정책 오류다.
영향과 권장 조치
부적합 원본이 불필요한 decode/canvas 작업에 들어가며, 큰 원본이 crop 결과 크기만 작아져 제출 검증을 통과하면 “선택 직후 원본 상한 검증” 계약을 우회한다. 기존 validator와 오류 메시지를 재사용해 crop source 생성 전에 반환하고 create/edit 회귀 test를 추가한다.
판정 기록
- 2026-07-31 — PRD의 명시적 처리 순서, Audio component와 validator·form tests를 대조해 확정.
- 2026-07-31 —
plan-task.md에 미완료 신규 TaskP4-R7로 전환. 애플리케이션 코드는 이번 리뷰 범위에서 수정하지 않았다. - 2026-07-31 —
P4-R7에서selectCoverImage가 기존 cover validator를 crop source 준비 전에 재사용하도록 수정했다. RED focused는 2 failed / 18 passed, GREEN focused는 2 files / 20 tests passed였다.npm run typecheck,npm run lint,npm run build, Audio directory LSP diagnostics는 통과했다. 개발 중 E2E는 사용자 지시에 따라 보류했다.
plan·goal 전환 및 종료 판정
REV-P4-010→plan-task.md신규P4-R7- 최종 결론: 신규 Medium 1건 수정 완료. Blocker/High 없음.
- 남은 항목: 기존 실제 개발 API Audio 수동 QA.
13. 종합 재점검 — 2026-07-31
- 검토 범위: Audio 목록·상세·multipart 생성/수정·UTC 예약·soft delete·player·Comments 연결을 OpenAPI와 PRD
AUDIO-*에 재대조했다. - 실행 증거: Audio를 포함한 도메인 묶음 36 files / 206 tests passed. exact server E2E 18/18과 현재 두 project mock E2E 109 passed / 5 skipped가 통과했다.
- 판정:
REV-P4-010/P4-R7수정 완료 상태가 유지되며 Audio 도메인 소유의 확정 신규 finding은 없다. - 교차 Phase: Audio cover에 영향을 주는 공용 crop 결함은
REV-P1-016~017/P1-R10~R11로만 추적한다. - 남은 위험: 실제 개발 API upload/예약/soft delete 수동 QA와 Safari Gate 복원이 필요하다.
- 신규 Phase 4 Task: 없음.
14. 요청 기준 재리뷰 — 2026-07-31
- 검토 범위: Audio list/detail/create/update/upload/player, UTC release, price·file policy, cover crop와 Comments 연결.
- 실행 증거: 관련 unit,
typecheck·lint·build와 4-project mock Audio journey가 0 failure였고 server allowlist 36 tests도 통과했다. - 판정:
P4-R7까지의 수정 완료 상태가 유지되며 Phase 4 소유의 신규 기능 결함은 없다. - 문서 교차 항목: 상단의 “교차 회귀 대기”와 이미 완료된 공통 crop 수정 이력 불일치는
REV-P10-008/P10-R7에서 정리한다. - 남은 위험: 실제 개발 API upload/예약/soft delete와 서버 파일 거부 수동 QA.
15. 최종 재검증 및 신규 판정 — 2026-07-31
발견 사항 요약
| ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal |
|---|---|---|---|---|---|
REV-P4-011 |
Medium | 수정 완료 | 완료된 network 실패를 뒤늦게 취소하면 upload 정산이 이중 실행되어 다음 session의 401을 무시한다 | P4-R8 |
P4-R8 |
REV-P4-011 — 완료된 network 실패를 뒤늦게 취소하면 upload 정산이 이중 실행되어 다음 session의 401을 무시한다
- 심각도: Medium
- 상태: 수정 완료
- 관련 요구사항:
AUDIO-017,AUTH-005, PRD §13의 upload 취소·재시도와 session 401 복구 - 관련 계약: Audio multipart upload와 HTTP 401 공통 처리. OpenAPI endpoint·DTO 변경 없음.
- 소유 Task: 신규
P4-R8
관찰 내용
uploadAudioContent의 resolve/reject helper는 호출될 때마다 보호 upload count를 감소시킨다. onload만 AbortSignal listener를 제거하고 onerror는 제거하지 않으므로, network error로 Promise가 이미 reject된 뒤 같은 signal이 abort되면 reject/정산이 다시 실행된다. count가 음수가 되고 authentication-expiry latch가 다음 login session까지 true로 남을 수 있다.
근거
- 코드:
src/features/audio-contents/api/upload-audio-content.ts:68-80은 settle guard 없이 모든 reject에서 count를 감소시키고,:96-100은 listener cleanup을onload에만 둔다. listener는:132에서 등록된다. - 테스트:
src/features/audio-contents/tests/audio-upload.test.ts:123-166의 cancel/retry와:191-228의 401 burst는 각각 독립 경로만 검증한다. - 문서:
AUDIO-017,AUTH-005, 기존P4-R2는 취소·재시도 이후에도 session expiry 흐름을 보존해야 한다.
재현 또는 검증 절차
- signal-bearing protected upload를 network error로 먼저 종료한 뒤 같은
AbortController를 abort한다. - 첫 session의 upload를 401로 종료해 만료 callback 1회를 확인하고, 새 token을 설정한다.
- 새 session의 upload도 401로 종료해 callback 총 2회를 기대하는 임시 진단 test를 focused 실행한다.
- 실제 결과는 exit 1, 1 failed였고
clearSession기대 2회 대비 실제 1회였다. 진단 코드는 판정 후 제거해 제품 test 변경을 남기지 않았다.
영향
network error와 cancel이 인접한 제한 조건 뒤에는 새로 로그인해도 다음 upload 401에서 session 제거·login 이동이 실행되지 않을 수 있다. 업로드 오류 자체는 표시되지만 인증 만료 복구가 누락된다.
권장 조치
요청별 settled guard와 공통 terminal cleanup으로 Promise settle, count 감소와 AbortSignal listener 해제를 정확히 한 번만 수행한다. network error→late abort→첫/새 session 401 순서를 회귀 test로 고정하고 새 upload abstraction은 만들지 않는다.
판정 기록
- 2026-07-31 — terminal handler data flow를 추적하고 임시 진단 test에서 새 session callback 누락을 재현해 Medium 확정.
- 2026-07-31 —
plan-task.md신규P4-R8로 전환하고P1-R12완료 뒤 실행하도록 의존성을 기록. 애플리케이션 코드는 수정하지 않음. - 2026-07-31 —
P4-R8에서 요청별 settled guard와 공통 terminal cleanup을 추가해 수정 완료. reviewer blocker로 fetch-first 순서와 주입 auth authoritative token 처리를 보강했다. upload focused 2 files / 13 tests, Audio feature 9 files / 58 tests, auth/app/shared 회귀 13 files / 91 tests, 전체 unit 79 files / 397 tests,npm run typecheck,npm run lint, 개발/운영 build, LSP diagnostics가 통과했다.
종료 판정
- 자동 검증:
P4-R8focused 2 files / 13 tests, Audio feature 9 files / 58 tests, 교차 auth focused 3 files / 22 tests,src/app src/features/auth src/shared/api포함 회귀 13 files / 91 tests, 전체 unit 79 files / 397 tests,npm run typecheck,npm run lint,npm run build:dev,npm run build:prod, targetedgit diff --check, 변경 파일 LSP diagnostics가 통과했다. E2E는 사용자 지시에 따라 반복 실행하지 않았다. - 최종 결론:
REV-P4-011수정 완료. Blocker/High 없음. - 남은 항목: 실제 개발 API upload/예약/soft delete·서버 파일 거부 수동 QA.
16. 2026-07-31 문서 기준 재리뷰
- 검토 범위: Audio list/detail/player/create/update/deactivate, XHR upload·cancel·auth lifecycle, UTC reservation, cover/audio file policy를
AUDIO-001~033·OpenAPI·P4·P10-T2에 대조했다. - 실행 증거: Audio contract/upload/auth lifecycle test는 full output에서 통과했고, type·lint·개발/운영 build·server E2E 36 tests도 통과했다. full unit 5 failure는
REV-P9-009/P9-R9, cover crop frame은 공용REV-P1-019/P1-R13으로 분리했다. - 판정: Audio endpoint·DTO·upload transport 소유의 확정 신규 발견 사항 없음. 후보·오탐·보류 0건, 신규 Phase 4 Task 없음.
- 남은 위험: 실제 upload·container/codec·예약·soft delete 수동 QA와
P1-R13,P9-R9회귀가 남는다.