Skip to content

feat(mindrecord): 작성 실패를 텔레메트리에 남긴다 — 쓰기 승격 3종 (#964) - #1278

Merged
Sadturtleman merged 30 commits into
developfrom
fix/964-write-failure-telemetry
Aug 29, 2026
Merged

Sadturtleman merged 30 commits into
developfrom
fix/964-write-failure-telemetry

Conversation

@Sadturtleman

@Sadturtleman Sadturtleman commented Aug 27, 2026 •

Copy link
Copy Markdown
Collaborator

Refs #964

#964 의 「쓰기 실패 — 승격 1순위」 분량이다. 읽기 3종(임시저장 목록·삭제·수신자 열람)은 #1109 가 이미 세웠고 인프라도 그때 들어왔다. 체크리스트의 나머지 읽기 항목은 선별이 남아 이 PR 로 닫지 않는다.

무엇을 승격했나

stage 자리 왜
diary_submit 일기 저장(정식·임시저장 공통) 방금 쓴 글이 서버에 닿지 못하는 자리. 재현이 어려워 실기 QA 로 안 잡힌다
daily_question_submit 데일리질문 답변 저장 같은 성질이고, upsert 라 실패가 기존 임시저장 상태와도 얽힌다 (#1018)
media_upload 작성 화면 미디어 업로드 실패하면 그 첨부만 빠진 채 저장이 이어져 「올린 줄 알았는데 없는」 기록이 남는다 (#716·#731)

일괄 치환하지 않았습니다

Crashlytics 는 non-fatal 을 최근 8건만 보관합니다. 흔한 읽기 실패까지 넣으면 네트워크가 한 번 끊길 때 잡음이 한도를 채워 실제 장애를 밀어냅니다.

그래서 이슈의 판단 기준대로 «사용자가 쓴 것이 날아가고 재현이 어려운» 경로만 골랐습니다. 목록 로드 실패 같은 읽기 쪽은 이번에 넣지 않았습니다.

가드 — 양방향

WriteFailureReportingTest 4건입니다.

테스트 지키는 것
일기 저장 실패가 diary_submit 으로 기록된다 승격 자리와 stage 문자열
데일리질문 저장 실패가 daily_question_submit 으로 기록된다 〃
미디어 업로드 실패가 media_upload 으로 기록된다 〃
일기 저장 성공은 기록하지 않는다 반대 방향 — 이게 없으면 「항상 기록」으로 만들어도 위 셋이 통과하고, 그러면 보관 한도를 잡음으로 채운다

계측 호출을 지우면 해당 테스트가 FAILED 인 것을 확인했습니다.

stage 문자열까지 단언합니다. 콘솔 필터가 그 값이라, 바뀌면 그 시점 이후 기록이 기존 이슈 그룹과 끊겨 추이를 잃습니다(기존 MindRecordFailureStage KDoc 의 규약).

딸려 나온 것

두 ViewModel 생성자에 ErrorReporter 가 늘어 호출부 20곳(단위 12 · 계측 4 · 나머지)을 함께 고쳤습니다. 계측 테스트 쪽은 이미 있던 FakeErrorReporter 를, 단위 테스트 쪽은 RecordingErrorReporter 를 씁니다. Firebase 의존은 추가하지 않습니다 — 주입 타입은 ErrorReporter 추상이고 실제 결선은 app 의 모듈에만 있습니다.

검증

항목 결과
ktlintCheck BUILD SUCCESSFUL
:feature:mindrecord:presentation:testDebugUnitTest BUILD SUCCESSFUL
:app:compileDebugAndroidTestKotlin · :app:assembleDebug BUILD SUCCESSFUL
MindRecordFlowAndroidTest (CI 와 같은 pixel2Api30) 3/3 통과

시안 대조 — 해당 없음. 화면·리소스 변경이 0건이고 텔레메트리 호출만 추가됩니다.

CI Test Plan

{
  "androidTest": {
    "mode": "full",
    "reason": "쓰기 계측 3종이 두 작성 ViewModel 의 생성자를 바꿔 app/src/androidTest 의 여러 파일이 함께 수정됐다. 변경 테스트가 20개 상한을 넘어 전체 회귀로 컴파일과 런타임 호환성을 함께 검증한다."
  }
}

이 모듈은 Log.e 도 ErrorReporter 도 0건이라, 사용자가 방금 쓴 글이 서버에 닿지 못해도
릴리즈에서 알 방법이 없었다. #1109 가 읽기 3종(임시저장 목록·삭제·수신자 열람)을 세웠고,
#964 체크리스트의 «쓰기 실패 — 승격 1순위» 가 남아 있었다.

승격한 자리 셋:

- DIARY_SUBMIT — 일기 저장(정식·임시저장 공통). 방금 쓴 글이 사라지는 자리다
- DAILY_QUESTION_SUBMIT — 데일리질문 답변 저장. upsert 라 실패가 기존 임시저장과 얽힌다
- MEDIA_UPLOAD — 작성 화면 미디어 업로드. 실패하면 첨부만 빠진 채 저장이 이어져
  «올린 줄 알았는데 없는» 기록이 남는다

일괄 치환하지 않았다. Crashlytics 가 non-fatal 을 최근 8건만 보관하므로, 흔한 읽기 실패까지
넣으면 잡음이 한도를 채워 실제 장애를 민다 — 사용자가 쓴 것이 날아가고 재현이 어려운
경로만 골랐다.

가드 4건: 세 stage 가 각각 그 문자열로 기록되는지와, **성공은 기록하지 않는지**. 뒤가
없으면 «항상 기록» 으로 만들어도 앞이 통과한다. 계측을 빼면 실패하는 것을 확인했다.
stage 문자열까지 고정한 이유는 콘솔 필터가 그 값이라 바뀌면 추이가 끊기기 때문이다.

ktlintCheck · testDebugUnitTest · :app:assembleDebug green, MindRecordFlowAndroidTest 는
CI 와 같은 pixel2Api30 에서 3/3 통과.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions
github-actions Bot requested review from 1hyok and koongmai August 27, 2026 11:37
@github-actions

Copy link
Copy Markdown
Contributor

🚧 리뷰 적체 가드 — 이 PR 을 닫습니다

리뷰 응답을 기다리는 PR 이 1건 남아 있습니다.

PR 열린 날 경과 제목
#1098 2026-08-24 3일 docs: QA 회차와 런타임 증거를 저장소 정본으로 이관 [base: develop]

이 저장소는 리뷰 응답을 기다리는 PR 이 1건이라도 남아 있으면 새 PR 을 열 수 없습니다 (#1136).

각 PR 에 필요한 리뷰는 한 건입니다 — 누구든 먼저 판정을 내면 그 PR 은 모두의 목록에서 빠집니다. 승인 뒤 변경을 다시 볼 의무는 없고, 변경요청도 작성자가 같은 리뷰어에게 재요청한 뒤 반영 커밋을 올린 경우에만 다시 리뷰어의 몫이 됩니다.

되살리는 법

위 리뷰를 끝낸 뒤 이 PR 을 다시 여세요. 브랜치는 지워지지 않았고 커밋도 그대로입니다.

gh pr reopen 1278

리뷰보다 이 PR 이 급하면 review-debt-exempt 라벨을 붙이고 다시 열면 통과합니다.

@github-actions github-actions Bot closed this Aug 27, 2026
@Sadturtleman Sadturtleman reopened this Aug 27, 2026
Repository Quality 가 계측 테스트 필요 여부의 명시적 결정을 요구한다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@1hyok 1hyok added the android-test PR head에서 Android Managed Device 계측 테스트 실행 label Aug 27, 2026

@1hyok 1hyok left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

직접 계측 경계와 한 호출부의 회귀 테스트가 빠져 있어 변경을 요청합니다. debug에서 Crashlytics 업로드가 꺼져 있어도 주입한 ErrorReporter fake로 실제 화면 행동이 리포팅 경계를 정확히 한 번 통과하는지는 관찰할 수 있습니다. 인라인 두 건을 반영한 뒤 QA Metadata를 androidTest.required: true와 정확한 testRef·동일 ref evidence로 갱신해 주세요.

Comment thread app/src/androidTest/java/com/afternote/afternote_fe/MindRecordFlowAndroidTest.kt Outdated
Comment thread app/src/androidTest/java/com/afternote/afternote_fe/MindRecordFlowAndroidTest.kt Outdated
1hyok and others added 8 commits August 27, 2026 21:06
리뷰 지적대로 앞선 가드 셋이 아무것도 지키지 못하고 있었다.

- androidTest 가 FakeErrorReporter() 를 만들자마자 버려, 새 계측 호출을 지워도 통과했다.
  reporter 를 밖에서 보존해 diary_submit 이 **정확히 1건**이고 성공한 재시도가 더 남기지
  않는 것까지 단언한다
- 데일리질문 계측 테스트가 빈 테마만 compose 하고 submit() 을 직접 불러, 화면-ViewModel
  결선을 지나지 않았다. 실제 DailyQuestionWriteScreen 을 띄우고 저장 버튼 클릭으로 실패를
  만든다 — 화면을 태우니 에디터가 되돌려 주는 HTML(`<p>…</p>`)이 실제 값이라 기대값도
  프로덕션 형태로 바뀌었다
- media_upload 가드가 일기 쪽만 봐서, 데일리질문 호출부를 지워도 통과했다. 같은 stage 의
  두 호출부를 각각 가드한다

역검증 3종 모두 확인했다 — 각 계측 호출을 지우면 그 테스트가 실패한다(실기는 CI 와 같은
pixel2Api30 에서).

PR 본문의 QA Metadata 는 develop 이 CI Test Plan 으로 교체해 그 형식으로 옮겼다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Sadturtleman

Copy link
Copy Markdown
Collaborator Author

세 지적 모두 맞습니다. 가드 셋이 실제로는 아무것도 지키지 못하고 있었습니다.

P1 — 실제 화면 실패 경계에서 단언

FakeErrorReporter() 를 만들자마자 버려서, 말씀대로 새 계측 호출을 지워도 통과했습니다. diaryFailureThenRetry_preservesInputAndSendsExactPayload 는 이미 실제 DiaryWriteScreen → production VM → 실패 repository 경계를 타므로 reporter 만 밖에서 보존했습니다.

assertEquals(listOf("diary_submit"), reporter.mindRecordStages)

실패 1회분만 기록되고 성공한 재시도가 더 남기지 않는 것까지 봅니다. 이 테스트를 CI Test Plan 의 선택 테스트로 선언했습니다.

P1 — 데일리질문도 실제 화면으로

빈 테마만 compose 하고 submit() 을 직접 부르던 것을, 실제 DailyQuestionWriteScreen 을 띄우고 저장 버튼 클릭으로 실패를 만들도록 바꿨습니다.

화면을 태우니 기대값이 하나 달라졌습니다 — 리치 에디터가 되돌려 주는 HTML 이 실제 값입니다.

- assertEquals("오늘은 용기 냈다", viewModel.uiState.value.answer)
+ assertEquals("<p>오늘은 용기 냈다</p>", viewModel.uiState.value.answer)

종전 테스트가 화면을 건너뛰어 원문 문자열이 남았던 것이고, 지금이 프로덕션 값입니다.

P1 — media_upload 두 호출부

같은 stage 인데 호출부가 둘이라, 일기 쪽만 보면 데일리질문 호출을 지워도 통과했습니다. DailyQuestionWriteViewModel 경계에서 media_upload 1건과 기존 업로드 오류 상태를 함께 단언하는 테스트를 더했습니다.

역검증 3종

지운 계측 결과
DIARY_SUBMIT diaryFailureThenRetry_... FAILED (pixel2Api30)
DAILY_QUESTION_SUBMIT dailyQuestion_failureThenRetry_... FAILED (pixel2Api30)
데일리질문 MEDIA_UPLOAD 데일리질문 미디어 업로드 실패도 media_upload 으로 기록된다 FAILED

본문 형식

QA Metadata 가 develop 에서 CI Test Plan 으로 교체돼 그 스키마로 옮겼습니다 — mode: "selected" 로 위 두 테스트를 지정했습니다.

ktlintCheck · testDebugUnitTest(5건) · :app:assembleDebug green, MindRecordFlowAndroidTest 는 pixel2Api30 에서 3/3 통과입니다.

Sadturtleman and others added 2 commits August 28, 2026 13:08
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@1hyok 1hyok left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

최신 HEAD 2b86faf 재리뷰입니다.

5be7cb9에서 기존 지적 3건이 모두 닫혔습니다. Diary 실제 화면의 실패→성공 재시도는 diary_submit을 정확히 1회 단언하고, DailyQuestion은 실제 저장 버튼을 거쳐 daily_question_submit 1회와 성공 재시도 시 추가 기록 없음까지 검증합니다. 데일리질문 미디어 업로드 호출부도 media_upload과 기존 UI 오류 상태를 함께 고정했습니다.

exact-head Ktlint, Android Lint, Unit Test, Screenshot, CodeQL, API 30·34 GMD가 모두 성공했습니다. 보안·성능 새 문제는 없고 정확성·유지보수성 blocker도 찾지 못했습니다. 제 변경 요청 기준으로는 해소됐으며, 최종 APPROVE는 현재 요청된 @koongmai의 승인 판단에 남깁니다.

@1hyok 1hyok left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

최신 HEAD 2b86faf를 다시 확인하고 승인합니다.

앞선 재리뷰에서 확인한 실제 Diary·DailyQuestion 화면 텔레메트리와 두 미디어 업로드 호출부 회귀 가드가 그대로 유지됐습니다. 승인 직전 14개 체크에 pending·failure가 없고 새 커밋도 없습니다.

@github-actions

github-actions Bot commented Aug 28, 2026 •

Copy link
Copy Markdown
Contributor

❌ Screenshot test fail — baseline 과 CI rendered PNG 차이.

📦 Artifact: https://github.com/Afternote/Afternote-FE/actions/runs/33172209346 (페이지 하단 Artifacts → screenshot-test-results)

🔍 확인 위치 (artifact zip 내부):

  • diff PNG: preview/debug/diffs/...
  • rendered PNG: preview/debug/rendered/... (CI 환경에서 새로 그린 결과)
  • baseline PNG: repo <module>/src/screenshotTestDebug/reference/... (:feature:home:presentation · :feature:mindrecord:presentation)

📝 조치 가이드:

  • 의도된 시각 변경 → 이 PR에 screenshot-baseline 라벨을 붙여 CI baseline 생성 요청
  • diff 가 anti-aliasing/font 차이로 보이면 docker 환경 변경 여부 (Dockerfile.screenshot) 점검

CI ↔ 로컬 환경 차이는 본 워크플로의 container 단계가 root fix 한다 (README 의 "Compose Preview Screenshot Testing (docker baseline)" 섹션 참조).

@Sadturtleman Sadturtleman changed the title feat(mindrecord): 작성 실패를 텔레메트리에 남긴다 — 쓰기 승격 3종 (refs 964) feat(mindrecord): 작성 실패를 텔레메트리에 남긴다 — 쓰기 승격 3종 (#964) Aug 28, 2026
@Sadturtleman
Sadturtleman merged commit 2429e08 into develop Aug 29, 2026
28 of 29 checks passed
@Sadturtleman
Sadturtleman deleted the fix/964-write-failure-telemetry branch August 29, 2026 06:45
Sadturtleman added a commit that referenced this pull request Aug 29, 2026
#1278(작성 실패 텔레메트리)이 develop 에 들어오며 `DailyQuestionWriteViewModel` 의
`errorReporter` 가 필수 인자가 됐다. develop 병합 뒤 이 계측이 컴파일되지 않았다.

앞 커밋 검증에서 샜다 — `:feature:mindrecord:presentation:testDebugUnitTest` 만 돌리고
`:app:compileDebugAndroidTestKotlin` 을 안 돌렸다. 계측 파일은 그 태스크에만 걸린다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

android-test PR head에서 Android Managed Device 계측 테스트 실행

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants