Skip to content

PushNotification·WebPage 삭제 리팩토링과 Swift Testing 적용

최윤진 edited this page Apr 6, 2026 · 1 revision

Note

PushNotification과 WebPage의 삭제 방식을 Todo와 동일한 즉시 soft delete + undo 복구 구조로 통일하고, Swift Testing 기반 테스트 적용 과정에서 확인한 Emulator credential 이슈까지 함께 정리했습니다.
해결 PR

테스트 코드 결과

  • 개선 전 코드에서 테스트 코드가 실패한 이유는 Try 5 확인
image image
기존 코드의 테스트 결과 개선 코드의 테스트 결과

문제 정의

  • Todo는 삭제 요청 시 즉시 soft delete 상태로 전환되고, undo는 그 상태를 바로 복구하는 구조
  • 반면 PushNotification, WebPagedeletingAt 기록 후 task queue로 지연 삭제를 수행하는 2단계 구조를 사용하고 있었음
  • 이로 인해 삭제 도메인 간 구현 방식이 달랐고, 로컬 Emulator 환경에서는 taskQueue().enqueue(...) 과정에서 credential 이슈가 발생해 integration test가 안정적으로 동작하지 않음
  • 리팩토링 목표
    • PushNotification, WebPage의 삭제 방식을 Todo와 동일한 방향으로 통일
    • undo 이후 다시 보이는 사용자 결과를 기준으로 테스트 가능하게 구성
    • 더 이상 사용하지 않는 queue 기반 함수와 설정 의존을 제거

해결 과정 및 시도 내용

Try 1: ViewModel 중심 Swift Testing 적용

  • 우선 PushNotification, WebPage에 대해 Swift Testing 기반 테스트를 추가하고, 삭제 후 즉시 숨김 및 undo 흐름을 검증을 시도
  • Spy 기반으로 ViewModel -> UseCase 호출 여부와 상태 변화를 검증하도록 구성
  • 결과: 빠른 회귀 테스트로는 유효했지만, 서버 코드가 바뀌어도 결과가 달라지지 않는 한계 존재

Try 2: Firebase SDK 기반 integration test 시도

  • 실제 앱 서비스 계층을 그대로 타는 방식으로 integration test를 계획
  • ViewModel -> UseCase -> Repository -> Service -> Firebase 경로를 테스트에서 직접 재현 시도
  • 결과: hosted/hostless test 환경 차이, Firestore 초기화 타이밍, useEmulator(...) 제약 때문에 안정적으로 유지하기 어려웠음

Try 3: REST 기반 Emulator integration test 전환

  • Firebase iOS SDK 대신, Auth / Firestore / Functions Emulator를 REST로 직접 호출하는 테스트 helper 구성
  • 삭제 요청, undo, 재조회 흐름을 로컬 Emulator 기준으로 재현
  • 결과: 서버 내부 구현이 실제로 테스트 결과에 반영되는 형태는 확보했지만, 초기에는 500 INTERNAL이 발생하여 원인 분석이 필요

Try 4: 2차 예외와 1차 예외 분리

  • 초기 로그에는 FieldValue.delete() 관련 예외만 보여 실제 실패 원인을 파악하기 어려웠음
  • catch 내부 cleanup 로직에서 발생한 2차 예외가 원래 예외를 덮고 있음을 확인하고, 원본 오류와 cleanup 오류를 분리 로깅하도록 수정
  • 결과: 실제 1차 오류가 queue enqueue 단계에서 발생한다는 점을 확인

Try 5: Emulator credential 이슈 확인

  • requestPushNotificationDeletion, requestWebPageDeletion 호출 시 callable auth 자체는 VALID로 통과
  • 하지만 taskQueue().enqueue(...) 수행 시 Admin SDK가 Google access token을 가져오지 못해 실패
  • 결과: 문제는 사용자 인증이 아니라, queue 기반 deletion 흐름이 로컬 Emulator에서 별도의 credential 요구를 만든다는 점이었음

Try 6: Todo와 동일한 즉시 soft delete 방식으로 통일

  • PushNotification, WebPage 모두 삭제 요청 시 바로 isDeleted = true로 저장하고, undoisDeleted = false로 즉시 복구하도록 변경
  • deletingAt, delayed task, completePushNotificationDeletion, completeWebPageDeletion 같은 queue 기반 경로를 제거
  • 클라이언트는 isDeleted == false만 기준으로 목록을 구성하도록 정리.
  • 최종 결과: 삭제 방식이 Todo와 동일한 구조로 통일되었고, queue credential 문제 없이 integration test로 undo 후 다시 보임을 검증 가능

발견된 사이드 이슈 및 해결 방법

1. Swift Testing만으로는 서버 변경 영향을 직접 반영하기 어려움

  • 현상: Spy 기반 테스트는 서버 구현이 바뀌어도 여전히 통과 가능
  • 해결: REST 기반 Emulator integration test를 별도로 추가해, 실제 서버 callable 결과가 테스트 성공/실패에 반영되도록 구성

2. Emulator 환경에서 task queue enqueue 시 credential 오류 발생

  • 현상: taskQueue().enqueue(...) 호출 시 metadata.google.internal access token 조회 실패로 500 INTERNAL 발생
  • 원인: callable auth와 별개로, Functions Emulator 내부의 Admin SDK가 queue enqueue에 필요한 Google credential을 요구
  • 해결: queue 기반 삭제 자체를 제거하고, Todo와 같은 즉시 soft delete 방식으로 변경하여 credential 의존 경로를 제거

3. FieldValue.delete() 예외가 원인처럼 보였지만 실제로는 2차 오류였음

  • 현상: 처음에는 FieldValue.delete() 관련 로그만 보여 해당 코드가 직접 원인으로 예상
  • 원인: 실제 1차 오류는 try 내부에서 먼저 발생했고, catch cleanup 중 2차 예외가 추가로 발생하며 로그를 오염
  • 해결: 원본 오류와 cleanup 오류를 분리 로깅해 1차 실패 지점을 정확히 드러내도록 수정

4. Firebase CLI가 성공 후에도 종료 코드를 비정상 반환함

  • 현상: Successful update operation, Deploy complete! 이후에도 Error: An unexpected error has occurred.가 출력
  • 해결: 실제 성공 로그를 기준으로 배포/삭제 성공 여부를 판단했고, 수정된 함수는 개별 배포, obsolete function은 개별 삭제 방식으로 마무리

5. 인덱스 제거 필요성 재검토

  • 현상: 삭제 방식이 바뀌었으므로 Firestore index도 정리해야 하는지 검토가 필요
  • 결과: 실제 변경은 write 방식과 로컬 후처리 필터 제거였고, where/orderBy 조합 자체는 바뀌지 않음
  • 해결: 인덱스는 유지하고, queue/export/deletingAt 의존 코드만 제거

Clone this wiki locally