Skip to content

[Fix]: 배치 점수 재계산 시 username 변경 사용자 fallback 복구 로직 추가#59

Merged
alexization merged 10 commits into
developfrom
fix-batch-identity-resync
Feb 19, 2026
Merged

[Fix]: 배치 점수 재계산 시 username 변경 사용자 fallback 복구 로직 추가#59
alexization merged 10 commits into
developfrom
fix-batch-identity-resync

Conversation

@alexization
Copy link
Copy Markdown
Owner

@alexization alexization commented Feb 19, 2026

🔎개요

배치 점수 재산출 시 GitHub 사용자명 변경으로 인해 발생하는 GITHUB_USER_NOT_FOUND
node_id 기반 GraphQL node(id:) 쿼리로 현재 username을 복구하고 재시도하는 로직을 추가했습니다.

📝작업 내용

  • GraphQLQueryBuildernode(id:) 쿼리 추가

    • buildUserLookupByNodeIdQuery(nodeId): 불변 식별자인 nodeId로 현재 login, email, avatarUrl 조회
  • GitHubGraphQLClient에 nodeId 기반 사용자 조회 메서드 추가

    • getUserInfoByNodeId(accessToken, nodeId): node(id:) 쿼리 실행 및 rate limit 처리
  • GitHubActivityService에 서비스 레이어 위임 메서드 추가

    • fetchUserByNodeId(nodeId): 토큰 풀에서 토큰 획득 후 GraphQL 클라이언트에 위임
  • ScoreRecalculationProcessor에 username 변경 fallback 로직 추가

    • GITHUB_USER_NOT_FOUND 발생 시 nodeId로 현재 username 조회
    • DB의 username, profileImage, email 갱신 후 점수 재계산 1회 재시도
    • nodeId 조회 실패 또는 재시도 실패 시 기존대로 예외 전파
  • OAuth 로그인 시 email 업데이트 누락 버그 수정

    • UserPersistenceService.updateProfile에 email 파라미터 추가
    • UserRegistrationService.handleExistingUser에서 email도 함께 전달
  • GitHubNodeUserResponse DTO 추가

    • GraphQL node(id:) 쿼리 응답 매핑용 record 클래스
  • 단위 테스트 추가/수정

    • username 변경 후 nodeId fallback 성공 케이스
    • nodeId 조회 결과 없음 케이스
    • fallback 재시도에서도 실패하는 케이스
    • 기존 NonRetryable 테스트를 USER_NOT_FOUND 외 케이스로 수정

👀변경 사항

  • 배치에서 username 기반 GitHub API 호출 실패 시 단순 skip이 아닌 1회 복구 재시도로 변경
  • UserPersistenceService.updateProfile 시그니처에 newEmail 파라미터 추가 (기존 호출부 모두 수정됨)

#️⃣관련 이슈

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • GitHub 사용자 정보를 노드 ID로 조회하는 기능 추가로 사용자 찾기 강화
  • 버그 수정

    • GitHub에서 사용자명 변경 감지 시 자동으로 프로필(사용자명, 아바타, 이메일)을 동기화하고 점수 재계산 수행
    • 프로필 업데이트 시 이메일 처리 개선 및 관련 오류 전파/처리 개선

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 19, 2026

Caution

Review failed

The head commit changed during the review from a2c05ed to 0745db3.

📝 Walkthrough

Walkthrough

ScoreRecalculationProcessor에 GitHub 사용자명 변경 폴백이 추가되었습니다. USER_NOT_FOUND 발생 시 nodeId로 GitHub에서 사용자 정보를 조회해 로컬 프로필(로그인, 아바타, 이메일)을 동기화하고 점수 재계산을 재시도하도록 제어 흐름이 변경되었습니다.

Changes

Cohort / File(s) Summary
점수 재계산 프로세서
src/main/java/com/gitranker/api/batch/processor/ScoreRecalculationProcessor.java
GitHub 비재시도 예외(USER_NOT_FOUND) 처리 시 nodeId 조회 폴백 추가. handleUsernameChanged 헬퍼와 recalculateScore로 흐름 분리, 예외 전파 및 BusinessException 래핑 로직 정리.
GitHub 인프라 레이어
src/main/java/com/gitranker/api/infrastructure/github/GitHubActivityService.java, src/main/java/com/gitranker/api/infrastructure/github/GitHubGraphQLClient.java, src/main/java/com/gitranker/api/infrastructure/github/util/GraphQLQueryBuilder.java
nodeId로 사용자 조회하는 API 추가: fetchUserByNodeId, getUserInfoByNodeId, buildUserLookupByNodeIdQuery 추가 및 기존 레이트리밋 처리 재사용.
GitHub DTO
src/main/java/com/gitranker/api/infrastructure/github/dto/GitHubNodeUserResponse.java
GraphQL node 조회 응답을 표현하는 새 레코드 추가(Data, Node, RateLimit)와 편의 접근자(getLogin, getEmail, getAvatarUrl, hasUser).
사용자 프로필 관리
src/main/java/com/gitranker/api/domain/user/service/UserPersistenceService.java, src/main/java/com/gitranker/api/domain/user/service/UserRegistrationService.java
updateProfile 시그니처가 (..., String newUsername, String newProfileImage)(..., String newUsername, String newProfileImage, String newEmail)로 확장되어 이메일 동기화가 가능해짐.
테스트 업데이트
src/test/java/com/gitranker/api/batch/processor/ScoreRecalculationProcessorTest.java, src/test/java/com/gitranker/api/domain/user/service/UserPersistenceServiceTest.java, src/test/java/com/gitranker/api/domain/user/service/UserRegistrationServiceTest.java
폴백 경로(노드ID 조회 → 프로필 업데이트 → 재시도) 검증용 테스트 추가/수정. UsernameFallbackTest 그룹 추가 및 모킹/검증에 email 인자 추가.

Sequence Diagram

sequenceDiagram
    autonumber
    actor Processor as ScoreRecalculationProcessor
    participant Pers as UserPersistenceService
    participant GitHubServ as GitHubActivityService
    participant GraphQL as GitHubGraphQLClient
    participant GH as GitHub GraphQL API

    Processor->>Pers: updateProfile(user, username, avatarUrl, email)
    alt updateProfile throws GitHubApiNonRetryableException(USER_NOT_FOUND)
        Processor->>GitHubServ: fetchUserByNodeId(nodeId)
        GitHubServ->>GraphQL: getUserInfoByNodeId(accessToken, nodeId)
        GraphQL->>GH: GraphQL query (user by nodeId)
        GH-->>GraphQL: GitHubNodeUserResponse
        GraphQL-->>GitHubServ: GitHubNodeUserResponse
        GitHubServ-->>Processor: GitHubNodeUserResponse
        alt user found
            Processor->>Pers: updateProfile(user, newLogin, newAvatar, newEmail)
            Pers-->>Processor: Updated User
            Processor->>Processor: recalculateScore(updatedUser)
            Processor-->>Processor: return updated User
        else user not found
            Processor-->>Processor: rethrow GitHubApiNonRetryableException
        end
    else updateProfile succeeds
        Pers-->>Processor: Updated User
        Processor->>Processor: recalculateScore(updatedUser)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

요약

GitHub 사용자명 변경 감지 및 처리를 위한 폴백 메커니즘이 도입되었습니다. 사용자를 찾을 수 없는 경우 시스템이 nodeId로 업데이트된 사용자 정보를 조회하고, 프로필(이메일 포함)을 갱신하며, 점수 재계산을 재시도합니다.

축제의 시

🐰 사용자가 달라져도 걱정 마,
노드로 찾아내어 프로필을 고치고,
다시 한 번 점수를 매기지, 폴백은 준비됐네 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목은 배치 점수 재계산 중 username 변경 사용자에 대한 fallback 복구 로직 추가라는 핵심 변경사항을 명확하게 요약하고 있으며, 제공된 변경사항 요약과 일치합니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-batch-identity-resync

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 and usage tips.

@alexization alexization self-assigned this Feb 19, 2026
@alexization alexization added bug Something isn't working labels Feb 19, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a9cdfbde48

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

String token = tokenPool.getToken();
GitHubNodeUserResponse response = graphQLClient.getUserInfoByNodeId(token, nodeId);

log.debug("nodeId 기반 사용자 조회 완료 - nodeId: {}, login: {}", nodeId, response.getLogin());
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Handle null node responses before reading login

When node(id:) returns no match, GitHub responds with data.node = null; this method logs response.getLogin() unconditionally, which dereferences data.node() and throws NullPointerException before ScoreRecalculationProcessor can run its hasUser() fallback path. In the batch step this turns an expected GITHUB_USER_NOT_FOUND skip/recovery path into an unexpected hard failure.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

@codex review

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (4)
src/main/java/com/gitranker/api/domain/user/service/UserRegistrationService.java (1)

67-69: isInfoChanged 조건에 명시적 괄호 추가 권장

&&||보다 우선순위가 높아 현재 로직은 정확하지만, 의도를 명확하게 하기 위해 이메일 비교 부분에 괄호를 추가하면 가독성이 향상됩니다.

♻️ 가독성 개선 제안
         boolean isInfoChanged = !user.getUsername().equals(attributes.username()) ||
                                 !user.getProfileImage().equals(attributes.profileImage()) ||
-                                (attributes.email()) != null && !attributes.email().equals(user.getEmail());
+                                (attributes.email() != null && !attributes.email().equals(user.getEmail()));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/gitranker/api/domain/user/service/UserRegistrationService.java`
around lines 67 - 69, isInfoChanged 계산식의 이메일 비교 연산에 명시적 괄호를 추가해 의도를 명확히 하세요;
UserRegistrationService 클래스의 해당 라인에서 현재 표현식 boolean isInfoChanged =
!user.getUsername().equals(attributes.username()) ||
!user.getProfileImage().equals(attributes.profileImage()) ||
(attributes.email()) != null && !attributes.email().equals(user.getEmail()); 에서
(attributes.email()) != null && !attributes.email().equals(user.getEmail()) 부분을
((attributes.email() != null) && !attributes.email().equals(user.getEmail())) 처럼
괄호로 감싸 가독성을 개선하십시오.
src/test/java/com/gitranker/api/batch/processor/ScoreRecalculationProcessorTest.java (2)

144-175: fallback 성공 경로에서 이메일 갱신 검증 누락

GitHubNodeUserResponse.Node에 이메일("new@email.com")이 포함되어 있고, User.updateProfile이 이메일 갱신을 지원하지만, result.getEmail() 검증이 없습니다. 이번 PR에서 이메일 미반영 버그를 수정했으므로 이 경로에서의 이메일 갱신을 명시적으로 검증하면 회귀 방지에 효과적입니다.

🧪 이메일 assertion 추가 제안
 assertThat(result.getUsername()).isEqualTo("newusername");
 assertThat(result.getProfileImage()).isEqualTo("https://new-avatar.png");
+assertThat(result.getEmail()).isEqualTo("new@email.com");
 assertThat(result.getTotalScore()).isGreaterThan(0);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/com/gitranker/api/batch/processor/ScoreRecalculationProcessorTest.java`
around lines 144 - 175, The test should assert that the user's email is updated
in the fallback-success path: in the test method
should_retryWithNewUsername_when_userNotFound(), after calling
processor.process(user) and before other assertions, add an assertion that
result.getEmail() equals the email from the GitHubNodeUserResponse.Node
("new@email.com") to ensure User.updateProfile correctly applies the email
change.

202-223: 재시도 실패 테스트에 핵심 verify 호출 누락 — 테스트 의도가 검증되지 않음

이 테스트의 목적은 fallback이 진입된 후 재시도도 실패할 때 예외가 전파되는 것을 검증하는 것입니다. 그러나 현재 코드에는:

  • verify(gitHubActivityService).fetchUserByNodeId("node1") 없음 → fallback 경로 자체가 진입됐는지 알 수 없음
  • verify(fullStrategy, times(2)).update(...) 없음 → 실제로 재시도가 일어났는지 확인 불가

verify 호출들이 없으면, 프로세서가 첫 번째 GITHUB_USER_NOT_FOUND 예외를 fallback 없이 그대로 전파해도 테스트가 통과합니다 — 사실상 lines 110-123의 NonRetryable 예외 테스트와 동일한 검증이 됩니다.

🧪 verify 호출 추가 제안
 assertThatThrownBy(() -> processor.process(user))
         .isInstanceOf(GitHubApiNonRetryableException.class);
+
+verify(gitHubActivityService).fetchUserByNodeId("node1");
+verify(fullStrategy, times(2)).update(eq(user), any());
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/com/gitranker/api/batch/processor/ScoreRecalculationProcessorTest.java`
around lines 202 - 223, The test should assert that the fallback path and the
retry occurred by adding verifications after calling processor.process(user):
verify that gitHubActivityService.fetchUserByNodeId("node1") was invoked to
confirm the fallback was triggered, and verify that fullStrategy.update(...) was
called twice (e.g., verify(fullStrategy, times(2)).update(eq(user), any())) to
ensure the retry attempt happened; add these verify calls at the end of
should_throwException_when_retryAlsoFails so the test fails if the processor
propagates the initial exception without entering the fallback and retry logic.
src/test/java/com/gitranker/api/domain/user/service/UserPersistenceServiceTest.java (1)

96-106: 이메일 업데이트 검증 누락

새로 추가된 newEmail 파라미터의 핵심 목적이 이메일 갱신이고, 이번 PR에서 이메일 미반영 버그를 수정했음에도 해당 필드가 실제로 반영됐는지 검증하지 않습니다.

🧪 이메일 assertion 추가 제안
 User result = userPersistenceService.updateProfile(user, "newname", "https://new.img", "new@email.com");

 assertThat(result.getUsername()).isEqualTo("newname");
+assertThat(result.getEmail()).isEqualTo("new@email.com");
 verify(userRepository).save(user);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/com/gitranker/api/domain/user/service/UserPersistenceServiceTest.java`
around lines 96 - 106, The test should_saveUser_when_profileUpdated in
UserPersistenceServiceTest currently verifies username and save invocation but
misses asserting the email change; update the test for updateProfile(...) to
also assert that result.getEmail() equals the new email string ("new@email.com")
(you can also assert the avatar URL via result.getProfileImage() if desired)
while keeping the existing when(userRepository.save(user)).thenReturn(user) and
verify(userRepository).save(user) checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/main/java/com/gitranker/api/infrastructure/github/dto/GitHubNodeUserResponse.java`:
- Around line 11-25: Accessor methods getLogin(), getEmail(), and getAvatarUrl()
can throw NPE when data or data.node() is null; modify these accessors
(getLogin, getEmail, getAvatarUrl in GitHubNodeUserResponse) to defensively
return null (or Optional) if data or data.node() is null, or alternatively move
the log call in GitHubActivityService.fetchUserByNodeId to occur only after
response.hasUser() is true; update whichever spot you choose (the response
accessors or the fetchUserByNodeId logging sequence) so that calls to
response.getLogin()/getEmail()/getAvatarUrl() are never invoked unless data and
node are non-null.

In
`@src/main/java/com/gitranker/api/infrastructure/github/GitHubActivityService.java`:
- Around line 39-46: fetchUserByNodeId currently calls response.getLogin()
directly which can NPE if GraphQL returned node == null; update
fetchUserByNodeId in GitHubActivityService to guard against nulls from
graphQLClient.getUserInfoByNodeId by checking response != null and
response.getLogin()/response.getNode() (or equivalent) before using them in the
log or elsewhere, and log a safe message when login/node is missing (e.g.,
include nodeId and indicate missing user) or return/propagate a null/empty
result consistently so callers like ScoreRecalculationProcessor using hasUser()
won't crash.

In
`@src/test/java/com/gitranker/api/batch/processor/ScoreRecalculationProcessorTest.java`:
- Around line 186-200: The test currently simulates a missing GitHub user by
constructing GitHubNodeUserResponse.Data with a Node whose fields are all null,
but the real GraphQL API returns data.node == null when the nodeId is not found;
update the test in ScoreRecalculationProcessorTest (the case that calls
processor.process(user)) to construct the mock response as new
GitHubNodeUserResponse(new GitHubNodeUserResponse.Data(null, null)) so data.node
is null (matching the real API) and ensure this still triggers the same
hasUser() path and exception behavior.

---

Nitpick comments:
In
`@src/main/java/com/gitranker/api/domain/user/service/UserRegistrationService.java`:
- Around line 67-69: isInfoChanged 계산식의 이메일 비교 연산에 명시적 괄호를 추가해 의도를 명확히 하세요;
UserRegistrationService 클래스의 해당 라인에서 현재 표현식 boolean isInfoChanged =
!user.getUsername().equals(attributes.username()) ||
!user.getProfileImage().equals(attributes.profileImage()) ||
(attributes.email()) != null && !attributes.email().equals(user.getEmail()); 에서
(attributes.email()) != null && !attributes.email().equals(user.getEmail()) 부분을
((attributes.email() != null) && !attributes.email().equals(user.getEmail())) 처럼
괄호로 감싸 가독성을 개선하십시오.

In
`@src/test/java/com/gitranker/api/batch/processor/ScoreRecalculationProcessorTest.java`:
- Around line 144-175: The test should assert that the user's email is updated
in the fallback-success path: in the test method
should_retryWithNewUsername_when_userNotFound(), after calling
processor.process(user) and before other assertions, add an assertion that
result.getEmail() equals the email from the GitHubNodeUserResponse.Node
("new@email.com") to ensure User.updateProfile correctly applies the email
change.
- Around line 202-223: The test should assert that the fallback path and the
retry occurred by adding verifications after calling processor.process(user):
verify that gitHubActivityService.fetchUserByNodeId("node1") was invoked to
confirm the fallback was triggered, and verify that fullStrategy.update(...) was
called twice (e.g., verify(fullStrategy, times(2)).update(eq(user), any())) to
ensure the retry attempt happened; add these verify calls at the end of
should_throwException_when_retryAlsoFails so the test fails if the processor
propagates the initial exception without entering the fallback and retry logic.

In
`@src/test/java/com/gitranker/api/domain/user/service/UserPersistenceServiceTest.java`:
- Around line 96-106: The test should_saveUser_when_profileUpdated in
UserPersistenceServiceTest currently verifies username and save invocation but
misses asserting the email change; update the test for updateProfile(...) to
also assert that result.getEmail() equals the new email string ("new@email.com")
(you can also assert the avatar URL via result.getProfileImage() if desired)
while keeping the existing when(userRepository.save(user)).thenReturn(user) and
verify(userRepository).save(user) checks.

Comment on lines +39 to +46
public GitHubNodeUserResponse fetchUserByNodeId(String nodeId) {
String token = tokenPool.getToken();
GitHubNodeUserResponse response = graphQLClient.getUserInfoByNodeId(token, nodeId);

log.debug("nodeId 기반 사용자 조회 완료 - nodeId: {}, login: {}", nodeId, response.getLogin());

return response;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

response.getLogin() 호출 시 NPE 위험

Line 43에서 response.getLogin()은 내부적으로 data.node().login()을 호출합니다. node(id:) 쿼리가 삭제된 사용자나 잘못된 nodeId를 조회하면 nodenull로 반환되어 NPE가 발생합니다. 호출자(ScoreRecalculationProcessor)의 hasUser() 체크 이전에 이 메서드 내부에서 먼저 크래시가 발생합니다.

🐛 NPE 방지를 위한 수정 제안
     public GitHubNodeUserResponse fetchUserByNodeId(String nodeId) {
         String token = tokenPool.getToken();
         GitHubNodeUserResponse response = graphQLClient.getUserInfoByNodeId(token, nodeId);
 
-        log.debug("nodeId 기반 사용자 조회 완료 - nodeId: {}, login: {}", nodeId, response.getLogin());
+        log.debug("nodeId 기반 사용자 조회 완료 - nodeId: {}, login: {}", nodeId,
+                response.hasUser() ? response.getLogin() : "N/A");
 
         return response;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/gitranker/api/infrastructure/github/GitHubActivityService.java`
around lines 39 - 46, fetchUserByNodeId currently calls response.getLogin()
directly which can NPE if GraphQL returned node == null; update
fetchUserByNodeId in GitHubActivityService to guard against nulls from
graphQLClient.getUserInfoByNodeId by checking response != null and
response.getLogin()/response.getNode() (or equivalent) before using them in the
log or elsewhere, and log a safe message when login/node is missing (e.g.,
include nodeId and indicate missing user) or return/propagate a null/empty
result consistently so callers like ScoreRecalculationProcessor using hasUser()
won't crash.

Repository owner deleted a comment from opencode-agent Bot Feb 19, 2026
Repository owner deleted a comment from opencode-agent Bot Feb 19, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/main/java/com/gitranker/api/infrastructure/github/dto/GitHubNodeUserResponse.java (1)

51-57: RateLimit 필드에 @JsonProperty 어노테이션 누락 — 다른 중첩 레코드와의 일관성.

DataNode 레코드는 모든 필드에 @JsonProperty를 사용하고 있으나, RateLimit은 생략되어 있습니다. Jackson의 파라미터 이름 감지 설정에 따라 동작에는 문제가 없을 수 있지만, 일관성을 위해 추가하는 것을 고려해 주세요.

♻️ 일관성을 위한 어노테이션 추가 제안
     public record RateLimit(
-            int limit,
-            int cost,
-            int remaining,
-            LocalDateTime resetAt
+            `@JsonProperty`("limit") int limit,
+            `@JsonProperty`("cost") int cost,
+            `@JsonProperty`("remaining") int remaining,
+            `@JsonProperty`("resetAt") LocalDateTime resetAt
     ) implements GitHubRateLimitInfo {
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/gitranker/api/infrastructure/github/dto/GitHubNodeUserResponse.java`
around lines 51 - 57, 레코드 RateLimit(구성요소: limit, cost, remaining, resetAt)에서 다른
중첩 레코드(Data, Node)와의 일관성을 위해 각 매개변수에 `@JsonProperty` 어노테이션을 추가하세요(예:
`@JsonProperty`("limit") int limit 등), 필요한 경우
com.fasterxml.jackson.annotation.JsonProperty를 import하고 GitHubNodeUserResponse
내부의 RateLimit 레코드 선언과 GitHubRateLimitInfo 인터페이스 관계를 유지하도록 수정하세요.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@src/main/java/com/gitranker/api/infrastructure/github/dto/GitHubNodeUserResponse.java`:
- Around line 51-57: 레코드 RateLimit(구성요소: limit, cost, remaining, resetAt)에서 다른
중첩 레코드(Data, Node)와의 일관성을 위해 각 매개변수에 `@JsonProperty` 어노테이션을 추가하세요(예:
`@JsonProperty`("limit") int limit 등), 필요한 경우
com.fasterxml.jackson.annotation.JsonProperty를 import하고 GitHubNodeUserResponse
내부의 RateLimit 레코드 선언과 GitHubRateLimitInfo 인터페이스 관계를 유지하도록 수정하세요.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3713b5ab72

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


GitHubNodeUserResponse response = executeQuery(accessToken, query, GitHubNodeUserResponse.class);

if (response.data().rateLimit() != null) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Guard node lookup response before reading rate limit

In getUserInfoByNodeId, response.data() is dereferenced without a null check, but GitHub GraphQL can legally return data: null with an errors payload on 200 responses for execution failures; in that case this line throws before the error is mapped to GitHubApi*Exception. In the batch flow this can surface as a generic BusinessException/runtime failure instead of the configured retry/skip exceptions, so a transient GitHub-side issue can fail the whole job rather than being retried or skipped.

Useful? React with 👍 / 👎.

배치에서 username 변경으로 USER_NOT_FOUND 발생 시 nodeId로 현재
login을 복구하기 위한 GraphQL node(id:) 쿼리와 응답 DTO 추가
node(id:) GraphQL 쿼리를 실행하여 nodeId로 현재 login, avatarUrl을
조회하는 getUserInfoByNodeId 메서드 추가
fetchUserByNodeId 메서드를 추가하여 GraphQLClient의 nodeId 조회를
서비스 레이어에서 위임
GITHUB_USER_NOT_FOUND 발생 시 nodeId로 현재 login을 조회하여
프로필 업데이트 후 점수 재계산을 1회 재시도하도록 변경
nodeId 기반 username 복구 성공, nodeId 조회 실패, 재시도 실패 케이스
테스트 추가 및 기존 NonRetryable 테스트를 USER_NOT_FOUND 외 케이스로 수정
GraphQL node(id:) 쿼리, DTO, processor fallback 로직에서 email도
함께 가져와 프로필 업데이트 시 username, profileImage, email 모두 반영
isInfoChanged에서 email 변경을 감지하지만 updateProfile 호출 시
email을 null로 전달하여 실제 업데이트가 누락되던 문제를 수정
- GitHubNodeUserResponse accessor 메서드에 null 방어 로직 추가
- nodeId 조회 실패 테스트에서 실제 API 응답과 동일하게 node=null로 수정
- isInfoChanged 이메일 비교 조건에 명시적 괄호 추가
- fallback 성공 테스트에 email assertion 추가
- 재시도 실패 테스트에 fallback/retry verify 추가
- UserPersistenceServiceTest에 email 변경 assertion 추가
GraphQL execution error 시 data:null 반환될 수 있어 rateLimit 접근 전
null 가드 추가하여 배치 fallback 경로에서 NPE 방지
@alexization alexization force-pushed the fix-batch-identity-resync branch from a2c05ed to 0745db3 Compare February 19, 2026 08:06
@alexization alexization changed the base branch from main to develop February 19, 2026 08:08
@alexization alexization merged commit 08c12c2 into develop Feb 19, 2026
1 check passed
@alexization alexization deleted the fix-batch-identity-resync branch February 19, 2026 08:11
alexization added a commit that referenced this pull request Feb 19, 2026
* feature: GraphQLQueryBuilder에 node(id:) 기반 사용자 조회 쿼리 추가

배치에서 username 변경으로 USER_NOT_FOUND 발생 시 nodeId로 현재
login을 복구하기 위한 GraphQL node(id:) 쿼리와 응답 DTO 추가

* feature: GitHubGraphQLClient에 nodeId 기반 사용자 조회 메서드 추가

node(id:) GraphQL 쿼리를 실행하여 nodeId로 현재 login, avatarUrl을
조회하는 getUserInfoByNodeId 메서드 추가

* feature: GitHubActivityService에 nodeId 기반 사용자 조회 메서드 추가

fetchUserByNodeId 메서드를 추가하여 GraphQLClient의 nodeId 조회를
서비스 레이어에서 위임

* feature: ScoreRecalculationProcessor에 username 변경 fallback 로직 추가

GITHUB_USER_NOT_FOUND 발생 시 nodeId로 현재 login을 조회하여
프로필 업데이트 후 점수 재계산을 1회 재시도하도록 변경

* test: ScoreRecalculationProcessor username 변경 fallback 단위 테스트 추가

nodeId 기반 username 복구 성공, nodeId 조회 실패, 재시도 실패 케이스
테스트 추가 및 기존 NonRetryable 테스트를 USER_NOT_FOUND 외 케이스로 수정

* feature: nodeId 기반 사용자 조회 시 email 필드도 함께 조회하도록 보강

GraphQL node(id:) 쿼리, DTO, processor fallback 로직에서 email도
함께 가져와 프로필 업데이트 시 username, profileImage, email 모두 반영

* fix: OAuth 로그인 시 email 변경이 실제로 반영되지 않던 버그 수정

isInfoChanged에서 email 변경을 감지하지만 updateProfile 호출 시
email을 null로 전달하여 실제 업데이트가 누락되던 문제를 수정

* fix: 코드 리뷰 반영 - NPE 방어, 테스트 보강, 가독성 개선

- GitHubNodeUserResponse accessor 메서드에 null 방어 로직 추가
- nodeId 조회 실패 테스트에서 실제 API 응답과 동일하게 node=null로 수정
- isInfoChanged 이메일 비교 조건에 명시적 괄호 추가
- fallback 성공 테스트에 email assertion 추가
- 재시도 실패 테스트에 fallback/retry verify 추가
- UserPersistenceServiceTest에 email 변경 assertion 추가

* fix: getUserInfoByNodeId에서 response.data() null 체크 추가

GraphQL execution error 시 data:null 반환될 수 있어 rateLimit 접근 전
null 가드 추가하여 배치 fallback 경로에서 NPE 방지
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: 배치 갱신 시 GitHub username 변경 사용자 USER_NOT_FOUND로 점수 재계산 실패

1 participant