From 9dfad913bcda360f165a614ca1fd0802a5c5e255 Mon Sep 17 00:00:00 2001 From: klaus Date: Sat, 28 Mar 2026 18:28:09 +0900 Subject: [PATCH] =?UTF-8?q?fix(member-info):=20=EA=B5=AC=EC=84=9C=EB=B2=84?= =?UTF-8?q?=20=EB=A9=A4=EB=B2=84=EC=A0=95=EB=B3=B4=20=EB=88=84=EB=9D=BD=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=20=ED=95=98=EC=9C=84=20=ED=98=B8=ED=99=98?= =?UTF-8?q?=EC=9D=84=20=EB=B3=B4=EC=9E=A5=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vividnext/sodalive/main/MainViewModel.kt | 17 ++++- .../notification/GetMemberInfoResponse.kt | 6 +- .../GetMemberInfoResponseCompatibilityTest.kt | 70 +++++++++++++++++++ docs/20260328_멤버정보응답하위호환수정.md | 49 +++++++++++++ 4 files changed, 136 insertions(+), 6 deletions(-) create mode 100644 app/src/test/java/kr/co/vividnext/sodalive/settings/notification/GetMemberInfoResponseCompatibilityTest.kt create mode 100644 docs/20260328_멤버정보응답하위호환수정.md diff --git a/app/src/main/java/kr/co/vividnext/sodalive/main/MainViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/main/MainViewModel.kt index 06cc7dad..52262bc5 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/main/MainViewModel.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/main/MainViewModel.kt @@ -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.base.BaseViewModel 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.EventRepository import kr.co.vividnext.sodalive.settings.notification.UpdateNotificationSettingRequest @@ -103,9 +104,19 @@ class MainViewModel( SharedPreferenceManager.point = data.point SharedPreferenceManager.role = data.role.name SharedPreferenceManager.isAuth = data.isAuth - SharedPreferenceManager.countryCode = data.countryCode.ifBlank { "KR" } - SharedPreferenceManager.isAdultContentVisible = data.isAdultContentVisible - SharedPreferenceManager.contentPreference = data.contentType.ordinal + + val localCountryCode = SharedPreferenceManager.countryCode.ifBlank { "KR" } + 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 = data.auditionNotice ?: false if ( diff --git a/app/src/main/java/kr/co/vividnext/sodalive/settings/notification/GetMemberInfoResponse.kt b/app/src/main/java/kr/co/vividnext/sodalive/settings/notification/GetMemberInfoResponse.kt index 00e91bac..1e93906a 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/settings/notification/GetMemberInfoResponse.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/settings/notification/GetMemberInfoResponse.kt @@ -21,11 +21,11 @@ data class GetMemberInfoResponse( @SerializedName("auditionNotice") val auditionNotice: Boolean?, @SerializedName("countryCode") - val countryCode: String, + val countryCode: String? = null, @SerializedName("isAdultContentVisible") - val isAdultContentVisible: Boolean, + val isAdultContentVisible: Boolean? = null, @SerializedName("contentType") - val contentType: ContentType + val contentType: ContentType? = null ) enum class MemberRole { diff --git a/app/src/test/java/kr/co/vividnext/sodalive/settings/notification/GetMemberInfoResponseCompatibilityTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/settings/notification/GetMemberInfoResponseCompatibilityTest.kt new file mode 100644 index 00000000..78dce271 --- /dev/null +++ b/app/src/test/java/kr/co/vividnext/sodalive/settings/notification/GetMemberInfoResponseCompatibilityTest.kt @@ -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) + } +} diff --git a/docs/20260328_멤버정보응답하위호환수정.md b/docs/20260328_멤버정보응답하위호환수정.md new file mode 100644 index 00000000..0275e247 --- /dev/null +++ b/docs/20260328_멤버정보응답하위호환수정.md @@ -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건(구서버 누락 필드 역직렬화/신규 필드 정상 매핑)이 모두 통과했다. + - 전체 단위 테스트와 디버그 빌드가 모두 성공했고, 마지막 재검증 명령에서도 성공을 재확인했다.