9 Commits

Author SHA1 Message Date
curry772 8947fc248f chore: elink-online-common 서브모듈 포인터 업데이트 (HSM 캐시/PropManager 연동)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 12:59:26 +09:00
curry772 58f659a179 chore: elink-online-common 서브모듈 포인터 업데이트 (HSM 연동)
feat: SafeNet HSM 연동 구현 (JDK 8 / SunPKCS11) 커밋 반영
  서브모듈 d08c7ef → acdb580

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 20:52:45 +09:00
curry772 745df6d073 Merge branch 'feature/inflow-control-improvement' into master 2026-04-27 17:25:10 +09:00
curry772 4a41398f03 Merge branch 'feature/jejubank-oauth-bypass' into master 2026-04-27 16:42:03 +09:00
curry772 3a578b3c70 토큰 발급 실패 오류 추가 - 미확인오류추가 2026-04-27 15:58:36 +09:00
curry772 4d479e4fd6 refactor: DualRequestProcessor 패키지 이동 반영
- standard-message-config.properties: requestProcessor.class 경로 수정
  com.eactive.eai.common.inflow.dual → com.eactive.eai.inbound.processor
- elink-online-common 서브모듈 포인터 업데이트

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 13:50:58 +09:00
curry772 780b9bd934 feat: 그룹 유량제어 메트릭 API 추가
- GroupMetricDTO: allowed/rejectedPerSecond/rejectedThreshold/rejectRatio 등
- InflowGroupMetricService: DualInflowControlManager를 통한 메트릭 조회·초기화
  (getManager() protected 처리 — 단위 테스트 서브클래싱 지원)
- InflowGroupMetricController: 4개 엔드포인트
  GET  /{groupId}/metric, GET  /metric
  POST /{groupId}/metric/reset, POST /metric/reset
