18 KiB
성인 콘텐츠 노출 정책 Deprecated 함수 제거 구현 계획
| 문서 항목 | 내용 |
|---|---|
| 상태 | Phase 1~3 완료, P3-R1 구현 완료·실행 검증 대기 |
| 작성일 | 2026-09-17 |
| 요구사항 기준 | docs/20260917_성인콘텐츠노출정책_Deprecated함수_제거/prd.md |
| API 기준 | 기존 API 응답 스키마 변경 없음 |
| 현재 Phase | Phase 3 후속 실행 검증 대기 |
| 현재 활성 Goal | P3-R1 실행 검증 |
| 다음 Goal | 없음 |
목표
MemberContentPreferencePolicy.isAdultVisibleByPolicy(...) / resolveCountryCodeByPolicy(...) Deprecated 경로를 MemberContentPreferenceService 정식 경로로 이전하고, Deprecated 함수와 전용 테스트를 제거한다. 사용자가 확정한 JP 강제 매핑의 2L 제거를 제외하고 성인 콘텐츠 노출 판정 결과는 유지한다.
현재 상태
| Phase | 상태 | 완료 Task | 활성/다음 Goal | 차단 또는 남은 조건 |
|---|---|---|---|---|
| 1 | 완료 | 1/1 |
없음 | 없음 |
| 2 | 완료 | 4/4 |
없음 | 없음 |
| 3 | 완료 | 1/1 |
없음 | 없음 |
| 3 후속 | 진행 중 | 0/1 |
활성: P3-R1 실행 검증 |
회귀 테스트 구현·소스 검토 완료, 자동 실행 대기 |
범위
포함
MemberContentPreferenceService에isAdultVisibleForQuery(member, isAdultContentVisible)추가.- production 12개 파일 27곳의
isAdultVisibleByPolicy(...)호출 치환 및MemberContentPreferenceService생성자 주입. MemberContentPreferencePolicy.kt,MemberContentPreferencePolicyTest.kt제거와 고유 검증 이전.- 영향 범위 focused test 및 ktlint 검증.
제외
isAdult판정 알고리즘 변경 및 JP 강제 매핑의2L제거 외 국가 강제 매핑 회원 ID 변경.- 각 서비스 public 메서드 시그니처 변경, 공개 API 스키마 변경.
CanController의 통화 강제 로직.
기술적 제약
- 기술 스택: Kotlin, Spring Boot 2.7.14, Gradle Wrapper, ktlint.
- 신규 abstraction을 만들지 않는다. 기존
MemberContentPreferenceService의resolveCountryCode+calculateIsAdultForQuery를 재사용한다. CountryContext는@RequestScope이므로 마이그레이션 대상은 웹 요청 경로에서만 호출되는 서비스로 한정한다(RISK-001).- 모든 production 변경 Task는
RED → GREEN → REFACTOR순서로 진행한다. - 테스트 실행은 변경 범위 focused test를 우선하고, 전체 회귀는 실행하지 않으며 생략 근거를 검증 기록에 남긴다.
Phase 1: 정식 경로 단일 진입점 확보
Phase 결과: MemberContentPreferenceService.isAdultVisibleForQuery(...)가 Deprecated 함수와 동일한 판정을 제공한다.
선행조건: PRD 확정.
Phase 완료 조건: P1-T1 완료 및 검증 기록 누적.
구현 항목
Task 1.1 isAdultVisibleForQuery 추가
Goal 실행 P1-T1: MemberContentPreferenceService에 접속 국가 계산과 성인 노출 판정을 묶은 단일 메서드를 추가한다.
- 시작 조건:
DEPREM-001확정. - 완료 증거: KR + 미인증 →
false, KR + 인증 → 전달값, 비KR → 전달값, 강제 매핑 회원은 헤더보다 강제 국가 우선인 focused test 통과. - 범위 밖: 호출부 치환.
Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/member/contentpreference/MemberContentPreferenceService.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/member/contentpreference/MemberContentPreferenceServiceTest.kt
Interfaces:
-
fun isAdultVisibleForQuery(member: Member, isAdultContentVisible: Boolean): Boolean -
구현은
calculateIsAdultForQuery(member, resolveCountryCode(member), isAdultContentVisible)로 한다. DB 조회를 추가하지 않는다. -
RED:
MemberContentPreferenceServiceTest에isAdultVisibleForQuery판정 테스트 4건(KR 미인증/KR 인증/비KR/강제 매핑)을 작성한다. -
RED 확인:
MemberContentPreferenceServiceTest실행 결과Unresolved reference: isAdultVisibleForQuery컴파일 실패를 확인했다. -
GREEN:
MemberContentPreferenceService에isAdultVisibleForQuery를 추가했다. -
GREEN 확인:
MemberContentPreferenceServiceTest25/25 통과. -
REFACTOR:
resolveCountryCode+calculateIsAdultForQuery재사용만으로 구현해 중복 계산이 없음을 확인했고 ktlint 통과.
Phase 2: production 호출부 마이그레이션
Phase 결과: production 코드에서 isAdultVisibleByPolicy(...) 호출이 0건이 된다.
선행조건: Phase 1 완료.
Phase 완료 조건: P2-T1~P2-T4와 P2-GATE 완료 및 검증 기록 누적.
구현 항목
Task 2.1 메인 탭 서비스 7개 치환
Goal 실행 P2-T1: 콘텐츠 메인 탭 서비스 7개가 MemberContentPreferenceService로 성인 노출 여부를 계산한다.
- 시작 조건:
P1-T1완료. - 완료 증거: 7개 파일에서
isAdultVisibleByPolicyimport/호출 0건, 컴파일 및 영향 범위 테스트 통과. - 범위 밖:
AudioContentService,ContentSeriesService.
Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/asmr/AudioContentMainTabAsmrService.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/replay/AudioContentMainTabLiveReplayService.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/series/AudioContentMainTabSeriesService.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/alarm/AudioContentMainTabAlarmService.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/content/AudioContentMainTabContentService.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/free/AudioContentMainTabFreeService.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/home/AudioContentMainTabHomeService.kt
Interfaces:
-
각 서비스 생성자에
private val memberContentPreferenceService: MemberContentPreferenceService를 추가한다. -
isAdultVisibleByPolicy(member, isAdultContentVisible)→memberContentPreferenceService.isAdultVisibleForQuery(member, isAdultContentVisible). -
AudioContentMainTabHomeService의 nullable member 처리(member?.let { ... } ?: false)는 유지한다. -
RED: 7개 서비스가 각각 대응
*Controller에서만 주입됨을 확인해RISK-001위반 경로가 없음을 검증했다. -
GREEN: 7개 파일의 호출부와 생성자를 치환했다.
-
GREEN 확인: 테스트 컴파일과
src/test/kotlin/kr/co/vividnext/sodalive/content42/42 통과로 확인했다. -
REFACTOR:
isAdultVisibleByPolicyimport를MemberContentPreferenceServiceimport로 교체했고 ktlint 통과.
Task 2.2 메인/큐레이션/테마 서비스 치환
Goal 실행 P2-T2: AudioContentMainService, AudioContentCurationService, AudioContentThemeService를 정식 경로로 치환한다.
- 시작 조건:
P2-T1완료. - 완료 증거: 3개 파일에서
isAdultVisibleByPolicy0건, 컴파일 및 영향 범위 테스트 통과. - 범위 밖: 다른 Task 대상 파일.
Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/content/main/AudioContentMainService.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/content/main/curation/AudioContentCurationService.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/content/theme/AudioContentThemeService.kt
Interfaces:
-
P2-T1과 동일한 치환 규칙을 적용한다. -
RED: 3개 서비스의 치환 대상이 모두
member를 받는 웹 조회 메서드임을 확인했다. -
GREEN: 3개 파일을 치환했다.
-
GREEN 확인: 테스트 컴파일 성공 및
content패키지 회귀 통과. -
REFACTOR: import 정리 후 ktlint 통과.
Task 2.3 AudioContentService 치환
Goal 실행 P2-T3: AudioContentService의 3곳을 정식 경로로 치환한다.
- 시작 조건:
P2-T2완료. - 완료 증거:
AudioContentServiceTest가 갱신된 생성자로 통과한다. - 범위 밖:
ContentSeriesService.
Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt - Modify:
src/test/kotlin/kr/co/vividnext/sodalive/content/AudioContentServiceTest.kt
Interfaces:
-
P2-T1과 동일한 치환 규칙을 적용하고, 테스트에서는MemberContentPreferenceService를 mock으로 주입한다. -
RED:
AudioContentServiceTest.kt:118에서No value passed for parameter 'memberContentPreferenceService'컴파일 실패를 확인했다. -
GREEN:
getDetail,getLatestCreatorAudioContent,getAudioContentList3곳을 치환하고 테스트에 mock을 주입했다. -
GREEN 확인:
AudioContentServiceTest16/16 통과. -
REFACTOR: import 정리 후 ktlint 통과.
Task 2.4 ContentSeriesService 치환
Goal 실행 P2-T4: ContentSeriesService의 5곳을 정식 경로로 치환한다.
- 시작 조건:
P2-T3완료. - 완료 증거: 시리즈 관련 영향 범위 테스트 통과,
isAdultVisibleByPolicy0건. - 범위 밖: Deprecated 함수 제거.
Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesService.kt - Test: 필요 시
src/test/kotlin/kr/co/vividnext/sodalive/content/series/main/SeriesMainControllerTest.kt등 영향 테스트 갱신
Interfaces:
-
P2-T1과 동일한 치환 규칙을 적용한다. -
RED: 테스트 컴파일을 실행해
ContentSeriesService를 직접 생성하는 테스트가 없어 추가 수정이 필요 없음을 확인했다. -
GREEN: 5곳(
getSeriesList,getSeriesListByGenre,getSeriesDetail,getSeriesContentList,getRecommendSeriesList)을 치환했다. -
GREEN 확인: 테스트 컴파일 성공 및 Spring 컨텍스트 로딩 테스트 통과.
-
REFACTOR: import 정리 후 ktlint 통과.
Phase 2 Gate
Goal 실행 P2-GATE: production에서 Deprecated 호출이 0건임을 확인한다.
isAdultVisibleByPolicy|resolveCountryCodeByPolicy검색 결과가src/main/kotlin에서 0건이다.- Phase 2 대상 파일의 영향 범위 테스트가 통과한다(
content42/42).
Phase 3: Deprecated 함수 제거와 테스트 정리
Phase 결과: Deprecated 함수와 전용 테스트가 제거되고 고유 검증이 정식 경로 테스트에 남는다.
선행조건: Phase 2 완료.
Phase 완료 조건: P3-T1, P3-GATE 완료 및 검증 기록 누적.
구현 항목
Task 3.1 Deprecated 정의 제거와 검증 이전
Goal 실행 P3-T1: MemberContentPreferencePolicy.kt와 MemberContentPreferencePolicyTest.kt를 제거하고 고유 검증을 이전한다.
- 시작 조건:
P2-GATE완료. - 완료 증거: 두 파일이 삭제되고
member/contentpreference테스트가 모두 통과한다. - 범위 밖:
MemberContentPreferenceCountryResolver.kt변경.
Files:
- Delete:
src/main/kotlin/kr/co/vividnext/sodalive/member/contentpreference/MemberContentPreferencePolicy.kt - Delete:
src/test/kotlin/kr/co/vividnext/sodalive/member/contentpreference/MemberContentPreferencePolicyTest.kt - Modify:
src/test/kotlin/kr/co/vividnext/sodalive/member/contentpreference/MemberContentPreferenceServiceTest.kt
Interfaces:
-
이전 대상 검증: 헤더 공백/소문자 정규화(
" us "→US), 국가 컨텍스트 없음 →KRfallback, 비KR에서 저장countryCode무시. -
이미 정식 경로 테스트에 존재하는 검증은 중복 추가하지 않는다.
-
RED: 로그인 회원의 헤더 공백/소문자 정규화와 KR fallback 검증을
MemberContentPreferenceServiceTest에 추가했다. 나머지 항목(비로그인 정규화, 저장countryCode미사용)은 기존 테스트가 이미 커버함을 확인해 중복 추가하지 않았다. -
GREEN:
MemberContentPreferencePolicy.kt와MemberContentPreferencePolicyTest.kt를 삭제했다. -
GREEN 확인:
member/contentpreference36/36 통과. -
REFACTOR: ktlint 통과 및 잔여 참조 0건 확인.
Phase 3 Gate
Goal 실행 P3-GATE: 전체 정리 상태를 확인한다.
isAdultVisibleByPolicy|resolveCountryCodeByPolicy참조가src/main/kotlin,src/test/kotlin에서 0건이다.member/contentpreference테스트와 Phase 2 영향 범위 테스트가 통과한다../gradlew ktlintMainSourceSetCheck ktlintTestSourceSetCheck가 통과한다.- PRD 성공 기준 체크박스를 갱신했다.
Phase 3 후속: 리뷰 검증 보강
Task 3.R1 저장 국가 무시 검증 이전
Goal 실행 P3-R1: 저장 국가가 KR인 미인증 회원이 US에서 조회할 때 저장 국가를 무시하는 기존 정책을 정식 서비스 테스트로 검증한다.
- 시작 조건: DEPREM-004 및
reviews/문서대비구현-review.md의 REV-002 확정. REV-001은 사용자 확인으로 결함 판정 철회;2L제거 유지. - 완료 증거: 아래 테스트 추가 및 조건·assertion 소스 검토, 후속 실행이 허용된 경우 focused test 결과 기록. 실행하지 않았으면 실행 검증 대기로 남기고 완료 처리하지 않는다.
- 범위 밖: production 정책·resolver·매핑 변경, API 변경, 테스트 helper 리팩터링, 전체 회귀 실행.
- TDD 예외: 현재 production은 올바르게 저장 국가를 무시하므로 신규 테스트는 처음부터 통과하는 것이 정상이다. 실패를 만들려고 정상 구현을 변경하지 않는다. 삭제된 테스트와 새 테스트의 입력·assertion 대조로 누락 검증 이전을 확인한다.
- 실행 제약: 이번 문서 갱신에서는 테스트를 실행하지 않는다. 기존 자동 테스트 미실행 지시가 유지되는 동안 후속 작업도 소스 검토까지만 진행하고 실행 상태를 별도로 남긴다.
Files:
- Modify:
src/test/kotlin/kr/co/vividnext/sodalive/member/contentpreference/MemberContentPreferenceServiceTest.kt - Update:
docs/20260917_성인콘텐츠노출정책_Deprecated함수_제거/plan-task.md - Update:
docs/20260917_성인콘텐츠노출정책_Deprecated함수_제거/reviews/문서대비구현-review.md
추가할 테스트: 기존 createMember, countryContext, service, JUnit import를 재사용한다.
@Test
@DisplayName("비KR 요청에서는 회원의 저장 국가와 무관하게 전달한 성인 노출값을 사용한다")
fun shouldIgnoreStoredCountryCodeWhenRequestCountryIsNotKr() {
val member = createMember(id = 2401L, withAuth = false).apply {
countryCode = "KR"
}
countryContext.setCountryCode("US")
assertEquals("US", service.resolveCountryCode(member))
assertTrue(service.isAdultVisibleForQuery(member, isAdultContentVisible = true))
assertFalse(service.isAdultVisibleForQuery(member, isAdultContentVisible = false))
}
- 누락 확인: 삭제된 정책 테스트와 비교해 저장 KR·요청 US·미인증 조건이 기존 정식 테스트에 없음을 확인했다(TDD 예외 적용).
- 최소 구현: 위 테스트 1건만 추가했다. 강제 매핑이 없는 ID 2401을 사용하고 production은 변경하지 않았다.
- 소스 검토: 국가 결과 US 및 노출 전달값 true/false 보존 assertion을 확인했다. 저장 국가 KR을 우선하면 국가 assertion과
true전달값 assertion이 실패한다. - 실행 검증: 자동 테스트 실행이 허용되는 후속 단계에서 아래 focused test와 ktlint를 실행하고 결과를 기록한다. 미실행이면 완료 표시하지 않는다.
- 기록 갱신: P3-T1의 기존 완료 기록을 유지하고, REV-002 보강 결과와 실행 대기 상태를 본 Task와 리뷰에 누적했다.
후속 실행용 명령(이번에는 미실행): 저장소와 호환되는 JDK 환경에서 실행한다.
./gradlew test --tests 'kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceServiceTest'
./gradlew ktlintTestSourceSetCheck
기대 결과: 신규 테스트를 포함한 서비스 테스트 및 테스트 소스 ktlint 성공. 테스트 한 파일의 검증 보강이므로 전체 회귀는 생략한다.
검증 기록
-
2026-09-17 Phase 3 후속 P3-R1: 삭제된 정책 테스트와 현재 서비스 테스트를 대조해 저장 KR·요청 US·미인증 조건의 누락을 확인하고
MemberContentPreferenceServiceTest.shouldIgnoreStoredCountryCodeWhenRequestCountryIsNotKr1건을 추가했다. 국가 결과 US와 전달값 true/false 보존 assertion을 소스로 검토했다. production은 변경하지 않았으며 기존 자동 테스트 미실행 지시에 따라 focused test와 ktlint는 실행하지 않아 Task 완료 처리는 보류했다. -
2026-09-17 리뷰 후속 문서 갱신: 사용자가 JP 매핑의
2L제거를 의도한 변경으로 확정했다. REV-002 검증 누락은P3-R1로 추가하고 REV-003 호출 수는 27곳으로 정정했다. 문서와 테스트 소스의 식별자·조건을 대조했으며 사용자 지시에 따라 자동 테스트 및 Gradle 명령은 실행하지 않았다. -
2026-09-17 Phase 1:
MemberContentPreferenceServiceTest실행 → RED에서Unresolved reference: isAdultVisibleForQuery확인, GREEN 후 25/25 통과. 검증 이유: 신규 판정 메서드가 Deprecated 함수와 동일한 결과를 내는지 확인. -
2026-09-17 Phase 2:
AudioContentServiceTest.kt:118컴파일 실패로 생성자 변경 영향을 확인한 뒤 mock 주입.src/test/kotlin/kr/co/vividnext/sodalive/content42/42 통과,AudioContentServiceTest16/16 통과. -
2026-09-17 Phase 3:
src/test/kotlin/kr/co/vividnext/sodalive/member/contentpreference36/36 통과(Deprecated 테스트 5건 삭제, 신규 3건 추가 반영). -
2026-09-17 빈 주입 회귀:
SpringBootIntegrationSampleTest1/1 통과로 12개 서비스에 추가된MemberContentPreferenceService주입이 애플리케이션 컨텍스트에서 정상 해석됨을 확인(순환 의존 없음). -
2026-09-17 스타일:
JAVA_HOME=<Android Studio JBR 21> ./gradlew ktlintMainSourceSetCheck ktlintTestSourceSetCheckBUILD SUCCESSFUL. JetBrains JBR 25는 Gradle 8.1.1과 비호환이어서 Java 21 JBR을 사용했다. -
2026-09-17 전체 회귀 생략:
./gradlew test전체 회귀는 실행하지 않았다. 근거는 변경이isAdult계산 위임 경로 치환으로 한정되고 공개 스키마/DB/보안 설정 변경이 없으며, 대신 변경 범위 focused test(content42,member/contentpreference36,AudioContentServiceTest16)와 컨텍스트 로딩 테스트, ktlint 전체 소스셋 검사를 실행했다.