Skip to content

[Test] Keychain/NetworkClient 단위 테스트 추가 및 Core 구조 정리#32

Merged
ddodle merged 3 commits into
mainfrom
test/network-keychain-tests
Jun 30, 2026
Merged

[Test] Keychain/NetworkClient 단위 테스트 추가 및 Core 구조 정리#32
ddodle merged 3 commits into
mainfrom
test/network-keychain-tests

Conversation

@ddodle

@ddodle ddodle commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

✨ PR 유형

어떤 변경 사항이 있나요??

  • 새로운 기능 추가
  • 버그 수정
  • 사용자 UI 디자인 변경 및 추가
  • 코드에 영향을 주지 않는 변경사항(오타 수정, 탭 사이즈 변경, 변수명 변경)
  • 코드 리팩토링
  • 주석 추가 및 수정
  • 문서 수정
  • 테스트 추가, 테스트 리팩토링
  • 빌드 부분 혹은 패키지 매니저 수정
  • 파일 혹은 폴더명 수정
  • 파일 혹은 폴더 삭제

🛠️ 작업내용

리팩토링 계획 Step 5(테스트 강화) 의 일부로 인증/네트워크 레이어 단위 테스트를 추가하고, 목표 디렉토리 구조에 맞춰 일부 파일을 정리했습니다.

1. Core 디렉토리 구조 정리 (refactor)

  • Config.swiftCore/Config/
  • APITargetType.swiftCore/NetworkAdapter/
  • Config.xcconfig 이동에 따른 project.pbxproj 경로 갱신

2. 단위 테스트 추가 (test) — 총 15개

  • KeychainTokenStoreTests (6개): 저장/조회/삭제/덮어쓰기, 빈 상태 nil 반환, 서로 다른 service 격리, actor 동시 접근 직렬화
  • NetworkClientTests (9개): Bearer 토큰 주입, public 경로 미주입, 401 → 리프레시 후 재시도 성공, 리프레시 실패 시 unauthorized + 콜백, 재시도 소진, 동시 401 발생 시 리프레시 정확히 1회 보장, 로그아웃 시 토큰 삭제

📋 추후 진행 상황

  • Step 5 잔여: UseCase / ViewModel 단위 테스트 추가
  • Step 4: Tuist 기반 멀티모듈 분리

📌 리뷰 포인트

  • NetworkClientTests의 동시성 시나리오(동시 401 → 리프레시 1회) 검증 방식
  • Keychain 테스트가 매 테스트마다 고유 service + tearDown 정리로 격리되는지

✅ Checklist

  • 커밋 메시지 컨벤션에 맞게 작성했습니다
  • 유지-보수를 위해 주석처리를 잘 작성하였는가?

Summary by CodeRabbit

  • 버그 수정

    • 빌드 설정이 공통 구성 파일을 일관되게 사용하도록 정리되어, Debug/Release 환경에서 설정 차이를 줄였습니다.
  • 테스트

    • 토큰 저장/조회, 초기 상태, 덮어쓰기, 삭제, 서비스 분리, 동시성 동작을 검증하는 테스트가 추가되었습니다.
    • 네트워크 인증 처리, 토큰 갱신 후 재시도, 401 응답 처리, 동시 요청 상황, 로그아웃 상태 변화를 확인하는 테스트가 추가되었습니다.

ddodle added 2 commits June 30, 2026 17:35
목표 디렉토리 구조에 맞춰 Config.swift는 Core/Config로,
APITargetType.swift는 Core/NetworkAdapter로 이동.
Config.xcconfig 이동에 따른 pbxproj 경로도 갱신.
- KeychainTokenStore: 저장/조회/삭제/덮어쓰기, 빈 상태 nil,
  service 격리, actor 동시 접근 직렬화 (6개)
- NetworkClient: Bearer 주입, 401 리프레시 재시도, 동시 401 시
  리프레시 1회 보장, 재시도 소진, 로그아웃 토큰 삭제 (9개)
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@ddodle, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 55 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ca6f7ed1-8e5c-40b1-b433-9556f51206a7

