Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -461,12 +461,12 @@ private class FakeDailyQuestionRepository : DailyQuestionRepository {
draftOnly: Boolean?,
): Result<List<DailyQuestion>> = unexpected("DailyQuestionRepository.getList")

override suspend fun create(payload: DailyQuestionCreatePayload): Result<Unit> = unexpected("DailyQuestionRepository.create")
override suspend fun create(payload: DailyQuestionCreatePayload): Result<Long> = unexpected("DailyQuestionRepository.create")

override suspend fun update(
id: Long,
payload: DailyQuestionUpdatePayload,
): Result<Unit> = unexpected("DailyQuestionRepository.update")
): Result<Long> = unexpected("DailyQuestionRepository.update")

override suspend fun delete(id: Long): Result<Unit> = unexpected("DailyQuestionRepository.delete")
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.afternote.feature.mindrecord.data.api

import com.afternote.core.network.model.BaseResponse
import com.afternote.feature.mindrecord.data.dto.DailyQuestionAnswerResponseDto
import com.afternote.feature.mindrecord.data.dto.DailyQuestionCreateRequestDto
import com.afternote.feature.mindrecord.data.dto.DailyQuestionListItemDto
import com.afternote.feature.mindrecord.data.dto.DailyQuestionUpdateRequestDto
Expand All @@ -27,13 +28,13 @@ interface DailyQuestionApiService {
@POST("daily-questions")
suspend fun createDailyQuestion(
@Body request: DailyQuestionCreateRequestDto,
): BaseResponse<Unit>
): BaseResponse<DailyQuestionAnswerResponseDto>

@PATCH("daily-questions/{userDailyQuestionId}")
suspend fun updateDailyQuestion(
@Path("userDailyQuestionId") userDailyQuestionId: Long,
@Body request: DailyQuestionUpdateRequestDto,
): BaseResponse<Unit>
): BaseResponse<DailyQuestionAnswerResponseDto>

@DELETE("daily-questions/{userDailyQuestionId}")
suspend fun deleteDailyQuestion(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,19 @@ data class TodayDailyQuestionDto(
@JsonNames("draft")
val isDraft: Boolean,
)

/**
* 생성·수정 응답 (`DailyQuestionAnswerResponse` 실측, 2026-08-23).
*
* 종전에는 `BaseResponse<Unit>` 으로 받아 방금 만든 답변의 식별자를 버렸다. 그러면 저장
* 직후 그 레코드를 가리키려고 목록을 다시 조회해 추측으로 찾아야 한다 (#573).
*
* `userDailyQuestionId`("내 답변")와 요청의 `questionId`("질문")는 의미가 다르다.
* 관찰된 값이 같더라도 섞어 쓰면 안 된다.
*/
@Serializable
data class DailyQuestionAnswerResponseDto(
@SerialName("userDailyQuestionId") val userDailyQuestionId: Long,
@SerialName("content") val content: String = "",
@SerialName("isDraft") val isDraft: Boolean = false,
)
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,23 @@ class DailyQuestionRepositoryImpl
api.getTodayDailyQuestion().requireData().toDomain()
}

override suspend fun create(payload: DailyQuestionCreatePayload): Result<Unit> =
override suspend fun create(payload: DailyQuestionCreatePayload): Result<Long> =
runCatchingCancellable {
api.createDailyQuestion(payload.toRequest()).requireStatus()
api
.createDailyQuestion(payload.toRequest())
.requireData()
.userDailyQuestionId
}

override suspend fun update(
id: Long,
payload: DailyQuestionUpdatePayload,
): Result<Unit> =
): Result<Long> =
runCatchingCancellable {
api
.updateDailyQuestion(userDailyQuestionId = id, request = payload.toRequest())
.requireStatus()
.requireData()
.userDailyQuestionId
}

override suspend fun delete(id: Long): Result<Unit> =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package com.afternote.feature.mindrecord.data.dto

import com.afternote.core.network.model.BaseResponse
import kotlinx.serialization.json.Json
import org.junit.Assert.assertEquals
import org.junit.Test

