From 8bcbee7e524861322e61312b5bff114c1b228899 Mon Sep 17 00:00:00 2001 From: curry772 Date: Thu, 30 Apr 2026 10:16:10 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=96=B4=EB=8C=91=ED=84=B0/=EC=9D=B8?= =?UTF-8?q?=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4/=ED=81=B4=EB=9D=BC?= =?UTF-8?q?=EC=9D=B4=EC=96=B8=ED=8A=B8=20=EC=9C=A0=EB=9F=89=EC=A0=9C?= =?UTF-8?q?=EC=96=B4=20=EB=A9=94=ED=8A=B8=EB=A6=AD=20API=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit InflowTargetMetricController / InflowTargetMetricService / TargetMetricDTO 추가. 어댑터·인터페이스·클라이언트별 허용/초당거절/임계거절 카운터 조회 및 초기화 API 제공. 단위테스트: InflowTargetMetricServiceTest(26건), InflowTargetMetricControllerTest(16건). Co-Authored-By: Claude Sonnet 4.6 --- .../inflow/InflowTargetMetricController.java | 166 +++++++++ .../inflow/InflowTargetMetricService.java | 157 +++++++++ .../eai/manage/inflow/TargetMetricDTO.java | 44 +++ .../InflowTargetMetricControllerTest.java | 301 ++++++++++++++++ .../inflow/InflowTargetMetricServiceTest.java | 325 ++++++++++++++++++ 5 files changed, 993 insertions(+) create mode 100644 src/main/java/com/eactive/eai/manage/inflow/InflowTargetMetricController.java create mode 100644 src/main/java/com/eactive/eai/manage/inflow/InflowTargetMetricService.java create mode 100644 src/main/java/com/eactive/eai/manage/inflow/TargetMetricDTO.java create mode 100644 src/test/java/com/eactive/eai/manage/inflow/InflowTargetMetricControllerTest.java create mode 100644 src/test/java/com/eactive/eai/manage/inflow/InflowTargetMetricServiceTest.java diff --git a/src/main/java/com/eactive/eai/manage/inflow/InflowTargetMetricController.java b/src/main/java/com/eactive/eai/manage/inflow/InflowTargetMetricController.java new file mode 100644 index 0000000..31bfc90 --- /dev/null +++ b/src/main/java/com/eactive/eai/manage/inflow/InflowTargetMetricController.java @@ -0,0 +1,166 @@ +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; + +/** + * 어댑터/인터페이스/클라이언트 유량제어 메트릭 API. + * + * DualInflowControlManager 가 활성화된 환경에서만 정상 응답한다. + * + * GET /manage/inflow/adapter/metric → 전체 어댑터 메트릭 + * GET /manage/inflow/adapter/{adapterId}/metric → 특정 어댑터 메트릭 + * POST /manage/inflow/adapter/metric/reset → 전체 어댑터 메트릭 초기화 + * POST /manage/inflow/adapter/{adapterId}/metric/reset → 특정 어댑터 메트릭 초기화 + * + * GET /manage/inflow/interface/metric → 전체 인터페이스 메트릭 + * 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") +public class InflowTargetMetricController { + + @Autowired + private InflowTargetMetricService metricService; + + // ========================================================================= + // 어댑터 + // ========================================================================= + + @GetMapping("/adapter/metric") + public ResponseEntity getAllAdapterMetrics() { + List list = metricService.getAllAdapterMetrics(); + return okList(list); + } + + @GetMapping("/adapter/{adapterId}/metric") + public ResponseEntity getAdapterMetric(@PathVariable String adapterId) { + TargetMetricDTO dto = metricService.getAdapterMetric(adapterId); + return okSingle(dto, "어댑터를 찾을 수 없습니다: " + adapterId); + } + + @PostMapping("/adapter/metric/reset") + public ResponseEntity resetAllAdapterMetrics() { + metricService.resetAllAdapterMetrics(); + return okReset("전체 어댑터 메트릭 초기화 완료"); + } + + @PostMapping("/adapter/{adapterId}/metric/reset") + public ResponseEntity resetAdapterMetric(@PathVariable String adapterId) { + boolean ok = metricService.resetAdapterMetric(adapterId); + return okResetSingle(ok, adapterId, "어댑터"); + } + + // ========================================================================= + // 인터페이스 + // ========================================================================= + + @GetMapping("/interface/metric") + public ResponseEntity getAllInterfaceMetrics() { + List list = metricService.getAllInterfaceMetrics(); + return okList(list); + } + + @GetMapping("/interface/{interfaceId}/metric") + public ResponseEntity getInterfaceMetric(@PathVariable String interfaceId) { + TargetMetricDTO dto = metricService.getInterfaceMetric(interfaceId); + return okSingle(dto, "인터페이스를 찾을 수 없습니다: " + interfaceId); + } + + @PostMapping("/interface/metric/reset") + public ResponseEntity resetAllInterfaceMetrics() { + metricService.resetAllInterfaceMetrics(); + return okReset("전체 인터페이스 메트릭 초기화 완료"); + } + + @PostMapping("/interface/{interfaceId}/metric/reset") + public ResponseEntity resetInterfaceMetric(@PathVariable String interfaceId) { + boolean ok = metricService.resetInterfaceMetric(interfaceId); + 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 + // ========================================================================= + + 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/manage/inflow/InflowTargetMetricService.java b/src/main/java/com/eactive/eai/manage/inflow/InflowTargetMetricService.java new file mode 100644 index 0000000..5eadad3 --- /dev/null +++ b/src/main/java/com/eactive/eai/manage/inflow/InflowTargetMetricService.java @@ -0,0 +1,157 @@ +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.AbstractInflowControlManager; +import com.eactive.eai.common.inflow.InflowControlUtil; +import com.eactive.eai.common.inflow.InflowTargetVO; +import com.eactive.eai.common.inflow.dual.DualInflowControlManager; + +@Service +public class InflowTargetMetricService { + + // ------------------------------------------------------------------------- + // 어댑터 + // ------------------------------------------------------------------------- + + public TargetMetricDTO getAdapterMetric(String adapterId) { + DualInflowControlManager manager = getDualManager(); + if (manager == null) return null; + InflowTargetVO vo = manager.getAdapterInflowThreashold(adapterId); + if (vo == null) return null; + return buildDTO(adapterId, "ADAPTER", vo, manager.getAdapterMetrics(adapterId)); + } + + public List getAllAdapterMetrics() { + DualInflowControlManager manager = getDualManager(); + if (manager == null) return new ArrayList<>(); + List result = new ArrayList<>(); + for (String id : manager.getAdapterAllKeys()) { + TargetMetricDTO dto = getAdapterMetric(id); + if (dto != null) result.add(dto); + } + return result; + } + + public boolean resetAdapterMetric(String adapterId) { + DualInflowControlManager manager = getDualManager(); + if (manager == null) return false; + if (manager.getAdapterInflowThreashold(adapterId) == null) return false; + manager.resetAdapterMetrics(adapterId); + return true; + } + + public void resetAllAdapterMetrics() { + DualInflowControlManager manager = getDualManager(); + if (manager != null) manager.resetAllAdapterMetrics(); + } + + // ------------------------------------------------------------------------- + // 인터페이스 + // ------------------------------------------------------------------------- + + public TargetMetricDTO getInterfaceMetric(String interfaceId) { + DualInflowControlManager manager = getDualManager(); + if (manager == null) return null; + InflowTargetVO vo = manager.getInterfaceInflowThreashold(interfaceId); + if (vo == null) return null; + return buildDTO(interfaceId, "INTERFACE", vo, manager.getInterfaceMetrics(interfaceId)); + } + + public List getAllInterfaceMetrics() { + DualInflowControlManager manager = getDualManager(); + if (manager == null) return new ArrayList<>(); + List result = new ArrayList<>(); + for (String id : manager.getInterfaceAllKeys()) { + TargetMetricDTO dto = getInterfaceMetric(id); + if (dto != null) result.add(dto); + } + return result; + } + + public boolean resetInterfaceMetric(String interfaceId) { + DualInflowControlManager manager = getDualManager(); + if (manager == null) return false; + if (manager.getInterfaceInflowThreashold(interfaceId) == null) return false; + manager.resetInterfaceMetrics(interfaceId); + return true; + } + + public void resetAllInterfaceMetrics() { + DualInflowControlManager manager = getDualManager(); + 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 + // ------------------------------------------------------------------------- + + protected DualInflowControlManager getDualManager() { + AbstractInflowControlManager base = InflowControlUtil.getInflowControlManager(); + return (base instanceof DualInflowControlManager) ? (DualInflowControlManager) 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/TargetMetricDTO.java b/src/main/java/com/eactive/eai/manage/inflow/TargetMetricDTO.java new file mode 100644 index 0000000..dd74320 --- /dev/null +++ b/src/main/java/com/eactive/eai/manage/inflow/TargetMetricDTO.java @@ -0,0 +1,44 @@ +package com.eactive.eai.manage.inflow; + +/** + * 어댑터/인터페이스/클라이언트 메트릭 DTO. + */ +public class TargetMetricDTO { + + private String targetId; + private String targetType; // "ADAPTER" | "INTERFACE" | "CLIENT" + private boolean activate; + private long allowed; + private long rejectedPerSecond; + private long rejectedThreshold; + private long totalRequests; + private double rejectRatio; + private long lastResetTime; + + public String getTargetId() { return targetId; } + public void setTargetId(String targetId) { this.targetId = targetId; } + + public String getTargetType() { return targetType; } + public void setTargetType(String targetType) { this.targetType = targetType; } + + 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; } +} diff --git a/src/test/java/com/eactive/eai/manage/inflow/InflowTargetMetricControllerTest.java b/src/test/java/com/eactive/eai/manage/inflow/InflowTargetMetricControllerTest.java new file mode 100644 index 0000000..a7dcd96 --- /dev/null +++ b/src/test/java/com/eactive/eai/manage/inflow/InflowTargetMetricControllerTest.java @@ -0,0 +1,301 @@ +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; + +/** + * InflowTargetMetricController 단위 테스트. + * + *

Spring MVC를 기동하지 않고 컨트롤러를 직접 인스턴스화하여 + * 응답 구조(success, data, count, message)를 검증한다. + */ +class InflowTargetMetricControllerTest { + + private InflowTargetMetricController controller; + private InflowTargetMetricService mockService; + + @BeforeEach + void setUp() { + controller = new InflowTargetMetricController(); + mockService = mock(InflowTargetMetricService.class); + ReflectionTestUtils.setField(controller, "metricService", mockService); + } + + // ------------------------------------------------------------------------- + // Helper + // ------------------------------------------------------------------------- + + 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 getAdapterMetric_found_successTrueAndDataPresent() { + TargetMetricDTO dto = makeDTO("A1", "ADAPTER", 900, 60, 40); + when(mockService.getAdapterMetric("A1")).thenReturn(dto); + + ResponseEntity response = controller.getAdapterMetric("A1"); + Map body = body(response); + + assertEquals(200, response.getStatusCodeValue()); + assertEquals(Boolean.TRUE, body.get("success")); + assertSame(dto, body.get("data")); + assertFalse(body.containsKey("message")); + } + + @Test + void getAdapterMetric_notFound_successFalseWithMessage() { + when(mockService.getAdapterMetric("GHOST")).thenReturn(null); + + Map body = body(controller.getAdapterMetric("GHOST")); + + assertEquals(Boolean.FALSE, body.get("success")); + assertTrue(body.get("message").toString().contains("GHOST")); + } + + // ========================================================================= + // 어댑터 — 전체 조회 + // ========================================================================= + + @Test + void getAllAdapterMetrics_returnsListAndCount() { + List list = Arrays.asList( + makeDTO("A1", "ADAPTER", 100, 5, 0), + makeDTO("A2", "ADAPTER", 200, 0, 10) + ); + when(mockService.getAllAdapterMetrics()).thenReturn(list); + + Map body = body(controller.getAllAdapterMetrics()); + + assertEquals(Boolean.TRUE, body.get("success")); + assertSame(list, body.get("data")); + assertEquals(2, body.get("count")); + } + + @Test + void getAllAdapterMetrics_emptyList_countZero() { + when(mockService.getAllAdapterMetrics()).thenReturn(Collections.emptyList()); + + Map body = body(controller.getAllAdapterMetrics()); + + assertEquals(Boolean.TRUE, body.get("success")); + assertEquals(0, body.get("count")); + } + + // ========================================================================= + // 어댑터 — reset + // ========================================================================= + + @Test + void resetAdapterMetric_success_successTrueWithTargetId() { + when(mockService.resetAdapterMetric("A1")).thenReturn(true); + + Map body = body(controller.resetAdapterMetric("A1")); + + assertEquals(Boolean.TRUE, body.get("success")); + assertTrue(body.get("message").toString().contains("A1")); + } + + @Test + void resetAdapterMetric_notFound_successFalseWithTargetId() { + when(mockService.resetAdapterMetric("GHOST")).thenReturn(false); + + Map body = body(controller.resetAdapterMetric("GHOST")); + + assertEquals(Boolean.FALSE, body.get("success")); + assertTrue(body.get("message").toString().contains("GHOST")); + } + + @Test + void resetAllAdapterMetrics_alwaysSuccessTrue() { + doNothing().when(mockService).resetAllAdapterMetrics(); + + Map body = body(controller.resetAllAdapterMetrics()); + + assertEquals(Boolean.TRUE, body.get("success")); + assertNotNull(body.get("message")); + verify(mockService).resetAllAdapterMetrics(); + } + + // ========================================================================= + // 인터페이스 — 단일 조회 + // ========================================================================= + + @Test + void getInterfaceMetric_found_successTrueAndDataPresent() { + TargetMetricDTO dto = makeDTO("IF1", "INTERFACE", 500, 20, 10); + when(mockService.getInterfaceMetric("IF1")).thenReturn(dto); + + Map body = body(controller.getInterfaceMetric("IF1")); + + assertEquals(Boolean.TRUE, body.get("success")); + assertSame(dto, body.get("data")); + } + + @Test + void getInterfaceMetric_notFound_successFalseWithMessage() { + when(mockService.getInterfaceMetric("GHOST")).thenReturn(null); + + Map body = body(controller.getInterfaceMetric("GHOST")); + + assertEquals(Boolean.FALSE, body.get("success")); + assertTrue(body.get("message").toString().contains("GHOST")); + } + + // ========================================================================= + // 인터페이스 — 전체 조회 + // ========================================================================= + + @Test + void getAllInterfaceMetrics_returnsListAndCount() { + List list = Collections.singletonList( + makeDTO("IF1", "INTERFACE", 100, 0, 0)); + when(mockService.getAllInterfaceMetrics()).thenReturn(list); + + Map body = body(controller.getAllInterfaceMetrics()); + + assertEquals(Boolean.TRUE, body.get("success")); + assertEquals(1, body.get("count")); + } + + // ========================================================================= + // 인터페이스 — reset + // ========================================================================= + + @Test + void resetInterfaceMetric_success_successTrue() { + when(mockService.resetInterfaceMetric("IF1")).thenReturn(true); + + Map body = body(controller.resetInterfaceMetric("IF1")); + + assertEquals(Boolean.TRUE, body.get("success")); + assertTrue(body.get("message").toString().contains("IF1")); + } + + @Test + void resetAllInterfaceMetrics_callsDelegateOnce() { + doNothing().when(mockService).resetAllInterfaceMetrics(); + + controller.resetAllInterfaceMetrics(); + + 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 new file mode 100644 index 0000000..91c13cb --- /dev/null +++ b/src/test/java/com/eactive/eai/manage/inflow/InflowTargetMetricServiceTest.java @@ -0,0 +1,325 @@ +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.InflowTargetVO; +import com.eactive.eai.common.inflow.dual.DualInflowControlManager; + +/** + * InflowTargetMetricService 단위 테스트. + * + *

getDualManager()를 오버라이드하여 DualInflowControlManager 의존성을 주입한다. + */ +class InflowTargetMetricServiceTest { + + // ------------------------------------------------------------------------- + // 테스트용 서브클래스 + // ------------------------------------------------------------------------- + + private static InflowTargetMetricService serviceWith(DualInflowControlManager manager) { + return new InflowTargetMetricService() { + @Override + protected DualInflowControlManager getDualManager() { return manager; } + }; + } + + // ------------------------------------------------------------------------- + // Helpers + // ------------------------------------------------------------------------- + + 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; + } + + // ========================================================================= + // DualManager null (비활성 환경) + // ========================================================================= + + @Test + void getAdapterMetric_managerIsNull_returnsNull() { + assertNull(serviceWith(null).getAdapterMetric("A1")); + } + + @Test + void getAllAdapterMetrics_managerIsNull_returnsEmptyList() { + assertTrue(serviceWith(null).getAllAdapterMetrics().isEmpty()); + } + + @Test + void resetAdapterMetric_managerIsNull_returnsFalse() { + assertFalse(serviceWith(null).resetAdapterMetric("A1")); + } + + @Test + void resetAllAdapterMetrics_managerIsNull_noException() { + assertDoesNotThrow(() -> serviceWith(null).resetAllAdapterMetrics()); + } + + @Test + void getInterfaceMetric_managerIsNull_returnsNull() { + assertNull(serviceWith(null).getInterfaceMetric("IF1")); + } + + @Test + void getClientMetric_managerIsNull_returnsNull() { + assertNull(serviceWith(null).getClientMetric("C1")); + } + + // ========================================================================= + // 어댑터 — 단일 조회 + // ========================================================================= + + @Test + void getAdapterMetric_notRegistered_returnsNull() { + DualInflowControlManager manager = mock(DualInflowControlManager.class); + when(manager.getAdapterInflowThreashold("A1")).thenReturn(null); + + assertNull(serviceWith(manager).getAdapterMetric("A1")); + } + + @Test + void getAdapterMetric_found_mapsFields() { + DualInflowControlManager manager = mock(DualInflowControlManager.class); + when(manager.getAdapterInflowThreashold("A1")).thenReturn(makeVO("A1")); + when(manager.getAdapterMetrics("A1")).thenReturn(makeMetrics(900, 60, 40)); + + TargetMetricDTO dto = serviceWith(manager).getAdapterMetric("A1"); + + assertNotNull(dto); + assertEquals("A1", dto.getTargetId()); + assertEquals("ADAPTER", dto.getTargetType()); + assertTrue(dto.isActivate()); + assertEquals(900, dto.getAllowed()); + assertEquals(60, dto.getRejectedPerSecond()); + assertEquals(40, dto.getRejectedThreshold()); + assertEquals(1000, dto.getTotalRequests()); + } + + @Test + void getAdapterMetric_noMetricsYet_returnsZeroCounters() { + DualInflowControlManager manager = mock(DualInflowControlManager.class); + when(manager.getAdapterInflowThreashold("A1")).thenReturn(makeVO("A1")); + when(manager.getAdapterMetrics("A1")).thenReturn(null); + + TargetMetricDTO dto = serviceWith(manager).getAdapterMetric("A1"); + + assertNotNull(dto); + assertEquals(0, dto.getAllowed()); + assertEquals(0, dto.getTotalRequests()); + assertEquals(0.0, dto.getRejectRatio(), 0.001); + } + + @Test + void getAdapterMetric_rejectRatioCalculation() { + DualInflowControlManager manager = mock(DualInflowControlManager.class); + when(manager.getAdapterInflowThreashold("A1")).thenReturn(makeVO("A1")); + // allowed=90, rejPs=5, rejTh=5 → reject=10/100 = 10.00% + when(manager.getAdapterMetrics("A1")).thenReturn(makeMetrics(90, 5, 5)); + + TargetMetricDTO dto = serviceWith(manager).getAdapterMetric("A1"); + + assertEquals(10.0, dto.getRejectRatio(), 0.01); + } + + @Test + void getAdapterMetric_zeroTotal_rejectRatioIsZero() { + DualInflowControlManager manager = mock(DualInflowControlManager.class); + when(manager.getAdapterInflowThreashold("A1")).thenReturn(makeVO("A1")); + when(manager.getAdapterMetrics("A1")).thenReturn(makeMetrics(0, 0, 0)); + + TargetMetricDTO dto = serviceWith(manager).getAdapterMetric("A1"); + + assertEquals(0.0, dto.getRejectRatio(), 0.0001); + } + + // ========================================================================= + // 어댑터 — 전체 조회 + // ========================================================================= + + @Test + void getAllAdapterMetrics_twoEntries_returnsBoth() { + DualInflowControlManager manager = mock(DualInflowControlManager.class); + when(manager.getAdapterAllKeys()).thenReturn(new String[]{"A1", "A2"}); + when(manager.getAdapterInflowThreashold("A1")).thenReturn(makeVO("A1")); + when(manager.getAdapterInflowThreashold("A2")).thenReturn(makeVO("A2")); + when(manager.getAdapterMetrics("A1")).thenReturn(makeMetrics(10, 0, 0)); + when(manager.getAdapterMetrics("A2")).thenReturn(makeMetrics(20, 5, 0)); + + List result = serviceWith(manager).getAllAdapterMetrics(); + + assertEquals(2, result.size()); + assertEquals("A1", result.get(0).getTargetId()); + assertEquals("A2", result.get(1).getTargetId()); + } + + @Test + void getAllAdapterMetrics_noKeys_returnsEmptyList() { + DualInflowControlManager manager = mock(DualInflowControlManager.class); + when(manager.getAdapterAllKeys()).thenReturn(new String[]{}); + + assertTrue(serviceWith(manager).getAllAdapterMetrics().isEmpty()); + } + + @Test + void getAllAdapterMetrics_notRegisteredSkipped() { + DualInflowControlManager manager = mock(DualInflowControlManager.class); + when(manager.getAdapterAllKeys()).thenReturn(new String[]{"A1", "GHOST"}); + when(manager.getAdapterInflowThreashold("A1")).thenReturn(makeVO("A1")); + when(manager.getAdapterInflowThreashold("GHOST")).thenReturn(null); + when(manager.getAdapterMetrics("A1")).thenReturn(null); + + List result = serviceWith(manager).getAllAdapterMetrics(); + + assertEquals(1, result.size()); + assertEquals("A1", result.get(0).getTargetId()); + } + + // ========================================================================= + // 어댑터 — reset + // ========================================================================= + + @Test + void resetAdapterMetric_notRegistered_returnsFalse() { + DualInflowControlManager manager = mock(DualInflowControlManager.class); + when(manager.getAdapterInflowThreashold("A1")).thenReturn(null); + + assertFalse(serviceWith(manager).resetAdapterMetric("A1")); + verify(manager, never()).resetAdapterMetrics(any()); + } + + @Test + void resetAdapterMetric_success_returnsTrueAndCallsManager() { + DualInflowControlManager manager = mock(DualInflowControlManager.class); + when(manager.getAdapterInflowThreashold("A1")).thenReturn(makeVO("A1")); + + assertTrue(serviceWith(manager).resetAdapterMetric("A1")); + verify(manager).resetAdapterMetrics("A1"); + } + + @Test + void resetAllAdapterMetrics_callsManager() { + DualInflowControlManager manager = mock(DualInflowControlManager.class); + + serviceWith(manager).resetAllAdapterMetrics(); + + verify(manager).resetAllAdapterMetrics(); + } + + // ========================================================================= + // 인터페이스 — 단일 조회 + // ========================================================================= + + @Test + void getInterfaceMetric_found_mapsFields() { + DualInflowControlManager manager = mock(DualInflowControlManager.class); + when(manager.getInterfaceInflowThreashold("IF1")).thenReturn(makeVO("IF1")); + when(manager.getInterfaceMetrics("IF1")).thenReturn(makeMetrics(50, 10, 5)); + + TargetMetricDTO dto = serviceWith(manager).getInterfaceMetric("IF1"); + + assertNotNull(dto); + assertEquals("IF1", dto.getTargetId()); + assertEquals("INTERFACE", dto.getTargetType()); + assertEquals(50, dto.getAllowed()); + assertEquals(65, dto.getTotalRequests()); + } + + @Test + void getInterfaceMetric_notRegistered_returnsNull() { + DualInflowControlManager manager = mock(DualInflowControlManager.class); + when(manager.getInterfaceInflowThreashold("IF1")).thenReturn(null); + + assertNull(serviceWith(manager).getInterfaceMetric("IF1")); + } + + @Test + void resetInterfaceMetric_success_returnsTrueAndCallsManager() { + DualInflowControlManager manager = mock(DualInflowControlManager.class); + when(manager.getInterfaceInflowThreashold("IF1")).thenReturn(makeVO("IF1")); + + assertTrue(serviceWith(manager).resetInterfaceMetric("IF1")); + verify(manager).resetInterfaceMetrics("IF1"); + } + + @Test + void resetInterfaceMetric_notRegistered_returnsFalse() { + DualInflowControlManager manager = mock(DualInflowControlManager.class); + when(manager.getInterfaceInflowThreashold("IF1")).thenReturn(null); + + assertFalse(serviceWith(manager).resetInterfaceMetric("IF1")); + } + + @Test + void resetAllInterfaceMetrics_callsManager() { + DualInflowControlManager manager = mock(DualInflowControlManager.class); + 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(); + } +}