📥 Commits

Reviewing files that changed from the base of the PR and between 4641a0d and 9111bf1.

📒 Files selected for processing (1)
  • .github/workflows/iOS.yml

Walkthrough

project.pbxproj에서 모든 타깃/구성의 xcconfig 참조 경로를 Resource/Config.xcconfig에서 Core/Config/Config.xcconfig로 교체하고, KeychainTokenStoreNetworkClient의 동작을 검증하는 테스트 스위트 두 개를 새로 추가한다.

Changes

xcconfig 경로 변경

Layer / File(s) Summary
xcconfig 경로 일괄 변경
Rephoto_iOS.xcodeproj/project.pbxproj
membershipExceptions 및 테스트 타깃·프로젝트·메인 타깃의 Debug/Release XCBuildConfiguration 전체에서 baseConfigurationReferenceRelativePathCore/Config/Config.xcconfig로 교체한다.

테스트 스위트 추가

Layer / File(s) Summary
KeychainTokenStore 테스트
Rephoto_iOSTests/KeychainTokenStoreTests.swift
테스트별 고유 service로 Keychain 오염을 방지하고, 저장·조회·nil 반환·덮어쓰기·clear·서비스 격리·동시성(직렬화) 케이스를 검증한다.
NetworkClient 테스트 인프라
Rephoto_iOSTests/NetworkClientTests.swift
makeClient/request/response 헬퍼, 갱신 호출 횟수를 집계하는 SpyRefreshService actor, NSLock 기반 CallbackFlag를 추가한다.
NetworkClient 동작 테스트
Rephoto_iOSTests/NetworkClientTests.swift
Bearer 헤더 주입(인증 필요/공개 경로), 2xx·에러 응답 처리, 401 갱신 성공/실패/한도 초과 재시도, 20개 동시 요청 thundering-herd 방지(갱신 1회 보장), 로그아웃 동작을 검증한다.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~15 minutes

Possibly related PRs

Poem

🐇 xcconfig 경로를 Core로 옮기고,
Keychain 토큰을 하나하나 확인했지,
Bearer 헤더도 401도 걱정 없어,
thundering-herd? 단 한 번만 갱신해!
토끼는 오늘도 테스트를 사랑해 🌸

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 제목이 테스트 추가와 Core 구조 정리를 핵심 변경으로 간결하게 요약합니다.
Description check ✅ Passed 템플릿의 필수 섹션을 모두 포함하고 있어 변경 내용, 향후 작업, 리뷰 포인트가 충분히 작성되었습니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch test/network-keychain-tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ddodle ddodle self-assigned this Jun 30, 2026
pbxproj가 참조하는 xcconfig 경로 이동에 맞춰 CI placeholder
생성 위치도 Resource → Core/Config로 변경.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
Rephoto_iOSTests/KeychainTokenStoreTests.swift (1)

77-78: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

defer 내 정리 Task가 await되지 않아 비결정적으로 실행됩니다.

defer { Task { try? await other.clear() } }는 fire-and-forget이라 테스트 종료 시점까지 clear()가 끝난다는 보장이 없습니다. 고유 UUID service를 쓰므로 실해는 없지만, 시뮬레이터 Keychain에 항목이 남을 수 있습니다. addTeardownBlock을 써서 정리를 확실히 await하는 편이 깔끔합니다.

