Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,9 @@ object NetworkModule { // 이 모듈은 오브젝트 클래스 선언해서 딱
fun provideJson(): Json =
Json {
ignoreUnknownKeys = true // dto로 정의되지 않은 키가 있어도 크래시 나지 않게 함
coerceInputValues =
true // 응답 json으로 유효하지 않은 필드 값이 오더라도 대응되는 dto 변수의 디폴트 값이 있다면 그 값으로 치환
// coerceInputValues 는 두지 않는다. non-null 프로퍼티에 null 이 와도 기본값이 있으면 조용히
// 치환해 계약 위반을 삼킨다 — 이 팀 기준은 «제외하더라도 세어서 보고» 다(#1010).
// 응답 DTO 에 보정형 기본값을 새로 두는 것은 ResponseDtoContractKonsistTest 가 막는다.
// useAlternativeNames 는 기본 true — DTO 프로퍼티에 @JsonNames("대체키") 를 달면
// 서버가 같은 필드를 다른 키로 내려줘도(명세·실서버 불일치, 키 리네임 과도기) 한 프로퍼티로 파싱된다.
// 디코딩 전용이라 요청 직렬화 출력 키는 항상 @SerialName 을 따름. 사용 예: mindrecord DailyQuestionListItem
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ class AppVersionContractTest {
private val json =
Json {
ignoreUnknownKeys = true
coerceInputValues = true
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ class AuthDtoExpiresInContractTest {
private val json =
Json {
ignoreUnknownKeys = true
coerceInputValues = true
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ class AuthDtoSocialLoginContractTest {
private val json =
Json {
ignoreUnknownKeys = true
coerceInputValues = true
}

private fun payload(newUserField: String) =
Expand Down Expand Up @@ -64,8 +63,9 @@ class AuthDtoSocialLoginContractTest {
}

@Test
fun `isNewUser 가 null - coerceInputValues 로 흡수되지 않고 실패한다`() {
// 기본값이 없어야 coerce 대상에서 빠진다. 기본값을 되살리면 이 테스트가 먼저 깨진다.
fun `isNewUser 가 null 이면 흡수하지 않고 실패한다`() {
// 전역 coerceInputValues 를 걷어냈으므로(#1494) 기본값 유무와 무관하게 null 은 실패한다.
// 이 단언은 그 «null 을 조용히 삼키지 않는다» 를 고정한다.
assertThrows(SerializationException::class.java) {
json.decodeFromString<BaseResponse<LoginDto.SocialLoginDto>>(payload(""","isNewUser":null"""))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ class AuthFindContractTest {
private val json =
Json {
ignoreUnknownKeys = true
coerceInputValues = true
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,6 @@ class ApiWireContractSmokeTest {
private val wireJson =
Json {
ignoreUnknownKeys = true
coerceInputValues = true
}

private lateinit var mockServer: MockServerContainer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ class AfternoteResponseDtoContractTest {
private val json =
Json {
ignoreUnknownKeys = true
coerceInputValues = true
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ class LeaveMessageBlockContractTest {
private val json =
Json {
ignoreUnknownKeys = true
coerceInputValues = true
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ class DailyQuestionImageContractTest {
private val json =
Json {
ignoreUnknownKeys = true
coerceInputValues = true
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,12 @@ import org.junit.Test
* `date`(사용자가 고른 일기 날짜)와 `createdAt`(레코드 생성 시각)은 **별개 필드**다.
* 한 프로퍼티에 묶으면 서버의 키 순서에 따라 값이 뒤바뀌므로 분리 상태를 함께 고정한다.
*
* Json 설정은 `NetworkModule.provideJson` 과 동일 (ignoreUnknownKeys + coerceInputValues).
* Json 설정은 `NetworkModule.provideJson` 과 동일 (ignoreUnknownKeys).
*/
class DiaryListContractTest {
private val json =
Json {
ignoreUnknownKeys = true
coerceInputValues = true
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,12 @@ import org.junit.Test
* - 실제 의미가 있는 명시적 `null` → 정상 파싱 (값이 없는 상태)
* - 클라가 모르는 기분 **값** → 그 칸만 비움 (표기가 늘어난 것)
*
* Json 설정은 `NetworkModule.provideJson` 과 동일 (ignoreUnknownKeys + coerceInputValues).
* Json 설정은 `NetworkModule.provideJson` 과 동일 (ignoreUnknownKeys).
*/
class MindRecordDtoContractTest {
private val json =
Json {
ignoreUnknownKeys = true
coerceInputValues = true
}

// ---------- 데일리질문 목록 ----------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,13 @@ import org.junit.Test
* `isAnswered`/`isDraft` 는 사정이 다르다 — 종전 키(`answered`/`draft`)로 잡혀 있어
* `getToday()` 가 항상 실패했던 이력(#548)이 있어 과도기 대비로 구 키를 함께 받는다.
*
* Json 설정은 `NetworkModule.provideJson` 과 동일 (ignoreUnknownKeys + coerceInputValues).
* Json 설정은 `NetworkModule.provideJson` 과 동일 (ignoreUnknownKeys).
*/
@OptIn(ExperimentalSerializationApi::class)
class TodayDailyQuestionContractTest {
private val json =
Json {
ignoreUnknownKeys = true
coerceInputValues = true
}

private fun decode(body: String): TodayDailyQuestionDto =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,13 @@ import org.junit.Test
* 항상 `false` 가 됐고, 그래서 모든 일기가 캘린더 점과 기록일수에서 빠졌다 — 이 파일이
* 그 회귀를 막는다.
*
* Json 설정은 `NetworkModule.provideJson` 과 동일 (ignoreUnknownKeys + coerceInputValues).
* Json 설정은 `NetworkModule.provideJson` 과 동일 (ignoreUnknownKeys).
*/
@OptIn(ExperimentalSerializationApi::class)
class WeeklyReportContractTest {
private val json =
Json {
ignoreUnknownKeys = true
coerceInputValues = true
}

// 카운트·요약·목록 키는 모두 계약이라 기본값이 없다 (#789) — 주(week) 형태만 바꿔가며
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ class DiaryUpdateRequestTest {
private val json =
Json {
ignoreUnknownKeys = true
coerceInputValues = true
}

private fun bodyOf(receiverIds: List<Long>?) =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import org.junit.Test
* (`afternote.kro.kr/v3/api-docs`) 실측: `DeliveryVerificationRequestDto` 스키마에서 required 제거,
* 두 필드 모두 nullable, description "두 서류 중 하나 이상 필수".
*
* Json 설정은 `NetworkModule.provideJson` 과 동일 (ignoreUnknownKeys + coerceInputValues).
* Json 설정은 `NetworkModule.provideJson` 과 동일 (ignoreUnknownKeys).
* `encodeDefaults` 미설정(기본 false) + DTO 의 `= null` default 조합이라, 제출하지 않은 슬롯은
* 페이로드에서 **키 자체가 생략**된다 — null 을 명시 전송하지 않아도 서버가 미제출로 해석하는
* 형태를 이 테스트가 고정한다.
Expand All @@ -20,7 +20,6 @@ class DeliveryVerificationContractTest {
private val json =
Json {
ignoreUnknownKeys = true
coerceInputValues = true
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,13 @@ import org.junit.Test
*
* 이 가드가 지키는 것은 그 계약이 깨졌을 때의 **실패 방향**이다. 예전처럼 `String? = null` 이면
* 키가 빠져도 파싱이 조용히 통과하고 화면이 제목 없이 열려, 서버 결함이 «제목이 비어 보이는»
* 정상 화면으로 위장된다. Json 설정은 `NetworkModule.provideJson` 과 동일 —
* `coerceInputValues` 는 **기본값이 있어야** 동작하므로 이 필드를 구해 주지 않는다.
* 정상 화면으로 위장된다. Json 설정은 `NetworkModule.provideJson` 과 동일 — 전역
* `coerceInputValues` 를 걷어냈으므로(#1494) 기본값 유무와 무관하게 `null` 은 실패로 남는다.
*/
class ReceivedAfternoteDetailTitleContractTest {
private val json =
Json {
ignoreUnknownKeys = true
coerceInputValues = true
}

@Test
Expand All @@ -49,7 +48,7 @@ class ReceivedAfternoteDetailTitleContractTest {
}

@Test
fun `제목이 명시적 null 인 응답 - coerceInputValues 가 구해 주지 않는다`() {
fun `제목이 명시적 null 인 응답 - 기본값으로 접히지 않고 실패한다`() {
val payload =
"""{"status":200,"code":200,"message":"성공","data":{"id":1,"category":"SOCIAL","title":null}}"""

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ class ReceiverAfternoteResponseDtoContractTest {
private val json =
Json {
ignoreUnknownKeys = true
coerceInputValues = true
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import org.junit.Test
* (필드 구성·타입은 Swagger 와 BE 소스로 확정, 이슈 #407 본문).
* 프로덕션 경로(`ReceiverAuthRepositoryImpl`)와 동일하게 Json 디코드 → `requireData()`/`requireStatus()`
* → `toDomain()` 을 통과시킨다 — Json 설정은 `NetworkModule.provideJson` 과 동일
* (ignoreUnknownKeys + coerceInputValues).
* (ignoreUnknownKeys).
*
* verify 성공 응답을 **accessCode 동봉·제거 두 형태 모두** 가드하는 이유: 서버가 마스터 키와 동일한
* `accessCode` 를 응답에서 제거할 예정이라([ReceiverEmailAuthVerifyDto] 참고), 제거 배포가 언제 나가든
Expand All @@ -28,7 +28,6 @@ class ReceiverEmailAuthContractTest {
private val json =
Json {
ignoreUnknownKeys = true
coerceInputValues = true
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,12 @@ import org.junit.Test
* BE `LocalDateTime` 직렬화라 타임존 표기 없는 마이크로초 포함 형태로 온다 (BE 커밋 5af499c8).
* 프로덕션 경로(`ReceiverAuthRepositoryImpl.getSenderMessage`)와 동일하게
* Json 디코드 → `requireData()` → `toDomain()` 을 통과시킨다 — Json 설정은
* `NetworkModule.provideJson` 과 동일 (ignoreUnknownKeys + coerceInputValues).
* `NetworkModule.provideJson` 과 동일 (ignoreUnknownKeys).
*/
class ReceiverMessageContractTest {
private val json =
Json {
ignoreUnknownKeys = true
coerceInputValues = true
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ class ReceiverAfternotePagingSourceTest {
private val json =
Json {
ignoreUnknownKeys = true
coerceInputValues = true
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ class TimeLetterDtoContractTest {
private val json =
Json {
ignoreUnknownKeys = true
coerceInputValues = true
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ import org.junit.Test
* - `hasNext = false` → 페이지네이션이 조용히 멈춤
* - `diaries = emptyList()` → 목록 소실이 "기록 없음" 으로 보임
*
* `coerceInputValues` 는 **기본값이 있어야** 동작하므로, 기본값을 두는 순간 "키 누락" 과
* "서버가 보낸 잘못된 값" 이 같은 결과로 수렴한다. 둘은 성격이 다르다 —
* 키 누락은 계약이 바뀐 것이라 실패해야 드러나고, 값 확장은 관대해도 된다(전용 직렬화기).
* 기본값을 두는 순간 "키 누락" 과 "서버가 보낸 정상 빈 값" 이 같은 결과로 수렴한다. 둘은 성격이
* 다르다 — 키 누락은 계약이 바뀐 것이라 실패해야 드러나고, 값 확장은 관대해도 된다(전용 직렬화기).
*
* 전역 `coerceInputValues` 는 #1494 에서 걷어냈다. 그래서 이제 `null` 이 온 경우는 기본값 유무와
* 무관하게 실패하고, 이 가드가 막는 것은 **키 누락**이 기본값으로 성공해 버리는 쪽이다.
*
* ### 검사 대상
* `..dto..` 패키지의 `@Serializable data class` 중 **이름에 `Request` 가 없는 것**(= 응답).
Expand Down Expand Up @@ -101,34 +103,15 @@ class ResponseDtoContractKonsistTest {
const val REQUEST_DTO = "Request"

/**
* #789 — mindrecord (2건). #933 이 26건을 해소한 뒤 #573 이 머지되며 남은 잔여다.
* BE `DailyQuestionAnswerResponse` record 에 두 키가 다 있어 서버는 항상 보낸다.
* 남은 잔여는 timeletter 1건뿐이다 — #790 이 추적하고, `ci-expected-failures.json` 에
* 의도된 실패로 등록돼 있다. mindrecord 2건(#789)·afternote 11건(#957)은 해소돼 빠졌다.
*
* 서버는 이 키를 `null` 로 보내지 않는다(BE `ReceivedTimeLetterResponse` 가 미공개 구간에
* `List.of()` 를 채운다). 그래서 `coerceInputValues` 없이도 파싱이 깨지지 않는다 — 이
* 기본값이 실제로 가리는 것은 «키 자체가 빠지는» 경우다.
*/
private val MINDRECORD =
setOf(
"DailyQuestionAnswerResponseDto.content",
"DailyQuestionAnswerResponseDto.isDraft",
)

/** #957 — afternote (11건). `AfternoteDetailDto.createdAt` 은 #676 에서 해소(BE 응답에 없는 필드라 삭제). */
private val AFTERNOTE =
setOf(
"AfternoteDetailDto.updatedAt",
"AfternotePlaylistDto.songs",
"AfternotePageDto.content",
"AfternotePageDto.page",
"AfternotePageDto.size",
"AfternotePageDto.hasNext",
"ReceivedAfternoteListDto.afternotes",
"ReceivedAfternoteListDto.totalCount",
"ReceivedAfternoteDetailDto.processingMethods",
"ReceivedPlaylistDto.songs",
"MusicSearchResponseDto.tracks",
)

/** #790 — timeletter (1건) */
private val TIMELETTER = setOf("ReceivedTimeLetterDto.blocks")

val KNOWN_COERCING_DEFAULTS = MINDRECORD + AFTERNOTE + TIMELETTER
val KNOWN_COERCING_DEFAULTS = TIMELETTER
}
}
Loading