23 KiB
23 KiB
메인 콘텐츠 추천 오디오 스냅샷 폴백 Plan/Task
시나리오 계약
- Happy path:
GET /api/v2/audio/recommendations가 사용자 visibility에 맞는NEW_AND_HOT,MOST_COMMENTED,RECOMMENDED_AUDIO스냅샷을 각각 조회하고, 비어 있는 섹션만 fallback refresh 후 재조회한다. Real surface:AudioRecommendationQueryServiceTest,AudioRecommendationEndToEndTest. - Independent fallback:
NEW_AND_HOT스냅샷이 존재해도MOST_COMMENTED또는RECOMMENDED_AUDIO가 비어 있으면 해당 섹션 fallback을 독립적으로 실행한다. Real surface:AudioRecommendationQueryServiceTest, 신규 fallback service test. - Visibility: 비회원/19금 노출 불가 회원은
*_SAFE, 19금 노출 가능 회원은*_ALLsection type으로 fallback을 수행한다. Real surface:AudioRecommendationQueryServiceTest. - Lock and wait: fallback은
lock:audio-recommendation-snapshot-refresh:{SECTION_TYPE}lock, 300ms lock wait, 1,500ms API wait, JVM single-flight, lock 안 double-check를 사용한다. Real surface: 신규 fallback service test. - Empty marker: 오디오 snapshot-backed 6개 section type은 refresh 결과 0건이면
targetId = 0marker를 저장하고, snapshot 조회 응답에서는 marker를 제외한다. Real surface:RecommendationSnapshotPersistenceAdapterTest. - Refresh reuse: fallback refresh는
AudioRecommendationSnapshotRefreshService의 스케줄러와 동일한 snapshot 생성 로직을 사용한다. Real surface:AudioRecommendationSnapshotRefreshServiceTest, 신규 fallback service test. - Adjacent regression:
GET /api/v2/audio/recommendationsURL, response field, 오디오 점수 산식, 배너/오리지널/최신/무료/포인트 섹션 조회 정책은 변경하지 않는다. Real surface:AudioRecommendationFacadeTest,AudioRecommendationEndToEndTest.
범위와 전제
- 이번 문서는
docs/20260712_메인_콘텐츠_추천_오디오_스냅샷_폴백/prd.md의 구현 계획이다. - 신규 공개 API, 신규 응답 필드, 운영 DDL 추가는 범위에 포함하지 않는다.
- 기존
recommendation_snapshot테이블과 오디오 관련RecommendedSectionType6개를 재사용한다. AudioRecommendationQueryService.getRecommendations의 snapshot-backed 3개 섹션만 이번 fallback 보강의 대상이다.findNewAndHotAudios전체보기 fallback은 기존 lazy refresh 동작을 유지하되, 공통 helper 변경 시 회귀 테스트로 보호한다.- 오디오 스냅샷은 기존
findLatestSnapshots(...)정책을 유지한다. 홈 추천처럼 대상일snapshotAtexact 조회로 바꾸는 것은 이번 범위에 포함하지 않는다. - 기존
AudioRecommendationSnapshotRefreshService는 일괄 refresh만 제공하므로, 단일 섹션 refresh 추가와 일괄 refresh 재사용 중 더 작은 변경을 구현 단계에서 선택한다. - 우선 권장안은 오디오 전용 fallback service를 두고, 홈 추천 fallback service의 lock/timeout/double-check/single-flight 패턴을 복제보다 작은 형태로 재사용 가능한 helper로 분리할지 검토하는 것이다.
기존 오디오 추천 로직 유지/변경 경계
- 유지:
GET /api/v2/audio/recommendationsendpoint와 응답 JSON 필드. - 유지:
NEW_AND_HOT,MOST_COMMENTED,RECOMMENDED_AUDIO점수 산식과 limit. - 유지:
SAFE/ALLvisibility variant 선택 기준. - 유지:
AudioRecommendationSnapshotScheduler의 매일 00:00 KST refresh. - 유지:
banners,originalSeries,latestAudios,freeAudios,pointAudios조회 경로. - 변경:
MOST_COMMENTED와RECOMMENDED_AUDIO만 비어 있어도 fallback refresh를 시도한다. - 변경:
NEW_AND_HOTfallback도 다른 두 섹션과 같은 공통 fallback 경로를 사용하도록 정리한다. - 추가: 오디오 snapshot-backed 6개 section type에 empty marker를 적용한다.
- 추가: 오디오 fallback refresh lock/timeout/single-flight 로그를 남긴다.
실행 명령
- 문서 명령 확인:
./gradlew tasks --all - 오디오 조회 service 테스트:
./gradlew test --tests kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationQueryServiceTest - 오디오 refresh service 테스트:
./gradlew test --tests kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationSnapshotRefreshServiceTest - snapshot marker 테스트:
./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.RecommendationSnapshotPersistenceAdapterTest - 오디오 API 회귀 테스트:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.content.recommendation.adapter.in.web.AudioRecommendationEndToEndTest --tests kr.co.vividnext.sodalive.v2.api.content.recommendation.application.AudioRecommendationFacadeTest - 포맷 검증:
./gradlew ktlintCheck - 전체 회귀:
./gradlew test
Phase 1: 문서와 현재 동작 고정
-
Task 1.1: PRD와 구현 계획 문서 작성
- 파일 경로:
- Create:
docs/20260712_메인_콘텐츠_추천_오디오_스냅샷_폴백/prd.md - Create:
docs/20260712_메인_콘텐츠_추천_오디오_스냅샷_폴백/plan-task.md
- Create:
- RED: 문서 작업은 TDD 예외. TDD 예외 사유: 코드 동작 변경 전 요구사항과 구현 순서를 고정하는 작업이다.
- GREEN: 오디오 snapshot-backed 3개 섹션의 독립 fallback, lock, timeout, empty marker, refresh 재사용 요구사항을 문서화한다.
- REFACTOR: 기존 메인 콘텐츠 추천 탭 PRD와 홈 추천 snapshot fallback 문서의 정책 차이를 반영해 범위/비범위를 정리한다.
- 기대 결과: 구현 시작 전에 PRD와 plan-task가 같은 디렉터리에 준비된다.
- 파일 경로:
-
Task 1.2: 현재 오디오 snapshot fallback 부재를 회귀 테스트로 고정
- 파일 경로:
- Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/content/recommendation/application/AudioRecommendationQueryServiceTest.kt
- Test:
- RED:
NEW_AND_HOT_AUDIO_SAFE는 기존 스냅샷이 있지만MOST_COMMENTED_AUDIO_SAFE만 비어 있을 때,MOST_COMMENTEDfallback port가 호출되어야 한다는 실패 테스트를 작성한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationQueryServiceTest - GREEN: 아직 구현하지 않는다. 이 task는 구현 단계에서 실패를 확인한 뒤 Phase 3에서 통과시킨다.
- REFACTOR:
RECOMMENDED_AUDIO_SAFE만 비어 있는 케이스도 별도 테스트로 추가해 두 섹션이NEW_AND_HOT존재 여부에 묶이지 않음을 고정한다. - 기대 결과: 현재 결함이 테스트로 재현된다.
- 파일 경로:
Phase 2: empty marker 저장 정책
-
Task 2.1: 오디오 스냅샷 section type empty marker 지원 추가
- 파일 경로:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/RecommendationSnapshotPersistenceAdapter.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/RecommendationSnapshotPersistenceAdapterTest.kt
- Modify:
- RED:
replaceSnapshots(NEW_AND_HOT_AUDIO_SAFE, snapshotAt, emptyList()),replaceSnapshots(MOST_COMMENTED_AUDIO_SAFE, snapshotAt, emptyList()),replaceSnapshots(RECOMMENDED_AUDIO_SAFE, snapshotAt, emptyList())호출 시 marker가 저장되고findSnapshots(...)는 빈 배열,existsSnapshot(...)는 true인 실패 테스트를 작성한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.RecommendationSnapshotPersistenceAdapterTest - GREEN:
supportsEmptySnapshotMarker(...)에 오디오 snapshot-backed 6개 section type을 추가한다. - REFACTOR: 홈 추천
AI_CHARACTER,CHEER_CREATOR,POPULAR_COMMUNITYmarker 동작 회귀 assertion을 유지한다. - 기대 결과: 데이터가 없는 오디오 섹션도 정상 refresh 완료 상태를 저장한다.
- 파일 경로:
-
Task 2.2: marker 대체와 latest 조회 제외 검증
- 파일 경로:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/RecommendationSnapshotPersistenceAdapter.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/RecommendationSnapshotPersistenceAdapterTest.kt
- Modify:
- RED: 오디오 marker가 있는 같은
sectionType,snapshotAt에 실제 row를 저장하면 marker가 제거되는 실패 테스트를 작성한다.findLatestSnapshots(...)에서도 marker가 반환되지 않음을 검증한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.RecommendationSnapshotPersistenceAdapterTest - GREEN: 기존 delete 후 save 흐름이 marker 대체를 보장하는지 확인하고, 조회 query의
target_id <> 0조건이 latest 조회에도 적용되게 유지한다. - REFACTOR: marker 관련 상수와 지원 section 판정 함수 이름이 오디오/홈 모두에 어색하지 않은지 정리한다.
- 기대 결과: marker가 사용자 응답이나 이후 실제 스냅샷을 오염시키지 않는다.
- 파일 경로:
Phase 3: 오디오 fallback orchestration
-
Task 3.1: 오디오 fallback port/service 추가
- 파일 경로:
- Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/content/recommendation/application/AudioRecommendationSnapshotFallbackService.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/content/recommendation/application/AudioRecommendationSnapshotFallbackServiceTest.kt
- Create:
- RED: 특정 오디오
sectionType의 latest snapshot이 없고 marker도 없으면 lock을 획득하고 refresh service를 호출한 뒤 같은 section latest snapshot을 재조회하는 실패 테스트를 작성한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationSnapshotFallbackServiceTest - GREEN:
RecommendationSnapshotPort,AudioRecommendationSnapshotRefreshService,RedissonClient를 사용하는 오디오 전용 fallback service를 추가한다. - REFACTOR: 홈 추천 fallback의 lock wait 300ms, API wait 1,500ms, single-flight, double-check 패턴을 맞추되, 공통화가 과하면 오디오 전용 최소 구현으로 유지한다.
- 기대 결과: 오디오 섹션 하나를 입력받아 fallback refresh와 재조회를 수행할 수 있다.
- 파일 경로:
-
Task 3.2: lock miss, timeout, 실패, marker 케이스 검증
- 파일 경로:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/content/recommendation/application/AudioRecommendationSnapshotFallbackService.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/content/recommendation/application/AudioRecommendationSnapshotFallbackServiceTest.kt
- Modify:
- RED: lock 획득 실패, 1,500ms timeout, refresh 예외, marker 존재, 동시 요청 single-flight 케이스를 실패 테스트로 작성한다.
- 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationSnapshotFallbackServiceTest - GREEN: 각 실패/대기 상황에서 빈 배열을 반환하고 warn/info log를 남기며 전체 API 예외로 전파하지 않게 구현한다.
- REFACTOR: fallback service가 상세 DTO 조립이나 점수 계산을 직접 하지 않도록 유지한다.
- 기대 결과: fallback이 요청 지연과 중복 refresh를 제한한다.
- 파일 경로:
-
Task 3.3: refresh service에 섹션 단위 refresh 경로 추가 또는 일괄 refresh 재사용 확정
- 파일 경로:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/content/recommendation/application/AudioRecommendationSnapshotRefreshService.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/content/recommendation/application/AudioRecommendationSnapshotRefreshServiceTest.kt
- Modify:
- RED:
refreshSection(sectionType, now)또는 동등한 경로가 입력 section type에 맞는 queryPort 함수와 limit을 사용해replaceSnapshots(...)를 호출하는 실패 테스트를 작성한다. 일괄 refresh 재사용을 선택하면 fallback service test에서refreshDailySnapshots()호출을 명시적으로 검증한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationSnapshotRefreshServiceTest - GREEN: 가장 작은 변경으로 스케줄러와 fallback이 같은 snapshot 생성 로직을 공유하게 한다.
- REFACTOR:
SAFE/ALLsection type 매핑 중복이 커지면 기존AudioRecommendationVisibilityextension 또는 작은 helper로 정리한다. - 기대 결과: fallback refresh와 scheduler refresh 사이에 산식 drift가 생기지 않는다.
- 파일 경로:
Phase 4: AudioRecommendationQueryService 연결
-
Task 4.1:
getRecommendations의 3개 snapshot 조회를 fallback 경로로 변경- 파일 경로:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/content/recommendation/application/AudioRecommendationQueryService.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/content/recommendation/application/AudioRecommendationQueryServiceTest.kt
- Modify:
- RED:
MOST_COMMENTED_AUDIO_SAFE만 비어 있을 때mostCommentedAudios가 fallback 재조회 결과로 조립되는 실패 테스트를 작성한다.RECOMMENDED_AUDIO_SAFE만 비어 있는 케이스도 추가한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationQueryServiceTest - GREEN:
NEW_AND_HOT,MOST_COMMENTED,RECOMMENDED_AUDIO각각에 대해findSnapshotsWithFallback(sectionType, offset, limit)형태의 경로를 사용한다. - REFACTOR: 기존
refreshMissingNewAndHotSnapshots(...)와 Redis 날짜 marker는 새 fallback 경로로 대체하거나 전체보기 전용으로만 남긴다. 사용하지 않게 되면 관련 의존성/상수를 제거한다. - 기대 결과: 3개 오디오 스냅샷 섹션이 서로 독립적으로 fallback을 실행한다.
- 파일 경로:
-
Task 4.2: visibility별 fallback section type 검증
- 파일 경로:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/content/recommendation/application/AudioRecommendationQueryService.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/content/recommendation/application/AudioRecommendationQueryServiceTest.kt
- Modify:
- RED: 비회원은
*_SAFE, 성인 콘텐츠 노출 가능 회원은*_ALLsection type으로 fallback service가 호출되는 실패 테스트를 작성한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationQueryServiceTest - GREEN: 기존
newAndHotSectionType,mostCommentedSectionType,recommendedAudioSectionType매핑을 fallback 호출에도 그대로 사용한다. - REFACTOR: 기존 성인 preference 조회 정책과
initializeDefaultPreference미호출 회귀 테스트를 유지한다. - 기대 결과: fallback이 현재 사용자 visibility와 다른 variant를 잘못 갱신하거나 조회하지 않는다.
- 파일 경로:
-
Task 4.3:
findNewAndHotAudios전체보기 회귀 정리- 파일 경로:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/content/recommendation/application/AudioRecommendationQueryService.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/content/recommendation/application/AudioRecommendationQueryServiceTest.kt
- Modify:
- RED: 전체보기
findNewAndHotAudios(member, offset, limit)가 기존처럼 offset/limit snapshot 순서를 유지하고, snapshot이 없을 때 새 fallback 경로 또는 기존 lazy refresh 정책 중 결정된 경로를 사용하는 테스트를 작성한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationQueryServiceTest - GREEN: 전체보기 동작을 구현 결정에 맞춰 최소 수정한다.
- REFACTOR: 홈 첫 화면 limit 12와 전체보기 paging limit이 섞이지 않도록 helper 인자를 명확히 유지한다.
- 기대 결과: 첫 화면 fallback 보강이 전체보기 paging을 깨지 않는다.
- 파일 경로:
Phase 5: API 회귀와 최종 검증
-
Task 5.1: 오디오 추천 API 응답 스키마 회귀 검증
- 파일 경로:
- Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/content/recommendation/adapter/in/web/AudioRecommendationEndToEndTest.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/content/recommendation/application/AudioRecommendationFacadeTest.kt
- Test:
- RED:
newAndHotAudios,mostCommentedAudios,recommendedAudios필드명이 유지되고 신규 필드가 추가되지 않는 회귀 테스트를 확인/보강한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.content.recommendation.adapter.in.web.AudioRecommendationEndToEndTest --tests kr.co.vividnext.sodalive.v2.api.content.recommendation.application.AudioRecommendationFacadeTest - GREEN: controller/facade/DTO 변경 없이 application service 결과가 기존 response로 매핑되게 한다.
- REFACTOR: 공개 API URL과 JSON field name 변경이 없음을 assertion으로 유지한다.
- 기대 결과: 클라이언트 공개 스키마는 변경되지 않는다.
- 파일 경로:
-
Task 5.2: focused regression 실행
- 파일 경로:
- Modify:
docs/20260712_메인_콘텐츠_추천_오디오_스냅샷_폴백/plan-task.md
- Modify:
- RED: 구현 task 완료 후 계획 문서에 기록할 focused command 목록을 확정한다.
- 실패 확인: 해당 없음. TDD 예외 사유: 검증 기록 문서화 task다.
- GREEN: 아래 명령을 실행하고 결과를 이 문서 하단 검증 기록에 누적한다.
- REFACTOR: 실패한 명령이 있으면 원인과 재실행 결과를 같은 task 아래에 기록한다.
- 실행 명령:
./gradlew test --tests kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationQueryServiceTest./gradlew test --tests kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationSnapshotRefreshServiceTest./gradlew test --tests kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationSnapshotFallbackServiceTest./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.RecommendationSnapshotPersistenceAdapterTest./gradlew test --tests kr.co.vividnext.sodalive.v2.api.content.recommendation.adapter.in.web.AudioRecommendationEndToEndTest --tests kr.co.vividnext.sodalive.v2.api.content.recommendation.application.AudioRecommendationFacadeTest
- 기대 결과: 오디오 fallback, marker, refresh, API 회귀가 최소 명령으로 검증된다.
- 파일 경로:
-
Task 5.3: 전체 회귀와 문서 검증
- 파일 경로:
- Modify:
docs/20260712_메인_콘텐츠_추천_오디오_스냅샷_폴백/plan-task.md
- Modify:
- RED: 구현 완료 후 전체 회귀 명령 실행 전에는 검증 기록이 구현 전 상태여야 한다.
- 실패 확인: 해당 없음. TDD 예외 사유: 검증 기록 문서화 task다.
- GREEN:
./gradlew ktlintCheck,./gradlew test,./gradlew tasks --all을 실행하고 결과를 문서 하단 검증 기록에 누적한다. - REFACTOR: PRD와 plan-task가 구현 결과와 어긋나면 먼저 문서를 갱신하고 필요한 focused test를 재실행한다.
- 기대 결과: 포맷, 전체 테스트, 문서 명령 유효성을 모두 확인한다.
- 파일 경로:
검증 기록
- 2026-07-12: PRD와 구현 계획 문서만 작성했다. 코드 변경은 수행하지 않았다.
- 2026-07-12: 문서 변경 검증을 수행했다.
git diff --check성공../gradlew tasks --all은 최초 샌드박스 실행에서~/.gradlelock 파일 권한 오류로 실패했고, 권한 승인 후 재실행해BUILD SUCCESSFUL로 통과했다.
- 2026-07-12: 오디오 snapshot-backed 3개 섹션의 독립 fallback 구현을 수행했다.
- RED 확인:
RecommendationSnapshotPersistenceAdapterTest.shouldSaveAudioEmptySnapshotMarkerWhenReplacingWithEmptySnapshots는 오디오 section marker 미지원으로 실패했다.AudioRecommendationSnapshotRefreshServiceTest.shouldRefreshRequestedAudioSnapshotSectionOnly는refreshSection미정의 컴파일 오류로 실패했다.AudioRecommendationSnapshotFallbackServiceTest는AudioRecommendationSnapshotFallbackService미정의 컴파일 오류로 실패했다.AudioRecommendationQueryServiceTest는 기존 생성자/조회 경로가 fallback service를 사용하지 않아 컴파일 오류로 실패했다.
- GREEN 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationQueryServiceTest성공../gradlew test --tests kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationSnapshotRefreshServiceTest성공../gradlew test --tests kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationSnapshotFallbackServiceTest성공../gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.RecommendationSnapshotPersistenceAdapterTest성공../gradlew test --tests kr.co.vividnext.sodalive.v2.api.content.recommendation.adapter.in.web.AudioRecommendationEndToEndTest --tests kr.co.vividnext.sodalive.v2.api.content.recommendation.application.AudioRecommendationFacadeTest성공.
- 포맷/문서 명령:
./gradlew ktlintCheck성공../gradlew tasks --all성공.git diff --check성공.
- 전체 회귀:
./gradlew test는 최초 300초 제한에 걸렸고, 재실행은 사용자 요청으로 중단했다. 전체 테스트는 사용자가 별도로 재실행하기로 했다.
- RED 확인:
- 2026-07-12: post-implementation review에서 blocking issue 2건을 확인하고 수정했다.
AudioRecommendationQueryService의 fallback 기준 시간을Asia/Seoul기준으로 분리해 JVM 기본 timezone 의존을 제거했다.AudioRecommendationSnapshotScheduler가 fallback과 동일한 6개 section lock을 획득한 뒤 일괄 refresh를 실행하도록 수정했다.- 추가 RED 확인:
AudioRecommendationSnapshotSchedulerTest.shouldSkipWhenSectionLockNotAcquired는 section lock miss에도 일 배치를 실행해 실패했다.AudioRecommendationSnapshotSchedulerTest.shouldRefreshOnlyWhenLockAcquired는 section lock unlock 검증 실패로 실패했다.
- 수정 후 검증:
./gradlew test --tests kr.co.vividnext.sodalive.v2.content.recommendation.adapter.out.scheduler.AudioRecommendationSnapshotSchedulerTest성공../gradlew test --tests kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationQueryServiceTest성공../gradlew test --tests kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationSnapshotFallbackServiceTest성공../gradlew test --tests kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationSnapshotRefreshServiceTest성공../gradlew ktlintCheck성공.