From 1c48dd834ac02df3093494feb8aaae61e438c8f7 Mon Sep 17 00:00:00 2001 From: curry772 Date: Fri, 11 Sep 2026 17:35:36 +0900 Subject: [PATCH] =?UTF-8?q?=ED=86=A0=ED=81=B0=20=ED=98=84=ED=99=A9=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=EC=97=90=20=EC=B5=9C?= =?UTF-8?q?=EA=B7=BC=20=EB=B0=9C=EA=B8=89=20=EC=9D=B4=EB=A0=A5=201?= =?UTF-8?q?=EA=B1=B4=20=ED=8F=AC=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 목록에서 issueHistory 가 null 로만 나와 "값이 없는 것"과 "안 실린 것"이 구분되지 않던 문제 - 목록은 최근 1건, 단건 조회는 보관 중인 전체(최대 10건)를 담는다 - 이력이 없으면 null 대신 빈 목록을 반환하고, serverName 도 함께 담는다 - 단위테스트 2건 추가 --- .../oauthtoken/OAuthTokenStatusDTO.java | 4 +- .../oauthtoken/OAuthTokenStatusService.java | 37 +++++++++++----- .../OAuthTokenStatusServiceTest.java | 42 ++++++++++++++++--- 3 files changed, 66 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/eactive/eai/manage/oauthtoken/OAuthTokenStatusDTO.java b/src/main/java/com/eactive/eai/manage/oauthtoken/OAuthTokenStatusDTO.java index 6368e5a..e76f0d2 100644 --- a/src/main/java/com/eactive/eai/manage/oauthtoken/OAuthTokenStatusDTO.java +++ b/src/main/java/com/eactive/eai/manage/oauthtoken/OAuthTokenStatusDTO.java @@ -90,10 +90,10 @@ public class OAuthTokenStatusDTO { String serverName; /** - * 토큰 발급 이력 (최근 순, 최대 10건). + * 토큰 발급 이력 (최근 순). * * 이 노드에서 일어난 발급만 담기며 재기동 시 사라진다. - * 목록 조회에는 담지 않고 어댑터그룹 단건 조회에서만 채운다. + * 단건 조회는 보관 중인 전체(최대 10건), 목록 조회는 최근 1건만 담는다. */ List issueHistory; } diff --git a/src/main/java/com/eactive/eai/manage/oauthtoken/OAuthTokenStatusService.java b/src/main/java/com/eactive/eai/manage/oauthtoken/OAuthTokenStatusService.java index 20f217c..eace632 100644 --- a/src/main/java/com/eactive/eai/manage/oauthtoken/OAuthTokenStatusService.java +++ b/src/main/java/com/eactive/eai/manage/oauthtoken/OAuthTokenStatusService.java @@ -19,6 +19,7 @@ import com.eactive.eai.adapter.AdapterPropManager; import com.eactive.eai.adapter.AdapterVO; import com.eactive.eai.authoutbound.OutboundOAuthCredentialVo; import com.eactive.eai.common.authoutbound.AccessTokenManagerByDB; +import com.eactive.eai.common.authoutbound.TokenIssueHistory; import com.eactive.eai.common.server.EAIServerManager; import com.eactive.eai.common.util.Logger; import com.openbanking.eai.common.token.AccessTokenVO; @@ -45,6 +46,9 @@ public class OAuthTokenStatusService { /** accessToken 중 노출할 앞자리 수 */ private static final int UNMASKED_LENGTH = 8; + /** 목록 조회에 담을 발급 이력 건수 */ + private static final int LIST_HISTORY_SIZE = 1; + /** * 등록된 모든 어댑터그룹의 토큰 현황을 반환한다. * @@ -53,39 +57,38 @@ public class OAuthTokenStatusService { public List getStatusList() { List result = new ArrayList(); for (String adapterGroupName : AccessTokenManagerByDB.getInstance().getRegisteredAdapterGroupNames()) { - // 목록에는 발급 이력을 담지 않는다. 그룹이 많으면 응답이 지나치게 커진다. - result.add(getStatus(adapterGroupName, false)); + // 목록에는 최근 1건만 담는다. 전체를 담으면 그룹 수만큼 응답이 커진다. + result.add(getStatus(adapterGroupName, LIST_HISTORY_SIZE)); } return result; } /** - * 어댑터그룹 하나의 토큰 현황을 발급 이력과 함께 반환한다. + * 어댑터그룹 하나의 토큰 현황을 발급 이력 전체와 함께 반환한다. * * @param adapterGroupName 어댑터그룹명 * @return 토큰 현황. 등록돼 있지 않으면 message 만 채워서 반환한다. */ public OAuthTokenStatusDTO getStatus(String adapterGroupName) { - return getStatus(adapterGroupName, true); + return getStatus(adapterGroupName, Integer.MAX_VALUE); } /** * 어댑터그룹 하나의 토큰 현황을 반환한다. * * @param adapterGroupName 어댑터그룹명 - * @param includeHistory 토큰 발급 이력 포함 여부 + * @param historyLimit 담을 발급 이력 건수 (최근 순) * @return 토큰 현황. 등록돼 있지 않으면 message 만 채워서 반환한다. */ - private OAuthTokenStatusDTO getStatus(String adapterGroupName, boolean includeHistory) { + private OAuthTokenStatusDTO getStatus(String adapterGroupName, int historyLimit) { OAuthTokenStatusDTO dto = new OAuthTokenStatusDTO(); dto.setAdapterGroupName(adapterGroupName); AccessTokenManagerByDB manager = AccessTokenManagerByDB.getInstance(); - if (includeHistory) { - dto.setServerName(findLocalServerName()); - dto.setIssueHistory(manager.getIssueHistories(adapterGroupName)); - } + // 이력은 노드 로컬이므로 어느 노드가 응답했는지 함께 담는다. + dto.setServerName(findLocalServerName()); + dto.setIssueHistory(latest(manager.getIssueHistories(adapterGroupName), historyLimit)); OutboundOAuthCredentialVo credential = manager.getOutboundOAuthCredentialVo(adapterGroupName); if (credential == null) { @@ -175,6 +178,20 @@ public class OAuthTokenStatusService { return dto; } + /** + * 발급 이력에서 최근 건만 잘라낸다. 이력은 이미 최근 순으로 정렬돼 있다. + * + * @param histories 발급 이력 + * @param limit 담을 건수 + * @return 최근 limit 건 + */ + private List latest(List histories, int limit) { + if (histories.size() <= limit) { + return histories; + } + return new ArrayList(histories.subList(0, limit)); + } + /** * 이 응답을 만든 서버명을 찾는다. 발급 이력이 노드 로컬이라 함께 알려준다. * diff --git a/src/test/java/com/eactive/eai/manage/oauthtoken/OAuthTokenStatusServiceTest.java b/src/test/java/com/eactive/eai/manage/oauthtoken/OAuthTokenStatusServiceTest.java index db29459..b94b2dc 100644 --- a/src/test/java/com/eactive/eai/manage/oauthtoken/OAuthTokenStatusServiceTest.java +++ b/src/test/java/com/eactive/eai/manage/oauthtoken/OAuthTokenStatusServiceTest.java @@ -8,6 +8,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.anyString; import java.lang.reflect.Field; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; @@ -23,6 +24,7 @@ import org.springframework.context.ApplicationContext; import com.eactive.eai.authoutbound.OutboundOAuthCredentialVo; import com.eactive.eai.common.authoutbound.AccessTokenManagerByDB; +import com.eactive.eai.common.authoutbound.TokenIssueHistory; import com.eactive.eai.common.util.ApplicationContextProvider; import com.openbanking.eai.common.token.OAuth2AccessTokenVO; @@ -64,6 +66,21 @@ class OAuthTokenStatusServiceTest { return credential; } + /** + * 최근 순으로 정렬된 발급 이력 count 건 (svc-0 이 최신). + * + * TokenIssueHistory 생성자는 package-private 이라 mock 으로 만든다. + */ + private List histories(int count) { + List result = new ArrayList<>(); + for (int i = 0; i < count; i++) { + TokenIssueHistory history = Mockito.mock(TokenIssueHistory.class); + Mockito.when(history.getServiceClass()).thenReturn("svc-" + i); + result.add(history); + } + return result; + } + /** 만료까지 expiresInSec 남은 토큰 */ private OAuth2AccessTokenVO token(String accessToken, long expiresInSec) { OAuth2AccessTokenVO vo = new OAuth2AccessTokenVO(); @@ -277,20 +294,35 @@ class OAuthTokenStatusServiceTest { } @Test - @DisplayName("9-1. 단건 조회에는 발급 이력을 담고 목록에는 담지 않는다") + @DisplayName("9-1. 단건 조회는 이력 전체, 목록은 최근 1건만 담는다") void 이력포함범위() { Set groups = new LinkedHashSet<>(Arrays.asList(GROUP)); Mockito.when(mockManager.getRegisteredAdapterGroupNames()).thenReturn(groups); registerCredential(GROUP); Mockito.when(mockManager.peekAccessTokenVO(GROUP)).thenReturn(token("abcdefghijkl", 600)); - Mockito.when(mockManager.getIssueHistories(GROUP)).thenReturn(Collections.emptyList()); + // histories() 안에서 mock 을 만들므로 when(...) 인자에 직접 넣으면 스터빙이 중첩된다. + List histories = histories(3); + Mockito.when(mockManager.getIssueHistories(GROUP)).thenReturn(histories); OAuthTokenStatusDTO single = service.getStatus(GROUP); OAuthTokenStatusDTO fromList = service.getStatusList().get(0); - assertNotNull(single.getIssueHistory(), "단건 조회에는 이력이 있어야 한다"); - assertNull(fromList.getIssueHistory(), "목록에는 이력을 담지 않는다"); - Mockito.verify(mockManager, Mockito.times(1)).getIssueHistories(GROUP); + assertEquals(3, single.getIssueHistory().size(), "단건 조회는 보관 중인 이력 전체"); + assertEquals(1, fromList.getIssueHistory().size(), "목록은 최근 1건만"); + assertEquals("svc-0", fromList.getIssueHistory().get(0).getServiceClass(), "최신 건이어야 한다"); + } + + @Test + @DisplayName("9-2. 이력이 없으면 빈 목록 (null 아님)") + void 이력없으면_빈목록() { + registerCredential(GROUP); + Mockito.when(mockManager.peekAccessTokenVO(GROUP)).thenReturn(token("abcdefghijkl", 600)); + Mockito.when(mockManager.getIssueHistories(GROUP)).thenReturn(Collections.emptyList()); + + OAuthTokenStatusDTO dto = service.getStatus(GROUP); + + assertNotNull(dto.getIssueHistory()); + assertTrue(dto.getIssueHistory().isEmpty()); } @Test