From 31171d01878233886e467a63c84a250972ed7584 Mon Sep 17 00:00:00 2001 From: curry772 Date: Thu, 7 May 2026 18:02:51 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=ED=81=B4=EB=9D=BC=EC=9D=B4=EC=96=B8?= =?UTF-8?q?=ED=8A=B8=20=EC=9C=A0=EB=9F=89=EC=A0=9C=EC=96=B4/RequestProcess?= =?UTF-8?q?or=20custom=20=ED=8C=A8=ED=82=A4=EC=A7=80=EB=A1=9C=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DJBRequestProcessor: inbound.processor → custom.inbound.processor 이동 - ClientInflowTargetMetricService/Controller: custom.inflow 패키지 신규 생성 클라이언트 메트릭 API(/manage/inflow/client/**)를 InflowTargetMetric*에서 분리 - InflowTargetMetricService/Controller: 클라이언트 관련 메서드/엔드포인트 제거 - InflowTargetBucketService: getClientDualManager() 분리 반영 - standard-message-config.properties: requestProcessor.class 경로 업데이트 - 단위 테스트: ClientInflowTargetMetric{Service,Controller}Test 추가, 기존 테스트에서 클라이언트 케이스 분리 Co-Authored-By: Claude Sonnet 4.6 --- elink-online-common | 2 +- .../processor/DJBRequestProcessor.java | 81 ++++++ .../ClientInflowTargetMetricController.java | 94 +++++++ .../ClientInflowTargetMetricService.java | 79 ++++++ .../inflow/InflowTargetBucketService.java | 10 +- .../inflow/InflowTargetMetricController.java | 35 +-- .../inflow/InflowTargetMetricService.java | 36 --- .../standard-message-config.properties | 2 +- .../processor/DJBRequestProcessorTest.java | 239 ++++++++++++++++++ ...lientInflowTargetMetricControllerTest.java | 144 +++++++++++ .../ClientInflowTargetMetricServiceTest.java | 177 +++++++++++++ .../inflow/InflowTargetBucketServiceTest.java | 56 ++-- .../InflowTargetMetricControllerTest.java | 88 ------- .../inflow/InflowTargetMetricServiceTest.java | 60 +---- 14 files changed, 860 insertions(+), 243 deletions(-) create mode 100644 src/main/java/com/eactive/eai/custom/inbound/processor/DJBRequestProcessor.java create mode 100644 src/main/java/com/eactive/eai/custom/inflow/ClientInflowTargetMetricController.java create mode 100644 src/main/java/com/eactive/eai/custom/inflow/ClientInflowTargetMetricService.java create mode 100644 src/test/java/com/eactive/eai/custom/inbound/processor/DJBRequestProcessorTest.java create mode 100644 src/test/java/com/eactive/eai/custom/inflow/ClientInflowTargetMetricControllerTest.java create mode 100644 src/test/java/com/eactive/eai/custom/inflow/ClientInflowTargetMetricServiceTest.java diff --git a/elink-online-common b/elink-online-common index 437961e..8900966 160000 --- a/elink-online-common +++ b/elink-online-common @@ -1 +1 @@ -Subproject commit 437961e7d518df958b2b3109bf19d0c8beec4b3f +Subproject commit 8900966d71dbde9ca8a09b5e97e5629c4fbe69fa diff --git a/src/main/java/com/eactive/eai/custom/inbound/processor/DJBRequestProcessor.java b/src/main/java/com/eactive/eai/custom/inbound/processor/DJBRequestProcessor.java new file mode 100644 index 0000000..d2f8759 --- /dev/null +++ b/src/main/java/com/eactive/eai/custom/inbound/processor/DJBRequestProcessor.java @@ -0,0 +1,81 @@ +package com.eactive.eai.custom.inbound.processor; + +import org.apache.commons.lang3.StringUtils; + +import com.eactive.eai.common.inflow.Bucket; +import com.eactive.eai.common.inflow.InflowTargetVO; +import com.eactive.eai.common.inflow.dual.ClientDualBucket; +import com.eactive.eai.common.inflow.dual.DualBucket; +import com.eactive.eai.common.inflow.dual.DualCustomBucket; +import com.eactive.eai.inbound.processor.RequestProcessor; + +/** + * 어댑터/인터페이스/클라이언트 유량제어 차단 시 어느 버킷(PER_SECOND/THRESHOLD)에서 + * 차단됐는지 에러 메시지에 포함하는 RequestProcessor 확장. + * + *

ClientDualInflowControlManager와 함께 사용: + *

+ * inflow.control.bucket.className=com.eactive.eai.common.inflow.dual.ClientDualInflowControlManager
+ * 
+ * + *

Spring Bean 등록 후 기존 RequestProcessor 대신 이 클래스를 어댑터에 설정한다. + */ +public class DJBRequestProcessor extends RequestProcessor { + + @Override + protected String checkClientInflow(Bucket bucket, String clientId) { + if (StringUtils.isBlank(clientId)) return null; + if (bucket instanceof ClientDualBucket) { + return ((ClientDualBucket) bucket).isClientPassDetail(clientId); + } + return null; + } + + @Override + protected InflowTargetVO resolveClientInflowThreshold(Bucket bucket, String clientId) { + if (bucket instanceof ClientDualBucket) { + return ((ClientDualBucket) bucket).getClientInflowThreshold(clientId); + } + return null; + } + + @Override + protected String checkAdapterInflow(Bucket bucket, String adapterGroupName) { + if (bucket instanceof DualBucket) { + return ((DualBucket) bucket).isAdapterPassDetail(adapterGroupName); + } + return super.checkAdapterInflow(bucket, adapterGroupName); + } + + @Override + protected String checkInterfaceInflow(Bucket bucket, String eaiSvcCd) { + if (bucket instanceof DualBucket) { + return ((DualBucket) bucket).isInterfacePassDetail(eaiSvcCd); + } + return super.checkInterfaceInflow(bucket, eaiSvcCd); + } + + /** + * 차단 원인에 따라 에러 메시지에 한도 정보를 포함. + *

+ */ + @Override + protected String buildInflowTargetErrorMsg(InflowTargetVO inflowTargetVO, String blockedType) { + if (DualCustomBucket.RESULT_BLOCKED_PER_SECOND.equals(blockedType)) { + return String.format("API 호출 한도 초과 [%s: %dreq/sec]", + inflowTargetVO.getName(), + inflowTargetVO.getThresholdPerSecond()); + } + if (DualCustomBucket.RESULT_BLOCKED_THRESHOLD.equals(blockedType)) { + return String.format("API 호출 한도 초과 [%s: %dreq/%s]", + inflowTargetVO.getName(), + inflowTargetVO.getThreshold(), + inflowTargetVO.getThresholdTimeUnit()); + } + return super.buildInflowTargetErrorMsg(inflowTargetVO, blockedType); + } +} diff --git a/src/main/java/com/eactive/eai/custom/inflow/ClientInflowTargetMetricController.java b/src/main/java/com/eactive/eai/custom/inflow/ClientInflowTargetMetricController.java new file mode 100644 index 0000000..b1a71f6 --- /dev/null +++ b/src/main/java/com/eactive/eai/custom/inflow/ClientInflowTargetMetricController.java @@ -0,0 +1,94 @@ +package com.eactive.eai.custom.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; + +import com.eactive.eai.manage.inflow.TargetMetricDTO; + +/** + * 클라이언트 유량제어 메트릭 API. + * + * ClientDualInflowControlManager 가 활성화된 환경에서만 정상 응답한다. + * + * GET /manage/inflow/client/metric → 전체 클라이언트 메트릭 + * GET /manage/inflow/client/{clientId}/metric → 특정 클라이언트 메트릭 + * POST /manage/inflow/client/metric/reset → 전체 클라이언트 메트릭 초기화 + * POST /manage/inflow/client/{clientId}/metric/reset → 특정 클라이언트 메트릭 초기화 + */ +@RestController +@RequestMapping("/manage/inflow") +public class ClientInflowTargetMetricController { + + @Autowired + private ClientInflowTargetMetricService metricService; + + @GetMapping("/client/metric") + public ResponseEntity getAllClientMetrics() { + List list = metricService.getAllClientMetrics(); + return okList(list); + } + + @GetMapping("/client/{clientId}/metric") + public ResponseEntity getClientMetric(@PathVariable String clientId) { + TargetMetricDTO dto = metricService.getClientMetric(clientId); + return okSingle(dto, "클라이언트를 찾을 수 없습니다: " + clientId); + } + + @PostMapping("/client/metric/reset") + public ResponseEntity resetAllClientMetrics() { + metricService.resetAllClientMetrics(); + return okReset("전체 클라이언트 메트릭 초기화 완료"); + } + + @PostMapping("/client/{clientId}/metric/reset") + public ResponseEntity resetClientMetric(@PathVariable String clientId) { + boolean ok = metricService.resetClientMetric(clientId); + return okResetSingle(ok, clientId, "클라이언트"); + } + + private ResponseEntity okList(List list) { + Map result = new HashMap<>(); + result.put("success", true); + result.put("data", list); + result.put("count", list.size()); + return ResponseEntity.ok(result); + } + + private ResponseEntity okSingle(TargetMetricDTO dto, String notFoundMsg) { + if (dto == null) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("message", notFoundMsg); + return ResponseEntity.ok(error); + } + Map result = new HashMap<>(); + result.put("success", true); + result.put("data", dto); + return ResponseEntity.ok(result); + } + + private ResponseEntity okReset(String message) { + Map result = new HashMap<>(); + result.put("success", true); + result.put("message", message); + return ResponseEntity.ok(result); + } + + private ResponseEntity okResetSingle(boolean ok, String targetId, String targetLabel) { + Map result = new HashMap<>(); + result.put("success", ok); + result.put("message", ok + ? "초기화 완료: " + targetId + : targetLabel + "를 찾을 수 없습니다: " + targetId); + return ResponseEntity.ok(result); + } +} diff --git a/src/main/java/com/eactive/eai/custom/inflow/ClientInflowTargetMetricService.java b/src/main/java/com/eactive/eai/custom/inflow/ClientInflowTargetMetricService.java new file mode 100644 index 0000000..39df98e --- /dev/null +++ b/src/main/java/com/eactive/eai/custom/inflow/ClientInflowTargetMetricService.java @@ -0,0 +1,79 @@ +package com.eactive.eai.custom.inflow; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.stereotype.Service; + +import com.eactive.eai.common.inflow.AbstractInflowControlManager; +import com.eactive.eai.common.inflow.InflowControlUtil; +import com.eactive.eai.common.inflow.InflowTargetVO; +import com.eactive.eai.common.inflow.dual.ClientDualInflowControlManager; +import com.eactive.eai.common.inflow.dual.DualInflowControlManager; +import com.eactive.eai.manage.inflow.TargetMetricDTO; + +@Service +public class ClientInflowTargetMetricService { + + public TargetMetricDTO getClientMetric(String clientId) { + ClientDualInflowControlManager manager = getClientDualManager(); + if (manager == null) return null; + InflowTargetVO vo = manager.getClientInflowThreshold(clientId); + if (vo == null) return null; + return buildDTO(clientId, "CLIENT", vo, manager.getClientMetrics(clientId)); + } + + public List getAllClientMetrics() { + ClientDualInflowControlManager manager = getClientDualManager(); + if (manager == null) return new ArrayList<>(); + List result = new ArrayList<>(); + for (String id : manager.getClientBucketMap().keySet()) { + TargetMetricDTO dto = getClientMetric(id); + if (dto != null) result.add(dto); + } + return result; + } + + public boolean resetClientMetric(String clientId) { + ClientDualInflowControlManager manager = getClientDualManager(); + if (manager == null) return false; + if (manager.getClientInflowThreshold(clientId) == null) return false; + manager.resetClientMetrics(clientId); + return true; + } + + public void resetAllClientMetrics() { + ClientDualInflowControlManager manager = getClientDualManager(); + if (manager != null) manager.resetAllClientMetrics(); + } + + protected ClientDualInflowControlManager getClientDualManager() { + AbstractInflowControlManager base = InflowControlUtil.getInflowControlManager(); + return (base instanceof ClientDualInflowControlManager) ? (ClientDualInflowControlManager) base : null; + } + + private TargetMetricDTO buildDTO(String targetId, String targetType, + InflowTargetVO vo, + DualInflowControlManager.TargetMetrics m) { + TargetMetricDTO dto = new TargetMetricDTO(); + dto.setTargetId(targetId); + dto.setTargetType(targetType); + dto.setActivate(vo.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; + } +} diff --git a/src/main/java/com/eactive/eai/manage/inflow/InflowTargetBucketService.java b/src/main/java/com/eactive/eai/manage/inflow/InflowTargetBucketService.java index 5773a9c..148d6de 100644 --- a/src/main/java/com/eactive/eai/manage/inflow/InflowTargetBucketService.java +++ b/src/main/java/com/eactive/eai/manage/inflow/InflowTargetBucketService.java @@ -9,6 +9,7 @@ import org.springframework.stereotype.Service; import com.eactive.eai.common.inflow.AbstractInflowControlManager; import com.eactive.eai.common.inflow.InflowControlUtil; import com.eactive.eai.common.inflow.InflowTargetVO; +import com.eactive.eai.common.inflow.dual.ClientDualInflowControlManager; import com.eactive.eai.common.inflow.dual.DualCustomBucket; import com.eactive.eai.common.inflow.dual.DualInflowControlManager; @@ -62,7 +63,7 @@ public class InflowTargetBucketService { // ------------------------------------------------------------------------- public TargetBucketStatusDTO getClientBucketStatus(String clientId) { - DualInflowControlManager manager = getDualManager(); + ClientDualInflowControlManager manager = getClientDualManager(); if (manager == null) return null; DualCustomBucket bucket = manager.getClientBucket(clientId); if (bucket == null) return null; @@ -70,7 +71,7 @@ public class InflowTargetBucketService { } public List getAllClientBucketStatus() { - DualInflowControlManager manager = getDualManager(); + ClientDualInflowControlManager manager = getClientDualManager(); if (manager == null) return new ArrayList<>(); return toDtoList("CLIENT", manager.getClientBucketMap()); } @@ -84,6 +85,11 @@ public class InflowTargetBucketService { return (base instanceof DualInflowControlManager) ? (DualInflowControlManager) base : null; } + protected ClientDualInflowControlManager getClientDualManager() { + AbstractInflowControlManager base = InflowControlUtil.getInflowControlManager(); + return (base instanceof ClientDualInflowControlManager) ? (ClientDualInflowControlManager) base : null; + } + private TargetBucketStatusDTO toDto(String targetId, String targetType, DualCustomBucket bucket) { InflowTargetVO vo = bucket.getInflowTargetVo(); List buckets = new ArrayList<>(); diff --git a/src/main/java/com/eactive/eai/manage/inflow/InflowTargetMetricController.java b/src/main/java/com/eactive/eai/manage/inflow/InflowTargetMetricController.java index 31bfc90..b73c832 100644 --- a/src/main/java/com/eactive/eai/manage/inflow/InflowTargetMetricController.java +++ b/src/main/java/com/eactive/eai/manage/inflow/InflowTargetMetricController.java @@ -13,7 +13,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** - * 어댑터/인터페이스/클라이언트 유량제어 메트릭 API. + * 어댑터/인터페이스 유량제어 메트릭 API. * * DualInflowControlManager 가 활성화된 환경에서만 정상 응답한다. * @@ -26,11 +26,6 @@ import org.springframework.web.bind.annotation.RestController; * GET /manage/inflow/interface/{interfaceId}/metric → 특정 인터페이스 메트릭 * POST /manage/inflow/interface/metric/reset → 전체 인터페이스 메트릭 초기화 * POST /manage/inflow/interface/{interfaceId}/metric/reset → 특정 인터페이스 메트릭 초기화 - * - * GET /manage/inflow/client/metric → 전체 클라이언트 메트릭 - * GET /manage/inflow/client/{clientId}/metric → 특정 클라이언트 메트릭 - * POST /manage/inflow/client/metric/reset → 전체 클라이언트 메트릭 초기화 - * POST /manage/inflow/client/{clientId}/metric/reset → 특정 클라이언트 메트릭 초기화 */ @RestController @RequestMapping("/manage/inflow") @@ -95,34 +90,6 @@ public class InflowTargetMetricController { return okResetSingle(ok, interfaceId, "인터페이스"); } - // ========================================================================= - // 클라이언트 - // ========================================================================= - - @GetMapping("/client/metric") - public ResponseEntity getAllClientMetrics() { - List list = metricService.getAllClientMetrics(); - return okList(list); - } - - @GetMapping("/client/{clientId}/metric") - public ResponseEntity getClientMetric(@PathVariable String clientId) { - TargetMetricDTO dto = metricService.getClientMetric(clientId); - return okSingle(dto, "클라이언트를 찾을 수 없습니다: " + clientId); - } - - @PostMapping("/client/metric/reset") - public ResponseEntity resetAllClientMetrics() { - metricService.resetAllClientMetrics(); - return okReset("전체 클라이언트 메트릭 초기화 완료"); - } - - @PostMapping("/client/{clientId}/metric/reset") - public ResponseEntity resetClientMetric(@PathVariable String clientId) { - boolean ok = metricService.resetClientMetric(clientId); - return okResetSingle(ok, clientId, "클라이언트"); - } - // ========================================================================= // Helpers // ========================================================================= diff --git a/src/main/java/com/eactive/eai/manage/inflow/InflowTargetMetricService.java b/src/main/java/com/eactive/eai/manage/inflow/InflowTargetMetricService.java index 5eadad3..9134e3d 100644 --- a/src/main/java/com/eactive/eai/manage/inflow/InflowTargetMetricService.java +++ b/src/main/java/com/eactive/eai/manage/inflow/InflowTargetMetricService.java @@ -85,42 +85,6 @@ public class InflowTargetMetricService { if (manager != null) manager.resetAllInterfaceMetrics(); } - // ------------------------------------------------------------------------- - // 클라이언트 - // ------------------------------------------------------------------------- - - public TargetMetricDTO getClientMetric(String clientId) { - DualInflowControlManager manager = getDualManager(); - if (manager == null) return null; - InflowTargetVO vo = manager.getClientInflowThreshold(clientId); - if (vo == null) return null; - return buildDTO(clientId, "CLIENT", vo, manager.getClientMetrics(clientId)); - } - - public List getAllClientMetrics() { - DualInflowControlManager manager = getDualManager(); - if (manager == null) return new ArrayList<>(); - List result = new ArrayList<>(); - for (String id : manager.getClientBucketMap().keySet()) { - TargetMetricDTO dto = getClientMetric(id); - if (dto != null) result.add(dto); - } - return result; - } - - public boolean resetClientMetric(String clientId) { - DualInflowControlManager manager = getDualManager(); - if (manager == null) return false; - if (manager.getClientInflowThreshold(clientId) == null) return false; - manager.resetClientMetrics(clientId); - return true; - } - - public void resetAllClientMetrics() { - DualInflowControlManager manager = getDualManager(); - if (manager != null) manager.resetAllClientMetrics(); - } - // ------------------------------------------------------------------------- // Private helpers // ------------------------------------------------------------------------- diff --git a/src/main/resources/standard-message-config.properties b/src/main/resources/standard-message-config.properties index c28227a..a3fec4f 100644 --- a/src/main/resources/standard-message-config.properties +++ b/src/main/resources/standard-message-config.properties @@ -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.DualRequestProcessor +requestProcessor.class=com.eactive.eai.custom.inbound.processor.DJBRequestProcessor # reader : parsing input data to StandardMessage reader.JSON=com.eactive.eai.message.parser.JsonReader reader.JSN=com.eactive.eai.message.parser.JsonReader diff --git a/src/test/java/com/eactive/eai/custom/inbound/processor/DJBRequestProcessorTest.java b/src/test/java/com/eactive/eai/custom/inbound/processor/DJBRequestProcessorTest.java new file mode 100644 index 0000000..9b796b6 --- /dev/null +++ b/src/test/java/com/eactive/eai/custom/inbound/processor/DJBRequestProcessorTest.java @@ -0,0 +1,239 @@ +package com.eactive.eai.custom.inbound.processor; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.context.ApplicationContext; +import org.springframework.test.util.ReflectionTestUtils; + +import com.eactive.eai.common.inflow.Bucket; +import com.eactive.eai.common.inflow.InflowTargetVO; +import com.eactive.eai.common.inflow.dual.ClientDualBucket; +import com.eactive.eai.common.inflow.dual.DualBucket; +import com.eactive.eai.common.inflow.dual.DualCustomBucket; +import com.eactive.eai.common.server.EAIServerManager; +import com.eactive.eai.common.util.ApplicationContextProvider; + +/** + * DJBRequestProcessor 단위 테스트. + * + *

RequestProcessor 클래스는 static 초기화 블록에서 EAIServerManager.getInstance()를 호출한다. + * @BeforeAll에서 mock ApplicationContext를 주입한 뒤 DJBRequestProcessor를 최초 로딩시킨다. + * + *

주의: DJBRequestProcessor를 필드 타입으로 선언하면 테스트 클래스 로딩 시 함께 로딩되어 + * @BeforeAll 실행 전에 NPE가 발생한다. 반드시 메서드 본문 내에서만 참조해야 한다. + */ +class DJBRequestProcessorTest { + + @BeforeAll + static void setupMockApplicationContext() { + ApplicationContext mockContext = mock(ApplicationContext.class); + EAIServerManager mockServerManager = mock(EAIServerManager.class); + when(mockContext.getBean(EAIServerManager.class)).thenReturn(mockServerManager); + when(mockServerManager.getLocalServerName()).thenReturn("TEST-SERVER"); + when(mockServerManager.getGroupInstId()).thenReturn("TEST-INST"); + + ReflectionTestUtils.setField(ApplicationContextProvider.class, "context", mockContext); + } + + private InflowTargetVO makeVO(String name, long perSec, long threshold, String unit) { + InflowTargetVO vo = new InflowTargetVO(); + vo.setName(name); + vo.setThresholdPerSecond(perSec); + vo.setThreshold(threshold); + vo.setThresholdTimeUnit(unit); + vo.setActivate(true); + return vo; + } + + // ------------------------------------------------------------------------- + // checkAdapterInflow + // ------------------------------------------------------------------------- + + @Test + void checkAdapterInflow_dualBucket_pass_returnsNull() { + DJBRequestProcessor processor = new DJBRequestProcessor(); + DualBucket mockBucket = mock(DualBucket.class); + when(mockBucket.isAdapterPassDetail("ADAPTER_A")).thenReturn(null); + + assertNull(processor.checkAdapterInflow(mockBucket, "ADAPTER_A")); + verify(mockBucket).isAdapterPassDetail("ADAPTER_A"); + } + + @Test + void checkAdapterInflow_dualBucket_blockedPerSecond() { + DJBRequestProcessor processor = new DJBRequestProcessor(); + DualBucket mockBucket = mock(DualBucket.class); + when(mockBucket.isAdapterPassDetail("ADAPTER_A")).thenReturn(DualCustomBucket.RESULT_BLOCKED_PER_SECOND); + + assertEquals(DualCustomBucket.RESULT_BLOCKED_PER_SECOND, + processor.checkAdapterInflow(mockBucket, "ADAPTER_A")); + } + + @Test + void checkAdapterInflow_dualBucket_blockedThreshold() { + DJBRequestProcessor processor = new DJBRequestProcessor(); + DualBucket mockBucket = mock(DualBucket.class); + when(mockBucket.isAdapterPassDetail("ADAPTER_A")).thenReturn(DualCustomBucket.RESULT_BLOCKED_THRESHOLD); + + assertEquals(DualCustomBucket.RESULT_BLOCKED_THRESHOLD, + processor.checkAdapterInflow(mockBucket, "ADAPTER_A")); + } + + @Test + void checkAdapterInflow_nonDualBucket_pass_returnsNull() { + DJBRequestProcessor processor = new DJBRequestProcessor(); + Bucket mockBucket = mock(Bucket.class); + when(mockBucket.isAdapterPass("ADAPTER_A")).thenReturn(true); + + assertNull(processor.checkAdapterInflow(mockBucket, "ADAPTER_A")); + } + + @Test + void checkAdapterInflow_nonDualBucket_blocked_returnsBlocked() { + DJBRequestProcessor processor = new DJBRequestProcessor(); + Bucket mockBucket = mock(Bucket.class); + when(mockBucket.isAdapterPass("ADAPTER_A")).thenReturn(false); + + assertEquals("BLOCKED", processor.checkAdapterInflow(mockBucket, "ADAPTER_A")); + } + + // ------------------------------------------------------------------------- + // checkInterfaceInflow + // ------------------------------------------------------------------------- + + @Test + void checkInterfaceInflow_dualBucket_pass_returnsNull() { + DJBRequestProcessor processor = new DJBRequestProcessor(); + DualBucket mockBucket = mock(DualBucket.class); + when(mockBucket.isInterfacePassDetail("IF_001")).thenReturn(null); + + assertNull(processor.checkInterfaceInflow(mockBucket, "IF_001")); + verify(mockBucket).isInterfacePassDetail("IF_001"); + } + + @Test + void checkInterfaceInflow_dualBucket_blockedPerSecond() { + DJBRequestProcessor processor = new DJBRequestProcessor(); + DualBucket mockBucket = mock(DualBucket.class); + when(mockBucket.isInterfacePassDetail("IF_001")).thenReturn(DualCustomBucket.RESULT_BLOCKED_PER_SECOND); + + assertEquals(DualCustomBucket.RESULT_BLOCKED_PER_SECOND, + processor.checkInterfaceInflow(mockBucket, "IF_001")); + } + + @Test + void checkInterfaceInflow_nonDualBucket_pass_returnsNull() { + DJBRequestProcessor processor = new DJBRequestProcessor(); + Bucket mockBucket = mock(Bucket.class); + when(mockBucket.isInterfacePass("IF_001")).thenReturn(true); + + assertNull(processor.checkInterfaceInflow(mockBucket, "IF_001")); + } + + @Test + void checkInterfaceInflow_nonDualBucket_blocked_returnsBlocked() { + DJBRequestProcessor processor = new DJBRequestProcessor(); + Bucket mockBucket = mock(Bucket.class); + when(mockBucket.isInterfacePass("IF_001")).thenReturn(false); + + assertEquals("BLOCKED", processor.checkInterfaceInflow(mockBucket, "IF_001")); + } + + // ------------------------------------------------------------------------- + // checkClientInflow — ClientDualBucket 기반 + // ------------------------------------------------------------------------- + + @Test + void checkClientInflow_clientDualBucket_pass_returnsNull() { + DJBRequestProcessor processor = new DJBRequestProcessor(); + ClientDualBucket mockBucket = mock(ClientDualBucket.class); + when(mockBucket.isClientPassDetail("CLIENT_A")).thenReturn(null); + + assertNull(processor.checkClientInflow(mockBucket, "CLIENT_A")); + verify(mockBucket).isClientPassDetail("CLIENT_A"); + } + + @Test + void checkClientInflow_clientDualBucket_blocked() { + DJBRequestProcessor processor = new DJBRequestProcessor(); + ClientDualBucket mockBucket = mock(ClientDualBucket.class); + when(mockBucket.isClientPassDetail("CLIENT_A")).thenReturn(DualCustomBucket.RESULT_BLOCKED_THRESHOLD); + + assertEquals(DualCustomBucket.RESULT_BLOCKED_THRESHOLD, + processor.checkClientInflow(mockBucket, "CLIENT_A")); + } + + @Test + void checkClientInflow_nonClientDualBucket_returnsNull() { + DJBRequestProcessor processor = new DJBRequestProcessor(); + Bucket mockBucket = mock(Bucket.class); + + assertNull(processor.checkClientInflow(mockBucket, "CLIENT_A")); + } + + @Test + void checkClientInflow_blankClientId_returnsNull() { + DJBRequestProcessor processor = new DJBRequestProcessor(); + ClientDualBucket mockBucket = mock(ClientDualBucket.class); + + assertNull(processor.checkClientInflow(mockBucket, "")); + verify(mockBucket, never()).isClientPassDetail(any()); + } + + // ------------------------------------------------------------------------- + // buildInflowTargetErrorMsg + // ------------------------------------------------------------------------- + + @Test + void buildInflowTargetErrorMsg_perSecond_includesReqPerSec() { + DJBRequestProcessor processor = new DJBRequestProcessor(); + InflowTargetVO vo = makeVO("결제API", 30, 1000, "MIN"); + + String msg = processor.buildInflowTargetErrorMsg(vo, DualCustomBucket.RESULT_BLOCKED_PER_SECOND); + + assertEquals("API 호출 한도 초과 [결제API: 30req/sec]", msg); + } + + @Test + void buildInflowTargetErrorMsg_threshold_includesReqPerUnit() { + DJBRequestProcessor processor = new DJBRequestProcessor(); + InflowTargetVO vo = makeVO("결제API", 30, 1000, "MIN"); + + String msg = processor.buildInflowTargetErrorMsg(vo, DualCustomBucket.RESULT_BLOCKED_THRESHOLD); + + assertEquals("API 호출 한도 초과 [결제API: 1000req/MIN]", msg); + } + + @Test + void buildInflowTargetErrorMsg_blockedFallback_simpleFormat() { + DJBRequestProcessor processor = new DJBRequestProcessor(); + InflowTargetVO vo = makeVO("결제API", 30, 1000, "MIN"); + + String msg = processor.buildInflowTargetErrorMsg(vo, "BLOCKED"); + + assertEquals("API 호출 한도 초과 [결제API]", msg); + } + + @Test + void buildInflowTargetErrorMsg_nullBlockedType_simpleFormat() { + DJBRequestProcessor processor = new DJBRequestProcessor(); + InflowTargetVO vo = makeVO("결제API", 30, 1000, "MIN"); + + String msg = processor.buildInflowTargetErrorMsg(vo, null); + + assertEquals("API 호출 한도 초과 [결제API]", msg); + } + + @Test + void buildInflowTargetErrorMsg_hourUnit() { + DJBRequestProcessor processor = new DJBRequestProcessor(); + InflowTargetVO vo = makeVO("조회API", 10, 500, "HOUR"); + + String msg = processor.buildInflowTargetErrorMsg(vo, DualCustomBucket.RESULT_BLOCKED_THRESHOLD); + + assertEquals("API 호출 한도 초과 [조회API: 500req/HOUR]", msg); + } +} diff --git a/src/test/java/com/eactive/eai/custom/inflow/ClientInflowTargetMetricControllerTest.java b/src/test/java/com/eactive/eai/custom/inflow/ClientInflowTargetMetricControllerTest.java new file mode 100644 index 0000000..ce996b9 --- /dev/null +++ b/src/test/java/com/eactive/eai/custom/inflow/ClientInflowTargetMetricControllerTest.java @@ -0,0 +1,144 @@ +package com.eactive.eai.custom.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; + +import com.eactive.eai.manage.inflow.TargetMetricDTO; + +/** + * ClientInflowTargetMetricController 단위 테스트. + * + *

Spring MVC를 기동하지 않고 컨트롤러를 직접 인스턴스화하여 + * 응답 구조(success, data, count, message)를 검증한다. + */ +class ClientInflowTargetMetricControllerTest { + + private ClientInflowTargetMetricController controller; + private ClientInflowTargetMetricService mockService; + + @BeforeEach + void setUp() { + controller = new ClientInflowTargetMetricController(); + mockService = mock(ClientInflowTargetMetricService.class); + ReflectionTestUtils.setField(controller, "metricService", mockService); + } + + private TargetMetricDTO makeDTO(String targetId, String targetType, + long allowed, long rejPs, long rejTh) { + TargetMetricDTO dto = new TargetMetricDTO(); + dto.setTargetId(targetId); + dto.setTargetType(targetType); + 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); + return dto; + } + + @SuppressWarnings("unchecked") + private Map body(ResponseEntity response) { + return (Map) response.getBody(); + } + + // ========================================================================= + // 단일 조회 + // ========================================================================= + + @Test + void getClientMetric_found_successTrueAndDataPresent() { + TargetMetricDTO dto = makeDTO("C1", "CLIENT", 300, 50, 20); + when(mockService.getClientMetric("C1")).thenReturn(dto); + + Map body = body(controller.getClientMetric("C1")); + + assertEquals(Boolean.TRUE, body.get("success")); + assertSame(dto, body.get("data")); + assertFalse(body.containsKey("message")); + } + + @Test + void getClientMetric_notFound_successFalseWithMessage() { + when(mockService.getClientMetric("GHOST")).thenReturn(null); + + Map body = body(controller.getClientMetric("GHOST")); + + assertEquals(Boolean.FALSE, body.get("success")); + assertTrue(body.get("message").toString().contains("GHOST")); + } + + // ========================================================================= + // 전체 조회 + // ========================================================================= + + @Test + void getAllClientMetrics_returnsListAndCount() { + List list = Arrays.asList( + makeDTO("C1", "CLIENT", 100, 5, 5), + makeDTO("C2", "CLIENT", 200, 0, 0), + makeDTO("C3", "CLIENT", 50, 10, 0) + ); + when(mockService.getAllClientMetrics()).thenReturn(list); + + Map body = body(controller.getAllClientMetrics()); + + assertEquals(Boolean.TRUE, body.get("success")); + assertSame(list, body.get("data")); + assertEquals(3, body.get("count")); + } + + @Test + void getAllClientMetrics_emptyList_countZero() { + when(mockService.getAllClientMetrics()).thenReturn(Collections.emptyList()); + + Map body = body(controller.getAllClientMetrics()); + + assertEquals(Boolean.TRUE, body.get("success")); + assertEquals(0, body.get("count")); + } + + // ========================================================================= + // reset + // ========================================================================= + + @Test + void resetClientMetric_success_successTrueWithTargetId() { + when(mockService.resetClientMetric("C1")).thenReturn(true); + + Map body = body(controller.resetClientMetric("C1")); + + assertEquals(Boolean.TRUE, body.get("success")); + assertTrue(body.get("message").toString().contains("C1")); + } + + @Test + void resetClientMetric_notFound_successFalseWithTargetId() { + when(mockService.resetClientMetric("GHOST")).thenReturn(false); + + Map body = body(controller.resetClientMetric("GHOST")); + + assertEquals(Boolean.FALSE, body.get("success")); + assertTrue(body.get("message").toString().contains("GHOST")); + } + + @Test + void resetAllClientMetrics_callsDelegateOnce() { + doNothing().when(mockService).resetAllClientMetrics(); + + controller.resetAllClientMetrics(); + + verify(mockService, times(1)).resetAllClientMetrics(); + } +} diff --git a/src/test/java/com/eactive/eai/custom/inflow/ClientInflowTargetMetricServiceTest.java b/src/test/java/com/eactive/eai/custom/inflow/ClientInflowTargetMetricServiceTest.java new file mode 100644 index 0000000..5e9325b --- /dev/null +++ b/src/test/java/com/eactive/eai/custom/inflow/ClientInflowTargetMetricServiceTest.java @@ -0,0 +1,177 @@ +package com.eactive.eai.custom.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.InflowTargetVO; +import com.eactive.eai.common.inflow.dual.ClientDualInflowControlManager; +import com.eactive.eai.common.inflow.dual.DualInflowControlManager; +import com.eactive.eai.manage.inflow.TargetMetricDTO; + +class ClientInflowTargetMetricServiceTest { + + private static ClientInflowTargetMetricService serviceWith(ClientDualInflowControlManager manager) { + return new ClientInflowTargetMetricService() { + @Override + protected ClientDualInflowControlManager getClientDualManager() { return manager; } + }; + } + + private InflowTargetVO makeVO(String name) { + InflowTargetVO vo = new InflowTargetVO(); + vo.setName(name); + 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; + } + + // ========================================================================= + // null 관리자 (비활성 환경) + // ========================================================================= + + @Test + void getClientMetric_managerIsNull_returnsNull() { + assertNull(serviceWith(null).getClientMetric("C1")); + } + + @Test + void getAllClientMetrics_managerIsNull_returnsEmptyList() { + assertTrue(serviceWith(null).getAllClientMetrics().isEmpty()); + } + + @Test + void resetClientMetric_managerIsNull_returnsFalse() { + assertFalse(serviceWith(null).resetClientMetric("C1")); + } + + @Test + void resetAllClientMetrics_managerIsNull_noException() { + assertDoesNotThrow(() -> serviceWith(null).resetAllClientMetrics()); + } + + // ========================================================================= + // 단일 조회 + // ========================================================================= + + @Test + void getClientMetric_found_mapsFields() { + ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class); + when(manager.getClientInflowThreshold("C1")).thenReturn(makeVO("C1")); + when(manager.getClientMetrics("C1")).thenReturn(makeMetrics(200, 30, 20)); + + TargetMetricDTO dto = serviceWith(manager).getClientMetric("C1"); + + assertNotNull(dto); + assertEquals("C1", dto.getTargetId()); + assertEquals("CLIENT", dto.getTargetType()); + assertTrue(dto.isActivate()); + assertEquals(200, dto.getAllowed()); + assertEquals(30, dto.getRejectedPerSecond()); + assertEquals(20, dto.getRejectedThreshold()); + assertEquals(250, dto.getTotalRequests()); + } + + @Test + void getClientMetric_notRegistered_returnsNull() { + ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class); + when(manager.getClientInflowThreshold("C1")).thenReturn(null); + + assertNull(serviceWith(manager).getClientMetric("C1")); + } + + @Test + void getClientMetric_noMetricsYet_returnsZeroCounters() { + ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class); + when(manager.getClientInflowThreshold("C1")).thenReturn(makeVO("C1")); + when(manager.getClientMetrics("C1")).thenReturn(null); + + TargetMetricDTO dto = serviceWith(manager).getClientMetric("C1"); + + assertNotNull(dto); + assertEquals(0, dto.getAllowed()); + assertEquals(0, dto.getTotalRequests()); + assertEquals(0.0, dto.getRejectRatio(), 0.001); + } + + @Test + void getClientMetric_rejectRatioCalculation() { + ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class); + when(manager.getClientInflowThreshold("C1")).thenReturn(makeVO("C1")); + // allowed=90, rejPs=5, rejTh=5 → reject=10/100 = 10.00% + when(manager.getClientMetrics("C1")).thenReturn(makeMetrics(90, 5, 5)); + + TargetMetricDTO dto = serviceWith(manager).getClientMetric("C1"); + + assertEquals(10.0, dto.getRejectRatio(), 0.01); + } + + // ========================================================================= + // 전체 조회 + // ========================================================================= + + @Test + void getAllClientMetrics_twoEntries_returnsBoth() { + ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class); + java.util.Map bucketMap = + new java.util.LinkedHashMap<>(); + bucketMap.put("C1", null); + bucketMap.put("C2", null); + when(manager.getClientBucketMap()).thenReturn(bucketMap); + when(manager.getClientInflowThreshold("C1")).thenReturn(makeVO("C1")); + when(manager.getClientInflowThreshold("C2")).thenReturn(makeVO("C2")); + when(manager.getClientMetrics("C1")).thenReturn(makeMetrics(10, 0, 0)); + when(manager.getClientMetrics("C2")).thenReturn(makeMetrics(20, 5, 0)); + + List result = serviceWith(manager).getAllClientMetrics(); + + assertEquals(2, result.size()); + } + + @Test + void getAllClientMetrics_noKeys_returnsEmptyList() { + ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class); + when(manager.getClientBucketMap()).thenReturn(new java.util.HashMap<>()); + + assertTrue(serviceWith(manager).getAllClientMetrics().isEmpty()); + } + + // ========================================================================= + // reset + // ========================================================================= + + @Test + void resetClientMetric_success_returnsTrueAndCallsManager() { + ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class); + when(manager.getClientInflowThreshold("C1")).thenReturn(makeVO("C1")); + + assertTrue(serviceWith(manager).resetClientMetric("C1")); + verify(manager).resetClientMetrics("C1"); + } + + @Test + void resetClientMetric_notRegistered_returnsFalseAndNoReset() { + ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class); + when(manager.getClientInflowThreshold("C1")).thenReturn(null); + + assertFalse(serviceWith(manager).resetClientMetric("C1")); + verify(manager, never()).resetClientMetrics(any()); + } + + @Test + void resetAllClientMetrics_callsManager() { + ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class); + serviceWith(manager).resetAllClientMetrics(); + verify(manager).resetAllClientMetrics(); + } +} diff --git a/src/test/java/com/eactive/eai/manage/inflow/InflowTargetBucketServiceTest.java b/src/test/java/com/eactive/eai/manage/inflow/InflowTargetBucketServiceTest.java index 2037ced..cde43ce 100644 --- a/src/test/java/com/eactive/eai/manage/inflow/InflowTargetBucketServiceTest.java +++ b/src/test/java/com/eactive/eai/manage/inflow/InflowTargetBucketServiceTest.java @@ -11,6 +11,7 @@ import java.util.concurrent.ConcurrentHashMap; import org.junit.jupiter.api.Test; import com.eactive.eai.common.inflow.InflowTargetVO; +import com.eactive.eai.common.inflow.dual.ClientDualInflowControlManager; import com.eactive.eai.common.inflow.dual.DualCustomBucket; import com.eactive.eai.common.inflow.dual.DualInflowControlManager; @@ -21,8 +22,8 @@ import io.github.bucket4j.local.LocalBucket; /** * InflowTargetBucketService 단위 테스트. * - *

getDualManager()를 protected로 오버라이드하는 내부 테스트 서브클래스를 사용하여 - * DualInflowControlManager 의존성을 주입한다. + *

어댑터/인터페이스: getDualManager()를 오버라이드하는 서브클래스 사용. + * 클라이언트: getClientDualManager()를 오버라이드하는 서브클래스 사용. */ class InflowTargetBucketServiceTest { @@ -33,9 +34,18 @@ class InflowTargetBucketServiceTest { private static InflowTargetBucketService serviceWith(DualInflowControlManager manager) { return new InflowTargetBucketService() { @Override - protected DualInflowControlManager getDualManager() { - return manager; - } + protected DualInflowControlManager getDualManager() { return manager; } + @Override + protected ClientDualInflowControlManager getClientDualManager() { return null; } + }; + } + + private static InflowTargetBucketService serviceWithClient(ClientDualInflowControlManager manager) { + return new InflowTargetBucketService() { + @Override + protected DualInflowControlManager getDualManager() { return manager; } + @Override + protected ClientDualInflowControlManager getClientDualManager() { return manager; } }; } @@ -98,12 +108,12 @@ class InflowTargetBucketServiceTest { @Test void getClientBucketStatus_managerIsNull_returnsNull() { - assertNull(serviceWith(null).getClientBucketStatus("CLIENT_A")); + assertNull(serviceWithClient(null).getClientBucketStatus("CLIENT_A")); } @Test void getAllClientBucketStatus_managerIsNull_returnsEmptyList() { - assertTrue(serviceWith(null).getAllClientBucketStatus().isEmpty()); + assertTrue(serviceWithClient(null).getAllClientBucketStatus().isEmpty()); } // ========================================================================= @@ -128,10 +138,10 @@ class InflowTargetBucketServiceTest { @Test void getClientBucketStatus_notFound_returnsNull() { - DualInflowControlManager manager = mock(DualInflowControlManager.class); + ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class); when(manager.getClientBucket("CLIENT_A")).thenReturn(null); - assertNull(serviceWith(manager).getClientBucketStatus("CLIENT_A")); + assertNull(serviceWithClient(manager).getClientBucketStatus("CLIENT_A")); } // ========================================================================= @@ -168,10 +178,10 @@ class InflowTargetBucketServiceTest { @Test void getClientBucketStatus_found_mapsClientType() { - DualInflowControlManager manager = mock(DualInflowControlManager.class); + ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class); when(manager.getClientBucket("CLIENT_A")).thenReturn(makeDualBucket("CLIENT_A", 50, 5000)); - TargetBucketStatusDTO dto = serviceWith(manager).getClientBucketStatus("CLIENT_A"); + TargetBucketStatusDTO dto = serviceWithClient(manager).getClientBucketStatus("CLIENT_A"); assertNotNull(dto); assertEquals("CLIENT_A", dto.getTargetId()); @@ -189,10 +199,10 @@ class InflowTargetBucketServiceTest { InflowTargetVO vo = makeVO("C1", 0, 1000, "DAY", true); DualCustomBucket bucket = new DualCustomBucket(null, freshBucket(1000), vo); - DualInflowControlManager manager = mock(DualInflowControlManager.class); + ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class); when(manager.getClientBucket("C1")).thenReturn(bucket); - TargetBucketStatusDTO dto = serviceWith(manager).getClientBucketStatus("C1"); + TargetBucketStatusDTO dto = serviceWithClient(manager).getClientBucketStatus("C1"); assertEquals(1, dto.getBuckets().size()); assertEquals("threshold", dto.getBuckets().get(0).getType()); @@ -203,10 +213,10 @@ class InflowTargetBucketServiceTest { InflowTargetVO vo = makeVO("C2", 100, 0, "SEC", true); DualCustomBucket bucket = new DualCustomBucket(freshBucket(100), null, vo); - DualInflowControlManager manager = mock(DualInflowControlManager.class); + ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class); when(manager.getClientBucket("C2")).thenReturn(bucket); - TargetBucketStatusDTO dto = serviceWith(manager).getClientBucketStatus("C2"); + TargetBucketStatusDTO dto = serviceWithClient(manager).getClientBucketStatus("C2"); assertEquals(1, dto.getBuckets().size()); assertEquals("perSecond", dto.getBuckets().get(0).getType()); @@ -214,10 +224,10 @@ class InflowTargetBucketServiceTest { @Test void toDto_bothBuckets_bucketInfoHasTwoEntries() { - DualInflowControlManager manager = mock(DualInflowControlManager.class); + ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class); when(manager.getClientBucket("C3")).thenReturn(makeDualBucket("C3", 100, 5000)); - TargetBucketStatusDTO dto = serviceWith(manager).getClientBucketStatus("C3"); + TargetBucketStatusDTO dto = serviceWithClient(manager).getClientBucketStatus("C3"); assertEquals(2, dto.getBuckets().size()); assertEquals("perSecond", dto.getBuckets().get(0).getType()); @@ -233,10 +243,10 @@ class InflowTargetBucketServiceTest { InflowTargetVO vo = makeVO("C4", 0, 100, "DAY", true); DualCustomBucket bucket = new DualCustomBucket(null, exhaustedBucket(100), vo); - DualInflowControlManager manager = mock(DualInflowControlManager.class); + ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class); when(manager.getClientBucket("C4")).thenReturn(bucket); - TargetBucketStatusDTO dto = serviceWith(manager).getClientBucketStatus("C4"); + TargetBucketStatusDTO dto = serviceWithClient(manager).getClientBucketStatus("C4"); GroupBucketStatusDTO.BucketInfo info = dto.getBuckets().get(0); assertEquals(0, info.getAvailableTokens()); @@ -248,10 +258,10 @@ class InflowTargetBucketServiceTest { InflowTargetVO vo = makeVO("C5", 0, 100, "DAY", true); DualCustomBucket bucket = new DualCustomBucket(null, freshBucket(100), vo); - DualInflowControlManager manager = mock(DualInflowControlManager.class); + ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class); when(manager.getClientBucket("C5")).thenReturn(bucket); - TargetBucketStatusDTO dto = serviceWith(manager).getClientBucketStatus("C5"); + TargetBucketStatusDTO dto = serviceWithClient(manager).getClientBucketStatus("C5"); GroupBucketStatusDTO.BucketInfo info = dto.getBuckets().get(0); assertEquals(100, info.getAvailableTokens()); @@ -280,13 +290,13 @@ class InflowTargetBucketServiceTest { @Test void getAllClientBucketStatus_twoEntries_returnsBoth() { - DualInflowControlManager manager = mock(DualInflowControlManager.class); + ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class); Map map = new ConcurrentHashMap<>(); map.put("CLIENT_A", makeDualBucket("CLIENT_A", 10, 1000)); map.put("CLIENT_B", makeDualBucket("CLIENT_B", 20, 2000)); when(manager.getClientBucketMap()).thenReturn(map); - List result = serviceWith(manager).getAllClientBucketStatus(); + List result = serviceWithClient(manager).getAllClientBucketStatus(); assertEquals(2, result.size()); } diff --git a/src/test/java/com/eactive/eai/manage/inflow/InflowTargetMetricControllerTest.java b/src/test/java/com/eactive/eai/manage/inflow/InflowTargetMetricControllerTest.java index a7dcd96..8de70fd 100644 --- a/src/test/java/com/eactive/eai/manage/inflow/InflowTargetMetricControllerTest.java +++ b/src/test/java/com/eactive/eai/manage/inflow/InflowTargetMetricControllerTest.java @@ -210,92 +210,4 @@ class InflowTargetMetricControllerTest { verify(mockService, times(1)).resetAllInterfaceMetrics(); } - - // ========================================================================= - // 클라이언트 — 단일 조회 - // ========================================================================= - - @Test - void getClientMetric_found_successTrueAndDataPresent() { - TargetMetricDTO dto = makeDTO("C1", "CLIENT", 300, 50, 20); - when(mockService.getClientMetric("C1")).thenReturn(dto); - - Map body = body(controller.getClientMetric("C1")); - - assertEquals(Boolean.TRUE, body.get("success")); - assertSame(dto, body.get("data")); - } - - @Test - void getClientMetric_notFound_successFalseWithMessage() { - when(mockService.getClientMetric("GHOST")).thenReturn(null); - - Map body = body(controller.getClientMetric("GHOST")); - - assertEquals(Boolean.FALSE, body.get("success")); - assertTrue(body.get("message").toString().contains("GHOST")); - } - - // ========================================================================= - // 클라이언트 — 전체 조회 - // ========================================================================= - - @Test - void getAllClientMetrics_returnsListAndCount() { - List list = Arrays.asList( - makeDTO("C1", "CLIENT", 100, 5, 5), - makeDTO("C2", "CLIENT", 200, 0, 0), - makeDTO("C3", "CLIENT", 50, 10, 0) - ); - when(mockService.getAllClientMetrics()).thenReturn(list); - - Map body = body(controller.getAllClientMetrics()); - - assertEquals(Boolean.TRUE, body.get("success")); - assertSame(list, body.get("data")); - assertEquals(3, body.get("count")); - } - - @Test - void getAllClientMetrics_emptyList_countZero() { - when(mockService.getAllClientMetrics()).thenReturn(Collections.emptyList()); - - Map body = body(controller.getAllClientMetrics()); - - assertEquals(Boolean.TRUE, body.get("success")); - assertEquals(0, body.get("count")); - } - - // ========================================================================= - // 클라이언트 — reset - // ========================================================================= - - @Test - void resetClientMetric_success_successTrue() { - when(mockService.resetClientMetric("C1")).thenReturn(true); - - Map body = body(controller.resetClientMetric("C1")); - - assertEquals(Boolean.TRUE, body.get("success")); - assertTrue(body.get("message").toString().contains("C1")); - } - - @Test - void resetClientMetric_notFound_successFalse() { - when(mockService.resetClientMetric("GHOST")).thenReturn(false); - - Map body = body(controller.resetClientMetric("GHOST")); - - assertEquals(Boolean.FALSE, body.get("success")); - assertTrue(body.get("message").toString().contains("GHOST")); - } - - @Test - void resetAllClientMetrics_callsDelegateOnce() { - doNothing().when(mockService).resetAllClientMetrics(); - - controller.resetAllClientMetrics(); - - verify(mockService, times(1)).resetAllClientMetrics(); - } } diff --git a/src/test/java/com/eactive/eai/manage/inflow/InflowTargetMetricServiceTest.java b/src/test/java/com/eactive/eai/manage/inflow/InflowTargetMetricServiceTest.java index 91c13cb..34a7ed5 100644 --- a/src/test/java/com/eactive/eai/manage/inflow/InflowTargetMetricServiceTest.java +++ b/src/test/java/com/eactive/eai/manage/inflow/InflowTargetMetricServiceTest.java @@ -13,12 +13,12 @@ import com.eactive.eai.common.inflow.dual.DualInflowControlManager; /** * InflowTargetMetricService 단위 테스트. * - *

getDualManager()를 오버라이드하여 DualInflowControlManager 의존성을 주입한다. + *

어댑터/인터페이스 테스트는 getDualManager()를 오버라이드하여 DualInflowControlManager 주입. */ class InflowTargetMetricServiceTest { // ------------------------------------------------------------------------- - // 테스트용 서브클래스 + // 테스트용 서브클래스 — 어댑터/인터페이스용 // ------------------------------------------------------------------------- private static InflowTargetMetricService serviceWith(DualInflowControlManager manager) { @@ -76,11 +76,6 @@ class InflowTargetMetricServiceTest { assertNull(serviceWith(null).getInterfaceMetric("IF1")); } - @Test - void getClientMetric_managerIsNull_returnsNull() { - assertNull(serviceWith(null).getClientMetric("C1")); - } - // ========================================================================= // 어댑터 — 단일 조회 // ========================================================================= @@ -271,55 +266,4 @@ class InflowTargetMetricServiceTest { serviceWith(manager).resetAllInterfaceMetrics(); verify(manager).resetAllInterfaceMetrics(); } - - // ========================================================================= - // 클라이언트 — 단일 조회 - // ========================================================================= - - @Test - void getClientMetric_found_mapsFields() { - DualInflowControlManager manager = mock(DualInflowControlManager.class); - when(manager.getClientInflowThreshold("C1")).thenReturn(makeVO("C1")); - when(manager.getClientMetrics("C1")).thenReturn(makeMetrics(200, 30, 20)); - - TargetMetricDTO dto = serviceWith(manager).getClientMetric("C1"); - - assertNotNull(dto); - assertEquals("C1", dto.getTargetId()); - assertEquals("CLIENT", dto.getTargetType()); - assertEquals(200, dto.getAllowed()); - assertEquals(250, dto.getTotalRequests()); - } - - @Test - void getClientMetric_notRegistered_returnsNull() { - DualInflowControlManager manager = mock(DualInflowControlManager.class); - when(manager.getClientInflowThreshold("C1")).thenReturn(null); - - assertNull(serviceWith(manager).getClientMetric("C1")); - } - - @Test - void resetClientMetric_success_returnsTrueAndCallsManager() { - DualInflowControlManager manager = mock(DualInflowControlManager.class); - when(manager.getClientInflowThreshold("C1")).thenReturn(makeVO("C1")); - - assertTrue(serviceWith(manager).resetClientMetric("C1")); - verify(manager).resetClientMetrics("C1"); - } - - @Test - void resetClientMetric_notRegistered_returnsFalse() { - DualInflowControlManager manager = mock(DualInflowControlManager.class); - when(manager.getClientInflowThreshold("C1")).thenReturn(null); - - assertFalse(serviceWith(manager).resetClientMetric("C1")); - } - - @Test - void resetAllClientMetrics_callsManager() { - DualInflowControlManager manager = mock(DualInflowControlManager.class); - serviceWith(manager).resetAllClientMetrics(); - verify(manager).resetAllClientMetrics(); - } }