diff --git a/feature/afternote/data/src/main/kotlin/com/afternote/feature/afternote/data/mapper/AfternoteDetailMapper.kt b/feature/afternote/data/src/main/kotlin/com/afternote/feature/afternote/data/mapper/AfternoteDetailMapper.kt index eb59d71d93..ab5da675b8 100644 --- a/feature/afternote/data/src/main/kotlin/com/afternote/feature/afternote/data/mapper/AfternoteDetailMapper.kt +++ b/feature/afternote/data/src/main/kotlin/com/afternote/feature/afternote/data/mapper/AfternoteDetailMapper.kt @@ -59,7 +59,7 @@ private fun AfternoteDetailDto.toDetailContent(type: AfternoteType): DetailConte } AfternoteType.MEMORIAL -> { - requireNotNull(memorial) { "playlist is required for MEMORIAL detail" }.toMemorialContent() + memorial?.toMemorialContent() ?: EMPTY_MEMORIAL_CONTENT } AfternoteType.ESTATE -> { @@ -67,6 +67,23 @@ private fun AfternoteDetailDto.toDetailContent(type: AfternoteType): DetailConte } } +/** + * 곡도 미디어도 아직 안 담은 추억 노트. + * + * 서버 상세는 임시저장을 걸러 내지 않고(`AfternoteService.getDetailAfternote` 는 소유자만 본다) 응답 형태로만 + * 가른다(`AfternotedetailResponse.of` 의 `Draft` 분기). 임시저장(`isDraft=true`)은 카테고리별 필수값 검증을 + * 건너뛰므로(`AfternoteValidator`) 곡을 한 곡도 안 담은 PLAYLIST 는 `playlist` 자체가 안 온다 — #808 이 + * `draftOnly=true` 목록과 이어쓰기를 붙이면 그런 임시저장이 이 분기로 들어오고, 여기서 던지면 이어쓰기 이전에 + * 조회부터 실패한다. 발행 상세는 서버가 응답을 조립하면서 최소 1곡을 강제하므로 + * (`AfternotedetailResponse.requirePublishedPlaylist`) 이 폴백이 발행분을 빈 추모 노트로 위장할 일은 없다 — + * 계정 정보와 같은 이유로, 빠진 값은 던지지 않고 빈 값으로 낮춘다. + */ +private val EMPTY_MEMORIAL_CONTENT = + DetailContent.Memorial( + songs = emptyList(), + media = MemorialMedia(photoUrl = null, videoUrl = null, thumbnailUrl = null), + ) + // 던지면 그 상세가 영영 안 열리므로 빠진 값은 빈 문자열로 낮춘다. private fun AfternoteDetailDto.toPublishedCredentials() = DetailCredentials( diff --git a/feature/afternote/data/src/test/java/com/afternote/feature/afternote/data/mapper/AfternoteDetailMapperTest.kt b/feature/afternote/data/src/test/java/com/afternote/feature/afternote/data/mapper/AfternoteDetailMapperTest.kt index 3ae50d2e54..5ed6bc9e4c 100644 --- a/feature/afternote/data/src/test/java/com/afternote/feature/afternote/data/mapper/AfternoteDetailMapperTest.kt +++ b/feature/afternote/data/src/test/java/com/afternote/feature/afternote/data/mapper/AfternoteDetailMapperTest.kt @@ -20,7 +20,7 @@ import org.junit.Test * [AfternoteDetailDto.toDomain] 회귀 가드 (작성자 상세). * 핵심 경계: 공통 필드와 타입별 [DetailContent] 분리, 부분·부재 credentials 의 빈 값 강등, * receivers null→emptyList, receiver 필드 null→"", processingMethods null→emptyList, - * memorialVideo null→video/thumbnail null. + * memorialVideo null→video/thumbnail null, playlist 부재(곡 없는 임시저장)→곡 0개·미디어 없음. */ class AfternoteDetailMapperTest { @Test @@ -271,20 +271,22 @@ class AfternoteDetailMapperTest { } @Test - fun `toDomain - playlist 타입에 playlist가 없으면 오류`() { - val exception = - assertThrows(IllegalArgumentException::class.java) { - AfternoteDetailDto( - isDraft = false, - receivers = emptyList(), - afternoteId = 1L, - category = "PLAYLIST", - title = "t", - updatedAt = UPDATED_AT, - ).toDomain() - } + fun `toDomain - 곡을 안 담은 임시저장 playlist 는 playlist 부재를 곡 0개·미디어 없음으로 낮춘다`() { + val result = + AfternoteDetailDto( + isDraft = true, + receivers = emptyList(), + afternoteId = 1L, + category = "PLAYLIST", + title = "t", + updatedAt = UPDATED_AT, + ).toDomain() - assertEquals("playlist is required for MEMORIAL detail", exception.message) + val memorial = result.content as DetailContent.Memorial + assertTrue(memorial.songs.isEmpty()) + assertNull(memorial.media.photoUrl) + assertNull(memorial.media.videoUrl) + assertNull(memorial.media.thumbnailUrl) } @Test