From 14ae1fe5914b240b902c43ede1a8621aa46f524e Mon Sep 17 00:00:00 2001 From: kyungmin Date: Sun, 30 Aug 2026 13:24:15 +0900 Subject: [PATCH 1/6] =?UTF-8?q?fix(timeletter):=20DTO=20=EA=B8=B0=EB=B3=B8?= =?UTF-8?q?=EA=B0=92=20=EC=A0=9C=EA=B1=B0=EB=A1=9C=20=ED=82=A4=20=EB=88=84?= =?UTF-8?q?=EB=9D=BD=20=EC=9D=80=ED=8F=90=20=EC=B0=A8=EB=8B=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TimeLetterDto/ReceivedTimeLetterDto의 = null·= emptyList() 기본값이 서버가 항상 보내는 키의 누락을 명시적 null·빈 값과 구분 못 하게 은폐하고 있었다. 라이브 OpenAPI(TimeLetterResponse/ReceivedTimeLetterResponse)와 대조해 계약대로 맞춘다: - TimeLetterResponse엔 deliveredAt이 아예 없어 TimeLetterDto에서 필드 자체를 제거하고, 매퍼에서 도메인 deliveredAt은 null로 둔다. - ReceivedTimeLetterResponse는 7개 필드 모두 항상 내려오므로 title/ blocks/sendAt/senderName/deliveredAt/createdAt의 기본값을 지운다. - isRead는 공개 전 상태에서 명시적 null이 올 수 있어 Boolean?로 바꾸고 매퍼에서 isRead ?: false로 도메인 기본값을 준다. - 목 데이터(TimeLetterMockFixtures)도 계약에 맞춰 동기화한다. 이미 (#790)을 겨냥해 작성돼 있던 TimeLetterDtoContractTest의 실패 4건을 모두 통과시킨다. Closes #790 Co-Authored-By: Claude Sonnet 5 --- .../data/network/TimeLetterMockFixtures.kt | 12 ++++++------ .../timeletter/data/dto/ReceivedTimeLetterDto.kt | 14 +++++++------- .../feature/timeletter/data/dto/TimeLetterDto.kt | 1 - .../data/mapper/ReceivedTimeLetterMapper.kt | 2 +- .../timeletter/data/mapper/TimeLetterMapper.kt | 2 +- 5 files changed, 15 insertions(+), 16 deletions(-) diff --git a/feature/timeletter/data/src/debug/kotlin/com/afternote/feature/timeletter/data/network/TimeLetterMockFixtures.kt b/feature/timeletter/data/src/debug/kotlin/com/afternote/feature/timeletter/data/network/TimeLetterMockFixtures.kt index cb55152d90..a91290572e 100644 --- a/feature/timeletter/data/src/debug/kotlin/com/afternote/feature/timeletter/data/network/TimeLetterMockFixtures.kt +++ b/feature/timeletter/data/src/debug/kotlin/com/afternote/feature/timeletter/data/network/TimeLetterMockFixtures.kt @@ -5,8 +5,8 @@ internal object TimeLetterMockFixtures { """ {"status":200,"code":0,"message":null,"data":{ "timeLetters":[ - {"id":1,"title":"미래의 나에게","sendAt":"2030-01-01T00:00:00","deliveredAt":null,"status":"SCHEDULED","blocks":[{"id":1,"blockType":"TEXT","blockOrder":1,"textContent":"지금 이 순간을 잊지 마."}],"receiverIds":[1]}, - {"id":2,"title":"10년 후의 나에게","sendAt":"2035-06-15T00:00:00","deliveredAt":null,"status":"SCHEDULED","blocks":[{"id":2,"blockType":"TEXT","blockOrder":1,"textContent":"지금보다 더 행복하길 바라."}],"receiverIds":[2]} + {"id":1,"title":"미래의 나에게","sendAt":"2030-01-01T00:00:00","status":"SCHEDULED","blocks":[{"id":1,"blockType":"TEXT","blockOrder":1,"textContent":"지금 이 순간을 잊지 마.","url":null,"mimeType":null}],"receiverIds":[1]}, + {"id":2,"title":"10년 후의 나에게","sendAt":"2035-06-15T00:00:00","status":"SCHEDULED","blocks":[{"id":2,"blockType":"TEXT","blockOrder":1,"textContent":"지금보다 더 행복하길 바라.","url":null,"mimeType":null}],"receiverIds":[2]} ], "totalCount":2 }} @@ -16,7 +16,7 @@ internal object TimeLetterMockFixtures { """ {"status":200,"code":0,"message":null,"data":{ "timeLetters":[ - {"id":3,"title":"임시저장 레터","sendAt":null,"deliveredAt":null,"status":"DRAFT","blocks":[],"receiverIds":[]} + {"id":3,"title":"임시저장 레터","sendAt":null,"status":"DRAFT","blocks":[],"receiverIds":[]} ], "totalCount":1 }} @@ -24,12 +24,12 @@ internal object TimeLetterMockFixtures { val MOCK_CREATE_JSON = """ - {"status":200,"code":0,"message":null,"data":{"id":99,"title":"새 타임레터","sendAt":null,"deliveredAt":null,"status":"DRAFT","blocks":[],"receiverIds":[]}} + {"status":200,"code":0,"message":null,"data":{"id":99,"title":"새 타임레터","sendAt":null,"status":"DRAFT","blocks":[],"receiverIds":[]}} """.trimIndent() val MOCK_UPDATE_JSON = """ - {"status":200,"code":0,"message":null,"data":{"id":99,"title":"수정된 타임레터","sendAt":"2030-01-01T00:00:00","deliveredAt":null,"status":"SCHEDULED","blocks":[{"id":1,"blockType":"TEXT","blockOrder":1,"textContent":"수정된 내용"}],"receiverIds":[1]}} + {"status":200,"code":0,"message":null,"data":{"id":99,"title":"수정된 타임레터","sendAt":"2030-01-01T00:00:00","status":"SCHEDULED","blocks":[{"id":1,"blockType":"TEXT","blockOrder":1,"textContent":"수정된 내용","url":null,"mimeType":null}],"receiverIds":[1]}} """.trimIndent() val MOCK_DELETE_JSON = @@ -39,6 +39,6 @@ internal object TimeLetterMockFixtures { fun detailJson(id: Long): String = """ - {"status":200,"code":0,"message":null,"data":{"id":$id,"title":"타임레터 #$id","sendAt":"2030-01-01T00:00:00","deliveredAt":null,"status":"SCHEDULED","blocks":[{"id":1,"blockType":"TEXT","blockOrder":1,"textContent":"내용입니다."}],"receiverIds":[1]}} + {"status":200,"code":0,"message":null,"data":{"id":$id,"title":"타임레터 #$id","sendAt":"2030-01-01T00:00:00","status":"SCHEDULED","blocks":[{"id":1,"blockType":"TEXT","blockOrder":1,"textContent":"내용입니다.","url":null,"mimeType":null}],"receiverIds":[1]}} """.trimIndent() } diff --git a/feature/timeletter/data/src/main/kotlin/com/afternote/feature/timeletter/data/dto/ReceivedTimeLetterDto.kt b/feature/timeletter/data/src/main/kotlin/com/afternote/feature/timeletter/data/dto/ReceivedTimeLetterDto.kt index a89774872c..d065e5cdb3 100644 --- a/feature/timeletter/data/src/main/kotlin/com/afternote/feature/timeletter/data/dto/ReceivedTimeLetterDto.kt +++ b/feature/timeletter/data/src/main/kotlin/com/afternote/feature/timeletter/data/dto/ReceivedTimeLetterDto.kt @@ -7,14 +7,14 @@ import kotlinx.serialization.Serializable data class ReceivedTimeLetterDto( @SerialName("id") val id: Long, @SerialName("timeLetterReceiverId") val timeLetterReceiverId: Long, - @SerialName("title") val title: String? = null, - @SerialName("blocks") val blocks: List = emptyList(), - @SerialName("sendAt") val sendAt: String? = null, + @SerialName("title") val title: String?, + @SerialName("blocks") val blocks: List, + @SerialName("sendAt") val sendAt: String?, @SerialName("status") val status: TimeLetterStatusDto, - @SerialName("senderName") val senderName: String? = null, - @SerialName("deliveredAt") val deliveredAt: String? = null, - @SerialName("createdAt") val createdAt: String? = null, - @SerialName("isRead") val isRead: Boolean, + @SerialName("senderName") val senderName: String?, + @SerialName("deliveredAt") val deliveredAt: String?, + @SerialName("createdAt") val createdAt: String?, + @SerialName("isRead") val isRead: Boolean?, ) @Serializable diff --git a/feature/timeletter/data/src/main/kotlin/com/afternote/feature/timeletter/data/dto/TimeLetterDto.kt b/feature/timeletter/data/src/main/kotlin/com/afternote/feature/timeletter/data/dto/TimeLetterDto.kt index 7cb0c18803..b04b444462 100644 --- a/feature/timeletter/data/src/main/kotlin/com/afternote/feature/timeletter/data/dto/TimeLetterDto.kt +++ b/feature/timeletter/data/src/main/kotlin/com/afternote/feature/timeletter/data/dto/TimeLetterDto.kt @@ -90,7 +90,6 @@ data class TimeLetterDto( @SerialName("id") val id: Long, @SerialName("title") val title: String?, @SerialName("sendAt") val sendAt: String?, - @SerialName("deliveredAt") val deliveredAt: String?, @SerialName("status") val status: TimeLetterStatusDto, @SerialName("blocks") val blocks: List, @SerialName("receiverIds") val receiverIds: List, diff --git a/feature/timeletter/data/src/main/kotlin/com/afternote/feature/timeletter/data/mapper/ReceivedTimeLetterMapper.kt b/feature/timeletter/data/src/main/kotlin/com/afternote/feature/timeletter/data/mapper/ReceivedTimeLetterMapper.kt index 7c5a756beb..3181420eba 100644 --- a/feature/timeletter/data/src/main/kotlin/com/afternote/feature/timeletter/data/mapper/ReceivedTimeLetterMapper.kt +++ b/feature/timeletter/data/src/main/kotlin/com/afternote/feature/timeletter/data/mapper/ReceivedTimeLetterMapper.kt @@ -16,7 +16,7 @@ fun ReceivedTimeLetterDto.toDomain(): ReceivedTimeLetter = senderName = senderName, deliveredAt = deliveredAt, createdAt = createdAt, - isRead = isRead, + isRead = isRead ?: false, ) fun ReceivedTimeLetterListDto.toDomain(): ReceivedTimeLetterList = diff --git a/feature/timeletter/data/src/main/kotlin/com/afternote/feature/timeletter/data/mapper/TimeLetterMapper.kt b/feature/timeletter/data/src/main/kotlin/com/afternote/feature/timeletter/data/mapper/TimeLetterMapper.kt index 24b6a37ce3..e7652c98b7 100644 --- a/feature/timeletter/data/src/main/kotlin/com/afternote/feature/timeletter/data/mapper/TimeLetterMapper.kt +++ b/feature/timeletter/data/src/main/kotlin/com/afternote/feature/timeletter/data/mapper/TimeLetterMapper.kt @@ -59,7 +59,7 @@ fun TimeLetterDto.toDomain(): TimeLetter = id = id, title = title, sendAt = sendAt, - deliveredAt = deliveredAt, + deliveredAt = null, status = status.toDomain(), blocks = blocks.map { it.toDomain() }, receiverIds = receiverIds, From adeb3eb31e7a39de86a26dd7ac308f9acaac595e Mon Sep 17 00:00:00 2001 From: kyungmin Date: Sun, 30 Aug 2026 15:14:19 +0900 Subject: [PATCH 2/6] =?UTF-8?q?docs(timeletter):=20DTO=20=EA=B8=B0?= =?UTF-8?q?=EB=B3=B8=EA=B0=92=20=EC=A0=9C=EA=B1=B0=20=EC=9D=B4=EC=9C=A0?= =?UTF-8?q?=EB=A5=BC=20=EC=A3=BC=EC=84=9D=EC=9C=BC=EB=A1=9C=20=EB=82=A8?= =?UTF-8?q?=EA=B8=B4=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit deliveredAt 필드 삭제, isRead nullable 변경처럼 코드만 봐선 의도가 안 드러나는 지점에 왜 그런지 짧게 남긴다. --- .../feature/timeletter/data/dto/ReceivedTimeLetterDto.kt | 1 + .../com/afternote/feature/timeletter/data/dto/TimeLetterDto.kt | 2 ++ .../feature/timeletter/data/mapper/ReceivedTimeLetterMapper.kt | 2 ++ .../feature/timeletter/data/mapper/TimeLetterMapper.kt | 2 ++ 4 files changed, 7 insertions(+) diff --git a/feature/timeletter/data/src/main/kotlin/com/afternote/feature/timeletter/data/dto/ReceivedTimeLetterDto.kt b/feature/timeletter/data/src/main/kotlin/com/afternote/feature/timeletter/data/dto/ReceivedTimeLetterDto.kt index d065e5cdb3..f206ad1e6f 100644 --- a/feature/timeletter/data/src/main/kotlin/com/afternote/feature/timeletter/data/dto/ReceivedTimeLetterDto.kt +++ b/feature/timeletter/data/src/main/kotlin/com/afternote/feature/timeletter/data/dto/ReceivedTimeLetterDto.kt @@ -14,6 +14,7 @@ data class ReceivedTimeLetterDto( @SerialName("senderName") val senderName: String?, @SerialName("deliveredAt") val deliveredAt: String?, @SerialName("createdAt") val createdAt: String?, + // 공개 전 미리보기에선 읽음 여부가 아직 정해지지 않아 서버가 명시적 null을 보낼 수 있다(#790). @SerialName("isRead") val isRead: Boolean?, ) diff --git a/feature/timeletter/data/src/main/kotlin/com/afternote/feature/timeletter/data/dto/TimeLetterDto.kt b/feature/timeletter/data/src/main/kotlin/com/afternote/feature/timeletter/data/dto/TimeLetterDto.kt index b04b444462..7264cad0e2 100644 --- a/feature/timeletter/data/src/main/kotlin/com/afternote/feature/timeletter/data/dto/TimeLetterDto.kt +++ b/feature/timeletter/data/src/main/kotlin/com/afternote/feature/timeletter/data/dto/TimeLetterDto.kt @@ -85,6 +85,8 @@ data class TimeLetterDeleteRequestDto( @SerialName("timeLetterIds") val timeLetterIds: List, ) +// deliveredAt 필드가 없다 — TimeLetterResponse(발신 응답) 계약엔 이 키가 아예 없다(#790). +// 배달 시각은 ReceivedTimeLetterDto 쪽(수신 응답)에만 존재한다. @Serializable data class TimeLetterDto( @SerialName("id") val id: Long, diff --git a/feature/timeletter/data/src/main/kotlin/com/afternote/feature/timeletter/data/mapper/ReceivedTimeLetterMapper.kt b/feature/timeletter/data/src/main/kotlin/com/afternote/feature/timeletter/data/mapper/ReceivedTimeLetterMapper.kt index 3181420eba..2a50034692 100644 --- a/feature/timeletter/data/src/main/kotlin/com/afternote/feature/timeletter/data/mapper/ReceivedTimeLetterMapper.kt +++ b/feature/timeletter/data/src/main/kotlin/com/afternote/feature/timeletter/data/mapper/ReceivedTimeLetterMapper.kt @@ -16,6 +16,8 @@ fun ReceivedTimeLetterDto.toDomain(): ReceivedTimeLetter = senderName = senderName, deliveredAt = deliveredAt, createdAt = createdAt, + // 공개 전엔 서버가 isRead를 null로 보낼 수 있다 — 도메인은 non-null 계약이라 미확정 상태를 + // 안전한 쪽(false=안 읽음)으로 접어 화면이 잘못 "읽음"으로 표시되지 않게 한다. isRead = isRead ?: false, ) diff --git a/feature/timeletter/data/src/main/kotlin/com/afternote/feature/timeletter/data/mapper/TimeLetterMapper.kt b/feature/timeletter/data/src/main/kotlin/com/afternote/feature/timeletter/data/mapper/TimeLetterMapper.kt index e7652c98b7..681198e58b 100644 --- a/feature/timeletter/data/src/main/kotlin/com/afternote/feature/timeletter/data/mapper/TimeLetterMapper.kt +++ b/feature/timeletter/data/src/main/kotlin/com/afternote/feature/timeletter/data/mapper/TimeLetterMapper.kt @@ -59,6 +59,8 @@ fun TimeLetterDto.toDomain(): TimeLetter = id = id, title = title, sendAt = sendAt, + // TimeLetterDto엔 deliveredAt이 없다(발신 응답 계약에 없는 필드, #790) — 도메인 필드는 + // ReceivedTimeLetter 쪽과 모델을 맞추기 위해 남겨두고 항상 null로 채운다. deliveredAt = null, status = status.toDomain(), blocks = blocks.map { it.toDomain() }, From 54ff5031918622586614101d16c055fe45665def Mon Sep 17 00:00:00 2001 From: kyungmin Date: Sun, 6 Sep 2026 02:04:34 +0900 Subject: [PATCH 3/6] =?UTF-8?q?fix(ci):=20DTO=20=EA=B8=B0=EB=B3=B8?= =?UTF-8?q?=EA=B0=92=20=EC=A0=9C=EA=B1=B0=20=EA=B5=AC=ED=98=84=EC=9D=B4=20?= =?UTF-8?q?=EB=81=9D=EB=82=AC=EC=9C=BC=EB=8B=88=20=EA=B8=B0=EB=8C=80=20?= =?UTF-8?q?=EC=8B=A4=ED=8C=A8=20=EB=93=B1=EB=A1=9D=EC=9D=84=20=EB=BA=80?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TimeLetterDtoContractTest 4건이 이제 실제로 통과해서, ci-expected-failures.json 이 "통과하는데 왜 기대 실패로 등록돼 있냐"고 게이트를 막고 있었다. #790/#873 구현이 이 PR로 끝났으니 등록을 제거한다. Co-Authored-By: Claude Sonnet 5 --- .github/ci-expected-failures.json | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/.github/ci-expected-failures.json b/.github/ci-expected-failures.json index ac499f97b3..172919cbb8 100644 --- a/.github/ci-expected-failures.json +++ b/.github/ci-expected-failures.json @@ -1,22 +1,6 @@ { "schemaVersion": 1, - "unitTests": [ - { - "task": ":feature:timeletter:data:testDebugUnitTest", - "className": "com.afternote.feature.timeletter.data.dto.TimeLetterDtoContractTest", - "tests": [ - "발신 응답의 명시적 null과 빈 배열은 정상 파싱된다", - "발신 응답에는 존재하지 않는 deliveredAt 없이 정상 파싱된다", - "수신 응답의 기본값 제거 대상 키가 빠지면 실패한다", - "수신 응답의 조건부 null과 공개 전 빈 blocks는 정상 파싱된다" - ], - "issues": [ - 790, - 873 - ], - "reason": "타임레터 DTO 누락 키 계약 게이트 — 구현 머지 전까지 의도된 실패" - } - ], + "unitTests": [], "androidTests": [], "screenshotModules": [ { From e95a771388cdeb7aaabeb6bd1f1b55d62344485f Mon Sep 17 00:00:00 2001 From: kyungmin Date: Sun, 6 Sep 2026 11:28:25 +0900 Subject: [PATCH 4/6] =?UTF-8?q?refactor(timeletter):=20=EB=B0=9C=EC=8B=A0?= =?UTF-8?q?=20TimeLetter=EC=9D=98=20=EC=A3=BD=EC=9D=80=20deliveredAt=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=EB=A5=BC=20=EC=A0=9C=EA=B1=B0=ED=95=9C?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TimeLetterDto엔 deliveredAt 키가 없어 항상 null로만 채워졌고, 유일하게 값이 채워지는 자리(RecipientTimeletterScreen.toTimeLetter())의 결과값도 소비하는 곳이 없었다. 도메인 모델이 "발신 편지에도 배달 시각이 있다"고 주장하지만 실제로는 아무 의미가 없는 필드라 걷어낸다. ReceivedTimeLetter.deliveredAt은 실제로 읽히므로 그대로 둔다. Co-Authored-By: Claude Sonnet 5 --- .../TimeLetterMindRecordCompletionAndroidTest.kt | 2 -- .../feature/timeletter/data/mapper/TimeLetterMapper.kt | 3 --- .../feature/timeletter/domain/model/TimeLetterModel.kt | 1 - .../domain/testing/FakeTimeLetterRepositoryTest.kt | 1 - .../timeletter/domain/testing/FakeTimeLetterRepository.kt | 1 - .../timeletter/presentation/component/DraftLetterItem.kt | 1 - .../timeletter/presentation/component/TimeLetterBlockItem.kt | 1 - .../timeletter/presentation/component/TimeLetterContent.kt | 2 -- .../timeletter/presentation/component/TimeletterListItem.kt | 1 - .../screen/recipient/RecipientTimeletterScreen.kt | 1 - .../presentation/screen/sender/DraftLetterScreen.kt | 5 ----- .../presentation/screen/sender/TimeLetterDetailScreen.kt | 1 - .../presentation/screen/sender/TimeletterScreen.kt | 2 -- .../screen/sender/TimeletterScreenScreenshotTest.kt | 2 -- .../feature/timeletter/presentation/TimeLetterFlowTest.kt | 1 - .../timeletter/presentation/TimeLetterLifecycleTest.kt | 1 - .../presentation/viewmodel/TimeletterViewModelTest.kt | 1 - 17 files changed, 27 deletions(-) diff --git a/app/src/androidTest/java/com/afternote/afternote_fe/TimeLetterMindRecordCompletionAndroidTest.kt b/app/src/androidTest/java/com/afternote/afternote_fe/TimeLetterMindRecordCompletionAndroidTest.kt index 42086d8f88..96c17a0faa 100644 --- a/app/src/androidTest/java/com/afternote/afternote_fe/TimeLetterMindRecordCompletionAndroidTest.kt +++ b/app/src/androidTest/java/com/afternote/afternote_fe/TimeLetterMindRecordCompletionAndroidTest.kt @@ -746,7 +746,6 @@ private fun completionCreatedLetter(call: FakeTimeLetterRepository.CreateCall): id = 901L, title = call.title, sendAt = call.sendAt, - deliveredAt = null, status = call.status, blocks = emptyList(), receiverIds = call.receiverIds, @@ -773,7 +772,6 @@ private fun completionDetailLetter(): TimeLetter = id = 509L, title = "상세 route 편지", sendAt = "2026-10-09T09:40:00", - deliveredAt = null, status = TimeLetterStatus.SCHEDULED, blocks = listOf( diff --git a/feature/timeletter/data/src/main/kotlin/com/afternote/feature/timeletter/data/mapper/TimeLetterMapper.kt b/feature/timeletter/data/src/main/kotlin/com/afternote/feature/timeletter/data/mapper/TimeLetterMapper.kt index 681198e58b..dba509e940 100644 --- a/feature/timeletter/data/src/main/kotlin/com/afternote/feature/timeletter/data/mapper/TimeLetterMapper.kt +++ b/feature/timeletter/data/src/main/kotlin/com/afternote/feature/timeletter/data/mapper/TimeLetterMapper.kt @@ -59,9 +59,6 @@ fun TimeLetterDto.toDomain(): TimeLetter = id = id, title = title, sendAt = sendAt, - // TimeLetterDto엔 deliveredAt이 없다(발신 응답 계약에 없는 필드, #790) — 도메인 필드는 - // ReceivedTimeLetter 쪽과 모델을 맞추기 위해 남겨두고 항상 null로 채운다. - deliveredAt = null, status = status.toDomain(), blocks = blocks.map { it.toDomain() }, receiverIds = receiverIds, diff --git a/feature/timeletter/domain/src/main/kotlin/com/afternote/feature/timeletter/domain/model/TimeLetterModel.kt b/feature/timeletter/domain/src/main/kotlin/com/afternote/feature/timeletter/domain/model/TimeLetterModel.kt index 271f1210e3..b3b302e92b 100644 --- a/feature/timeletter/domain/src/main/kotlin/com/afternote/feature/timeletter/domain/model/TimeLetterModel.kt +++ b/feature/timeletter/domain/src/main/kotlin/com/afternote/feature/timeletter/domain/model/TimeLetterModel.kt @@ -4,7 +4,6 @@ data class TimeLetter( val id: Long, val title: String?, val sendAt: String?, - val deliveredAt: String?, val status: TimeLetterStatus, val blocks: List, val receiverIds: List, diff --git a/feature/timeletter/domain/src/test/kotlin/com/afternote/feature/timeletter/domain/testing/FakeTimeLetterRepositoryTest.kt b/feature/timeletter/domain/src/test/kotlin/com/afternote/feature/timeletter/domain/testing/FakeTimeLetterRepositoryTest.kt index 935aa8c3f3..8de26bc64f 100644 --- a/feature/timeletter/domain/src/test/kotlin/com/afternote/feature/timeletter/domain/testing/FakeTimeLetterRepositoryTest.kt +++ b/feature/timeletter/domain/src/test/kotlin/com/afternote/feature/timeletter/domain/testing/FakeTimeLetterRepositoryTest.kt @@ -54,7 +54,6 @@ class FakeTimeLetterRepositoryTest { id = 1L, title = "편지", sendAt = null, - deliveredAt = null, status = status, blocks = emptyList(), receiverIds = listOf(7L), diff --git a/feature/timeletter/domain/src/testFixtures/kotlin/com/afternote/feature/timeletter/domain/testing/FakeTimeLetterRepository.kt b/feature/timeletter/domain/src/testFixtures/kotlin/com/afternote/feature/timeletter/domain/testing/FakeTimeLetterRepository.kt index 1055a4f0f0..5ef38106b4 100644 --- a/feature/timeletter/domain/src/testFixtures/kotlin/com/afternote/feature/timeletter/domain/testing/FakeTimeLetterRepository.kt +++ b/feature/timeletter/domain/src/testFixtures/kotlin/com/afternote/feature/timeletter/domain/testing/FakeTimeLetterRepository.kt @@ -115,7 +115,6 @@ class FakeTimeLetterRepository( id = id, title = title, sendAt = sendAt, - deliveredAt = null, status = status, blocks = blocks.mapIndexed { index, block -> block.toStoredBlock(id, index) }, receiverIds = receiverIds, diff --git a/feature/timeletter/presentation/src/main/kotlin/com/afternote/feature/timeletter/presentation/component/DraftLetterItem.kt b/feature/timeletter/presentation/src/main/kotlin/com/afternote/feature/timeletter/presentation/component/DraftLetterItem.kt index d59d2d113b..343436f0b1 100644 --- a/feature/timeletter/presentation/src/main/kotlin/com/afternote/feature/timeletter/presentation/component/DraftLetterItem.kt +++ b/feature/timeletter/presentation/src/main/kotlin/com/afternote/feature/timeletter/presentation/component/DraftLetterItem.kt @@ -60,7 +60,6 @@ private fun DraftLetterItemPrev() { id = 1L, title = "미래의 나에게", sendAt = "2026-12-31T00:00:00", - deliveredAt = null, status = TimeLetterStatus.DRAFT, blocks = emptyList(), receiverIds = listOf(1L), diff --git a/feature/timeletter/presentation/src/main/kotlin/com/afternote/feature/timeletter/presentation/component/TimeLetterBlockItem.kt b/feature/timeletter/presentation/src/main/kotlin/com/afternote/feature/timeletter/presentation/component/TimeLetterBlockItem.kt index 554fd5cd0c..a0d2781030 100644 --- a/feature/timeletter/presentation/src/main/kotlin/com/afternote/feature/timeletter/presentation/component/TimeLetterBlockItem.kt +++ b/feature/timeletter/presentation/src/main/kotlin/com/afternote/feature/timeletter/presentation/component/TimeLetterBlockItem.kt @@ -148,7 +148,6 @@ private fun TimeLetterBlockItemPrev() { id = 1L, title = "미래의 나에게", sendAt = "2026-12-31T00:00:00", - deliveredAt = null, status = TimeLetterStatus.SCHEDULED, blocks = emptyList(), receiverIds = listOf(1L), diff --git a/feature/timeletter/presentation/src/main/kotlin/com/afternote/feature/timeletter/presentation/component/TimeLetterContent.kt b/feature/timeletter/presentation/src/main/kotlin/com/afternote/feature/timeletter/presentation/component/TimeLetterContent.kt index 5d84fa96ab..7c87447179 100644 --- a/feature/timeletter/presentation/src/main/kotlin/com/afternote/feature/timeletter/presentation/component/TimeLetterContent.kt +++ b/feature/timeletter/presentation/src/main/kotlin/com/afternote/feature/timeletter/presentation/component/TimeLetterContent.kt @@ -151,7 +151,6 @@ private val previewLetters = id = 1L, title = "미래의 나에게", sendAt = "2026-12-31T00:00:00", - deliveredAt = null, status = TimeLetterStatus.SCHEDULED, blocks = emptyList(), receiverIds = listOf(1L), @@ -160,7 +159,6 @@ private val previewLetters = id = 2L, title = "10년 후의 나에게", sendAt = "2035-01-01T00:00:00", - deliveredAt = null, status = TimeLetterStatus.SCHEDULED, blocks = emptyList(), receiverIds = listOf(2L), diff --git a/feature/timeletter/presentation/src/main/kotlin/com/afternote/feature/timeletter/presentation/component/TimeletterListItem.kt b/feature/timeletter/presentation/src/main/kotlin/com/afternote/feature/timeletter/presentation/component/TimeletterListItem.kt index 475e8da43c..f0e0472bc6 100644 --- a/feature/timeletter/presentation/src/main/kotlin/com/afternote/feature/timeletter/presentation/component/TimeletterListItem.kt +++ b/feature/timeletter/presentation/src/main/kotlin/com/afternote/feature/timeletter/presentation/component/TimeletterListItem.kt @@ -141,7 +141,6 @@ private fun TimeLetterItemPreview() { id = 1L, title = "미래의 나에게", sendAt = "2026-12-31T00:00:00", - deliveredAt = null, status = TimeLetterStatus.SCHEDULED, blocks = emptyList(), receiverIds = listOf(1L), diff --git a/feature/timeletter/presentation/src/main/kotlin/com/afternote/feature/timeletter/presentation/screen/recipient/RecipientTimeletterScreen.kt b/feature/timeletter/presentation/src/main/kotlin/com/afternote/feature/timeletter/presentation/screen/recipient/RecipientTimeletterScreen.kt index 17520ffb1a..d972931609 100644 --- a/feature/timeletter/presentation/src/main/kotlin/com/afternote/feature/timeletter/presentation/screen/recipient/RecipientTimeletterScreen.kt +++ b/feature/timeletter/presentation/src/main/kotlin/com/afternote/feature/timeletter/presentation/screen/recipient/RecipientTimeletterScreen.kt @@ -228,7 +228,6 @@ private fun ReceivedTimeLetter.toTimeLetter() = id = id, title = title, sendAt = deliveredAt, - deliveredAt = deliveredAt, status = status, blocks = blocks, receiverIds = listOf(id), diff --git a/feature/timeletter/presentation/src/main/kotlin/com/afternote/feature/timeletter/presentation/screen/sender/DraftLetterScreen.kt b/feature/timeletter/presentation/src/main/kotlin/com/afternote/feature/timeletter/presentation/screen/sender/DraftLetterScreen.kt index c97ae8ac2f..9936f549c3 100644 --- a/feature/timeletter/presentation/src/main/kotlin/com/afternote/feature/timeletter/presentation/screen/sender/DraftLetterScreen.kt +++ b/feature/timeletter/presentation/src/main/kotlin/com/afternote/feature/timeletter/presentation/screen/sender/DraftLetterScreen.kt @@ -120,7 +120,6 @@ private fun DraftLetterScreenNormalPreview() { id = 1L, title = "첫 번째 레터", sendAt = "2026-12-25T00:00:00", - deliveredAt = null, status = TimeLetterStatus.DRAFT, blocks = emptyList(), receiverIds = listOf(1L), @@ -129,7 +128,6 @@ private fun DraftLetterScreenNormalPreview() { id = 2L, title = "두 번째 레터", sendAt = "2026-06-01T00:00:00", - deliveredAt = null, status = TimeLetterStatus.DRAFT, blocks = emptyList(), receiverIds = listOf(2L), @@ -138,7 +136,6 @@ private fun DraftLetterScreenNormalPreview() { id = 3L, title = null, sendAt = null, - deliveredAt = null, status = TimeLetterStatus.DRAFT, blocks = emptyList(), receiverIds = emptyList(), @@ -163,7 +160,6 @@ private fun DraftLetterScreenEditModePreview() { id = 1L, title = "첫 번째 레터", sendAt = "2026-12-25T00:00:00", - deliveredAt = null, status = TimeLetterStatus.DRAFT, blocks = emptyList(), receiverIds = listOf(1L), @@ -172,7 +168,6 @@ private fun DraftLetterScreenEditModePreview() { id = 2L, title = "두 번째 레터", sendAt = "2026-06-01T00:00:00", - deliveredAt = null, status = TimeLetterStatus.DRAFT, blocks = emptyList(), receiverIds = listOf(2L), diff --git a/feature/timeletter/presentation/src/main/kotlin/com/afternote/feature/timeletter/presentation/screen/sender/TimeLetterDetailScreen.kt b/feature/timeletter/presentation/src/main/kotlin/com/afternote/feature/timeletter/presentation/screen/sender/TimeLetterDetailScreen.kt index 6718339c00..459968a06b 100644 --- a/feature/timeletter/presentation/src/main/kotlin/com/afternote/feature/timeletter/presentation/screen/sender/TimeLetterDetailScreen.kt +++ b/feature/timeletter/presentation/src/main/kotlin/com/afternote/feature/timeletter/presentation/screen/sender/TimeLetterDetailScreen.kt @@ -284,7 +284,6 @@ private fun TimeLetterDetailScreenPrev() { id = 1L, title = "미래의 나에게", sendAt = "2026-12-31T00:00:00", - deliveredAt = null, status = TimeLetterStatus.SCHEDULED, blocks = listOf( diff --git a/feature/timeletter/presentation/src/main/kotlin/com/afternote/feature/timeletter/presentation/screen/sender/TimeletterScreen.kt b/feature/timeletter/presentation/src/main/kotlin/com/afternote/feature/timeletter/presentation/screen/sender/TimeletterScreen.kt index 84639183fa..faef622c1a 100644 --- a/feature/timeletter/presentation/src/main/kotlin/com/afternote/feature/timeletter/presentation/screen/sender/TimeletterScreen.kt +++ b/feature/timeletter/presentation/src/main/kotlin/com/afternote/feature/timeletter/presentation/screen/sender/TimeletterScreen.kt @@ -133,7 +133,6 @@ private val previewLetters = id = 1L, title = "미래의 나에게", sendAt = "2026-12-31T00:00:00", - deliveredAt = null, status = TimeLetterStatus.SCHEDULED, blocks = emptyList(), receiverIds = listOf(1L), @@ -142,7 +141,6 @@ private val previewLetters = id = 2L, title = "10년 후의 나에게", sendAt = "2035-01-01T00:00:00", - deliveredAt = null, status = TimeLetterStatus.SCHEDULED, blocks = emptyList(), receiverIds = listOf(2L), diff --git a/feature/timeletter/presentation/src/screenshotTest/kotlin/com/afternote/feature/timeletter/presentation/screen/sender/TimeletterScreenScreenshotTest.kt b/feature/timeletter/presentation/src/screenshotTest/kotlin/com/afternote/feature/timeletter/presentation/screen/sender/TimeletterScreenScreenshotTest.kt index 8cfb978a22..668bda0ad5 100644 --- a/feature/timeletter/presentation/src/screenshotTest/kotlin/com/afternote/feature/timeletter/presentation/screen/sender/TimeletterScreenScreenshotTest.kt +++ b/feature/timeletter/presentation/src/screenshotTest/kotlin/com/afternote/feature/timeletter/presentation/screen/sender/TimeletterScreenScreenshotTest.kt @@ -86,7 +86,6 @@ private val previewLetters = id = 1L, title = "미래의 나에게", sendAt = "2026-12-31T00:00:00", - deliveredAt = null, status = TimeLetterStatus.SCHEDULED, blocks = emptyList(), receiverIds = listOf(1L), @@ -95,7 +94,6 @@ private val previewLetters = id = 2L, title = "10년 후의 나에게", sendAt = "2035-01-01T00:00:00", - deliveredAt = null, status = TimeLetterStatus.SCHEDULED, blocks = emptyList(), receiverIds = listOf(2L), diff --git a/feature/timeletter/presentation/src/test/kotlin/com/afternote/feature/timeletter/presentation/TimeLetterFlowTest.kt b/feature/timeletter/presentation/src/test/kotlin/com/afternote/feature/timeletter/presentation/TimeLetterFlowTest.kt index 49c1844219..95f7d99b1c 100644 --- a/feature/timeletter/presentation/src/test/kotlin/com/afternote/feature/timeletter/presentation/TimeLetterFlowTest.kt +++ b/feature/timeletter/presentation/src/test/kotlin/com/afternote/feature/timeletter/presentation/TimeLetterFlowTest.kt @@ -73,7 +73,6 @@ class TimeLetterFlowTest { id = 1L, title = call.title, sendAt = call.sendAt, - deliveredAt = null, status = call.status, blocks = emptyList(), receiverIds = call.receiverIds, diff --git a/feature/timeletter/presentation/src/test/kotlin/com/afternote/feature/timeletter/presentation/TimeLetterLifecycleTest.kt b/feature/timeletter/presentation/src/test/kotlin/com/afternote/feature/timeletter/presentation/TimeLetterLifecycleTest.kt index 7454ecf752..d60ca98406 100644 --- a/feature/timeletter/presentation/src/test/kotlin/com/afternote/feature/timeletter/presentation/TimeLetterLifecycleTest.kt +++ b/feature/timeletter/presentation/src/test/kotlin/com/afternote/feature/timeletter/presentation/TimeLetterLifecycleTest.kt @@ -406,7 +406,6 @@ private fun timeLetter( id = id, title = title, sendAt = sendAt, - deliveredAt = null, status = status, blocks = blocks, receiverIds = receiverIds, diff --git a/feature/timeletter/presentation/src/test/kotlin/com/afternote/feature/timeletter/presentation/viewmodel/TimeletterViewModelTest.kt b/feature/timeletter/presentation/src/test/kotlin/com/afternote/feature/timeletter/presentation/viewmodel/TimeletterViewModelTest.kt index d1f54f36dc..c144434c64 100644 --- a/feature/timeletter/presentation/src/test/kotlin/com/afternote/feature/timeletter/presentation/viewmodel/TimeletterViewModelTest.kt +++ b/feature/timeletter/presentation/src/test/kotlin/com/afternote/feature/timeletter/presentation/viewmodel/TimeletterViewModelTest.kt @@ -137,7 +137,6 @@ class TimeletterViewModelTest { id = 1L, title = "제목", sendAt = null, - deliveredAt = null, status = TimeLetterStatus.SCHEDULED, blocks = emptyList(), receiverIds = emptyList(), From a66593850ce94a04c823e24b2bbab54158ff9ae1 Mon Sep 17 00:00:00 2001 From: kyungmin Date: Tue, 8 Sep 2026 19:09:17 +0900 Subject: [PATCH 5/6] =?UTF-8?q?fix(ci):=20=EA=B8=B0=EB=8C=80=20=EC=8B=A4?= =?UTF-8?q?=ED=8C=A8=20=EB=AA=A9=EB=A1=9D=200=EA=B1=B4=EC=9D=84=20schemaVe?= =?UTF-8?q?rsion=20=EA=B2=80=EC=A6=9D=EC=9C=BC=EB=A1=9C=20=EC=95=88?= =?UTF-8?q?=EC=A0=84=ED=95=98=EA=B2=8C=20=ED=97=88=EC=9A=A9=ED=95=9C?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TimeLetter DTO 계약 게이트(#790)가 이 PR로 닫히고 develop 은 이미 screenshotModules 게이트(#1360·#1361)를 닫아둬서, develop 을 머지하면 unitTests·screenshotModules 가 모두 빈 배열이 된다. 기존 "둘의 합이 0보다 커야 한다" 단언은 이 정당한 종료 상태와 파일이 사고로 `{}`가 되는 사고를 구분하지 못해 항상 걸린다. validateExpectedFailuresConfig 에 schemaVersion 강제를 추가해(다른 정책 파일들과 동일한 패턴) 사고 케이스만 정확히 잡고, 이제 불필요해진 비공허 단언은 제거한다. Co-Authored-By: Claude Sonnet 5 --- .github/scripts/ci-expected-failures.mjs | 5 ++ .github/scripts/ci-expected-failures.test.mjs | 59 +++++++++++++++---- 2 files changed, 54 insertions(+), 10 deletions(-) diff --git a/.github/scripts/ci-expected-failures.mjs b/.github/scripts/ci-expected-failures.mjs index 97ef17bfef..70ad349e76 100644 --- a/.github/scripts/ci-expected-failures.mjs +++ b/.github/scripts/ci-expected-failures.mjs @@ -34,6 +34,11 @@ export function validateExpectedFailuresConfig(config) { if (!config || typeof config !== "object" || Array.isArray(config)) { throw new Error("기대 실패 목록 최상위 값은 객체여야 합니다."); } + // 파일이 사고로 `{}`(또는 다른 스키마)로 대체돼도 이후 루프는 빈 배열을 순회할 뿐이라 + // 조용히 통과한다 — schemaVersion 을 강제해 "다 닫혀서 0건"과 "사고로 0건"을 구분한다. + if (config.schemaVersion !== 1) { + throw new Error(`지원하지 않는 기대 실패 목록 schemaVersion 입니다: ${config.schemaVersion}`); + } for (const [index, entry] of (config.unitTests ?? []).entries()) { const label = `unitTests[${index}]`; requireNonEmptyString(entry?.task, `${label}.task`); diff --git a/.github/scripts/ci-expected-failures.test.mjs b/.github/scripts/ci-expected-failures.test.mjs index 0dec8a3589..d7cced8530 100644 --- a/.github/scripts/ci-expected-failures.test.mjs +++ b/.github/scripts/ci-expected-failures.test.mjs @@ -81,29 +81,52 @@ test("저장소의 기대 실패 목록은 스키마를 지키고 추적 이슈 assert.ok(entry.issues.length > 0); assert.ok(entry.reason.length > 0); } - assert.ok((config.unitTests?.length ?? 0) + (config.screenshotModules?.length ?? 0) > 0); + // 목록이 전부 비는 것 자체는 정상 상태다(모든 기대 실패 게이트가 닫힘) — 파일이 사고로 + // 비워지는 사고는 schemaVersion 강제(아래 스키마 위반 테스트)가 잡는다. }); test("스키마 위반은 로드 시점에 거부된다", () => { assert.throws(() => validateExpectedFailuresConfig(null), /객체여야/); + assert.throws(() => validateExpectedFailuresConfig({}), /schemaVersion/); + assert.throws(() => validateExpectedFailuresConfig({ schemaVersion: 2 }), /schemaVersion/); assert.throws( - () => validateExpectedFailuresConfig({ unitTests: [{ ...sampleConfig.unitTests[0], task: ":feature:x:assemble" }] }), + () => + validateExpectedFailuresConfig({ + schemaVersion: 1, + unitTests: [{ ...sampleConfig.unitTests[0], task: ":feature:x:assemble" }], + }), /testDebugUnitTest/, ); assert.throws( - () => validateExpectedFailuresConfig({ unitTests: [{ ...sampleConfig.unitTests[0], tests: [] }] }), + () => + validateExpectedFailuresConfig({ + schemaVersion: 1, + unitTests: [{ ...sampleConfig.unitTests[0], tests: [] }], + }), /tests/, ); assert.throws( - () => validateExpectedFailuresConfig({ unitTests: [{ ...sampleConfig.unitTests[0], issues: [] }] }), + () => + validateExpectedFailuresConfig({ + schemaVersion: 1, + unitTests: [{ ...sampleConfig.unitTests[0], issues: [] }], + }), /issues/, ); assert.throws( - () => validateExpectedFailuresConfig({ screenshotModules: [{ module: "feature:setting", issues: [1], reason: "r" }] }), + () => + validateExpectedFailuresConfig({ + schemaVersion: 1, + screenshotModules: [{ module: "feature:setting", issues: [1], reason: "r" }], + }), /project path/, ); assert.throws( - () => validateExpectedFailuresConfig({ screenshotModules: [{ module: ":feature:setting:presentation", issues: [1], reason: " " }] }), + () => + validateExpectedFailuresConfig({ + schemaVersion: 1, + screenshotModules: [{ module: ":feature:setting:presentation", issues: [1], reason: " " }], + }), /reason/, ); }); @@ -243,19 +266,35 @@ test("기대 실패 목록·스크립트 변경은 full validation 을 강제한 test("androidTest 스키마: FQCN·테스트 목록·추적 이슈가 강제된다", () => { assert.throws( - () => validateExpectedFailuresConfig({ androidTests: [{ ...sampleConfig.androidTests[0], className: "not a class" }] }), + () => + validateExpectedFailuresConfig({ + schemaVersion: 1, + androidTests: [{ ...sampleConfig.androidTests[0], className: "not a class" }], + }), /FQCN/, ); assert.throws( - () => validateExpectedFailuresConfig({ androidTests: [{ ...sampleConfig.androidTests[0], tests: [] }] }), + () => + validateExpectedFailuresConfig({ + schemaVersion: 1, + androidTests: [{ ...sampleConfig.androidTests[0], tests: [] }], + }), /비어 있지 않은 배열/, ); assert.throws( - () => validateExpectedFailuresConfig({ androidTests: [{ ...sampleConfig.androidTests[0], issues: [] }] }), + () => + validateExpectedFailuresConfig({ + schemaVersion: 1, + androidTests: [{ ...sampleConfig.androidTests[0], issues: [] }], + }), /추적 이슈/, ); assert.throws( - () => validateExpectedFailuresConfig({ androidTests: [{ ...sampleConfig.androidTests[0], devices: [] }] }), + () => + validateExpectedFailuresConfig({ + schemaVersion: 1, + androidTests: [{ ...sampleConfig.androidTests[0], devices: [] }], + }), /비어 있지 않은 배열/, ); assert.doesNotThrow(() => validateExpectedFailuresConfig(sampleConfig)); From 4164c357386c544fd86b13866731480c1b9a7952 Mon Sep 17 00:00:00 2001 From: kyungmin Date: Tue, 8 Sep 2026 19:25:26 +0900 Subject: [PATCH 6/6] =?UTF-8?q?fix(timeletter):=20develop=20merge=20?= =?UTF-8?q?=EB=A1=9C=20=EB=90=98=EC=82=B4=EC=95=84=EB=82=9C=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EC=9D=98=20deliveredAt=20=EC=9D=B8=EC=9E=90?= =?UTF-8?q?=EB=A5=BC=20=EB=BA=80=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit develop 이 이 두 테스트를 건드리며 TimeLetter(deliveredAt = null) 구성을 다시 넣어 auto-merge 됐는데, 발신 TimeLetter 모델은 이 브랜치에서 deliveredAt 필드 자체가 사라져 컴파일이 깨졌다. Co-Authored-By: Claude Sonnet 5 --- .../presentation/viewmodel/DraftLetterViewModelTest.kt | 1 - .../presentation/viewmodel/TimeLetterWriteViewModelTest.kt | 1 - 2 files changed, 2 deletions(-) diff --git a/feature/timeletter/presentation/src/test/kotlin/com/afternote/feature/timeletter/presentation/viewmodel/DraftLetterViewModelTest.kt b/feature/timeletter/presentation/src/test/kotlin/com/afternote/feature/timeletter/presentation/viewmodel/DraftLetterViewModelTest.kt index 375d1c5cd5..43a2e86603 100644 --- a/feature/timeletter/presentation/src/test/kotlin/com/afternote/feature/timeletter/presentation/viewmodel/DraftLetterViewModelTest.kt +++ b/feature/timeletter/presentation/src/test/kotlin/com/afternote/feature/timeletter/presentation/viewmodel/DraftLetterViewModelTest.kt @@ -176,7 +176,6 @@ class DraftLetterViewModelTest { id = 1L, title = "draft", sendAt = null, - deliveredAt = null, status = TimeLetterStatus.DRAFT, blocks = emptyList(), receiverIds = listOf(1L), diff --git a/feature/timeletter/presentation/src/test/kotlin/com/afternote/feature/timeletter/presentation/viewmodel/TimeLetterWriteViewModelTest.kt b/feature/timeletter/presentation/src/test/kotlin/com/afternote/feature/timeletter/presentation/viewmodel/TimeLetterWriteViewModelTest.kt index d62a1254e9..c9d58600c2 100644 --- a/feature/timeletter/presentation/src/test/kotlin/com/afternote/feature/timeletter/presentation/viewmodel/TimeLetterWriteViewModelTest.kt +++ b/feature/timeletter/presentation/src/test/kotlin/com/afternote/feature/timeletter/presentation/viewmodel/TimeLetterWriteViewModelTest.kt @@ -213,7 +213,6 @@ class TimeLetterWriteViewModelTest { id = 10L, title = "existing title", sendAt = "2026-08-29T19:30:00", - deliveredAt = null, status = status, blocks = emptyList(), receiverIds = listOf(1L),