docs(v2): 공통 접근 가드 계획을 보강한다

This commit is contained in:
2026-07-14 20:44:06 +09:00
parent 9f70bcd3eb
commit 98db0b657e
3 changed files with 226 additions and 13 deletions

View File

@@ -46,20 +46,55 @@
- 검증 기록:
- 2026-07-14: 기존 호출부 조사 결과 Access, 오디오/시리즈 진입, Live 상세/입장, Creator Channel 진입, Community 상세, Chat/DM 진입을 초기 후보로 확정했다.
- [ ] **Task 0.3: 구현 직전 호출부 및 동작 차이 상세 목록 확정**
- [x] **Task 0.3: 구현 직전 호출부 및 동작 차이 상세 목록 확정**
- 파일:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/main/`
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/`
- `app/src/main/java/kr/co/vividnext/sodalive/v2/live/`
- `app/src/main/java/kr/co/vividnext/sodalive/mypage/MyPageFragment.kt` (호출부 확인만 수행하고 수정하지 않음)
- `docs/20260714_V2_공통_접근_가드와_도메인_액션/plan-task.md`
- 작업: 각 Action별 호출 함수, 접근 조건, Intent extra, Activity result, callback/refresh 차이를 기록한다.
- 검증 기준:
- 공통 계약으로 보존할 동작과 화면에 남길 후처리가 구분된다.
- Activity result 흐름은 adapter에서 만들 명시적 결과와 feature 단일 handler 후보가 기록된다.
- observer가 필요한 흐름은 Activity 밖의 소비자 수, lifecycle, replay 요구사항이 함께 기록된다.
- 확인 결과:
- Access: `HomeMainFragment`, `ContentMainFragment`, `ChatMainFragment`는 기존 `ensureMainV2NavigationAllowed`를 사용했고, `CreatorChannelActivity`, `HomeOnAirLiveActivity`는 자체 `ensureLoginAndAdultAuth`를 중복 구현하고 있었다.
- Access: `MainV2Activity`는 chat tab, audio notification route처럼 로그인 차단 UX가 필요한 navigation guard와 push token/member/deeplink 초기화처럼 부수효과 없이 허용 여부만 확인하는 흐름이 섞여 있었다. Phase 1에서는 두 용도를 `ensureV2Access(AccessRequirement.Login)``isV2AccessAllowed(AccessRequirement.Login)`로 분리해 기존 route 처리 순서와 invalid audio content id 반환 계약을 유지한다.
- Content: `ContentMainFragment`의 오디오/시리즈 상세 진입은 ID 검사 후 판단 가능한 성인 여부를 guard에 전달하며, banner/overview/전체 탭 이동은 login guard만 필요하다.
- Live: `HomeMainFragment``HomeOnAirLiveActivity`는 로그인 후 room detail을 조회하고, detail의 `isAdult`로 성인 Access를 다시 검사한다. Creator Channel 현재 라이브도 동일 입장 흐름을 사용한다.
- Creator/Community: `CreatorChannelActivity`는 Activity 자체 진입 로그인 guard와 Community/FanTalk/Live ActivityResult refresh를 소유한다. Phase 1에서는 result adapter를 만들지 않고 기존 launcher/refresh를 유지한다.
- Chat: title bar 및 Home following chat은 login guard 후 기존 AI/DM intent helper를 사용한다. DM room result adapter나 observer 변경은 Phase 5 대상으로 유지한다.
- Legacy MyPage: `MyPageFragment.showLoginActivity`는 실행 Activity가 `MainV2Activity`이면 공개 `showLoginActivity()`를 호출하므로 해당 호환 진입점을 유지해야 한다.
#### 상세 호출부 및 계약
| Action 후보 | 호출부 | Access 조건 | Intent extra / 입력 | Result / callback 차이 | Phase 1 처리 |
| --- | --- | --- | --- | --- | --- |
| Login/Adult Access | `HomeMainFragment.openWithLoginGuard`, `openFollowingTab`, title bar cash/search/bell, `ContentMainFragment` title/overview/banner/detail, `ChatMainFragment` title bar, `CreatorChannelActivity` community/FanTalk/live, `HomeOnAirLiveActivity` live entry | Login 또는 `forAdultContent(isAdult)` | 화면별 기존 Activity/Intent helper 그대로 사용 | 화면별 후처리는 호출부에 유지 | `ensureV2Access`로 전환하고 정책 판단만 공통화 |
| MainV2 chat tab | `MainV2Activity.selectChatTabWithLoginGuard`, `openChatTab`, initial chat filter | Login | `EXTRA_CHAT_FILTER`는 기존 consume/open 흐름 유지 | 탭 전환은 허용 시에만 `viewModel.clickTab(CHAT)` | `ensureV2Access(Login)` 사용, legacy `showLoginActivity()`는 guard 위임만 유지 |
| Legacy MyPage login bridge | `MyPageFragment.showLoginActivity` -> `MainV2Activity.showLoginActivity` | Login | `MainV2Activity.intent.extras` | 레거시 Activity 타입 분기와 로그인 화면 이동 계약 유지 | `MainV2Activity.showLoginActivity()`를 유지하고 `ensureV2Access(Login)`에 위임 |
| MainV2 deeplink/audio notification | `onCreate`, `onNewIntent`, `handleAudioNotificationRoute`, `executeDeeplink` | Login 허용 여부 확인 또는 Login guard | `EXTRA_DATA`, `deep_link`, `EXTRA_AUDIO_NOTIFICATION_ROUTE`, `EXTRA_AUDIO_CONTENT_ID` | audio route가 먼저 소비되고 invalid audio content id는 `false` 반환해 기존 후속 처리 가능 | route 순서와 반환 계약 보존, side-effect 없는 분기는 `isV2AccessAllowed(Login)` 사용 |
| Content 상세/전체/overview | `ContentMainFragment.openAudioContentDetail`, `openSeriesDetail`, `openContentOverview`, `openContentAllTab` | Login 또는 콘텐츠 성인 여부에 따른 AdultContent | `Constants.EXTRA_AUDIO_CONTENT_ID`, series id, `ContentOverviewType`, `MainContentAllType`, `ContentSort` | Activity result 없음, 전체 탭 이동은 `MainV2Activity.openContentAllTab` 호출 | Phase 1에서는 guard만 공통화, Content Action 계약은 Phase 2에서 구현 |
| Live entry | `HomeMainFragment.onLiveClick`, `HomeOnAirLiveActivity.enterLiveRoom`, `CreatorChannelActivity.onCreatorChannelCurrentLiveClicked` | Login 후 room detail의 `isAdult`로 AdultContent | room id/live id, room detail | 기존 `liveCoordinator.enterLiveRoom` 및 on-air 목록 refresh/상태는 호출부에 유지 | Phase 1에서는 중복 adult guard 제거, Live Action은 Phase 3 |
| Creator / Community | `CreatorChannelActivity.onCreatorChannelCommunityPostClicked`, Home creator/profile/community 진입 | Login | creator id, post id, existing `newIntent` | Creator Channel의 Home/Community/FanTalk refresh 조합과 launcher는 Activity 소유 유지 | Phase 1에서는 login guard만 공통화, result adapter는 Phase 4 |
| Chat / DM | `HomeMainFragment.openFollowingChat`, title bar search/cash, `MainV2Activity` chat route | Login | AI room id, DM room id, existing chat intent helpers | DM room result adapter/observer 없음, 기존 Activity 전환만 수행 | Phase 1에서는 login guard만 공통화, Chat Action은 Phase 5 |
#### observer 판단
- Phase 1 Access는 one-shot UI navigation/action 차단이므로 전역 observer나 EventBus를 추가하지 않는다.
- ActivityResult/refresh가 있는 Creator Channel Community/FanTalk는 기존 launcher와 Activity 내부 composition handler 후보를 Phase 4에서 정리한다.
- Live room entry는 detail 조회 후 접근 판단이 필요하므로 repository/result observer가 아니라 호출부 callback 조합을 유지하고 Phase 3에서 Action 계약을 분리한다.
#### 검증 기록
- 2026-07-14: `rg``explore` 조사로 Access, Content, Live, Creator, Community, Chat 후보의 호출부와 차이를 확정했다. Phase 1 구현 범위는 Access로 제한하고, Content 이후 도메인 Action은 카탈로그 상태로 유지한다.
- 2026-07-14: 리뷰 보완으로 `MainV2Activity`의 직접 `isLoggedIn()`/`showLoginActivity()` 소유 여부를 재확인했고, 로그인 UX 실행이 필요한 경로와 단순 허용 여부 확인 경로를 분리해 공통 Access 정책으로 이관하기로 확정했다.
- 2026-07-14: 리뷰 게이트 보완으로 Action 후보별 호출 함수, Access 조건, Intent 입력, Result/callback 차이, observer 판단을 표로 추가해 Task 0.3 검증 기준을 충족하도록 문서를 보강했다.
- 2026-07-14: Phase 0/1 리뷰 답변에 따라 레거시 `MyPageFragment`에서 `MainV2Activity.showLoginActivity()`를 호출하는 호환 경로를 상세 호출부 표에 추가했다. 레거시 파일은 수정하지 않았다.
### Phase 1: V2 공통 Access 정책과 실행기
- [ ] **Task 1.1: Access 판단 단위 테스트 작성**
- [x] **Task 1.1: Access 판단 단위 테스트 작성**
- 생성 예정 파일:
- `app/src/test/java/kr/co/vividnext/sodalive/v2/access/AccessPolicyTest.kt`
- 검증 대상:
@@ -74,8 +109,10 @@
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.access.AccessPolicyTest"
```
- 기대 결과: 구현 전 RED, 구현 후 GREEN.
- 검증 기록:
- 2026-07-14: 테스트를 먼저 추가한 뒤 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.access.AccessPolicyTest"`를 실행해 `AccessPolicy`, `AccessRequirement`, `AccessDecision`, `AccessState` 미정의 컴파일 오류로 RED를 확인했다.
- [ ] **Task 1.2: Access 입력·출력 계약과 순수 정책 구현**
- [x] **Task 1.2: Access 입력·출력 계약과 순수 정책 구현**
- 생성 예정 파일:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/access/AccessRequirement.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/v2/access/AccessDecision.kt`
@@ -83,8 +120,10 @@
- `app/src/main/java/kr/co/vividnext/sodalive/v2/access/AccessPolicy.kt`
- 작업: 로그인, 국가/본인인증, 성인 콘텐츠 표시 설정을 명시적 입력과 결과로 판단한다.
- 검증 기준: Android, Activity, Fragment, Dialog, Retrofit 의존 없이 Task 1.1 테스트가 통과한다.
- 검증 기록:
- 2026-07-14: `AccessRequirement`, `AccessDecision`, `AccessState`, `AccessPolicy`를 추가하고 같은 단일 테스트 명령을 재실행해 GREEN을 확인했다.
- [ ] **Task 1.3: Activity/Fragment 공용 Access 실행기 구현**
- [x] **Task 1.3: Activity/Fragment 공용 Access 실행기 구현**
- 생성 예정 파일:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/access/V2AccessGuard.kt`
- 수정 가능 파일:
@@ -97,10 +136,17 @@
- `Allowed`에서만 원래 action을 한 번 실행한다.
- 거부 결과에서 원래 action을 실행하지 않는다.
- 기존 Intent flag/extra와 인증 완료 후 흐름을 유지한다.
- 검증 기록:
- 2026-07-14: `V2AccessGuard`를 추가해 Activity/Fragment 공용 `ensureV2Access`를 제공하고, 기존 `LoginActivity` extra/flag, `V2ModalDialog`, `Auth.auth`, `MyPageViewModel.authVerify`, `SplashActivity`, `ContentSettingsActivity` extra 계약을 source test로 고정했다.
- 2026-07-14: `MainV2Activity`의 초기화/조건부 실행처럼 로그인 UX를 띄우지 않아야 하는 경로를 위해 같은 `AccessPolicy`와 `AccessStateProvider`를 사용하는 `isV2AccessAllowed`를 추가했다.
- 2026-07-14: 리뷰 보완으로 기존 `MainV2Activity.isLoggedIn()`의 `token.isNotBlank() && token.length > 10` 기준을 `AccessPolicy`에 보존하고, 10자/11자/공백 토큰 경계 테스트를 추가했다.
- 2026-07-14: 레거시 `MyPageFragment` 본문을 수정하지 않고 기존 `MainV2Activity.showLoginActivity()` 호출 계약을 유지하기 위해 `MainV2Activity`에 얇은 호환 메서드를 남기고 내부에서 `ensureV2Access(AccessRequirement.Login)`을 호출하게 했다.
- 2026-07-14: 사용자 확인 결과 현재 `token.length > 10` 기준은 실제 토큰 계약에서 동작 변경을 만들지 않으므로 추가 코드 변경 없이 유지한다.
- [ ] **Task 1.4: 기존 Access 구현을 공통 실행기로 전환**
- [x] **Task 1.4: 기존 Access 구현을 공통 실행기로 전환**
- 수정 예정 파일:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2LoginGuard.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2Activity.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragment.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/v2/main/chat/ChatMainFragment.kt`
@@ -113,17 +159,32 @@
- 검증 기준:
- `CreatorChannelActivity`, `HomeOnAirLiveActivity`에 별도 로그인/성인 판단 함수가 남지 않는다.
- 대상 클릭 handler에 직접 token 조건문이 남지 않는다.
- 검증 기록:
- 2026-07-14: `HomeMainFragment`, `ContentMainFragment`, `ChatMainFragment`, `CreatorChannelActivity`, `HomeOnAirLiveActivity` 호출부를 `ensureV2Access`와 `AccessRequirement`로 전환했다. `MainV2LoginGuard.kt`와 Activity별 `ensureLoginAndAdultAuth` 구현을 제거했다.
- 2026-07-14: `MainV2Activity`의 chat tab, audio notification route는 `ensureV2Access(AccessRequirement.Login)`로 전환하고, deeplink 실행 전 조건, push token update, member info 조회 등은 `isV2AccessAllowed(AccessRequirement.Login)`로 전환했다. Activity 내부 직접 `isLoggedIn()` 구현은 제거했고, 기존 외부 호출 호환용 `showLoginActivity()`는 공통 guard 위임만 수행한다.
- 2026-07-14: `rg -n "ensureMainV2NavigationAllowed|ensureLoginAndAdultAuth" app/src/main/java/kr/co/vividnext/sodalive/v2 app/src/test/java/kr/co/vividnext/sodalive/v2 --glob '*.kt'` 결과 production 잔존은 없고, source test의 제거 확인 assertion만 남았다.
- 2026-07-14: 대상 UI 파일의 `SharedPreferenceManager.(token|countryCode|isAuth|isAdultContentVisible)` 검색 결과 Creator Channel의 API auth header 생성 함수만 남았고, 접근 정책 직접 판단은 제거됐다.
- [ ] **Task 1.5: 기존 Access source test를 새 계약에 맞게 전환**
- [x] **Task 1.5: 기존 Access source test를 새 계약에 맞게 전환**
- 수정 예정 파일:
- `app/src/test/java/kr/co/vividnext/sodalive/v2/main/MainV2LoginGuardSourceTest.kt`
- `app/src/test/java/kr/co/vividnext/sodalive/v2/main/MainV2ActivitySourceTest.kt`
- `app/src/test/java/kr/co/vividnext/sodalive/v2/main/MainV2AudioNotificationRouteSourceTest.kt`
- `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLoginGuardSourceTest.kt`
- `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainTitleBarActionSourceTest.kt`
- `app/src/test/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragmentLoginGuardSourceTest.kt`
- `app/src/test/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragmentSourceTest.kt`
- `app/src/test/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainTitleBarActionSourceTest.kt`
- `app/src/test/java/kr/co/vividnext/sodalive/v2/main/chat/ChatMainTitleBarActionSourceTest.kt`
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt`
- 작업: 이전 함수명 고정 assertion을 새 공통 Access 사용 및 기존 UX 보존 검증으로 최소 수정한다.
- 검증 기준: 정책은 `AccessPolicyTest`, 호출 연결은 필요한 source test가 담당한다.
- 검증 기록:
- 2026-07-14: `MainV2LoginGuardSourceTest`, Home/Content/Chat title bar source test, Home/Content login guard source test, `CreatorChannelActivitySourceTest`의 Access 관련 assertion을 새 `V2AccessGuard` 계약 기준으로 전환했다.
- 2026-07-14: `MainV2ActivitySourceTest`와 `MainV2AudioNotificationRouteSourceTest`를 새 `ensureV2Access`/`isV2AccessAllowed` 경계 기준으로 갱신하고, `MainV2Activity`에 직접 `isLoggedIn()` 판단 함수가 남지 않음을 고정했다.
- 2026-07-14: `MainV2LoginGuardSourceTest`에 `MainV2Activity.showLoginActivity()`가 기존 호출명은 유지하되 `ensureV2Access(AccessRequirement.Login)`으로 위임하는지 확인하는 assertion을 추가했다.
- [ ] **Task 1.6: Access Phase 검증 및 문서 반영**
- [x] **Task 1.6: Access Phase 검증 및 문서 반영**
- 파일:
- `docs/agent-guides/build-test-style.md`
- `docs/20260714_V2_공통_접근_가드와_도메인_액션/plan-task.md`
@@ -132,10 +193,154 @@
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.access.*"
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.*"
./gradlew :app:compileDebugKotlin
./gradlew :app:ktlintCheck
git diff --check
./gradlew :app:ktlintCheck --rerun-tasks
git diff --check HEAD
```
- 기대 결과: 신규 Access 테스트와 영향받은 기존 테스트, 컴파일, ktlint, whitespace 검증 통과.
- 기대 결과: 신규 Access 테스트와 영향받은 기존 테스트, 컴파일, whitespace 검증 통과한다. `ktlintCheck`는 전체 기준선을 재실행하고, 저장소 기존 위반으로 실패하면 Phase 1 변경 파일에 신규 위반이 없음을 완료 gate로 확인한다.
- 검증 기록:
- 2026-07-14: Access 관련 좁은 회귀 명령 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.access.*" --tests "kr.co.vividnext.sodalive.v2.main.*" --tests "kr.co.vividnext.sodalive.v2.live.onair.*" --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest.현재 라이브 카드는 동일한 enterLiveRoom 플로우로 입장한다" --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest.커뮤니티 게시물 클릭 source는 상세 Activity newIntent로 진입하고 invalid postId를 무시한다" --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest.FanTalk fixed plus source는 Activity overlay와 write result refresh 계약을 연결한다" --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest.FanTalk item click source는 owner gate와 상세 ActivityResult refresh를 연결한다"`가 `BUILD SUCCESSFUL`로 통과했다.
- 2026-07-14: 더 넓은 관련 테스트 묶음은 497개 중 `CreatorChannelActivitySourceTest > 팬Talk 섹션은 Figma ListComment layout과 bind로 렌더링한다` 1개가 `@drawable/ic_chevron_down_white` source assertion으로 실패했다. Access 변경 파일과 무관한 기존 FanTalk layout assertion으로 확인해 별도 관찰로 남긴다.
- 2026-07-14: `./gradlew :app:compileDebugKotlin`은 `BUILD SUCCESSFUL`로 통과했다.
- 2026-07-14: `./gradlew :app:ktlintCheck`는 이번 변경과 무관한 `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeFollowingFragmentSourceTest.kt:118-120` 기존 max line length/argument wrapping 위반으로 실패했다. 해당 파일에는 이번 diff가 없음을 `git diff -- app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeFollowingFragmentSourceTest.kt`로 확인했다.
- 2026-07-14: `git diff --check`는 whitespace 오류 없이 통과했다.
- 2026-07-14: `MainV2LoginGuardSourceTest`의 신규 adapter assertion line length를 수정한 뒤 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.access.*" --tests "kr.co.vividnext.sodalive.v2.main.*" && ./gradlew :app:compileDebugKotlin && ./gradlew :app:ktlintCheck && git diff --check`를 재실행해 모두 `BUILD SUCCESSFUL` 또는 무출력 통과를 확인했다.
- 2026-07-14: 리뷰 게이트에서 `token.isNotBlank() && token.length > 10` 보존과 `mypage` adapter 제거가 차단 이슈로 확인되어, `AccessPolicy` 로그인 기준과 `MainV2Activity.showLoginActivity()` 호환 메서드 위임 방식으로 보완했다.
- 2026-07-14: 보완 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.access.*" --tests "kr.co.vividnext.sodalive.v2.main.*" && ./gradlew :app:compileDebugKotlin && ./gradlew :app:ktlintCheck && git diff --check`를 재실행해 모두 `BUILD SUCCESSFUL` 또는 무출력 통과를 확인했다.
- 2026-07-14: 추가 리뷰에서 11자 이상 공백 토큰이 허용되는 회귀를 확인해 `AccessPolicy`에 `isNotBlank()` 조건을 복원하고 공백 토큰 회귀 테스트와 PRD 문구를 보강했다.
- 2026-07-14: Kotlin LSP 진단은 환경에 `kotlin-lsp`가 설치되어 있지 않아 실행할 수 없었고, Gradle compile/test로 대체 검증했다.
- [x] **Task 1.7: 리뷰 보완으로 Access 실행 계약과 source test 범위 강화**
- 생성 파일:
- `app/src/test/java/kr/co/vividnext/sodalive/v2/access/V2AccessGuardTest.kt`
- 수정 파일:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/access/V2AccessGuard.kt`
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt`
- `docs/agent-guides/build-test-style.md`
- `docs/20260714_V2_공통_접근_가드와_도메인_액션/plan-task.md`
- 작업:
- `AccessDecision` 실행 분기를 Android 비의존 함수로 분리해 `Allowed` callback과 거부 UX callback 실행 횟수를 실제 입력·출력으로 검증한다.
- Creator Channel 커뮤니티 게시물 클릭 source test가 Activity 전체가 아닌 해당 handler 본문에서 guard와 launcher 순서를 확인하게 한다.
- Task 1.4와 Task 1.5의 실제 변경 파일 목록을 현재 diff와 일치시킨다.
- 검증 기준:
- `Allowed`는 원래 action을 정확히 한 번 실행하고 거부 UX callback은 실행하지 않는다.
- 모든 거부 결과는 원래 action을 실행하지 않고 대응 UX callback만 정확히 한 번 실행한다.
- 커뮤니티 게시물 handler 밖의 다른 로그인 guard로 source test가 통과하지 않는다.
- 실행 명령:
```bash
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.access.V2AccessGuardTest"
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest.커뮤니티 게시물 클릭 source는 상세 Activity newIntent로 진입하고 invalid postId를 무시한다"
```
- 기대 결과: 실행 계약 테스트는 구현 전 RED, 구현 후 GREEN이고 강화된 source test가 통과한다.
- 검증 기록:
- 2026-07-14: `V2AccessGuardTest`를 먼저 추가하고 단일 테스트를 실행해 `executeAccessDecision` 미정의 컴파일 오류로 RED를 확인했다.
- 2026-07-14: 기존 `V2AccessGuard.handleDecision`의 분기를 Android 비의존 `executeAccessDecision`으로 추출한 뒤 같은 단일 테스트가 `BUILD SUCCESSFUL`로 전환되는 것을 확인했다. 네 가지 `AccessDecision`별 반환값과 실행 callback이 정확히 하나인지 검증한다.
- 2026-07-14: Creator Channel 커뮤니티 handler source에서 guard 문자열을 일시 제거한 변이 입력으로 대상 테스트가 실패하는 것을 확인한 뒤, 실제 handler source 검사로 복원해 `BUILD SUCCESSFUL`을 확인했다.
- 2026-07-14: Access, Main, On-air 전체와 변경 영향 Creator Channel 4개 테스트를 묶어 재실행해 `BUILD SUCCESSFUL`을 확인했다.
- 2026-07-14: `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `./gradlew tasks --all`을 실행해 모두 `BUILD SUCCESSFUL`을 확인했다. ktlint에는 기존 `.editorconfig`의 `disabled_rules` 속성 사용에 대한 deprecation warning이 남아 있다.
- 2026-07-14: `git diff --check HEAD`를 실행해 staged와 unstaged를 합친 전체 변경에 whitespace 오류가 없음을 확인했다.
- 2026-07-14: 사용자 확인에 따라 현재 layout에 존재하지 않아 기존 테스트를 실패시키던 `ic_chevron_down_white` source assertion은 수동 삭제한 상태를 유지한다. Phase 1 운영 코드 변경과는 무관한 기존 테스트 정리다.
- [x] **Task 1.8: 코드 리뷰 후 ktlint 재현성 보완**
- 수정 파일:
- `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeFollowingFragmentSourceTest.kt`
- `docs/20260714_V2_공통_접근_가드와_도메인_액션/plan-task.md`
- 작업:
- 캐시를 배제한 `ktlintCheck`에서 재현된 기존 source test 3줄의 max line length와 argument wrapping 위반만 수정한다.
- 기존 인증 문자열과 테스트 의미는 변경하지 않는다.
- 검증 기준:
- `./gradlew :app:ktlintCheck --rerun-tasks`로 전체 기준선을 재검사하고, Phase 1 변경 파일에 신규 위반이 없음을 보고서에서 확인한다.
- 수정한 source test와 Access 관련 회귀 테스트가 통과한다.
- `git diff --check HEAD`가 통과한다.
- RED 검증 기록:
- 2026-07-14: `./gradlew :app:ktlintCheck --rerun-tasks`를 실행해 `HomeFollowingFragmentSourceTest.kt:118-120`의 max line length와 argument wrapping 위반으로 `BUILD FAILED`를 재현했다. 해당 파일은 기존 Phase 1 diff에 없었으므로, 이전 통과 기록은 Gradle up-to-date 캐시에 의해 실제 전체 린트를 재실행하지 않은 결과였다.
- GREEN 검증 기록:
- 2026-07-14: 세 문자열 assertion만 ktlint 규칙에 맞게 줄바꿈하고 `HomeFollowingFragmentSourceTest`를 `--rerun-tasks`로 실행해 `BUILD SUCCESSFUL`을 확인했다.
- 2026-07-14: 수정 후 `./gradlew :app:ktlintCheck --rerun-tasks`를 재실행한 결과 `ktlintTestSourceSetCheck` 보고서는 0건이 되었고 Phase 1 변경 파일에서 위반이 검출되지 않았다. 전체 명령은 Phase 1 범위 밖 파일의 기존 ktlint 위반으로 실패했으며, 레거시 운영 코드 위반과 함께 `app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ui/ContentCommentedAudioAdapter.kt:29`의 기존 V2 위반 1건도 포함되므로 전체 통과가 아닌 기준선 제약으로 기록한다.
- 2026-07-14: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.access.*" --tests "kr.co.vividnext.sodalive.v2.main.home.HomeFollowingFragmentSourceTest" --rerun-tasks`를 실행해 Access 회귀와 수정한 source test가 `BUILD SUCCESSFUL`로 통과함을 확인했다.
- 2026-07-14: `git diff --check HEAD`는 whitespace 오류 없이 통과했다.
- [x] **Task 1.9: 코드 리뷰 후 FanTalk source test와 ktlint gate 정정**
- 수정 파일:
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt`
- `docs/20260714_V2_공통_접근_가드와_도메인_액션/prd.md`
- `docs/20260714_V2_공통_접근_가드와_도메인_액션/plan-task.md`
- 작업:
- FanTalk write와 item click source test가 Activity 전체가 아닌 해당 함수 본문에서 login guard와 launcher 순서를 확인하게 한다.
- PRD의 ktlint 완료 기준을 현재 저장소 기준선에 맞춰 `ktlintCheck` 전체 재실행과 Phase 변경 파일 신규 위반 없음으로 명확히 한다.
- 기존 ktlint 실패 기록에서 V2 파일 위반 1건을 레거시 위반으로 잘못 분류한 표현을 정정한다.
- 검증 기준:
- FanTalk write/item click handler 밖의 다른 `ensureV2Access` 문자열로 source test가 통과하지 않는다.
- Phase 1 완료 판정은 전체 ktlint 통과로 오인되지 않고 기준선 제약과 변경 파일 신규 위반 여부를 함께 기록한다.
- 관련 source test와 whitespace 검증이 통과한다.
- 검증 기록:
- 2026-07-14: `openFanTalkWrite`와 `onCreatorChannelFanTalkItemClicked` 본문만 `sourceSection`으로 추출해 guard, launcher, 실행 순서를 검증하도록 source test를 강화했다.
- 2026-07-14: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest.FanTalk fixed plus source는 Activity overlay와 write result refresh 계약을 연결한다" --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest.FanTalk item click source는 owner gate와 상세 ActivityResult refresh를 연결한다" --rerun-tasks`를 실행해 `BUILD SUCCESSFUL`을 확인했다.
- 2026-07-14: `./gradlew :app:ktlintCheck --rerun-tasks`는 `ktlintMainSourceSetCheck` 기준선 위반으로 실패했다. 보고서에는 기존 main source 위반 551줄이 남아 있고, 그중 `app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ui/ContentCommentedAudioAdapter.kt:29`의 V2 max line length 위반 1건이 포함된다.
- 2026-07-14: `./gradlew :app:ktlintTestSourceSetCheck --rerun-tasks`는 `BUILD SUCCESSFUL`로 통과해 이번에 수정한 source test 파일에는 신규 ktlint 위반이 없음을 확인했다.
- 2026-07-14: `git diff --check HEAD`는 whitespace 오류 없이 통과했다.
- 2026-07-14: 리뷰어 게이트에서 P2/P3 반영, assertion 약화 없음, 문서 모순 없음에 대해 조건부 없는 `PASS`를 확인했다.
- [x] **Task 1.10: Phase 0/1 재리뷰 결과 반영**
- 수정 파일:
- `app/src/test/java/kr/co/vividnext/sodalive/v2/main/MainV2LoginGuardSourceTest.kt`
- `docs/20260714_V2_공통_접근_가드와_도메인_액션/plan-task.md`
- 작업:
- `V2AccessGuard.handleDecision`이 각 거부 결과를 로그인, 본인인증, 콘텐츠 설정 UX callback에 연결하는 실제 결선을 source assertion으로 고정한다.
- Task 1.6의 ktlint 기대 결과를 PRD의 최종 gate와 일치시킨다.
- 검증 기준:
- 로그인, 본인인증, 콘텐츠 설정 callback 결선이 서로 바뀌면 source test가 실패한다.
- `ktlintCheck`는 전체 기준선을 재실행하고, 기존 위반으로 실패하면 Phase 1 변경 파일의 신규 위반이 없음을 확인한다.
- 관련 회귀 테스트, 컴파일, test source ktlint, whitespace 검증이 통과한다.
- 실행 명령:
```bash
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.MainV2LoginGuardSourceTest" --rerun-tasks
./gradlew :app:compileDebugKotlin
./gradlew :app:ktlintCheck --rerun-tasks
./gradlew :app:ktlintTestSourceSetCheck --rerun-tasks
git diff --check HEAD
```
- 기대 결과: 결선 source test, 컴파일, test source ktlint, whitespace 검증이 통과하고 전체 ktlint 기준선 제약이 기존 기록과 일치한다.
- 검증 기록:
- 2026-07-14: `handleDecision` 본문만 추출해 로그인, 본인인증, 콘텐츠 설정 `AccessDecision`이 각각 `startLogin`, `showAuthDialog`, `startContentSettings` callback에 연결되는지 검증하도록 source test를 강화했다.
- 2026-07-14: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.MainV2LoginGuardSourceTest" --rerun-tasks`를 실행해 테스트 1건이 실패 없이 통과하고 `BUILD SUCCESSFUL`임을 확인했다.
- 2026-07-14: 위 회귀 테스트에서 production/test source를 새로 컴파일했으며, 별도 `./gradlew :app:compileDebugKotlin`도 `BUILD SUCCESSFUL`로 통과했다.
- 2026-07-14: `./gradlew :app:ktlintCheck --rerun-tasks`는 기존 main source 기준선 위반 551줄로 실패했다. Phase 1 변경 main source의 신규 위반은 0건이고, V2 위반 1건은 변경하지 않은 `ContentCommentedAudioAdapter.kt:29`의 기존 max line length 위반이다.
- 2026-07-14: `./gradlew :app:ktlintTestSourceSetCheck --rerun-tasks`는 `BUILD SUCCESSFUL`로 통과했으며 test source 보고서 위반은 0건이다.
- 2026-07-14: `./gradlew tasks --all`은 `BUILD SUCCESSFUL`로 통과해 문서 변경 후 Gradle task 구성을 확인했다.
- 2026-07-14: `git diff --check HEAD`는 whitespace 오류 없이 통과했다.
- [x] **Task 1.11: Phase 0/1 리뷰의 Activity별 Access 결선 테스트 보강**
- 수정 파일:
- `app/src/test/java/kr/co/vividnext/sodalive/v2/live/onair/HomeOnAirLiveEntryPolicyTest.kt`
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt`
- `docs/20260714_V2_공통_접근_가드와_도메인_액션/plan-task.md`
- 작업:
- On-air 라이브 입장이 로그인 가드, room detail 조회, 성인 콘텐츠 가드, 기존 입장 함수 순서로 실행되는지 함수 본문 source로 검증한다.
- Creator Channel 화면 진입이 유효한 creator ID 확인 후 로그인 가드를 거치며, 거부 시 화면 조립 전에 종료되는지 검증한다.
- Creator Channel AI 채팅이 성인 콘텐츠 가드 통과 후에만 채팅방 생성을 요청하는지 클릭 listener 범위에서 검증한다.
- 검증 기준:
- 각 함수 밖의 다른 `ensureV2Access` 문자열로 테스트가 통과하지 않는다.
- 각 가드 문자열을 제거한 변이 source에서는 대응 테스트가 실패한다.
- production 코드는 변경하지 않고 관련 source test와 기존 Access 회귀 테스트가 통과한다.
- 실행 명령:
```bash
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.live.onair.HomeOnAirLiveEntryPolicyTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest" --rerun-tasks
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.access.*" --tests "kr.co.vividnext.sodalive.v2.main.*" --tests "kr.co.vividnext.sodalive.v2.live.onair.*" --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest" --rerun-tasks
./gradlew :app:compileDebugKotlin
./gradlew :app:ktlintTestSourceSetCheck --rerun-tasks
git diff --check HEAD
```
- 기대 결과: 변이 source RED 확인 후 실제 source 기준 대상 테스트와 관련 회귀 테스트, 컴파일, test source ktlint, whitespace 검증이 통과한다.
- RED 검증 기록:
- 2026-07-14: 세 결선 테스트에서 각 대상 가드 문자열을 제거한 변이 source를 사용해 실행한 결과, On-air 로그인/성인 결선 1건과 Creator Channel 진입/AI 채팅 결선 2건이 각각 실패하고 `BUILD FAILED`가 발생함을 확인했다.
- GREEN 검증 기록:
- 2026-07-14: 변이를 제거하고 실제 production source를 대상으로 신규 결선 테스트 3건을 `--rerun-tasks`로 실행해 모두 통과하고 `BUILD SUCCESSFUL`임을 확인했다.
- 2026-07-14: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.access.*" --tests "kr.co.vividnext.sodalive.v2.main.*" --tests "kr.co.vividnext.sodalive.v2.live.onair.*" --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest" --rerun-tasks`를 실행해 관련 회귀 테스트 503건이 실패, 오류, 스킵 없이 통과했다.
- 2026-07-14: `./gradlew :app:compileDebugKotlin`은 `BUILD SUCCESSFUL`로 통과했다.
- 2026-07-14: `./gradlew :app:ktlintTestSourceSetCheck --rerun-tasks`는 `BUILD SUCCESSFUL`로 통과해 test source 신규 위반이 없음을 확인했다.
- 2026-07-14: `./gradlew tasks --all`은 `BUILD SUCCESSFUL`로 통과해 문서 변경 후 Gradle task 구성을 확인했다.
- 2026-07-14: `git diff --check HEAD`는 whitespace 오류 없이 통과했다.
- 2026-07-14: Task 1.11에서는 production 코드를 추가 변경하지 않고 기존 source test 클래스 두 곳과 계획 문서만 보강했다.
### Phase 2: Content 공통 Action
- [ ] **Task 2.1: 오디오/시리즈 진입 호출부와 계약 확정**
@@ -363,3 +568,4 @@
- 2026-07-14: 문서 작성 후 `./gradlew tasks --all`을 실행해 계획에 사용하는 `:app:testDebugUnitTest`, `:app:compileDebugKotlin`, `:app:ktlintCheck` 작업이 존재함을 확인했으며 명령은 `BUILD SUCCESSFUL`로 종료되었다. `git diff --check`도 whitespace 오류 없이 통과했다.
- 2026-07-14: Community 업로드 후 Home/Community 탭 갱신 설계를 구체화했다. 초기 구현은 전역 observer 대신 `CommunityChange` 결과와 Creator Channel의 단일 `handleCommunityChange` composition handler를 사용하며, 레거시 `ActivityResult`는 `v2` adapter에서 변경 결과로 변환한다.
- 2026-07-14: 사용자 확인에 따라 Activity result가 필요한 모든 도메인 Action에 공통 결과 전달 원칙을 확장했다. 기본 방식은 `ActivityResult -> 명시적 도메인 결과 -> feature 단일 handler`이며, Action/Repository 직접 결과도 동일 handler로 합류한다. `SharedFlow`/observer는 Activity 밖의 독립 소비자와 lifecycle/replay 요구가 확인될 때만 도입한다.
- 2026-07-14: Phase 0/1 리뷰 답변을 반영해 Legacy MyPage 호출부와 로그인 token 정책 확인, FanTalk source assertion 수동 삭제 사유를 기록했다. 문서 변경 후 `./gradlew tasks --all`은 `BUILD SUCCESSFUL`, `git diff --check HEAD`는 무출력으로 통과했다.

