Repository navigation
refactor(core): 전역 coerceInputValues 를 걷어낸다 (#1494) - #1495
Conversation
non-null 프로퍼티에 null 이 와도 기본값이 있으면 조용히 치환해, 계약 위반이 텔레메트리 없이 삼켜졌다. 이 팀 기준은 «제외하더라도 세어서 보고» 다(#1010). 응답 DTO 중 실제 coercion 대상은 ReceivedTimeLetterDto.blocks 하나뿐이고, BE ReceivedTimeLetterResponse 가 미공개 구간에 List.of() 를 채우므로 서버는 이 키를 null 로 보내지 않는다. 나머지 5곳은 요청 DTO 라 디코딩과 무관하다. konsist 허용목록에서 이미 해소된 13건을 지우고, 프로덕션 설정을 복제하던 core/network 테스트 픽스처 5곳도 같이 맞춘다.
Sadturtleman
left a comment
There was a problem hiding this comment.
방향과 조사에 동의합니다. 이슈가 든 6곳 중 5곳이 요청 DTO 라 무관하다는 것을 실코드로 가려낸 것, 남은 1건(ReceivedTimeLetterDto.blocks)을 BE 소스로 「미공개 구간에도 빈 목록을 채운다」까지 확인한 것, ignoreUnknownKeys 는 남긴 경계까지 정확합니다.
[blocker] 계약 테스트 14개가 아직 coerceInputValues = true 로 돕니다
이 PR 이 프로덕션에서 걷어낸 설정을 테스트가 그대로 켜 두고 있습니다.
$ git grep -l "coerceInputValues = true" -- '*/src/test/*' → 14 파일
feature/mindrecord/data/.../DiaryListContractTest.kt
feature/mindrecord/data/.../MindRecordDtoContractTest.kt
feature/receiver/data/.../ReceiverAfternoteResponseDtoContractTest.kt
feature/timeletter/data/.../TimeLetterDtoContractTest.kt
… 외 10
두 파일은 주석으로 사실이 아닌 것을 단언하게 됩니다.
/** Json 설정은 `NetworkModule.provideJson` 과 동일 (ignoreUnknownKeys + coerceInputValues). */이 테스트들의 존재 이유가 「프로덕션과 같은 Json 설정에서 계약이 지켜지는가」인데, 이 PR 이후로는 더 관대한 설정에서 통과합니다. 서버가 non-null 자리에 null 을 보내면 프로덕션은 이제 던지는데 테스트는 여전히 초록입니다 — 이 PR 이 만든 드리프트라 이 PR 이 닫는 것이 맞습니다.
- 14개 파일의
coerceInputValues = true를 걷고, 위 두 주석도 사실에 맞게 고쳐 주세요. - 걷은 뒤 깨지는 테스트가 있으면 그게 곧 이 PR 의 실제 영향 범위입니다 — 지금은 그게 가려져 있습니다.
곁들여(막지 않음)
coerceInputValues 는 「non-null + 기본값에 null」 말고 unknown enum 값도 기본값으로 접습니다. 응답 DTO 중 enum 타입에 기본값을 둔 자리를 찾아봤는데 0건이라 실제 영향은 없어 보입니다. 조사 표에 그 축도 한 줄 남겨 두시면 다음 사람이 다시 안 찾습니다.
…-coerce-input-values
프로덕션에서 `coerceInputValues` 를 걷어내고도 계약 테스트 15곳이 그대로 켜 두고 있었다. 「프로덕션과 같은 설정에서 계약이 지켜지는가」를 보는 테스트가 **더 관대한 설정에서** 통과해, 서버가 non-null 자리에 null 을 보내면 프로덕션은 던지는데 테스트는 초록인 드리프트였다. - 테스트 Json 15곳의 `coerceInputValues = true` 제거 — 리뷰 시점 14곳에 develop 병합으로 들어온 `ReceiverAfternotePagingSourceTest` 1곳을 더했다. - 「provideJson 과 동일 (ignoreUnknownKeys + coerceInputValues)」 주석 7곳을 사실에 맞게 정정. 걷어낸 지금 테스트 설정은 `provideJson` 과 실제로 동일해졌다. - `ReceivedAfternoteDetailTitleContractTest` 의 사라진 기제를 가리키던 KDoc·테스트명 정정. 걷어낸 뒤 깨지는 테스트는 없다. `:feature:timeletter:data` 의 4건은 제거 전후가 동일한 #790·#873 의도된 실패(`ci-expected-failures.json`)로, 같은 태스크를 제거 전 트리에서 돌려 대조했다.
|
리뷰 반영했습니다. blocker 와 곁들임 둘 다 처리했고, 곁들임 쪽은 실측 결과가 지적과 달라 정정해 남깁니다. [blocker] 계약 테스트의
|
Closes #1494
무엇을 걷어냈나
coerceInputValues는 non-null 프로퍼티에null이 와도 기본값이 있으면 조용히 그 값으로 바꾼다. 전 앱의 모든 응답 디코딩에 걸리고, 치환됐다는 기록은 어디에도 남지 않는다. #1010 이 세운 기준(«제외하더라도 세어서 보고»,ErrorReporter)과 방향이 반대다.ignoreUnknownKeys는 유지한다 — 서버의 필드 추가는 정상적인 진화이고, 선언하지 않은 키는 어차피 못 쓴다.이슈 본문의 「6곳」은 실제로 1곳이었다
이슈를 쓸 때
non-null + 기본값프로퍼티 6곳을 적었는데, 실코드를 열어 보니 5곳이 요청 DTO 였다. 요청 DTO 는 인코딩만 하므로coerceInputValues와 무관하다.AfternoteCreateGalleryRequestDto.categoryAfternoteCreatePlaylistRequestDto.category·.receiversAfternoteCreateAccountRequestDto.receiversAfternotePlaylistRequestDto.songsReceivedTimeLetterDto.blocks:konsist:test의ResponseDtoContractKonsistTest로 교차 확인했다. 허용목록 14건 중 13건이 이미 해소됐고 남은 실제 위반은 이 1건이다.그 1건이 안전한 근거 — BE 실코드
ReceivedTimeLetterResponse.java가 미공개 구간에 빈 목록을 채운다.서버는
blocks를null로 보내지 않는다. 그래서 플래그를 걷어도 파싱이 깨지지 않는다. 이 기본값이 실제로 가리는 것은 «키 자체가 빠지는» 경우이고, 그건 #790 이 추적한다(ci-expected-failures.json에 의도된 실패로 등록됨). 그래서 이 PR 은ReceivedTimeLetterDto에 손대지 않는다.두 번째 축 — unknown enum (리뷰 지적 반영, 실측 정정)
coerceInputValues는 「non-null + 기본값에 null」 말고 unknown enum 값도 접는다. 리뷰에서 이 축이 0건으로 보인다는 지적이 있어 전수 재조사했고, 0건이 아니라 1건이다.응답 DTO 의 enum 타입 프로퍼티를 전수(
@Serializableenum 9종 × 사용처)로 훑으면, 기본값을 가진 자리는DeliveryConditionItemDto.inactivityPeriod: InactivityPeriodDto? = null한 곳이다(응답 DTO). nullable + 기본값이라 coerce 대상이 된다.동작 차이는 실측했다.
지금 당장의 위험은 0이다. FE
InactivityPeriodDto와 BEInactivityPeriod.java의 값 집합이THREE_MONTHS·SIX_MONTHS·ONE_YEAR로 일치한다. 이 변경이 발화하는 것은 BE 가 값을 새로 추가했을 때이고, 그때 조용히 null 이 되는 대신 터지는 것이 이 PR 이 세우려는 실패 방향 그대로다. 다음 사람이 다시 안 찾도록 조사 축으로 남긴다.함께 정리한 것
coerceInputValues설명도 현재 상태로 고쳤다.coerceInputValues = true제거 (리뷰 blocker). 아래 별도 절.AuthDtoSocialLoginContractTest의 테스트 1건 이름 정정. 「coerceInputValues로 흡수되지 않고」 가 사라진 설정을 가리키게 됐다. 단언 내용은 그대로다.리뷰 blocker 반영 — 테스트가 프로덕션보다 관대했다
프로덕션에서 걷어낸 설정을 계약 테스트가 그대로 켜 두고 있어, 「프로덕션과 같은 Json 설정에서 계약이 지켜지는가」를 보는 테스트가 더 관대한 설정에서 통과하고 있었다. 이 PR 이 만든 드리프트라 이 PR 에서 닫는다.
coerceInputValues = true제거 — 리뷰 시점 14곳에, develop 병합으로 들어온ReceiverAfternotePagingSourceTest1곳이 더해졌다.provideJson과 동일 (ignoreUnknownKeys + coerceInputValues)」 →(ignoreUnknownKeys). 걷어낸 지금 테스트 설정은provideJson과 실제로 동일해졌다.ReceivedAfternoteDetailTitleContractTest의 KDoc·테스트명이 사라진 기제를 가리키고 있어 함께 정정했다.걷어낸 뒤 새로 깨지는 테스트는 없다.
:feature:timeletter:data의 4건은 제거 전후가 동일한 #790·#873 의도된 실패이며, 제거 전 트리에서 같은 태스크를 돌려 대조했다(4 failed → 4 failed, 동일 4건).검증
:core:network:testDebugUnitTest:konsist:test:feature:{afternote,mindrecord,receiver}:data:testDebugUnitTestktlintCheck(전 모듈):konsist:ktlintCheck부재로 죽던 Ktlint job 은 #1447(develop) 병합으로 해소:feature:timeletter:data:testDebugUnitTestci-expected-failures.json의 #790·#873 의도된 실패CI 기대 실패 제외 :feature:timeletter:data:testDebugUnitTest: 4건)InactivityPeriodDto로 coerce 유/무 동작 차이 확인 후 프로브 제거CI Test Plan
{ "androidTest": { "mode": "none", "reason": "변경 경계가 DI 로 제공되는 Json 설정과 JVM 계약 테스트·konsist 규칙이며, 계기 테스트가 닿는 UI·기기 경계가 없습니다.", "tests": [] } }