/**
* 생성·수정 응답의 `userDailyQuestionId` 계약을 고정한다 (#573).
*
* 종전에는 `BaseResponse<Unit>` 으로 받아 이 값을 버렸다. 그러면 저장 직후 그 레코드를
* 가리키려고 목록을 다시 조회해 추측으로 골라야 한다.
*
* 아래 본문은 2026-08-23 `afternote.kro.kr` 실측 응답 그대로다.
*/
class DailyQuestionAnswerResponseContractTest {
private val json = Json { ignoreUnknownKeys = true }

@Test
fun `생성 응답에서 내 답변 식별자를 읽는다`() {
val body =
"""
{
"status": 200, "code": 200, "message": "성공",
"data": { "userDailyQuestionId": 19, "content": "응답 계약 실측",
"isDraft": true, "receivers": [] }
}
""".trimIndent()

val decoded =
json.decodeFromString(
BaseResponse.serializer(DailyQuestionAnswerResponseDto.serializer()),
body,
)

assertEquals(19L, decoded.data!!.userDailyQuestionId)
assertEquals("응답 계약 실측", decoded.data!!.content)
assertEquals(true, decoded.data!!.isDraft)
}

@Test
fun `수정 응답도 같은 스키마다`() {
val body =
"""
{
"status": 200, "code": 200, "message": "성공",
"data": { "userDailyQuestionId": 19, "content": "응답 계약 실측 수정",
"isDraft": true, "receivers": [] }
}
""".trimIndent()

val decoded =
json.decodeFromString(
BaseResponse.serializer(DailyQuestionAnswerResponseDto.serializer()),
body,
)

assertEquals(19L, decoded.data!!.userDailyQuestionId)
}

@Test
fun `식별자가 없으면 실패로 드러난다`() {
// 기본값을 두지 않는다 — 이 값이 없으면 저장 직후 흐름이 성립하지 않으므로
// 조용히 0 으로 메우지 않고 파싱에서 막는다.
val body =
"""
{
"status": 200, "code": 200, "message": "성공",
"data": { "content": "식별자 없음", "isDraft": true }
}
""".trimIndent()

val failed =
runCatching {
json.decodeFromString(
BaseResponse.serializer(DailyQuestionAnswerResponseDto.serializer()),
body,
)
}.isFailure

assertEquals(true, failed)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.afternote.feature.mindrecord.data.repositoryimpl
import com.afternote.core.network.model.BaseResponse
import com.afternote.feature.mindrecord.data.api.DailyQuestionApiService
import com.afternote.feature.mindrecord.data.api.DiaryApiService
import com.afternote.feature.mindrecord.data.dto.DailyQuestionAnswerResponseDto
import com.afternote.feature.mindrecord.data.dto.DailyQuestionCreateRequestDto
import com.afternote.feature.mindrecord.data.dto.DailyQuestionListItemDto
import com.afternote.feature.mindrecord.data.dto.DailyQuestionUpdateRequestDto
Expand Down Expand Up @@ -88,12 +89,13 @@ private class NeverReturningDailyQuestionApi : DailyQuestionApiService {

override suspend fun getTodayDailyQuestion(): BaseResponse<TodayDailyQuestionDto> = awaitCancellation()

override suspend fun createDailyQuestion(request: DailyQuestionCreateRequestDto): BaseResponse<Unit> = awaitCancellation()
override suspend fun createDailyQuestion(request: DailyQuestionCreateRequestDto): BaseResponse<DailyQuestionAnswerResponseDto> =
awaitCancellation()

override suspend fun updateDailyQuestion(
userDailyQuestionId: Long,
request: DailyQuestionUpdateRequestDto,
): BaseResponse<Unit> = awaitCancellation()
): BaseResponse<DailyQuestionAnswerResponseDto> = awaitCancellation()

override suspend fun deleteDailyQuestion(userDailyQuestionId: Long): BaseResponse<Unit> = awaitCancellation()
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,19 @@ interface DailyQuestionRepository {

suspend fun getToday(): Result<TodayDailyQuestion>

suspend fun create(payload: DailyQuestionCreatePayload): Result<Unit>
/**
* 답변 생성.
*
* 서버가 돌려주는 `userDailyQuestionId`("내 답변" 식별자)를 그대로 올린다 — 저장 직후
* 그 레코드를 가리켜야 하는 흐름(이어쓰기·수정·삭제)이 목록을 다시 뒤지지 않게 한다 (#573).
*/
suspend fun create(payload: DailyQuestionCreatePayload): Result<Long>

/** 답변 수정. 생성과 같은 스키마로 응답하므로 식별자를 그대로 올린다 (#573). */
suspend fun update(
id: Long,
payload: DailyQuestionUpdatePayload,
): Result<Unit>
): Result<Long>

suspend fun delete(id: Long): Result<Unit>
}
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,17 @@ class DailyQuestionWriteViewModel
)
}
result
.onSuccess {
_uiState.update { it.copy(submitState = SubmitState.Succeeded) }
.onSuccess { savedId ->
_uiState.update {
it.copy(
submitState = SubmitState.Succeeded,
// 서버가 돌려준 "내 답변" 식별자를 그대로 든다 (#573).
// 임시저장 뒤 이어서 저장하면 목록을 다시 뒤지지 않고 이 값으로 PATCH 한다 —
// 종전에는 응답을 버려 `resumeDraft()` 가 목록을 재조회해 첫 draft 를
// 추측으로 골랐다.
draftId = if (isDraft) savedId else null,
)
}
// 임시저장이 하나 늘었으니 툴바 숫자도 따라가야 한다 (#769).
if (isDraft) loadDraftCount()
}.onFailure { e ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,15 +181,16 @@ private class FakeDailyQuestionRepository(

override suspend fun getToday(): Result<TodayDailyQuestion> = onGetToday()

override suspend fun create(payload: DailyQuestionCreatePayload): Result<Unit> {
override suspend fun create(payload: DailyQuestionCreatePayload): Result<Long> {
createCallCount += 1
return Result.success(Unit)
// 서버가 돌려주는 "내 답변" 식별자 (#573).
return Result.success(CREATED_ANSWER_ID)
}

override suspend fun update(
id: Long,
payload: DailyQuestionUpdatePayload,
): Result<Unit> = error("update 는 이 시나리오에서 호출되면 안 됨")
): Result<Long> = error("update 는 이 시나리오에서 호출되면 안 됨")

override suspend fun delete(id: Long): Result<Unit> = error("delete 는 이 시나리오에서 호출되면 안 됨")
}
Expand All @@ -201,6 +202,8 @@ private object NoopPhotoUploadRepository : PhotoUploadRepository {
): Result<String> = error("upload 는 이 시나리오에서 호출되면 안 됨")
}

private const val CREATED_ANSWER_ID = 19L

/** 툴바 카운트는 이 테스트의 관심사가 아니다 — 0건으로 고정한다 (#769). */
private fun noopDraftLoader() =
MindRecordDraftLoader(
Expand Down Expand Up @@ -232,12 +235,12 @@ private object EmptyDraftDailyQuestionRepository : DailyQuestionRepository {

override suspend fun getToday(): Result<TodayDailyQuestion> = error("호출되면 안 됨")

override suspend fun create(payload: DailyQuestionCreatePayload): Result<Unit> = error("호출되면 안 됨")
override suspend fun create(payload: DailyQuestionCreatePayload): Result<Long> = error("호출되면 안 됨")

override suspend fun update(
id: Long,
payload: DailyQuestionUpdatePayload,
): Result<Unit> = error("호출되면 안 됨")
): Result<Long> = error("호출되면 안 됨")

override suspend fun delete(id: Long): Result<Unit> = error("호출되면 안 됨")
}
Original file line number Diff line number Diff line change
Expand Up @@ -136,12 +136,12 @@ private class FakeDailyQuestionDraftRepository(

override suspend fun getToday(): Result<TodayDailyQuestion> = error("호출되면 안 됨")

override suspend fun create(payload: DailyQuestionCreatePayload): Result<Unit> = error("호출되면 안 됨")
override suspend fun create(payload: DailyQuestionCreatePayload): Result<Long> = error("호출되면 안 됨")

override suspend fun update(
id: Long,
payload: DailyQuestionUpdatePayload,
): Result<Unit> = error("호출되면 안 됨")
): Result<Long> = error("호출되면 안 됨")

override suspend fun delete(id: Long): Result<Unit> {
if (removesOnDelete) remaining.removeAll { it.dailyQuestionId == id }
Expand Down
Loading