- 단위 테스트 2종 (Service 16건, Controller 8건) 전체 통과
- elink-online-common 서브모듈 포인터 업데이트

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 09:40:46 +09:00
curry772 42f52a4974 refactor: InflowGroupBucketService 리플렉션 제거
Field 리플렉션으로 groupBucketList에 직접 접근하던 getCustomGroupBucket()을
InflowControlManager.getGroupBucket() public 메서드 호출로 교체.
불필요한 java.lang.reflect.Field, Map import 및 @SuppressWarnings 제거.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 15:21:15 +09:00
curry772 1a79c39e76 Merge branch 'feature/standard-message-djb' 2026-04-23 14:14:58 +09:00
9 changed files with 633 additions and 20 deletions
@@ -1,9 +1,11 @@
package com.eactive.eai.authserver.custom;
import java.security.Principal;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
@@ -28,6 +30,7 @@ import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.eactive.eai.adapter.http.dynamic.UnkownMessageLogUtils;
import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException;
import com.eactive.eai.authserver.config.RequestContextData;
import com.eactive.eai.authserver.dao.TokenIssuanceLogDAO;
@@ -42,8 +45,6 @@ import com.eactive.eai.common.util.UUID;
import com.eactive.eai.data.entity.onl.authserver.TokenIssuanceLog;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.security.Principal;
@Controller
public class DJErpOAuth2Controller {
public static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
@@ -160,6 +161,12 @@ public class DJErpOAuth2Controller {
clientId, grantType, scopes);
logger.error(e.getMessage());
logTokenIssuance(false, false, e.getMessage(), null);
Properties logProp = new Properties();
logProp.put("clientId", clientId);
UnkownMessageLogUtils.logUnkownMessage(this.getClass().getSimpleName(), this.getClass().getSimpleName(),
tokenRequest.toString(), logProp, request, response, "RECEAIIRP301", e);
String errorJson = MessageUtil.makeJsonErrorMessage(MessageUtil.ERROR_CODE_AUTH_FAIL, e.getMessage());
int statusCode = NumberUtils.toInt(StringUtils.left(e.getCode(), 3), HttpStatus.UNAUTHORIZED.value());
return ResponseEntity.status(statusCode).body(errorJson);
@@ -169,6 +176,12 @@ public class DJErpOAuth2Controller {
clientId, grantType, scopes);
logger.error(e.getMessage());
logTokenIssuance(false, false, e.getMessage(), null);
Properties logProp = new Properties();
logProp.put("clientId", clientId);
UnkownMessageLogUtils.logUnkownMessage(this.getClass().getSimpleName(), this.getClass().getSimpleName(),
tokenRequest.toString(), logProp, request, response, "RECEAIIRP301", e);
String errorJson = MessageUtil.makeJsonErrorMessage(MessageUtil.ERROR_CODE_AUTH_FAIL, "Unauthorized. [Unknown]");
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(errorJson);
@@ -0,0 +1,40 @@
package com.eactive.eai.manage.inflow;
public class GroupMetricDTO {
private String groupId;
private String groupName;
private boolean activate;
private long allowed;
private long rejectedPerSecond;
private long rejectedThreshold;
private long totalRequests;
private double rejectRatio;
private long lastResetTime;
public String getGroupId() { return groupId; }
public void setGroupId(String groupId) { this.groupId = groupId; }
public String getGroupName() { return groupName; }
public void setGroupName(String groupName) { this.groupName = groupName; }
public boolean isActivate() { return activate; }
public void setActivate(boolean activate) { this.activate = activate; }
public long getAllowed() { return allowed; }
public void setAllowed(long allowed) { this.allowed = allowed; }
public long getRejectedPerSecond() { return rejectedPerSecond; }
public void setRejectedPerSecond(long rejectedPerSecond) { this.rejectedPerSecond = rejectedPerSecond; }
public long getRejectedThreshold() { return rejectedThreshold; }
public void setRejectedThreshold(long rejectedThreshold) { this.rejectedThreshold = rejectedThreshold; }
public long getTotalRequests() { return totalRequests; }
public void setTotalRequests(long totalRequests) { this.totalRequests = totalRequests; }
public double getRejectRatio() { return rejectRatio; }
public void setRejectRatio(double rejectRatio) { this.rejectRatio = rejectRatio; }
public long getLastResetTime() { return lastResetTime; }
public void setLastResetTime(long lastResetTime) { this.lastResetTime = lastResetTime; }
}
@@ -1,9 +1,7 @@
package com.eactive.eai.manage.inflow;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.springframework.stereotype.Service;
@@ -72,21 +70,8 @@ public class InflowGroupBucketService {
return dto;
}
/**
* 그룹 버킷 조회 (리플렉션 사용)
*/
public CustomGroupBucket getCustomGroupBucket(String groupId) {
try {
InflowControlManager manager = InflowControlManager.getInstance();
Field field = InflowControlManager.class.getDeclaredField("groupBucketList");
field.setAccessible(true);
@SuppressWarnings("unchecked")
Map<String, CustomGroupBucket> groupBucketList =
(Map<String, CustomGroupBucket>) field.get(manager);
return groupBucketList.get(groupId);
} catch (Exception e) {
return null;
}
return InflowControlManager.getInstance().getGroupBucket(groupId);
}
public List<GroupBucketStatusDTO> getAllGroupBucketStatus() {
@@ -0,0 +1,85 @@
package com.eactive.eai.manage.inflow;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/manage/inflow/group")
public class InflowGroupMetricController {
@Autowired
private InflowGroupMetricService metricService;
/**
* 특정 그룹의 메트릭 조회
* GET /manage/inflow/group/{groupId}/metric
*/
@GetMapping("/{groupId}/metric")
public ResponseEntity<?> getGroupMetric(@PathVariable String groupId) {
GroupMetricDTO metric = metricService.getGroupMetric(groupId);
if (metric == null) {
Map<String, Object> error = new HashMap<>();
error.put("success", false);
error.put("message", "그룹을 찾을 수 없습니다: " + groupId);
return ResponseEntity.ok(error);
}
Map<String, Object> result = new HashMap<>();
result.put("success", true);
result.put("data", metric);
return ResponseEntity.ok(result);
}
/**
* 모든 그룹의 메트릭 조회
* GET /manage/inflow/group/metric
*/
@GetMapping("/metric")
public ResponseEntity<?> getAllGroupMetrics() {
List<GroupMetricDTO> list = metricService.getAllGroupMetrics();
Map<String, Object> result = new HashMap<>();
result.put("success", true);
result.put("data", list);
result.put("count", list.size());
return ResponseEntity.ok(result);
}
/**
* 특정 그룹의 메트릭 초기화
* POST /manage/inflow/group/{groupId}/metric/reset
*/
@PostMapping("/{groupId}/metric/reset")
public ResponseEntity<?> resetGroupMetric(@PathVariable String groupId) {
boolean ok = metricService.resetGroupMetric(groupId);
Map<String, Object> result = new HashMap<>();
result.put("success", ok);
result.put("message", ok ? "초기화 완료: " + groupId : "그룹을 찾을 수 없습니다: " + groupId);
return ResponseEntity.ok(result);
}
/**
* 전체 그룹 메트릭 초기화
* POST /manage/inflow/group/metric/reset
*/
@PostMapping("/metric/reset")
public ResponseEntity<?> resetAllGroupMetrics() {
metricService.resetAllGroupMetrics();
Map<String, Object> result = new HashMap<>();
result.put("success", true);
result.put("message", "전체 그룹 메트릭 초기화 완료");
return ResponseEntity.ok(result);
}
}
@@ -0,0 +1,81 @@
package com.eactive.eai.manage.inflow;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Service;
import com.eactive.eai.common.inflow.Bucket;
import com.eactive.eai.common.inflow.InflowControlUtil;
import com.eactive.eai.common.inflow.InflowGroupVO;
import com.eactive.eai.common.inflow.dual.DualInflowControlManager;
@Service
public class InflowGroupMetricService {
public GroupMetricDTO getGroupMetric(String groupId) {
DualInflowControlManager manager = getManager();
if (manager == null) return null;
InflowGroupVO groupVo = manager.getGroupInflowThreshold(groupId);
if (groupVo == null) return null;
return buildDTO(groupId, groupVo, manager.getGroupMetrics(groupId));
}
public List<GroupMetricDTO> getAllGroupMetrics() {
DualInflowControlManager manager = getManager();
if (manager == null) return new ArrayList<>();
List<GroupMetricDTO> result = new ArrayList<>();
for (String groupId : manager.getGroupAllKeys()) {
GroupMetricDTO dto = getGroupMetric(groupId);
if (dto != null) result.add(dto);
}
return result;
}
public boolean resetGroupMetric(String groupId) {
DualInflowControlManager manager = getManager();
if (manager == null) return false;
if (manager.getGroupInflowThreshold(groupId) == null) return false;
manager.resetGroupMetrics(groupId);
return true;
}
public void resetAllGroupMetrics() {
DualInflowControlManager manager = getManager();
if (manager != null) manager.resetAllGroupMetrics();
}
protected DualInflowControlManager getManager() {
Bucket bucket = InflowControlUtil.getBucket();
return (bucket instanceof DualInflowControlManager)
? (DualInflowControlManager) bucket
: null;
}
private GroupMetricDTO buildDTO(String groupId, InflowGroupVO groupVo,
DualInflowControlManager.TargetMetrics m) {
GroupMetricDTO dto = new GroupMetricDTO();
dto.setGroupId(groupId);
dto.setGroupName(groupVo.getGroupName());
dto.setActivate(groupVo.isActivate());
if (m != null) {
long allowed = m.allowed.get();
long rejPs = m.rejectedPerSecond.get();
long rejTh = m.rejectedThreshold.get();
long total = allowed + rejPs + rejTh;
dto.setAllowed(allowed);
dto.setRejectedPerSecond(rejPs);
dto.setRejectedThreshold(rejTh);
dto.setTotalRequests(total);
dto.setRejectRatio(total > 0
? Math.round((double)(rejPs + rejTh) / total * 10000) / 100.0
: 0.0);
dto.setLastResetTime(m.lastResetTime);
}
return dto;
}
}
@@ -8,7 +8,7 @@ mapper.definition=standard-message-mapping-config-djb.properties
# StandardMessage Coordinator implementation
message.coordinator.class=com.eactive.eai.custom.message.StandardMessageCoordinatorDJB
# RequestProcessor implementation
#requestProcessor.class=com.eactive.eai.inbound.processor.RequestProcessor
requestProcessor.class=com.eactive.eai.inbound.processor.DualRequestProcessor
# reader : parsing input data to StandardMessage
reader.JSON=com.eactive.eai.message.parser.JsonReader
reader.JSN=com.eactive.eai.message.parser.JsonReader
@@ -0,0 +1,173 @@
package com.eactive.eai.manage.inflow;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.http.ResponseEntity;
import org.springframework.test.util.ReflectionTestUtils;
/**
* InflowGroupMetricController 단위 테스트.
*
* <p>Spring MVC를 기동하지 않고 컨트롤러를 직접 인스턴스화하여
* 응답 구조(success, data, count, message)를 검증한다.
*/
class InflowGroupMetricControllerTest {
private InflowGroupMetricController controller;
private InflowGroupMetricService mockService;
@BeforeEach
void setUp() {
controller = new InflowGroupMetricController();
mockService = mock(InflowGroupMetricService.class);
ReflectionTestUtils.setField(controller, "metricService", mockService);
}
// -------------------------------------------------------------------------
// Helper
// -------------------------------------------------------------------------
private GroupMetricDTO makeDTO(String groupId, String groupName,
long allowed, long rejPs, long rejTh) {
GroupMetricDTO dto = new GroupMetricDTO();
dto.setGroupId(groupId);
dto.setGroupName(groupName);
dto.setActivate(true);
dto.setAllowed(allowed);
dto.setRejectedPerSecond(rejPs);
dto.setRejectedThreshold(rejTh);
long total = allowed + rejPs + rejTh;
dto.setTotalRequests(total);
dto.setRejectRatio(total > 0 ? (double)(rejPs + rejTh) / total * 100 : 0.0);
dto.setLastResetTime(System.currentTimeMillis());
return dto;
}
@SuppressWarnings("unchecked")
private Map<String, Object> body(ResponseEntity<?> response) {
return (Map<String, Object>) response.getBody();
}
// =========================================================================
// GET /{groupId}/metric
// =========================================================================
@Test
void getGroupMetric_found_successTrueAndDataPresent() {
GroupMetricDTO dto = makeDTO("G1", "결제그룹", 900, 60, 40);
when(mockService.getGroupMetric("G1")).thenReturn(dto);
ResponseEntity<?> response = controller.getGroupMetric("G1");
Map<String, Object> body = body(response);
assertEquals(200, response.getStatusCodeValue());
assertEquals(Boolean.TRUE, body.get("success"));
assertSame(dto, body.get("data"));
assertFalse(body.containsKey("message"), "성공 응답에 message 필드는 없어야 합니다");
}
@Test
void getGroupMetric_notFound_successFalseWithMessage() {
when(mockService.getGroupMetric("GHOST")).thenReturn(null);
ResponseEntity<?> response = controller.getGroupMetric("GHOST");
Map<String, Object> body = body(response);
assertEquals(200, response.getStatusCodeValue());
assertEquals(Boolean.FALSE, body.get("success"));
assertTrue(body.get("message").toString().contains("GHOST"),
"오류 메시지에 groupId가 포함되어야 합니다");
}
// =========================================================================
// GET /metric
// =========================================================================
@Test
void getAllGroupMetrics_returnsListAndCount() {
List<GroupMetricDTO> list = Arrays.asList(
makeDTO("G1", "그룹1", 100, 5, 0),
makeDTO("G2", "그룹2", 200, 0, 10)
);
when(mockService.getAllGroupMetrics()).thenReturn(list);
ResponseEntity<?> response = controller.getAllGroupMetrics();
Map<String, Object> body = body(response);
assertEquals(Boolean.TRUE, body.get("success"));
assertSame(list, body.get("data"));
assertEquals(2, body.get("count"));
}
@Test
void getAllGroupMetrics_emptyList_countZero() {
when(mockService.getAllGroupMetrics()).thenReturn(Collections.emptyList());
ResponseEntity<?> response = controller.getAllGroupMetrics();
Map<String, Object> body = body(response);
assertEquals(Boolean.TRUE, body.get("success"));
assertEquals(0, body.get("count"));
}
// =========================================================================
// POST /{groupId}/metric/reset
// =========================================================================
@Test
void resetGroupMetric_success_successTrueWithGroupId() {
when(mockService.resetGroupMetric("G1")).thenReturn(true);
ResponseEntity<?> response = controller.resetGroupMetric("G1");
Map<String, Object> body = body(response);
assertEquals(Boolean.TRUE, body.get("success"));
assertTrue(body.get("message").toString().contains("G1"),
"성공 메시지에 groupId가 포함되어야 합니다");
}
@Test
void resetGroupMetric_notFound_successFalseWithGroupId() {
when(mockService.resetGroupMetric("GHOST")).thenReturn(false);
ResponseEntity<?> response = controller.resetGroupMetric("GHOST");
Map<String, Object> body = body(response);
assertEquals(Boolean.FALSE, body.get("success"));
assertTrue(body.get("message").toString().contains("GHOST"),
"오류 메시지에 groupId가 포함되어야 합니다");
}
// =========================================================================
// POST /metric/reset
// =========================================================================
@Test
void resetAllGroupMetrics_alwaysSuccessTrue() {
doNothing().when(mockService).resetAllGroupMetrics();
ResponseEntity<?> response = controller.resetAllGroupMetrics();
Map<String, Object> body = body(response);
assertEquals(Boolean.TRUE, body.get("success"));
assertNotNull(body.get("message"));
verify(mockService).resetAllGroupMetrics();
}
@Test
void resetAllGroupMetrics_callsDelegateOnce() {
doNothing().when(mockService).resetAllGroupMetrics();
controller.resetAllGroupMetrics();
verify(mockService, times(1)).resetAllGroupMetrics();
}
}
@@ -0,0 +1,236 @@
package com.eactive.eai.manage.inflow;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.util.List;
import org.junit.jupiter.api.Test;
import com.eactive.eai.common.inflow.InflowGroupVO;
import com.eactive.eai.common.inflow.dual.DualInflowControlManager;
/**
* InflowGroupMetricService 단위 테스트.
*
* <p>getManager()를 protected로 오버라이드하는 내부 테스트 서브클래스를 사용하여
* mockito-inline(static mock) 없이 DualInflowControlManager 의존성을 주입한다.
*/
class InflowGroupMetricServiceTest {
// -------------------------------------------------------------------------
// 테스트용 서브클래스 — getManager()를 오버라이드하여 mock 주입
// -------------------------------------------------------------------------
private static InflowGroupMetricService serviceWith(DualInflowControlManager manager) {
return new InflowGroupMetricService() {
@Override
protected DualInflowControlManager getManager() {
return manager;
}
};
}
// -------------------------------------------------------------------------
// Helpers
// -------------------------------------------------------------------------
private InflowGroupVO makeGroupVO(String groupId, String groupName) {
InflowGroupVO vo = new InflowGroupVO();
vo.setGroupId(groupId);
vo.setGroupName(groupName);
vo.setActivate(true);
return vo;
}
private DualInflowControlManager.TargetMetrics makeMetrics(long allowed, long rejPs, long rejTh) {
DualInflowControlManager.TargetMetrics m = new DualInflowControlManager.TargetMetrics();
m.allowed.set(allowed);
m.rejectedPerSecond.set(rejPs);
m.rejectedThreshold.set(rejTh);
return m;
}
// =========================================================================
// getGroupMetric
// =========================================================================
@Test
void getGroupMetric_managerIsNull_returnsNull() {
assertNull(serviceWith(null).getGroupMetric("G1"));
}
@Test
void getGroupMetric_groupNotFound_returnsNull() {
DualInflowControlManager manager = mock(DualInflowControlManager.class);
when(manager.getGroupInflowThreshold("G1")).thenReturn(null);
assertNull(serviceWith(manager).getGroupMetric("G1"));
}
@Test
void getGroupMetric_noMetricsYet_returnsZeroCounters() {
DualInflowControlManager manager = mock(DualInflowControlManager.class);
when(manager.getGroupInflowThreshold("G1")).thenReturn(makeGroupVO("G1", "결제그룹"));
when(manager.getGroupMetrics("G1")).thenReturn(null);
GroupMetricDTO dto = serviceWith(manager).getGroupMetric("G1");
assertNotNull(dto);
assertEquals("G1", dto.getGroupId());
assertEquals("결제그룹", dto.getGroupName());
assertTrue(dto.isActivate());
assertEquals(0, dto.getAllowed());
assertEquals(0, dto.getRejectedPerSecond());
assertEquals(0, dto.getRejectedThreshold());
assertEquals(0, dto.getTotalRequests());
assertEquals(0.0, dto.getRejectRatio());
assertEquals(0, dto.getLastResetTime());
}
@Test
void getGroupMetric_withMetrics_returnsCorrectValues() {
DualInflowControlManager manager = mock(DualInflowControlManager.class);
when(manager.getGroupInflowThreshold("G1")).thenReturn(makeGroupVO("G1", "결제그룹"));
when(manager.getGroupMetrics("G1")).thenReturn(makeMetrics(900, 60, 40));
GroupMetricDTO dto = serviceWith(manager).getGroupMetric("G1");
assertEquals(900, dto.getAllowed());
assertEquals(60, dto.getRejectedPerSecond());
assertEquals(40, dto.getRejectedThreshold());
assertEquals(1000, dto.getTotalRequests());
}
@Test
void getGroupMetric_rejectRatioCalculation() {
DualInflowControlManager manager = mock(DualInflowControlManager.class);
when(manager.getGroupInflowThreshold("G1")).thenReturn(makeGroupVO("G1", "G1"));
// allowed=90, rejPs=5, rejTh=5 → reject=10/100 = 10.00%
when(manager.getGroupMetrics("G1")).thenReturn(makeMetrics(90, 5, 5));
GroupMetricDTO dto = serviceWith(manager).getGroupMetric("G1");
assertEquals(10.0, dto.getRejectRatio(), 0.01);
}
@Test
void getGroupMetric_zeroTotal_rejectRatioIsZero() {
DualInflowControlManager manager = mock(DualInflowControlManager.class);
when(manager.getGroupInflowThreshold("G1")).thenReturn(makeGroupVO("G1", "G1"));
when(manager.getGroupMetrics("G1")).thenReturn(makeMetrics(0, 0, 0));
GroupMetricDTO dto = serviceWith(manager).getGroupMetric("G1");
assertEquals(0.0, dto.getRejectRatio(), 0.0001);
}
@Test
void getGroupMetric_lastResetTimeIsPropagated() {
DualInflowControlManager.TargetMetrics m = makeMetrics(10, 0, 0);
m.lastResetTime = 1234567890L;
DualInflowControlManager manager = mock(DualInflowControlManager.class);
when(manager.getGroupInflowThreshold("G1")).thenReturn(makeGroupVO("G1", "G1"));
when(manager.getGroupMetrics("G1")).thenReturn(m);
GroupMetricDTO dto = serviceWith(manager).getGroupMetric("G1");
assertEquals(1234567890L, dto.getLastResetTime());
}
// =========================================================================
// getAllGroupMetrics
// =========================================================================
@Test
void getAllGroupMetrics_managerIsNull_returnsEmptyList() {
List<GroupMetricDTO> result = serviceWith(null).getAllGroupMetrics();
assertNotNull(result);
assertTrue(result.isEmpty());
}
@Test
void getAllGroupMetrics_noGroups_returnsEmptyList() {
DualInflowControlManager manager = mock(DualInflowControlManager.class);
when(manager.getGroupAllKeys()).thenReturn(new String[]{});
assertTrue(serviceWith(manager).getAllGroupMetrics().isEmpty());
}
@Test
void getAllGroupMetrics_twoGroups_returnsBoth() {
DualInflowControlManager manager = mock(DualInflowControlManager.class);
when(manager.getGroupAllKeys()).thenReturn(new String[]{"G1", "G2"});
when(manager.getGroupInflowThreshold("G1")).thenReturn(makeGroupVO("G1", "그룹1"));
when(manager.getGroupInflowThreshold("G2")).thenReturn(makeGroupVO("G2", "그룹2"));
when(manager.getGroupMetrics("G1")).thenReturn(makeMetrics(10, 0, 0));
when(manager.getGroupMetrics("G2")).thenReturn(makeMetrics(20, 5, 0));
List<GroupMetricDTO> result = serviceWith(manager).getAllGroupMetrics();
assertEquals(2, result.size());
assertEquals("G1", result.get(0).getGroupId());
assertEquals("G2", result.get(1).getGroupId());
}
@Test
void getAllGroupMetrics_groupNotFoundIsSkipped() {
DualInflowControlManager manager = mock(DualInflowControlManager.class);
when(manager.getGroupAllKeys()).thenReturn(new String[]{"G1", "GHOST"});
when(manager.getGroupInflowThreshold("G1")).thenReturn(makeGroupVO("G1", "그룹1"));
when(manager.getGroupInflowThreshold("GHOST")).thenReturn(null);
when(manager.getGroupMetrics("G1")).thenReturn(makeMetrics(5, 0, 0));
List<GroupMetricDTO> result = serviceWith(manager).getAllGroupMetrics();
assertEquals(1, result.size());
assertEquals("G1", result.get(0).getGroupId());
}
// =========================================================================
// resetGroupMetric
// =========================================================================
@Test
void resetGroupMetric_managerIsNull_returnsFalse() {
assertFalse(serviceWith(null).resetGroupMetric("G1"));
}
@Test
void resetGroupMetric_groupNotFound_returnsFalse() {
DualInflowControlManager manager = mock(DualInflowControlManager.class);
when(manager.getGroupInflowThreshold("G1")).thenReturn(null);
assertFalse(serviceWith(manager).resetGroupMetric("G1"));
verify(manager, never()).resetGroupMetrics(any());
}
@Test
void resetGroupMetric_success_returnsTrueAndCallsManager() {
DualInflowControlManager manager = mock(DualInflowControlManager.class);
when(manager.getGroupInflowThreshold("G1")).thenReturn(makeGroupVO("G1", "그룹1"));
assertTrue(serviceWith(manager).resetGroupMetric("G1"));
verify(manager).resetGroupMetrics("G1");
}
// =========================================================================
// resetAllGroupMetrics
// =========================================================================
@Test
void resetAllGroupMetrics_managerIsNull_noException() {
assertDoesNotThrow(() -> serviceWith(null).resetAllGroupMetrics());
}
@Test
void resetAllGroupMetrics_callsManagerReset() {
DualInflowControlManager manager = mock(DualInflowControlManager.class);
serviceWith(manager).resetAllGroupMetrics();
verify(manager).resetAllGroupMetrics();
}
}