View File

@@ -71,7 +71,7 @@
#### Requirements
- Access 요구사항은 최소 `Login`, `AdultContent`를 명시적으로 표현한다.
- `AdultContent`는 로그인 확인 후 국가/본인인증, 성인 콘텐츠 표시 설정 순서로 평가한다.
- 로그인 여부는 기존과 동일하게 `SharedPreferenceManager.token.isBlank()` 기준을 사용한다.
- 로그인 여부는 기존 `MainV2Activity.isLoggedIn()`과 동일하게 `SharedPreferenceManager.token.isNotBlank() && SharedPreferenceManager.token.length > 10` 기준을 사용한다.
- `countryCode.ifBlank { "KR" } == "KR"`이면 국내 사용자로 판단한다.
- 국내 사용자이고 `SharedPreferenceManager.isAuth == false`이면 본인인증 필요 결과를 반환한다.
- `SharedPreferenceManager.isAdultContentVisible == false`이면 성인 콘텐츠 설정 필요 결과를 반환한다.
@@ -223,7 +223,7 @@ Access 판단 결과를 기존 로그인, 본인인증, 설정 이동 UX에 연
- Community 변경처럼 호출 화면이 갱신 대상을 결정하는 경우 도메인 결과와 feature invalidation handler를 분리하고, observer는 Activity 밖의 다수 소비자에게 전파할 요구가 생길 때만 도입한다.
- 각 도메인 Phase에서 Activity result, callback, refresh 후처리를 조사하고 단일 handler가 필요한 흐름만 최소 범위로 통합한다.
- 기존 source test가 이전 함수명을 고정한 경우 새 공개 계약을 검증하도록 최소 수정한다.
- 각 Phase는 관련 단위 테스트, `:app:compileDebugKotlin`, `:app:ktlintCheck`, `git diff --check`를 통과해야 한다.
- 각 Phase는 관련 단위 테스트, `:app:compileDebugKotlin`, `git diff --check`를 통과해야 한다. `:app:ktlintCheck`는 전체 기준선을 재실행하고, 저장소 기존 위반으로 실패하는 경우 Phase 변경 파일에 신규 ktlint 위반이 없음을 완료 gate로 삼아 실패 위치를 `plan-task.md`에 기록한다.
- 테스트 클래스가 추가되면 `docs/agent-guides/build-test-style.md`에 단일 실행 예시를 추가한다.
- 작업 범위가 변경되면 구현 전에 `plan-task.md`를 먼저 갱신한다.
@@ -232,7 +232,7 @@ Access 판단 결과를 기존 로그인, 본인인증, 설정 이동 UX에 연
## 10. Metrics
- 로그인과 성인 접근 정책을 독립적으로 판단하는 구현이 `v2` 내 한 곳에만 존재한다.
- `CreatorChannelActivity``HomeOnAirLiveActivity`에 화면 전용 `ensureLoginAndAdultAuth` 구현이 남지 않는다.
- 대상 UI 호출부에서 `SharedPreferenceManager.token.isBlank()`로 개별 행동 접근을 판단하지 않는다.
- 대상 UI 호출부에서 `SharedPreferenceManager.token`로 개별 행동 접근을 판단하지 않는다.
- 오디오, 시리즈, 라이브, 크리에이터, 커뮤니티, 채팅의 합의된 호출부가 각각 단일 Action 진입점을 사용한다.
- Creator Channel의 Community 작성, 수정, 삭제, 고정 변경 성공 경로가 명시적 `CommunityChange`를 거쳐 하나의 composition handler에서 projection을 갱신한다.
- 공통 Action 공개 계약이 feature 전용 UI model 또는 DTO에 의존하지 않는다.

View File

@@ -78,6 +78,13 @@ DM 채팅 테스트 예시:
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.*"
```
Access Phase 테스트 예시:
```bash
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.access.*"
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.access.V2AccessGuardTest"
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.*"
```
참고:
- 신규 Kotlin 테스트 메서드는 backtick 함수명을 사용하고, 테스트명은 한글 문장으로 작성한다.
- 기존 영어 테스트명을 수정하지 않는 최소 변경 상황을 제외하고, 새 테스트명에 영어 문장을 사용하지 않는다.