9.4 KiB
generate-url 자막 후속 구현 검증
범위와 결과
- 기준:
prd.md§7,plan-task.mdP2-T1. Phase 1은 재구현하지 않았다. - 이번 production 변경은
content/GenerateUrlResponse.kt의 기존 DTO import/nullable 필드 추가와content/AudioContentService.kt의generateUrl본문뿐이다. DTO는GenerateUrlResponse(contentUrl: String, subtitle: ContentSubtitleResponse? = null)이다. - 기존
orderRepository.isExistOrdered를 그대로 사용한다. 구매 없는 소유자/무료, 비활성 KEEP, 만료 RENTAL은 빈contentUrl과 명시적 nullsubtitle이다. 활성 구매의 기존 음원 경로/만료 기간/envelope는 유지했다. - 구매자는 하나의
expirationTime을 음원 서명과SubtitleJobService.subtitle(audioContent, langContext.lang, expirationTime)에 전달한다. 자막만 미지정/도래한 공개일 또는 소유자 조건을 적용한다. 예약 구매자의 기존 음원 URL은 유지한다. - 기존
SubtitleHttpIntegrationTest의 15개 메서드/도우미를 보존하고 HTTP 테스트 3개와 세 테스트가 공용으로 사용하는 요청 도우미 1개만 추가했다. 다른 dirty 파일, PRD/plan/final-report, controller/DB/Lambda는 수정하지 않았다.
새 HTTP 테스트
클래스: kr.co.vividnext.sodalive.v2.subtitle.SubtitleHttpIntegrationTest. 실행은 아래 세 메서드 selector만 사용했다.
shouldIncludeRequestedSubtitleInGenerateUrl: 활성 KEEP 구매/공개 완료 ko·ja, 정확한{contentUrl,subtitle}및 자막 3필드, 상세와 동일한 자막, 음원/SRT 경로와 같은 14,400,000ms 서명 인자, 기존 success envelope.shouldReturnNullSubtitleInGenerateUrlWhenUnavailable: PENDING/최신 job 미등록/부분 완료 ko 누락/미지원 en의 명시적 null과 음원 URL 보존, 부분 완료 ja 정상 제공.shouldPreserveGenerateUrlPurchaseAndReleaseRestrictions: 미구매자·소유자·무료 및 비활성/만료 주문 차단, 활성 RENTAL 구매자의 예약 음원 URL 보존/자막 차단, 활성 구매 소유자의 예약 예외, 공개일 null/과거 허용, 익명 401.
실제 EMAIL 로그인/JWT, RANDOM_PORT Tomcat HTTP, H2와 기존 fixture/callback 도우미를 사용했다. 외부 S3/CloudFront 경계만 기존 mock을 재사용했다. 서명 URL은 전달 경로/만료 인자 검증이며 실제 CloudFront 암호학적 서명이나 다운로드 검증은 아니다.
명령과 증거
소유 root는 아래 VERIFY_ROOT다. build/project-cache/TMPDIR/Node/Bun 캐시를 격리했으며 init script는 production 소스를 필터링하지 않는다. 테스트 compile만 이 클래스와 필요한 support 3파일로 한정한다. test.outputs.upToDateWhen { false }로 실제 실행을 강제한다.
export VERIFY_ROOT=/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/generate-url-subtitle.fSBkJM/implementer
export TMPDIR="$VERIFY_ROOT/tmp" NODE_COMPILE_CACHE="$VERIFY_ROOT/node-cache"
export npm_config_cache="$VERIFY_ROOT/node-cache" BUN_INSTALL_CACHE_DIR="$VERIFY_ROOT/bun-cache"
export AWS_EC2_METADATA_DISABLED=true
G=(./gradlew --offline --no-daemon --max-workers=2
-Pkotlin.compiler.execution.strategy=in-process -Dorg.gradle.jvmargs=-Xmx4g
--project-cache-dir "$VERIFY_ROOT/project-cache"
--init-script "$VERIFY_ROOT/verification.init.gradle")
S=(--tests 'kr.co.vividnext.sodalive.v2.subtitle.SubtitleHttpIntegrationTest.shouldIncludeRequestedSubtitleInGenerateUrl'
--tests 'kr.co.vividnext.sodalive.v2.subtitle.SubtitleHttpIntegrationTest.shouldReturnNullSubtitleInGenerateUrlWhenUnavailable'
--tests 'kr.co.vividnext.sodalive.v2.subtitle.SubtitleHttpIntegrationTest.shouldPreserveGenerateUrlPurchaseAndReleaseRestrictions')
# RED: production 변경 전
"${G[@]}" test "${S[@]}" > "$VERIFY_ROOT/red-assertions.log" 2>&1
# 첫 GREEN
"${G[@]}" test "${S[@]}" ktlintCheck bootJar > "$VERIFY_ROOT/green-build-lint.log" 2>&1
# 입력 3파일을 고정/확인한 production+test lint
"${G[@]}" verifyLintScope ktlintCheck bootJar > "$VERIFY_ROOT/final-verified-scope-lint-build.log" 2>&1
# 기존 dirty EOF 보존 후 최종 HTTP/컴파일/테스트 lint/bootJar
"${G[@]}" test "${S[@]}" verifyLintScope ktlintTestSourceSetCheck bootJar > "$VERIFY_ROOT/final-green-build-test-lint.log" 2>&1
| 검증 | 결과/증거 |
|---|---|
| RED | exit 1, 50초, 3 tests/3 failures/0 errors/0 skipped. 모두 has("subtitle")에서 expected true/actual false. red-assertions.log, red-summary.log |
| 첫 GREEN | exit 0, BUILD SUCCESSFUL in 3m 35s, HTTP 3개 통과 및 compile/bootJar. lint의 정확한 입력 증거는 이후 고정한 실행을 기준으로 한다. |
| 정확한 변경 lint | 입력은 production 2파일+이 테스트 1파일, Kotlin script 0개임을 verifyLintScope assertion으로 확인. production lint는 기존 EOF 빈 줄 1건 실패: AudioContentService.kt:1482:1 Needless blank line(s). 그 외 지적 없음. final-verified-scope-lint-build.log |
| 최종 GREEN | exit 0, BUILD SUCCESSFUL in 3m 30s, compileKotlin/compileTestKotlin/bootJar 실제 실행, 테스트 lint 통과. HTTP XML 3 tests/0 failures/0 errors/0 skipped, timestamp 2026-11-10T22:07:38 (UTC). final-green-build-test-lint.log, green-summary.log |
최초 임시 init의 상대 source 경로로 test NO-SOURCE가 된 red.log는 RED 증거가 아니다. project.file로 수정 후 실제 assertion RED를 확인했다. final-scope-lint-build.log도 lint scope introspection assertion 실패 기록이며 HTTP 실패가 아니다. 이후 init에서 lint 입력 파일을 명시적으로 고정하고 검증했다. 이 이력과 로그를 삭제하지 않았다.
보존 사항과 남은 게이트
- 시작 전
git diff에도 있던 서비스 EOF 빈 줄은 타 dirty 변경 보존 지시에 따라 유지했다.git diff --check역시 같은 EOF 1건을 지적하므로 lint 전체 clean을 주장하지 않는다. 테스트/새 DTO lint 오류는 없다. - LSP 도구는 제공되지 않았다. Kotlin compile과 실제 3파일 ktlint 결과로 대체했다. 기존 Kotlin 경고와 테스트 종료 중 Redis reconnect 경고가 있으나 새 테스트 실패/오류는 없다.
- 기존 15개 테스트/기타 기존 테스트/전체 test/check/build는 실행하지 않았다. 사용자 전체 테스트 명령은
./gradlew test이며 이번 승인된 자동 검증 범위 밖이다. - RED PID 14462/HTTP 50806, 첫 GREEN PID 15417/HTTP 50833, 최종 GREEN PID 16741/HTTP 50879는 테스트 종료로 정상 종료했다. 세 PID와 HTTP/Redis port 50806/50833/50879/50803/50831/50877에
ps/lsof조회 결과가 없었다. 공유 프로세스를 종료하거나 task 취소를 호출하지 않았다. 부모 재검증을 위해 소유 root/init/log/JAR는 남겼다. - AWS/외부 저장소/운영 DB/배포/live ko·ja·mixed/2h 처리/앱/실제 CloudFront 다운로드는 미검증이다. 부모 재검증과 독립 리뷰도 이 보고서의 완료 주장에 포함하지 않는다. credential/JWT/request body는 로그/보고서에 출력하지 않았으며 최종 XML과 로그의 관련 패턴 검색 결과도 없었다.
부모 재검증
- 같은 격리 init 및 정확히 새 메서드 3개 selector로 직접 재실행했다.
parent-verification.log의 결과는 exit0,BUILD SUCCESSFUL in 37s이며 test는 실제 실행, bootJar/테스트 lint는 동일 코드의 UP-TO-DATE 확인이다. - XML: 3 tests/0 failures/0 errors/0 skipped, UTC timestamp
2026-11-10T22:12:51. 실제 Tomcat PID17759, HTTP port50914다. - 부모가 DTO·generateUrl의 전후 diff 및 추가 테스트 전체를 읽었다. 공유 자막 조회와 기존 구매 판정·음원 URL이 유지된다.
- 독립 리뷰와 임시 자원 정리는 아래 후속 기록으로 남긴다. 기존 production EOF lint1건은 성공으로 덮어쓰지 않는다.
독립 리뷰 및 사용 계약
- 제한 독립 리뷰
ses_e387b1e8fffensjb6dnoRk82ST: APPROVE. 이번 production delta의 명세·품질 blocker나 새 회귀 없음. data.contentUrl과 기존 envelope는 유지하며data.subtitle만 추가된다. 요청 언어는 기존Accept-Language/LangContext를 따른다.- 자막 응답 예시:
{
"contentUrl": "https://cdn.example.com/audio-signed-url",
"subtitle": {
"language": "ko-KR",
"format": "SRT",
"url": "https://cdn.example.com/subtitle-signed-url"
}
}
- 구매/공개 조건을 충족하지 못하거나 미완료·해당 언어 자막이 없으면
subtitle은 명시적 null이다. 기존 URL 권한을 확대하지 않는다. - 부모 HTTP PID17759 및 자식 PID14462/15417/16741, port50914, 소유 root 참조 프로세스는
ps/lsof/pgrep에서 부재를 확인했다. - 리뷰 승인/결과 문서 보존 후 작업 소유 root
generate-url-subtitle.fSBkJM과 하위 build/cache/log/JAR를 제거했고test ! -eexit0으로 확인했다. 위 임시 log/init 경로는 과거 검증 위치이며 공유 캐시는 건드리지 않았다.
일반 개발 환경에서 이번 후속 테스트만 실행하는 명령:
./gradlew test \
--tests 'kr.co.vividnext.sodalive.v2.subtitle.SubtitleHttpIntegrationTest.shouldIncludeRequestedSubtitleInGenerateUrl' \
--tests 'kr.co.vividnext.sodalive.v2.subtitle.SubtitleHttpIntegrationTest.shouldReturnNullSubtitleInGenerateUrlWhenUnavailable' \
--tests 'kr.co.vividnext.sodalive.v2.subtitle.SubtitleHttpIntegrationTest.shouldPreserveGenerateUrlPurchaseAndReleaseRestrictions'