fix(member-info): 구서버 멤버정보 누락 필드 하위 호환을 보장한다
This commit is contained in:
@@ -13,6 +13,7 @@ import kr.co.vividnext.sodalive.audio_content.PlaybackTrackingData
|
|||||||
import kr.co.vividnext.sodalive.audio_content.PlaybackTrackingRepository
|
import kr.co.vividnext.sodalive.audio_content.PlaybackTrackingRepository
|
||||||
import kr.co.vividnext.sodalive.base.BaseViewModel
|
import kr.co.vividnext.sodalive.base.BaseViewModel
|
||||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||||
|
import kr.co.vividnext.sodalive.settings.ContentType
|
||||||
import kr.co.vividnext.sodalive.settings.event.EventItem
|
import kr.co.vividnext.sodalive.settings.event.EventItem
|
||||||
import kr.co.vividnext.sodalive.settings.event.EventRepository
|
import kr.co.vividnext.sodalive.settings.event.EventRepository
|
||||||
import kr.co.vividnext.sodalive.settings.notification.UpdateNotificationSettingRequest
|
import kr.co.vividnext.sodalive.settings.notification.UpdateNotificationSettingRequest
|
||||||
@@ -103,9 +104,19 @@ class MainViewModel(
|
|||||||
SharedPreferenceManager.point = data.point
|
SharedPreferenceManager.point = data.point
|
||||||
SharedPreferenceManager.role = data.role.name
|
SharedPreferenceManager.role = data.role.name
|
||||||
SharedPreferenceManager.isAuth = data.isAuth
|
SharedPreferenceManager.isAuth = data.isAuth
|
||||||
SharedPreferenceManager.countryCode = data.countryCode.ifBlank { "KR" }
|
|
||||||
SharedPreferenceManager.isAdultContentVisible = data.isAdultContentVisible
|
val localCountryCode = SharedPreferenceManager.countryCode.ifBlank { "KR" }
|
||||||
SharedPreferenceManager.contentPreference = data.contentType.ordinal
|
val resolvedCountryCode = data.countryCode?.ifBlank { "KR" } ?: localCountryCode
|
||||||
|
val resolvedIsAdultContentVisible =
|
||||||
|
data.isAdultContentVisible ?: SharedPreferenceManager.isAdultContentVisible
|
||||||
|
val resolvedContentType =
|
||||||
|
data.contentType
|
||||||
|
?: ContentType.entries.getOrNull(SharedPreferenceManager.contentPreference)
|
||||||
|
?: ContentType.ALL
|
||||||
|
|
||||||
|
SharedPreferenceManager.countryCode = resolvedCountryCode
|
||||||
|
SharedPreferenceManager.isAdultContentVisible = resolvedIsAdultContentVisible
|
||||||
|
SharedPreferenceManager.contentPreference = resolvedContentType.ordinal
|
||||||
SharedPreferenceManager.isAuditionNotification =
|
SharedPreferenceManager.isAuditionNotification =
|
||||||
data.auditionNotice ?: false
|
data.auditionNotice ?: false
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ data class GetMemberInfoResponse(
|
|||||||
@SerializedName("auditionNotice")
|
@SerializedName("auditionNotice")
|
||||||
val auditionNotice: Boolean?,
|
val auditionNotice: Boolean?,
|
||||||
@SerializedName("countryCode")
|
@SerializedName("countryCode")
|
||||||
val countryCode: String,
|
val countryCode: String? = null,
|
||||||
@SerializedName("isAdultContentVisible")
|
@SerializedName("isAdultContentVisible")
|
||||||
val isAdultContentVisible: Boolean,
|
val isAdultContentVisible: Boolean? = null,
|
||||||
@SerializedName("contentType")
|
@SerializedName("contentType")
|
||||||
val contentType: ContentType
|
val contentType: ContentType? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
enum class MemberRole {
|
enum class MemberRole {
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package kr.co.vividnext.sodalive.settings.notification
|
||||||
|
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import kr.co.vividnext.sodalive.settings.ContentType
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertNull
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class GetMemberInfoResponseCompatibilityTest {
|
||||||
|
|
||||||
|
private val gson = Gson()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `구서버 응답에서 신규 필드가 없어도 역직렬화된다`() {
|
||||||
|
val json = """
|
||||||
|
{
|
||||||
|
"can": 10,
|
||||||
|
"point": 120,
|
||||||
|
"isAuth": true,
|
||||||
|
"gender": "F",
|
||||||
|
"signupDate": "2024-01-01, 00:00:00",
|
||||||
|
"chargeCount": 3,
|
||||||
|
"role": "USER",
|
||||||
|
"messageNotice": true,
|
||||||
|
"followingChannelLiveNotice": false,
|
||||||
|
"followingChannelUploadContentNotice": true,
|
||||||
|
"auditionNotice": false
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
val response = gson.fromJson(json, GetMemberInfoResponse::class.java)
|
||||||
|
|
||||||
|
assertEquals(10, response.can)
|
||||||
|
assertEquals(120, response.point)
|
||||||
|
assertTrue(response.isAuth)
|
||||||
|
assertEquals(MemberRole.USER, response.role)
|
||||||
|
assertNull(response.countryCode)
|
||||||
|
assertNull(response.isAdultContentVisible)
|
||||||
|
assertNull(response.contentType)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `신규 필드가 있으면 정상 매핑된다`() {
|
||||||
|
val json = """
|
||||||
|
{
|
||||||
|
"can": 10,
|
||||||
|
"point": 120,
|
||||||
|
"isAuth": true,
|
||||||
|
"gender": "F",
|
||||||
|
"signupDate": "2024-01-01, 00:00:00",
|
||||||
|
"chargeCount": 3,
|
||||||
|
"role": "CREATOR",
|
||||||
|
"messageNotice": true,
|
||||||
|
"followingChannelLiveNotice": false,
|
||||||
|
"followingChannelUploadContentNotice": true,
|
||||||
|
"auditionNotice": false,
|
||||||
|
"countryCode": "US",
|
||||||
|
"isAdultContentVisible": true,
|
||||||
|
"contentType": "FEMALE"
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
val response = gson.fromJson(json, GetMemberInfoResponse::class.java)
|
||||||
|
|
||||||
|
assertEquals("US", response.countryCode)
|
||||||
|
assertEquals(true, response.isAdultContentVisible)
|
||||||
|
assertEquals(ContentType.FEMALE, response.contentType)
|
||||||
|
}
|
||||||
|
}
|
||||||
49
docs/20260328_멤버정보응답하위호환수정.md
Normal file
49
docs/20260328_멤버정보응답하위호환수정.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# 20260328_멤버정보응답하위호환수정.md
|
||||||
|
|
||||||
|
## 개요
|
||||||
|
- 이전 서버의 `/member/info` 응답에 `countryCode`, `isAdultContentVisible`, `contentType`가 없어도 신규 앱이 동일하게 동작하도록 하위 호환을 보장한다.
|
||||||
|
|
||||||
|
## 요구사항 해석(확정)
|
||||||
|
- `GetMemberInfoResponse`의 신규 필드 3개는 구서버 응답에서 누락될 수 있으므로 nullable로 처리한다.
|
||||||
|
- `MainViewModel.getMemberInfo()` 동기화 시 누락된 값은 로컬 저장값(없으면 안전 기본값)으로 대체한다.
|
||||||
|
|
||||||
|
## 완료 기준 (Acceptance Criteria)
|
||||||
|
- [x] AC1: 구서버 응답(JSON에 신규 3개 필드 누락) 역직렬화가 실패하지 않는다.
|
||||||
|
- [x] AC2: 구서버 응답 수신 시 `SharedPreferenceManager.countryCode/isAdultContentVisible/contentPreference`가 null로 오염되지 않고 기존 동작을 유지한다.
|
||||||
|
- [x] AC3: 관련 단위 테스트와 디버그 빌드가 성공한다.
|
||||||
|
|
||||||
|
## 구현 체크리스트
|
||||||
|
- [x] `app/src/main/java/kr/co/vividnext/sodalive/settings/notification/GetMemberInfoResponse.kt`
|
||||||
|
- 신규 필드(`countryCode`, `isAdultContentVisible`, `contentType`)를 nullable + default null로 변경
|
||||||
|
- [x] `app/src/main/java/kr/co/vividnext/sodalive/main/MainViewModel.kt`
|
||||||
|
- 멤버 정보 동기화 시 신규 필드 null-safe fallback 적용
|
||||||
|
- [x] `app/src/test/java/kr/co/vividnext/sodalive/settings/notification/GetMemberInfoResponseCompatibilityTest.kt`
|
||||||
|
- 구서버 응답 누락 필드 역직렬화 및 fallback 동작 검증 테스트 추가
|
||||||
|
- [x] 검증 실행
|
||||||
|
- `lsp_diagnostics`(수정 파일)
|
||||||
|
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.settings.notification.GetMemberInfoResponseCompatibilityTest"`
|
||||||
|
- `./gradlew :app:testDebugUnitTest`
|
||||||
|
- `./gradlew :app:assembleDebug`
|
||||||
|
|
||||||
|
## 검증 기록
|
||||||
|
- 기록 템플릿(후속 누적):
|
||||||
|
- YYYY-MM-DD
|
||||||
|
- 무엇/왜/어떻게:
|
||||||
|
- 실행 명령/도구:
|
||||||
|
- `명령 또는 사용 도구`
|
||||||
|
- 결과:
|
||||||
|
|
||||||
|
- 2026-03-28
|
||||||
|
- 무엇/왜/어떻게: 구서버(`/member/info`)에서 신규 필드 3종이 누락돼도 신규 앱이 동일 동작하도록 응답 모델 nullable 처리 + 멤버 정보 동기화 fallback 로직을 적용했고, 역직렬화 호환 테스트를 추가했다.
|
||||||
|
- 실행 명령/도구:
|
||||||
|
- `apply_patch(GetMemberInfoResponse.kt, MainViewModel.kt, GetMemberInfoResponseCompatibilityTest.kt, docs/20260328_멤버정보응답하위호환수정.md)`
|
||||||
|
- `lsp_diagnostics(GetMemberInfoResponse.kt, MainViewModel.kt, GetMemberInfoResponseCompatibilityTest.kt)`
|
||||||
|
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.settings.notification.GetMemberInfoResponseCompatibilityTest"`
|
||||||
|
- `./gradlew :app:testDebugUnitTest`
|
||||||
|
- `./gradlew :app:assembleDebug`
|
||||||
|
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.settings.notification.GetMemberInfoResponseCompatibilityTest" :app:assembleDebug`
|
||||||
|
- `read(app/build/test-results/testDebugUnitTest/TEST-kr.co.vividnext.sodalive.settings.notification.GetMemberInfoResponseCompatibilityTest.xml)`
|
||||||
|
- 결과:
|
||||||
|
- `lsp_diagnostics`는 `.kt` LSP 서버 미구성으로 실행 불가를 확인했다.
|
||||||
|
- 호환성 테스트 2건(구서버 누락 필드 역직렬화/신규 필드 정상 매핑)이 모두 통과했다.
|
||||||
|
- 전체 단위 테스트와 디버그 빌드가 모두 성공했고, 마지막 재검증 명령에서도 성공을 재확인했다.
|
||||||
Reference in New Issue
Block a user