OAuth 토큰 발급 이력 조회 기능 추가
- 어댑터그룹별 최근 10건을 노드 메모리에 보관. 성공뿐 아니라 실패도 남긴다
(발급 실패는 캐시에 아무것도 남지 않아 사후 추적이 어려웠음)
- 기록 항목 : 시각, SCHEDULE/RETRY, 구현체, 성공여부, 소요시간, 만료시각, 실패사유
- accessToken 은 기록 시점에 앞 8자만 남겨 보관
- GET /manage/oauth-token/status/{어댑터그룹명} 에만 포함. 목록 조회에는 미포함
- 이력이 노드 로컬이므로 응답에 serverName 을 함께 담는다
- 단위테스트 6건 추가
This commit is contained in:
@@ -68,6 +68,15 @@ public class AccessTokenManagerByDB implements Lifecycle {
|
|||||||
*/
|
*/
|
||||||
private Map<String, OutboundOAuthCredentialVo> outboundOAuthCredentialVos;
|
private Map<String, OutboundOAuthCredentialVo> outboundOAuthCredentialVos;
|
||||||
|
|
||||||
|
/** 어댑터그룹별로 보관할 토큰 발급 이력 건수 */
|
||||||
|
private static final int ISSUE_HISTORY_SIZE = 10;
|
||||||
|
|
||||||
|
/** 이력에 남길 accessToken 앞자리 수 */
|
||||||
|
private static final int UNMASKED_TOKEN_LENGTH = 8;
|
||||||
|
|
||||||
|
/** 어댑터그룹별 토큰 발급 이력. 이 노드 메모리에만 존재하며 재기동 시 사라진다. */
|
||||||
|
private final Map<String, Deque<TokenIssueHistory>> issueHistories = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
OutboundOAuthCredentialDao outboundOAuthCredentialDao;
|
OutboundOAuthCredentialDao outboundOAuthCredentialDao;
|
||||||
|
|
||||||
@@ -315,7 +324,19 @@ public class AccessTokenManagerByDB implements Lifecycle {
|
|||||||
HttpClientAccessTokenServiceByDB service = HttpClientAccessTokenServiceFactoryByDB.createFactory(type);
|
HttpClientAccessTokenServiceByDB service = HttpClientAccessTokenServiceFactoryByDB.createFactory(type);
|
||||||
if(service != null) {
|
if(service != null) {
|
||||||
logger.debug("Executing token service of type: {} for adapter group: {}", type, adapterGroupName);
|
logger.debug("Executing token service of type: {} for adapter group: {}", type, adapterGroupName);
|
||||||
accessToken = (AccessTokenVO) service.execute(adapterGroupName, properties, outboundOAuthCredentialVo);
|
|
||||||
|
long startTime = System.currentTimeMillis();
|
||||||
|
try {
|
||||||
|
accessToken = (AccessTokenVO) service.execute(adapterGroupName, properties,
|
||||||
|
outboundOAuthCredentialVo);
|
||||||
|
} catch (Exception e) {
|
||||||
|
recordIssueHistory(adapterGroupName, TokenIssueHistory.TRIGGER_SCHEDULE, type, startTime, null,
|
||||||
|
toFailReason(e));
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
recordIssueHistory(adapterGroupName, TokenIssueHistory.TRIGGER_SCHEDULE, type, startTime,
|
||||||
|
accessToken, null);
|
||||||
|
|
||||||
// 발급에 실패했는데도 빈 토큰을 반환하는 구현체가 있다. 그대로 캐시되면
|
// 발급에 실패했는데도 빈 토큰을 반환하는 구현체가 있다. 그대로 캐시되면
|
||||||
// 만료시각이 없어 재발급 대상이 되지 않으므로 실패로 처리한다.
|
// 만료시각이 없어 재발급 대상이 되지 않으므로 실패로 처리한다.
|
||||||
@@ -328,9 +349,13 @@ public class AccessTokenManagerByDB implements Lifecycle {
|
|||||||
return accessToken;
|
return accessToken;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
recordIssueHistory(adapterGroupName, TokenIssueHistory.TRIGGER_SCHEDULE, type,
|
||||||
|
System.currentTimeMillis(), null, "토큰 발급 구현체를 찾을 수 없음");
|
||||||
logger.warn("Token service not found for type: {} and adapter group: {}", type, adapterGroupName);
|
logger.warn("Token service not found for type: {} and adapter group: {}", type, adapterGroupName);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
recordIssueHistory(adapterGroupName, TokenIssueHistory.TRIGGER_SCHEDULE, null,
|
||||||
|
System.currentTimeMillis(), null, "어댑터 설정을 찾을 수 없음");
|
||||||
logger.warn("No valid adapter configuration found for adapter group: {}", adapterGroupName);
|
logger.warn("No valid adapter configuration found for adapter group: {}", adapterGroupName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,6 +417,78 @@ public class AccessTokenManagerByDB implements Lifecycle {
|
|||||||
lifecycle.removeLifecycleListener(listener);
|
lifecycle.removeLifecycleListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. 기능 : 토큰 발급 이력을 남긴다.
|
||||||
|
* 2. 처리 개요 : 어댑터그룹별로 최근 ISSUE_HISTORY_SIZE 건만 노드 메모리에 보관한다.
|
||||||
|
* 성공뿐 아니라 실패도 남긴다. 실패는 캐시에 아무것도 남지 않아 사후 추적이 어렵기 때문이다.
|
||||||
|
* 3. 주의사항 : accessToken 은 앞 UNMASKED_TOKEN_LENGTH 자만 남겨 보관한다.
|
||||||
|
*
|
||||||
|
* @param adapterGroupName 어댑터그룹명
|
||||||
|
* @param trigger SCHEDULE / RETRY
|
||||||
|
* @param serviceClass 사용한 발급 구현체 클래스명
|
||||||
|
* @param startTime 발급 시도 시각
|
||||||
|
* @param accessToken 발급된 토큰. 실패 시 null.
|
||||||
|
* @param failReason 실패 사유. 성공 시 null.
|
||||||
|
**/
|
||||||
|
private void recordIssueHistory(String adapterGroupName, String trigger, String serviceClass, long startTime,
|
||||||
|
AccessTokenVO accessToken, String failReason) {
|
||||||
|
String reason = failReason;
|
||||||
|
String maskedToken = null;
|
||||||
|
Date expiration = null;
|
||||||
|
|
||||||
|
if (reason == null) {
|
||||||
|
if (accessToken == null || StringUtils.isBlank(accessToken.getAccessToken())) {
|
||||||
|
reason = "발급된 토큰이 비어 있음";
|
||||||
|
} else {
|
||||||
|
maskedToken = maskToken(accessToken.getAccessToken());
|
||||||
|
expiration = accessToken.getExpiration();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TokenIssueHistory history = new TokenIssueHistory(startTime, trigger, serviceClass, maskedToken, expiration,
|
||||||
|
reason);
|
||||||
|
|
||||||
|
Deque<TokenIssueHistory> histories = issueHistories.computeIfAbsent(adapterGroupName,
|
||||||
|
key -> new ArrayDeque<TokenIssueHistory>());
|
||||||
|
|
||||||
|
synchronized (histories) {
|
||||||
|
histories.addFirst(history);
|
||||||
|
while (histories.size() > ISSUE_HISTORY_SIZE) {
|
||||||
|
histories.removeLast();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 예외 메시지가 비어 있는 경우를 대비해 클래스명이라도 이력에 남긴다. */
|
||||||
|
private String toFailReason(Exception e) {
|
||||||
|
return StringUtils.defaultIfBlank(e.getMessage(), e.getClass().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** accessToken 앞자리만 남기고 마스킹한다. */
|
||||||
|
private String maskToken(String accessToken) {
|
||||||
|
if (accessToken.length() <= UNMASKED_TOKEN_LENGTH) {
|
||||||
|
return StringUtils.repeat('*', accessToken.length());
|
||||||
|
}
|
||||||
|
return StringUtils.substring(accessToken, 0, UNMASKED_TOKEN_LENGTH) + "***";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. 기능 : 어댑터그룹의 토큰 발급 이력을 최근 순으로 반환한다.
|
||||||
|
* 2. 처리 개요 : 상태 조회 API 에서 사용한다. 이 노드에서 일어난 발급만 담긴다.
|
||||||
|
*
|
||||||
|
* @param adapterGroupName 어댑터그룹명
|
||||||
|
* @return 최근 순 이력 목록. 없으면 빈 목록.
|
||||||
|
**/
|
||||||
|
public List<TokenIssueHistory> getIssueHistories(String adapterGroupName) {
|
||||||
|
Deque<TokenIssueHistory> histories = issueHistories.get(adapterGroupName);
|
||||||
|
if (histories == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
synchronized (histories) {
|
||||||
|
return new ArrayList<>(histories);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 1. 기능 : 등록된 OAuth 인증 정보의 어댑터그룹명 목록을 반환한다.
|
* 1. 기능 : 등록된 OAuth 인증 정보의 어댑터그룹명 목록을 반환한다.
|
||||||
* 2. 처리 개요 : 상태 조회 API 에서 사용한다.
|
* 2. 처리 개요 : 상태 조회 API 에서 사용한다.
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package com.eactive.eai.common.authoutbound;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 아웃바운드 OAuth 토큰 발급 이력 한 건.
|
||||||
|
*
|
||||||
|
* 진단 목적이므로 성공뿐 아니라 실패도 남긴다. accessToken 은 마스킹된 값만 담는다.
|
||||||
|
* 이 인스턴스는 노드 메모리에만 존재하며 재기동 시 사라진다.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public class TokenIssueHistory {
|
||||||
|
|
||||||
|
/** 스케줄러의 주기 발급 */
|
||||||
|
public static final String TRIGGER_SCHEDULE = "SCHEDULE";
|
||||||
|
|
||||||
|
/** 거래 중 재발급 */
|
||||||
|
public static final String TRIGGER_RETRY = "RETRY";
|
||||||
|
|
||||||
|
private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
|
||||||
|
|
||||||
|
/** 발급을 시도한 시각 */
|
||||||
|
private final String issuedAt;
|
||||||
|
|
||||||
|
/** SCHEDULE | RETRY */
|
||||||
|
private final String trigger;
|
||||||
|
|
||||||
|
/** 사용한 발급 구현체 클래스명 */
|
||||||
|
private final String serviceClass;
|
||||||
|
|
||||||
|
private final boolean success;
|
||||||
|
|
||||||
|
/** 발급에 걸린 시간(ms) */
|
||||||
|
private final long elapsedMs;
|
||||||
|
|
||||||
|
/** 앞 8자만 남긴 accessToken. 실패 시 null. */
|
||||||
|
private final String accessTokenMasked;
|
||||||
|
|
||||||
|
/** 발급된 토큰의 만료 시각. 실패 시 null. */
|
||||||
|
private final String expiration;
|
||||||
|
|
||||||
|
/** 실패 사유. 성공 시 null. */
|
||||||
|
private final String failReason;
|
||||||
|
|
||||||
|
TokenIssueHistory(long startTime, String trigger, String serviceClass, String accessTokenMasked, Date expiration,
|
||||||
|
String failReason) {
|
||||||
|
SimpleDateFormat formatter = new SimpleDateFormat(DATE_FORMAT);
|
||||||
|
|
||||||
|
this.issuedAt = formatter.format(new Date(startTime));
|
||||||
|
this.trigger = trigger;
|
||||||
|
this.serviceClass = serviceClass;
|
||||||
|
this.elapsedMs = System.currentTimeMillis() - startTime;
|
||||||
|
this.failReason = failReason;
|
||||||
|
this.success = failReason == null;
|
||||||
|
this.accessTokenMasked = accessTokenMasked;
|
||||||
|
this.expiration = expiration == null ? null : formatter.format(expiration);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
package com.eactive.eai.manage.oauthtoken;
|
package com.eactive.eai.manage.oauthtoken;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.eactive.eai.common.authoutbound.TokenIssueHistory;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -82,4 +85,15 @@ public class OAuthTokenStatusDTO {
|
|||||||
|
|
||||||
/** 조회 불가 사유 등 부가 메시지 */
|
/** 조회 불가 사유 등 부가 메시지 */
|
||||||
String message;
|
String message;
|
||||||
|
|
||||||
|
/** 이 응답을 만든 서버. 발급 이력이 노드 로컬이라 어느 노드인지 함께 알려준다. */
|
||||||
|
String serverName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 토큰 발급 이력 (최근 순, 최대 10건).
|
||||||
|
*
|
||||||
|
* 이 노드에서 일어난 발급만 담기며 재기동 시 사라진다.
|
||||||
|
* 목록 조회에는 담지 않고 어댑터그룹 단건 조회에서만 채운다.
|
||||||
|
*/
|
||||||
|
List<TokenIssueHistory> issueHistory;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import com.eactive.eai.adapter.AdapterPropManager;
|
|||||||
import com.eactive.eai.adapter.AdapterVO;
|
import com.eactive.eai.adapter.AdapterVO;
|
||||||
import com.eactive.eai.authoutbound.OutboundOAuthCredentialVo;
|
import com.eactive.eai.authoutbound.OutboundOAuthCredentialVo;
|
||||||
import com.eactive.eai.common.authoutbound.AccessTokenManagerByDB;
|
import com.eactive.eai.common.authoutbound.AccessTokenManagerByDB;
|
||||||
|
import com.eactive.eai.common.server.EAIServerManager;
|
||||||
import com.eactive.eai.common.util.Logger;
|
import com.eactive.eai.common.util.Logger;
|
||||||
import com.openbanking.eai.common.token.AccessTokenVO;
|
import com.openbanking.eai.common.token.AccessTokenVO;
|
||||||
import com.openbanking.eai.common.token.OAuth2AccessTokenVO;
|
import com.openbanking.eai.common.token.OAuth2AccessTokenVO;
|
||||||
@@ -52,22 +53,39 @@ public class OAuthTokenStatusService {
|
|||||||
public List<OAuthTokenStatusDTO> getStatusList() {
|
public List<OAuthTokenStatusDTO> getStatusList() {
|
||||||
List<OAuthTokenStatusDTO> result = new ArrayList<OAuthTokenStatusDTO>();
|
List<OAuthTokenStatusDTO> result = new ArrayList<OAuthTokenStatusDTO>();
|
||||||
for (String adapterGroupName : AccessTokenManagerByDB.getInstance().getRegisteredAdapterGroupNames()) {
|
for (String adapterGroupName : AccessTokenManagerByDB.getInstance().getRegisteredAdapterGroupNames()) {
|
||||||
result.add(getStatus(adapterGroupName));
|
// 목록에는 발급 이력을 담지 않는다. 그룹이 많으면 응답이 지나치게 커진다.
|
||||||
|
result.add(getStatus(adapterGroupName, false));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 어댑터그룹 하나의 토큰 현황을 발급 이력과 함께 반환한다.
|
||||||
|
*
|
||||||
|
* @param adapterGroupName 어댑터그룹명
|
||||||
|
* @return 토큰 현황. 등록돼 있지 않으면 message 만 채워서 반환한다.
|
||||||
|
*/
|
||||||
|
public OAuthTokenStatusDTO getStatus(String adapterGroupName) {
|
||||||
|
return getStatus(adapterGroupName, true);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 어댑터그룹 하나의 토큰 현황을 반환한다.
|
* 어댑터그룹 하나의 토큰 현황을 반환한다.
|
||||||
*
|
*
|
||||||
* @param adapterGroupName 어댑터그룹명
|
* @param adapterGroupName 어댑터그룹명
|
||||||
|
* @param includeHistory 토큰 발급 이력 포함 여부
|
||||||
* @return 토큰 현황. 등록돼 있지 않으면 message 만 채워서 반환한다.
|
* @return 토큰 현황. 등록돼 있지 않으면 message 만 채워서 반환한다.
|
||||||
*/
|
*/
|
||||||
public OAuthTokenStatusDTO getStatus(String adapterGroupName) {
|
private OAuthTokenStatusDTO getStatus(String adapterGroupName, boolean includeHistory) {
|
||||||
OAuthTokenStatusDTO dto = new OAuthTokenStatusDTO();
|
OAuthTokenStatusDTO dto = new OAuthTokenStatusDTO();
|
||||||
dto.setAdapterGroupName(adapterGroupName);
|
dto.setAdapterGroupName(adapterGroupName);
|
||||||
|
|
||||||
AccessTokenManagerByDB manager = AccessTokenManagerByDB.getInstance();
|
AccessTokenManagerByDB manager = AccessTokenManagerByDB.getInstance();
|
||||||
|
|
||||||
|
if (includeHistory) {
|
||||||
|
dto.setServerName(findLocalServerName());
|
||||||
|
dto.setIssueHistory(manager.getIssueHistories(adapterGroupName));
|
||||||
|
}
|
||||||
OutboundOAuthCredentialVo credential = manager.getOutboundOAuthCredentialVo(adapterGroupName);
|
OutboundOAuthCredentialVo credential = manager.getOutboundOAuthCredentialVo(adapterGroupName);
|
||||||
|
|
||||||
if (credential == null) {
|
if (credential == null) {
|
||||||
@@ -157,6 +175,19 @@ public class OAuthTokenStatusService {
|
|||||||
return dto;
|
return dto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 이 응답을 만든 서버명을 찾는다. 발급 이력이 노드 로컬이라 함께 알려준다.
|
||||||
|
*
|
||||||
|
* @return 서버명. 조회하지 못하면 null.
|
||||||
|
*/
|
||||||
|
private String findLocalServerName() {
|
||||||
|
try {
|
||||||
|
return EAIServerManager.getInstance().getLocalServerName();
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 확인이 필요한 상태 메시지를 하나로 합친다.
|
* 확인이 필요한 상태 메시지를 하나로 합친다.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
package com.eactive.eai.common.authoutbound;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import com.openbanking.eai.common.token.AccessTokenVO;
|
||||||
|
import com.openbanking.eai.common.token.OAuth2AccessTokenVO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AccessTokenManagerByDB 의 토큰 발급 이력 단위테스트
|
||||||
|
*
|
||||||
|
* 이력 기록은 private 이라 리플렉션으로 호출한다. 조회(getIssueHistories)는 공개 API 다.
|
||||||
|
*/
|
||||||
|
class TokenIssueHistoryTest {
|
||||||
|
|
||||||
|
private static final String GROUP = "TESTGRP";
|
||||||
|
|
||||||
|
private AccessTokenManagerByDB manager;
|
||||||
|
private Method recordMethod;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() throws Exception {
|
||||||
|
manager = new AccessTokenManagerByDB();
|
||||||
|
recordMethod = AccessTokenManagerByDB.class.getDeclaredMethod("recordIssueHistory", String.class, String.class,
|
||||||
|
String.class, long.class, AccessTokenVO.class, String.class);
|
||||||
|
recordMethod.setAccessible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void record(String trigger, String serviceClass, AccessTokenVO token, String failReason) throws Exception {
|
||||||
|
recordMethod.invoke(manager, GROUP, trigger, serviceClass, System.currentTimeMillis(), token, failReason);
|
||||||
|
}
|
||||||
|
|
||||||
|
private OAuth2AccessTokenVO token(String accessToken) {
|
||||||
|
OAuth2AccessTokenVO vo = new OAuth2AccessTokenVO();
|
||||||
|
vo.setAccessToken(accessToken);
|
||||||
|
vo.setExpiration(new Date(System.currentTimeMillis() + 600_000L));
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("1. 발급 성공 이력을 남기고 토큰은 마스킹한다")
|
||||||
|
void 성공이력() throws Exception {
|
||||||
|
record(TokenIssueHistory.TRIGGER_SCHEDULE, "com.example.TokenService", token("abcdefghijklmnop"), null);
|
||||||
|
|
||||||
|
List<TokenIssueHistory> histories = manager.getIssueHistories(GROUP);
|
||||||
|
|
||||||
|
assertEquals(1, histories.size());
|
||||||
|
TokenIssueHistory history = histories.get(0);
|
||||||
|
assertTrue(history.isSuccess());
|
||||||
|
assertEquals("SCHEDULE", history.getTrigger());
|
||||||
|
assertEquals("com.example.TokenService", history.getServiceClass());
|
||||||
|
assertEquals("abcdefgh***", history.getAccessTokenMasked());
|
||||||
|
assertNull(history.getFailReason());
|
||||||
|
assertFalse(history.getAccessTokenMasked().contains("ijklmnop"), "토큰 뒷부분이 남으면 안 된다");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("2. 실패 이력은 사유를 남기고 토큰은 담지 않는다")
|
||||||
|
void 실패이력() throws Exception {
|
||||||
|
record(TokenIssueHistory.TRIGGER_RETRY, "com.example.TokenService", null, "Connection refused");
|
||||||
|
|
||||||
|
TokenIssueHistory history = manager.getIssueHistories(GROUP).get(0);
|
||||||
|
|
||||||
|
assertFalse(history.isSuccess());
|
||||||
|
assertEquals("RETRY", history.getTrigger());
|
||||||
|
assertEquals("Connection refused", history.getFailReason());
|
||||||
|
assertNull(history.getAccessTokenMasked());
|
||||||
|
assertNull(history.getExpiration());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("3. 빈 토큰은 사유를 채워 실패로 남긴다")
|
||||||
|
void 빈토큰은_실패() throws Exception {
|
||||||
|
record(TokenIssueHistory.TRIGGER_SCHEDULE, "com.example.TokenService", new OAuth2AccessTokenVO(), null);
|
||||||
|
|
||||||
|
TokenIssueHistory history = manager.getIssueHistories(GROUP).get(0);
|
||||||
|
|
||||||
|
assertFalse(history.isSuccess());
|
||||||
|
assertTrue(history.getFailReason().contains("비어 있음"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("4. 최근 10건만 보관하고 최신이 앞에 온다")
|
||||||
|
void 최근10건만_보관() throws Exception {
|
||||||
|
for (int i = 1; i <= 15; i++) {
|
||||||
|
record(TokenIssueHistory.TRIGGER_SCHEDULE, "svc-" + i, token("token-" + i), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<TokenIssueHistory> histories = manager.getIssueHistories(GROUP);
|
||||||
|
|
||||||
|
assertEquals(10, histories.size());
|
||||||
|
assertEquals("svc-15", histories.get(0).getServiceClass(), "최신 건이 앞에 와야 한다");
|
||||||
|
assertEquals("svc-6", histories.get(9).getServiceClass(), "11번째부터는 밀려나야 한다");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("5. 이력이 없는 어댑터그룹은 빈 목록")
|
||||||
|
void 이력없음() {
|
||||||
|
assertTrue(manager.getIssueHistories("NO-SUCH-GROUP").isEmpty());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import static org.mockito.ArgumentMatchers.anyString;
|
|||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -275,6 +276,23 @@ class OAuthTokenStatusServiceTest {
|
|||||||
assertFalse(list.get(1).isCached());
|
assertFalse(list.get(1).isCached());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("9-1. 단건 조회에는 발급 이력을 담고 목록에는 담지 않는다")
|
||||||
|
void 이력포함범위() {
|
||||||
|
Set<String> 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());
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("10. 조회가 토큰 발급을 유발하지 않는다 (peek 만 호출)")
|
@DisplayName("10. 조회가 토큰 발급을 유발하지 않는다 (peek 만 호출)")
|
||||||
void 발급유발없음() {
|
void 발급유발없음() {
|
||||||
|
|||||||
Reference in New Issue
Block a user