♻️ 제안
-        let other = KeychainTokenStore(service: "com.rephoto.tests.other.\(UUID().uuidString)")
-        defer { Task { try? await other.clear() } }
+        let other = KeychainTokenStore(service: "com.rephoto.tests.other.\(UUID().uuidString)")
+        addTeardownBlock { try? await other.clear() }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Rephoto_iOSTests/KeychainTokenStoreTests.swift` around lines 77 - 78, The
cleanup for the secondary KeychainTokenStore is being launched inside a
non-awaited Task from defer, so the test does not guarantee that other.clear()
finishes before teardown. Replace the defer-based fire-and-forget cleanup with a
deterministic XCTest teardown approach such as addTeardownBlock, and reference
KeychainTokenStore and its clear() call so the cleanup is awaited reliably.
Rephoto_iOSTests/NetworkClientTests.swift (1)

54-75: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

핸들러 클로저 내부 단언은 핸들러가 호출되지 않으면 조용히 통과합니다.

test_request_authRequiredPath_injectsBearerToken/test_request_publicPath_doesNotInjectTokenXCTAssertStubURLProtocol.handler 내부에서만 실행됩니다. 요청이 핸들러에 도달하기 전에 처리되거나 핸들러가 호출되지 않으면 단언이 한 번도 실행되지 않아 테스트가 거짓으로 통과합니다. StubURLProtocol이 요청을 기록하므로(recordedRequests), 호출 이후에 기록된 요청에 대해 헤더를 단언하는 편이 더 견고합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Rephoto_iOSTests/NetworkClientTests.swift` around lines 54 - 75, The
Authorization assertions in test_request_authRequiredPath_injectsBearerToken and
test_request_publicPath_doesNotInjectToken are only executed inside
StubURLProtocol.handler, so the tests can pass even if the handler is never
called. Move the header checks to assertions after client.request completes,
using StubURLProtocol.recordedRequests to verify the captured request instead.
Keep the behavior checks tied to makeClient, request(path:method:), and
StubURLProtocol so the tests fail if the request never reaches the stub.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Rephoto_iOS.xcodeproj/project.pbxproj`:
- Line 38: The base configuration path has been moved to
Core/Config/Config.xcconfig in the Xcode project, but the CI workflow still
generates the old Rephoto_iOS/Resource/Config.xcconfig placeholder. Update the
placeholder creation path in the iOS workflow so it matches the new
Config.xcconfig location used by project.pbxproj, and verify any related
bootstrap steps reference the same Core/Config path.

---

Nitpick comments:
In `@Rephoto_iOSTests/KeychainTokenStoreTests.swift`:
- Around line 77-78: The cleanup for the secondary KeychainTokenStore is being
launched inside a non-awaited Task from defer, so the test does not guarantee
that other.clear() finishes before teardown. Replace the defer-based
fire-and-forget cleanup with a deterministic XCTest teardown approach such as
addTeardownBlock, and reference KeychainTokenStore and its clear() call so the
cleanup is awaited reliably.

In `@Rephoto_iOSTests/NetworkClientTests.swift`:
- Around line 54-75: The Authorization assertions in
test_request_authRequiredPath_injectsBearerToken and
test_request_publicPath_doesNotInjectToken are only executed inside
StubURLProtocol.handler, so the tests can pass even if the handler is never
called. Move the header checks to assertions after client.request completes,
using StubURLProtocol.recordedRequests to verify the captured request instead.
Keep the behavior checks tied to makeClient, request(path:method:), and
StubURLProtocol so the tests fail if the request never reaches the stub.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d268f106-c00b-4a39-85ef-e50d8ed079c6

📥 Commits

Reviewing files that changed from the base of the PR and between 1231d1a and 4641a0d.

📒 Files selected for processing (5)
  • Rephoto_iOS.xcodeproj/project.pbxproj
  • Rephoto_iOS/Core/Config/Config.swift
  • Rephoto_iOS/Core/NetworkAdapter/APITargetType.swift
  • Rephoto_iOSTests/KeychainTokenStoreTests.swift
  • Rephoto_iOSTests/NetworkClientTests.swift

Comment thread Rephoto_iOS.xcodeproj/project.pbxproj
@ddodle ddodle merged commit dcf4ea6 into main Jun 30, 2026
2 checks passed
@ddodle ddodle deleted the test/network-keychain-tests branch June 30, 2026 08:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant