From 131bebbf500196670eb0f7463371d13b6b10b7dd Mon Sep 17 00:00:00 2001 From: kimwuju Date: Wed, 14 Oct 2026 17:33:25 +0900 Subject: [PATCH 1/3] =?UTF-8?q?fix:=20=EC=9E=90=EB=A3=8C=EB=A5=BC=20?= =?UTF-8?q?=EC=98=AC=EB=A6=AC=EC=A7=80=20=EC=95=8A=EC=9D=80=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EC=9E=90=EC=9D=98=20isGit=C2=B7isPortfolio=EA=B0=80?= =?UTF-8?q?=20500=20=EB=8C=80=EC=8B=A0=20false=EB=A5=BC=20=EB=8F=8C?= =?UTF-8?q?=EB=A0=A4=EC=A4=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 자료를 한 번도 올리지 않은 사용자에게 인증 서버는 gitUrls·fileUrl을 null로 준다. 두 API가 그 값에 바로 isEmpty()를 불러 NPE로 500이 났다. 응답 자체가 null인 경우까지 false로 답한다. fileUrl은 빈 문자열도 없는 것으로 본다 -- launchIfMissing과 같은 기준이다. Co-Authored-By: Claude Opus 5.5 --- .../controller/MetaDataController.java | 7 ++- .../controller/MetaDataControllerTest.java | 58 +++++++++++++++++++ 2 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 src/test/java/repit/repit_api_server/domain/metadata/controller/MetaDataControllerTest.java diff --git a/src/main/java/repit/repit_api_server/domain/metadata/controller/MetaDataController.java b/src/main/java/repit/repit_api_server/domain/metadata/controller/MetaDataController.java index 118ac1b0..980b7f76 100644 --- a/src/main/java/repit/repit_api_server/domain/metadata/controller/MetaDataController.java +++ b/src/main/java/repit/repit_api_server/domain/metadata/controller/MetaDataController.java @@ -43,12 +43,14 @@ public ResponseEntity getMetaData( } + // 자료를 한 번도 올리지 않은 사용자는 인증 서버가 두 값을 null로 준다. 없는 것이지 오류가 아니다. @GetMapping("/isGit") public ResponseEntity isGit( @AuthenticationPrincipal AuthUser authUser ) { MetaDataResponse metaData = metaService.getMetaData(authUser.token()); - return ResponseEntity.ok(!metaData.getGitUrls().isEmpty()); + return ResponseEntity.ok(metaData != null && metaData.getGitUrls() != null + && !metaData.getGitUrls().isEmpty()); } @GetMapping("/isPortfolio") @@ -56,6 +58,7 @@ public ResponseEntity isPortfolio( @AuthenticationPrincipal AuthUser authUser ) { MetaDataResponse metaData = metaService.getMetaData(authUser.token()); - return ResponseEntity.ok(!metaData.getFileUrl().isEmpty()); + return ResponseEntity.ok(metaData != null && metaData.getFileUrl() != null + && !metaData.getFileUrl().isBlank()); } } diff --git a/src/test/java/repit/repit_api_server/domain/metadata/controller/MetaDataControllerTest.java b/src/test/java/repit/repit_api_server/domain/metadata/controller/MetaDataControllerTest.java new file mode 100644 index 00000000..43c64f01 --- /dev/null +++ b/src/test/java/repit/repit_api_server/domain/metadata/controller/MetaDataControllerTest.java @@ -0,0 +1,58 @@ +package repit.repit_api_server.domain.metadata.controller; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import repit.repit_api_server.domain.metadata.dto.response.MetaDataResponse; +import repit.repit_api_server.domain.metadata.service.AnalysisLaunchService; +import repit.repit_api_server.domain.metadata.service.MetaService; +import repit.repit_api_server.global.auth.AuthUser; +import repit.repit_api_server.global.response.UserResponse; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +/** + * 자료를 올리지 않은 사용자에게 인증 서버는 두 값을 null로 준다. 그것을 500이 아니라 false로 답한다. + */ +@ExtendWith(MockitoExtension.class) +class MetaDataControllerTest { + + private static final AuthUser USER = new AuthUser(new UserResponse(), "Bearer token"); + + @Mock + private MetaService metaService; + @Mock + private AnalysisLaunchService analysisLaunchService; + @InjectMocks + private MetaDataController controller; + + @Test + void 자료가_없으면_false() { + when(metaService.getMetaData("Bearer token")).thenReturn(new MetaDataResponse(null, null)); + + assertThat(controller.isGit(USER).getBody()).isFalse(); + assertThat(controller.isPortfolio(USER).getBody()).isFalse(); + } + + @Test + void 인증_서버가_빈_응답을_주면_false() { + when(metaService.getMetaData("Bearer token")).thenReturn(null); + + assertThat(controller.isGit(USER).getBody()).isFalse(); + assertThat(controller.isPortfolio(USER).getBody()).isFalse(); + } + + @Test + void 자료가_있으면_true() { + when(metaService.getMetaData("Bearer token")) + .thenReturn(new MetaDataResponse(List.of("https://github.com/a/b"), "https://s3/x.pdf")); + + assertThat(controller.isGit(USER).getBody()).isTrue(); + assertThat(controller.isPortfolio(USER).getBody()).isTrue(); + } +} From 028592eb074ba78a09debd8f8cdc0d2fdfd3e718 Mon Sep 17 00:00:00 2001 From: kimwuju Date: Wed, 14 Oct 2026 17:33:25 +0900 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20=EC=9D=B8=EC=A6=9D=20=EC=84=9C?= =?UTF-8?q?=EB=B2=84=20=EC=9D=BD=EA=B8=B0=20=EC=A0=9C=ED=95=9C=EC=9D=84=20?= =?UTF-8?q?5=EC=B4=88=EC=97=90=EC=84=9C=2010=EC=B4=88=EB=A1=9C=20=EB=8A=98?= =?UTF-8?q?=EB=A6=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 자료 업로드 중 인증 서버 createMetaData가 5초를 넘겨 502로 나갔다. 인증 서버에는 저장이 끝나 있었고, 올린 S3 파일은 고아로 남고 분석은 시작되지 않았다. 직접 부르면 0.18초라 일시적인 지연이다. 분석 서버처럼 auth-server.read-timeout을 따로 두고 10초로 잡는다. 채팅 서버는 5초 그대로다. getUser도 같은 클라이언트라 인증 서버가 멈추면 요청 하나가 최대 약 30초(재시도 3회) 붙잡힌다. Co-Authored-By: Claude Opus 5.5 --- .../repit_api_server/global/config/HttpInterfaceConfig.java | 3 ++- src/main/resources/application.yaml | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/repit/repit_api_server/global/config/HttpInterfaceConfig.java b/src/main/java/repit/repit_api_server/global/config/HttpInterfaceConfig.java index aeebe200..f36af5a0 100644 --- a/src/main/java/repit/repit_api_server/global/config/HttpInterfaceConfig.java +++ b/src/main/java/repit/repit_api_server/global/config/HttpInterfaceConfig.java @@ -46,7 +46,8 @@ public AiServerApi aiServerApi(RestClient.Builder restClientBuilder, public AuthServerApi authServerApi(RestClient.Builder restClientBuilder, @Value("${auth-server.base-url}") String authServerBaseUrl, @Value("${external-api.connect-timeout:3s}") String connectTimeout, - @Value("${external-api.read-timeout:5s}") String readTimeout) { + // 인증 서버도 따로 둔다. 자료 저장 응답이 5초를 넘기는 일이 있다. + @Value("${auth-server.read-timeout:10s}") String readTimeout) { return buildClient(restClientBuilder, AuthServerClient.SERVER_NAME, authServerBaseUrl, connectTimeout, readTimeout, AuthServerApi.class); } diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 95fb880d..7cf53c47 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -68,6 +68,10 @@ spring: auth-server: base-url: ${AUTH_SERVER} + # 자료 저장(createMetaData)이 5초를 넘긴 일이 있다. 저장은 됐는데 여기서 끊겨 업로드가 실패로 나가고, + # 올린 S3 파일은 고아로 남고 분석도 시작되지 않았다. 사용자 확인(getUser)도 같은 값을 쓴다 -- 재시도 + # 3회라 인증 서버가 멈추면 요청 하나가 최대 약 30초 붙잡힌다. + read-timeout: 10s ai-server: base-url: ${AI_SERVER} From 867ac8f29922aa5cc658dc61821f16817b86fb10 Mon Sep 17 00:00:00 2001 From: kimwuju Date: Wed, 14 Oct 2026 17:39:32 +0900 Subject: [PATCH 3/3] =?UTF-8?q?revert:=20=EC=9D=B8=EC=A6=9D=20=EC=84=9C?= =?UTF-8?q?=EB=B2=84=20=EC=9D=BD=EA=B8=B0=20=EC=A0=9C=ED=95=9C=EC=9D=84=20?= =?UTF-8?q?=EB=8B=A4=EC=8B=9C=205=EC=B4=88=EB=A1=9C=20=EB=90=98=EB=8F=8C?= =?UTF-8?q?=EB=A6=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 10초는 createMetaData뿐 아니라 사용자 확인(getUser)과 메타데이터 조회(getMetaData)에도 걸린다. 두 GET은 3회 재시도라 인증 서버가 느리면 요청 스레드가 최대 약 30.6초 묶여, 기존 약 15.6초의 두 배로 다른 인증 의존 API까지 지연이 번진다. 이 PR에서는 빼고 null 처리만 남긴다. This reverts commit 028592e. Co-Authored-By: Claude Opus 5.5 --- .../repit_api_server/global/config/HttpInterfaceConfig.java | 3 +-- src/main/resources/application.yaml | 4 ---- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/main/java/repit/repit_api_server/global/config/HttpInterfaceConfig.java b/src/main/java/repit/repit_api_server/global/config/HttpInterfaceConfig.java index f36af5a0..aeebe200 100644 --- a/src/main/java/repit/repit_api_server/global/config/HttpInterfaceConfig.java +++ b/src/main/java/repit/repit_api_server/global/config/HttpInterfaceConfig.java @@ -46,8 +46,7 @@ public AiServerApi aiServerApi(RestClient.Builder restClientBuilder, public AuthServerApi authServerApi(RestClient.Builder restClientBuilder, @Value("${auth-server.base-url}") String authServerBaseUrl, @Value("${external-api.connect-timeout:3s}") String connectTimeout, - // 인증 서버도 따로 둔다. 자료 저장 응답이 5초를 넘기는 일이 있다. - @Value("${auth-server.read-timeout:10s}") String readTimeout) { + @Value("${external-api.read-timeout:5s}") String readTimeout) { return buildClient(restClientBuilder, AuthServerClient.SERVER_NAME, authServerBaseUrl, connectTimeout, readTimeout, AuthServerApi.class); } diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 7cf53c47..95fb880d 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -68,10 +68,6 @@ spring: auth-server: base-url: ${AUTH_SERVER} - # 자료 저장(createMetaData)이 5초를 넘긴 일이 있다. 저장은 됐는데 여기서 끊겨 업로드가 실패로 나가고, - # 올린 S3 파일은 고아로 남고 분석도 시작되지 않았다. 사용자 확인(getUser)도 같은 값을 쓴다 -- 재시도 - # 3회라 인증 서버가 멈추면 요청 하나가 최대 약 30초 붙잡힌다. - read-timeout: 10s ai-server: base-url: ${AI_SERVER}