diff --git a/elink-online-common b/elink-online-common index d81aeee..d08c7ef 160000 --- a/elink-online-common +++ b/elink-online-common @@ -1 +1 @@ -Subproject commit d81aeeef676f61f94da7107859920c7ba5beb593 +Subproject commit d08c7efcdcd8e20f210ff6bdd025283d07606955 diff --git a/src/main/java/com/eactive/eai/manage/inflow/GroupMetricDTO.java b/src/main/java/com/eactive/eai/manage/inflow/GroupMetricDTO.java new file mode 100644 index 0000000..92c6ada --- /dev/null +++ b/src/main/java/com/eactive/eai/manage/inflow/GroupMetricDTO.java @@ -0,0 +1,40 @@ +package com.eactive.eai.manage.inflow; + +public class GroupMetricDTO { + private String groupId; + private String groupName; + private boolean activate; + private long allowed; + private long rejectedPerSecond; + private long rejectedThreshold; + private long totalRequests; + private double rejectRatio; + private long lastResetTime; + + public String getGroupId() { return groupId; } + public void setGroupId(String groupId) { this.groupId = groupId; } + + public String getGroupName() { return groupName; } + public void setGroupName(String groupName) { this.groupName = groupName; } + + public boolean isActivate() { return activate; } + public void setActivate(boolean activate) { this.activate = activate; } + + public long getAllowed() { return allowed; } + public void setAllowed(long allowed) { this.allowed = allowed; } + + public long getRejectedPerSecond() { return rejectedPerSecond; } + public void setRejectedPerSecond(long rejectedPerSecond) { this.rejectedPerSecond = rejectedPerSecond; } + + public long getRejectedThreshold() { return rejectedThreshold; } + public void setRejectedThreshold(long rejectedThreshold) { this.rejectedThreshold = rejectedThreshold; } + + public long getTotalRequests() { return totalRequests; } + public void setTotalRequests(long totalRequests) { this.totalRequests = totalRequests; } + + public double getRejectRatio() { return rejectRatio; } + public void setRejectRatio(double rejectRatio) { this.rejectRatio = rejectRatio; } + + public long getLastResetTime() { return lastResetTime; } + public void setLastResetTime(long lastResetTime) { this.lastResetTime = lastResetTime; } +} diff --git a/src/main/java/com/eactive/eai/manage/inflow/InflowGroupBucketService.java b/src/main/java/com/eactive/eai/manage/inflow/InflowGroupBucketService.java index 1bb4d6f..ceabb45 100644 --- a/src/main/java/com/eactive/eai/manage/inflow/InflowGroupBucketService.java +++ b/src/main/java/com/eactive/eai/manage/inflow/InflowGroupBucketService.java @@ -1,9 +1,7 @@ package com.eactive.eai.manage.inflow; -import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; -import java.util.Map; import org.springframework.stereotype.Service; @@ -72,21 +70,8 @@ public class InflowGroupBucketService { return dto; } - /** - * 그룹 버킷 조회 (리플렉션 사용) - */ public CustomGroupBucket getCustomGroupBucket(String groupId) { - try { - InflowControlManager manager = InflowControlManager.getInstance(); - Field field = InflowControlManager.class.getDeclaredField("groupBucketList"); - field.setAccessible(true); - @SuppressWarnings("unchecked") - Map groupBucketList = - (Map) field.get(manager); - return groupBucketList.get(groupId); - } catch (Exception e) { - return null; - } + return InflowControlManager.getInstance().getGroupBucket(groupId); } public List getAllGroupBucketStatus() { diff --git a/src/main/java/com/eactive/eai/manage/inflow/InflowGroupMetricController.java b/src/main/java/com/eactive/eai/manage/inflow/InflowGroupMetricController.java new file mode 100644 index 0000000..de27d30 --- /dev/null +++ b/src/main/java/com/eactive/eai/manage/inflow/InflowGroupMetricController.java @@ -0,0 +1,85 @@ +package com.eactive.eai.manage.inflow; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/manage/inflow/group") +public class InflowGroupMetricController { + + @Autowired + private InflowGroupMetricService metricService; + + /** + * 특정 그룹의 메트릭 조회 + * GET /manage/inflow/group/{groupId}/metric + */ + @GetMapping("/{groupId}/metric") + public ResponseEntity getGroupMetric(@PathVariable String groupId) { + GroupMetricDTO metric = metricService.getGroupMetric(groupId); + + if (metric == null) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("message", "그룹을 찾을 수 없습니다: " + groupId); + return ResponseEntity.ok(error); + } + + Map result = new HashMap<>(); + result.put("success", true); + result.put("data", metric); + return ResponseEntity.ok(result); + } + + /** + * 모든 그룹의 메트릭 조회 + * GET /manage/inflow/group/metric + */ + @GetMapping("/metric") + public ResponseEntity getAllGroupMetrics() { + List list = metricService.getAllGroupMetrics(); + + Map result = new HashMap<>(); + result.put("success", true); + result.put("data", list); + result.put("count", list.size()); + return ResponseEntity.ok(result); + } + + /** + * 특정 그룹의 메트릭 초기화 + * POST /manage/inflow/group/{groupId}/metric/reset + */ + @PostMapping("/{groupId}/metric/reset") + public ResponseEntity resetGroupMetric(@PathVariable String groupId) { + boolean ok = metricService.resetGroupMetric(groupId); + + Map result = new HashMap<>(); + result.put("success", ok); + result.put("message", ok ? "초기화 완료: " + groupId : "그룹을 찾을 수 없습니다: " + groupId); + return ResponseEntity.ok(result); + } + + /** + * 전체 그룹 메트릭 초기화 + * POST /manage/inflow/group/metric/reset + */ + @PostMapping("/metric/reset") + public ResponseEntity resetAllGroupMetrics() { + metricService.resetAllGroupMetrics(); + + Map result = new HashMap<>(); + result.put("success", true); + result.put("message", "전체 그룹 메트릭 초기화 완료"); + return ResponseEntity.ok(result); + } +} diff --git a/src/main/java/com/eactive/eai/manage/inflow/InflowGroupMetricService.java b/src/main/java/com/eactive/eai/manage/inflow/InflowGroupMetricService.java new file mode 100644 index 0000000..6ad2d72 --- /dev/null +++ b/src/main/java/com/eactive/eai/manage/inflow/InflowGroupMetricService.java @@ -0,0 +1,81 @@ +package com.eactive.eai.manage.inflow; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.stereotype.Service; + +import com.eactive.eai.common.inflow.Bucket; +import com.eactive.eai.common.inflow.InflowControlUtil; +import com.eactive.eai.common.inflow.InflowGroupVO; +import com.eactive.eai.common.inflow.dual.DualInflowControlManager; + +@Service +public class InflowGroupMetricService { + + public GroupMetricDTO getGroupMetric(String groupId) { + DualInflowControlManager manager = getManager(); + if (manager == null) return null; + + InflowGroupVO groupVo = manager.getGroupInflowThreshold(groupId); + if (groupVo == null) return null; + + return buildDTO(groupId, groupVo, manager.getGroupMetrics(groupId)); + } + + public List getAllGroupMetrics() { + DualInflowControlManager manager = getManager(); + if (manager == null) return new ArrayList<>(); + + List result = new ArrayList<>(); + for (String groupId : manager.getGroupAllKeys()) { + GroupMetricDTO dto = getGroupMetric(groupId); + if (dto != null) result.add(dto); + } + return result; + } + + public boolean resetGroupMetric(String groupId) { + DualInflowControlManager manager = getManager(); + if (manager == null) return false; + if (manager.getGroupInflowThreshold(groupId) == null) return false; + manager.resetGroupMetrics(groupId); + return true; + } + + public void resetAllGroupMetrics() { + DualInflowControlManager manager = getManager(); + if (manager != null) manager.resetAllGroupMetrics(); + } + + protected DualInflowControlManager getManager() { + Bucket bucket = InflowControlUtil.getBucket(); + return (bucket instanceof DualInflowControlManager) + ? (DualInflowControlManager) bucket + : null; + } + + private GroupMetricDTO buildDTO(String groupId, InflowGroupVO groupVo, + DualInflowControlManager.TargetMetrics m) { + GroupMetricDTO dto = new GroupMetricDTO(); + dto.setGroupId(groupId); + dto.setGroupName(groupVo.getGroupName()); + dto.setActivate(groupVo.isActivate()); + + if (m != null) { + long allowed = m.allowed.get(); + long rejPs = m.rejectedPerSecond.get(); + long rejTh = m.rejectedThreshold.get(); + long total = allowed + rejPs + rejTh; + dto.setAllowed(allowed); + dto.setRejectedPerSecond(rejPs); + dto.setRejectedThreshold(rejTh); + dto.setTotalRequests(total); + dto.setRejectRatio(total > 0 + ? Math.round((double)(rejPs + rejTh) / total * 10000) / 100.0 + : 0.0); + dto.setLastResetTime(m.lastResetTime); + } + return dto; + } +} diff --git a/src/main/resources/standard-message-config.properties b/src/main/resources/standard-message-config.properties index 0e55322..c28227a 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.RequestProcessor +requestProcessor.class=com.eactive.eai.inbound.processor.DualRequestProcessor # reader : parsing input data to StandardMessage reader.JSON=com.eactive.eai.message.parser.JsonReader reader.JSN=com.eactive.eai.message.parser.JsonReader diff --git a/src/test/java/com/eactive/eai/manage/inflow/InflowGroupMetricControllerTest.java b/src/test/java/com/eactive/eai/manage/inflow/InflowGroupMetricControllerTest.java new file mode 100644 index 0000000..4233287 --- /dev/null +++ b/src/test/java/com/eactive/eai/manage/inflow/InflowGroupMetricControllerTest.java @@ -0,0 +1,173 @@ +package com.eactive.eai.manage.inflow; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.http.ResponseEntity; +import org.springframework.test.util.ReflectionTestUtils; + +/** + * InflowGroupMetricController 단위 테스트. + * + *

Spring MVC를 기동하지 않고 컨트롤러를 직접 인스턴스화하여 + * 응답 구조(success, data, count, message)를 검증한다. + */ +class InflowGroupMetricControllerTest { + + private InflowGroupMetricController controller; + private InflowGroupMetricService mockService; + + @BeforeEach + void setUp() { + controller = new InflowGroupMetricController(); + mockService = mock(InflowGroupMetricService.class); + ReflectionTestUtils.setField(controller, "metricService", mockService); + } + + // ------------------------------------------------------------------------- + // Helper + // ------------------------------------------------------------------------- + + private GroupMetricDTO makeDTO(String groupId, String groupName, + long allowed, long rejPs, long rejTh) { + GroupMetricDTO dto = new GroupMetricDTO(); + dto.setGroupId(groupId); + dto.setGroupName(groupName); + dto.setActivate(true); + dto.setAllowed(allowed); + dto.setRejectedPerSecond(rejPs); + dto.setRejectedThreshold(rejTh); + long total = allowed + rejPs + rejTh; + dto.setTotalRequests(total); + dto.setRejectRatio(total > 0 ? (double)(rejPs + rejTh) / total * 100 : 0.0); + dto.setLastResetTime(System.currentTimeMillis()); + return dto; + } + + @SuppressWarnings("unchecked") + private Map body(ResponseEntity response) { + return (Map) response.getBody(); + } + + // ========================================================================= + // GET /{groupId}/metric + // ========================================================================= + + @Test + void getGroupMetric_found_successTrueAndDataPresent() { + GroupMetricDTO dto = makeDTO("G1", "결제그룹", 900, 60, 40); + when(mockService.getGroupMetric("G1")).thenReturn(dto); + + ResponseEntity response = controller.getGroupMetric("G1"); + Map body = body(response); + + assertEquals(200, response.getStatusCodeValue()); + assertEquals(Boolean.TRUE, body.get("success")); + assertSame(dto, body.get("data")); + assertFalse(body.containsKey("message"), "성공 응답에 message 필드는 없어야 합니다"); + } + + @Test + void getGroupMetric_notFound_successFalseWithMessage() { + when(mockService.getGroupMetric("GHOST")).thenReturn(null); + + ResponseEntity response = controller.getGroupMetric("GHOST"); + Map body = body(response); + + assertEquals(200, response.getStatusCodeValue()); + assertEquals(Boolean.FALSE, body.get("success")); + assertTrue(body.get("message").toString().contains("GHOST"), + "오류 메시지에 groupId가 포함되어야 합니다"); + } + + // ========================================================================= + // GET /metric + // ========================================================================= + + @Test + void getAllGroupMetrics_returnsListAndCount() { + List list = Arrays.asList( + makeDTO("G1", "그룹1", 100, 5, 0), + makeDTO("G2", "그룹2", 200, 0, 10) + ); + when(mockService.getAllGroupMetrics()).thenReturn(list); + + ResponseEntity response = controller.getAllGroupMetrics(); + Map body = body(response); + + assertEquals(Boolean.TRUE, body.get("success")); + assertSame(list, body.get("data")); + assertEquals(2, body.get("count")); + } + + @Test + void getAllGroupMetrics_emptyList_countZero() { + when(mockService.getAllGroupMetrics()).thenReturn(Collections.emptyList()); + + ResponseEntity response = controller.getAllGroupMetrics(); + Map body = body(response); + + assertEquals(Boolean.TRUE, body.get("success")); + assertEquals(0, body.get("count")); + } + + // ========================================================================= + // POST /{groupId}/metric/reset + // ========================================================================= + + @Test + void resetGroupMetric_success_successTrueWithGroupId() { + when(mockService.resetGroupMetric("G1")).thenReturn(true); + + ResponseEntity response = controller.resetGroupMetric("G1"); + Map body = body(response); + + assertEquals(Boolean.TRUE, body.get("success")); + assertTrue(body.get("message").toString().contains("G1"), + "성공 메시지에 groupId가 포함되어야 합니다"); + } + + @Test + void resetGroupMetric_notFound_successFalseWithGroupId() { + when(mockService.resetGroupMetric("GHOST")).thenReturn(false); + + ResponseEntity response = controller.resetGroupMetric("GHOST"); + Map body = body(response); + + assertEquals(Boolean.FALSE, body.get("success")); + assertTrue(body.get("message").toString().contains("GHOST"), + "오류 메시지에 groupId가 포함되어야 합니다"); + } + + // ========================================================================= + // POST /metric/reset + // ========================================================================= + + @Test + void resetAllGroupMetrics_alwaysSuccessTrue() { + doNothing().when(mockService).resetAllGroupMetrics(); + + ResponseEntity response = controller.resetAllGroupMetrics(); + Map body = body(response); + + assertEquals(Boolean.TRUE, body.get("success")); + assertNotNull(body.get("message")); + verify(mockService).resetAllGroupMetrics(); + } + + @Test + void resetAllGroupMetrics_callsDelegateOnce() { + doNothing().when(mockService).resetAllGroupMetrics(); + + controller.resetAllGroupMetrics(); + + verify(mockService, times(1)).resetAllGroupMetrics(); + } +} diff --git a/src/test/java/com/eactive/eai/manage/inflow/InflowGroupMetricServiceTest.java b/src/test/java/com/eactive/eai/manage/inflow/InflowGroupMetricServiceTest.java new file mode 100644 index 0000000..1f5888f --- /dev/null +++ b/src/test/java/com/eactive/eai/manage/inflow/InflowGroupMetricServiceTest.java @@ -0,0 +1,236 @@ +package com.eactive.eai.manage.inflow; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import com.eactive.eai.common.inflow.InflowGroupVO; +import com.eactive.eai.common.inflow.dual.DualInflowControlManager; + +/** + * InflowGroupMetricService 단위 테스트. + * + *

getManager()를 protected로 오버라이드하는 내부 테스트 서브클래스를 사용하여 + * mockito-inline(static mock) 없이 DualInflowControlManager 의존성을 주입한다. + */ +class InflowGroupMetricServiceTest { + + // ------------------------------------------------------------------------- + // 테스트용 서브클래스 — getManager()를 오버라이드하여 mock 주입 + // ------------------------------------------------------------------------- + + private static InflowGroupMetricService serviceWith(DualInflowControlManager manager) { + return new InflowGroupMetricService() { + @Override + protected DualInflowControlManager getManager() { + return manager; + } + }; + } + + // ------------------------------------------------------------------------- + // Helpers + // ------------------------------------------------------------------------- + + private InflowGroupVO makeGroupVO(String groupId, String groupName) { + InflowGroupVO vo = new InflowGroupVO(); + vo.setGroupId(groupId); + vo.setGroupName(groupName); + vo.setActivate(true); + return vo; + } + + private DualInflowControlManager.TargetMetrics makeMetrics(long allowed, long rejPs, long rejTh) { + DualInflowControlManager.TargetMetrics m = new DualInflowControlManager.TargetMetrics(); + m.allowed.set(allowed); + m.rejectedPerSecond.set(rejPs); + m.rejectedThreshold.set(rejTh); + return m; + } + + // ========================================================================= + // getGroupMetric + // ========================================================================= + + @Test + void getGroupMetric_managerIsNull_returnsNull() { + assertNull(serviceWith(null).getGroupMetric("G1")); + } + + @Test + void getGroupMetric_groupNotFound_returnsNull() { + DualInflowControlManager manager = mock(DualInflowControlManager.class); + when(manager.getGroupInflowThreshold("G1")).thenReturn(null); + + assertNull(serviceWith(manager).getGroupMetric("G1")); + } + + @Test + void getGroupMetric_noMetricsYet_returnsZeroCounters() { + DualInflowControlManager manager = mock(DualInflowControlManager.class); + when(manager.getGroupInflowThreshold("G1")).thenReturn(makeGroupVO("G1", "결제그룹")); + when(manager.getGroupMetrics("G1")).thenReturn(null); + + GroupMetricDTO dto = serviceWith(manager).getGroupMetric("G1"); + + assertNotNull(dto); + assertEquals("G1", dto.getGroupId()); + assertEquals("결제그룹", dto.getGroupName()); + assertTrue(dto.isActivate()); + assertEquals(0, dto.getAllowed()); + assertEquals(0, dto.getRejectedPerSecond()); + assertEquals(0, dto.getRejectedThreshold()); + assertEquals(0, dto.getTotalRequests()); + assertEquals(0.0, dto.getRejectRatio()); + assertEquals(0, dto.getLastResetTime()); + } + + @Test + void getGroupMetric_withMetrics_returnsCorrectValues() { + DualInflowControlManager manager = mock(DualInflowControlManager.class); + when(manager.getGroupInflowThreshold("G1")).thenReturn(makeGroupVO("G1", "결제그룹")); + when(manager.getGroupMetrics("G1")).thenReturn(makeMetrics(900, 60, 40)); + + GroupMetricDTO dto = serviceWith(manager).getGroupMetric("G1"); + + assertEquals(900, dto.getAllowed()); + assertEquals(60, dto.getRejectedPerSecond()); + assertEquals(40, dto.getRejectedThreshold()); + assertEquals(1000, dto.getTotalRequests()); + } + + @Test + void getGroupMetric_rejectRatioCalculation() { + DualInflowControlManager manager = mock(DualInflowControlManager.class); + when(manager.getGroupInflowThreshold("G1")).thenReturn(makeGroupVO("G1", "G1")); + // allowed=90, rejPs=5, rejTh=5 → reject=10/100 = 10.00% + when(manager.getGroupMetrics("G1")).thenReturn(makeMetrics(90, 5, 5)); + + GroupMetricDTO dto = serviceWith(manager).getGroupMetric("G1"); + + assertEquals(10.0, dto.getRejectRatio(), 0.01); + } + + @Test + void getGroupMetric_zeroTotal_rejectRatioIsZero() { + DualInflowControlManager manager = mock(DualInflowControlManager.class); + when(manager.getGroupInflowThreshold("G1")).thenReturn(makeGroupVO("G1", "G1")); + when(manager.getGroupMetrics("G1")).thenReturn(makeMetrics(0, 0, 0)); + + GroupMetricDTO dto = serviceWith(manager).getGroupMetric("G1"); + + assertEquals(0.0, dto.getRejectRatio(), 0.0001); + } + + @Test + void getGroupMetric_lastResetTimeIsPropagated() { + DualInflowControlManager.TargetMetrics m = makeMetrics(10, 0, 0); + m.lastResetTime = 1234567890L; + + DualInflowControlManager manager = mock(DualInflowControlManager.class); + when(manager.getGroupInflowThreshold("G1")).thenReturn(makeGroupVO("G1", "G1")); + when(manager.getGroupMetrics("G1")).thenReturn(m); + + GroupMetricDTO dto = serviceWith(manager).getGroupMetric("G1"); + + assertEquals(1234567890L, dto.getLastResetTime()); + } + + // ========================================================================= + // getAllGroupMetrics + // ========================================================================= + + @Test + void getAllGroupMetrics_managerIsNull_returnsEmptyList() { + List result = serviceWith(null).getAllGroupMetrics(); + + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + @Test + void getAllGroupMetrics_noGroups_returnsEmptyList() { + DualInflowControlManager manager = mock(DualInflowControlManager.class); + when(manager.getGroupAllKeys()).thenReturn(new String[]{}); + + assertTrue(serviceWith(manager).getAllGroupMetrics().isEmpty()); + } + + @Test + void getAllGroupMetrics_twoGroups_returnsBoth() { + DualInflowControlManager manager = mock(DualInflowControlManager.class); + when(manager.getGroupAllKeys()).thenReturn(new String[]{"G1", "G2"}); + when(manager.getGroupInflowThreshold("G1")).thenReturn(makeGroupVO("G1", "그룹1")); + when(manager.getGroupInflowThreshold("G2")).thenReturn(makeGroupVO("G2", "그룹2")); + when(manager.getGroupMetrics("G1")).thenReturn(makeMetrics(10, 0, 0)); + when(manager.getGroupMetrics("G2")).thenReturn(makeMetrics(20, 5, 0)); + + List result = serviceWith(manager).getAllGroupMetrics(); + + assertEquals(2, result.size()); + assertEquals("G1", result.get(0).getGroupId()); + assertEquals("G2", result.get(1).getGroupId()); + } + + @Test + void getAllGroupMetrics_groupNotFoundIsSkipped() { + DualInflowControlManager manager = mock(DualInflowControlManager.class); + when(manager.getGroupAllKeys()).thenReturn(new String[]{"G1", "GHOST"}); + when(manager.getGroupInflowThreshold("G1")).thenReturn(makeGroupVO("G1", "그룹1")); + when(manager.getGroupInflowThreshold("GHOST")).thenReturn(null); + when(manager.getGroupMetrics("G1")).thenReturn(makeMetrics(5, 0, 0)); + + List result = serviceWith(manager).getAllGroupMetrics(); + + assertEquals(1, result.size()); + assertEquals("G1", result.get(0).getGroupId()); + } + + // ========================================================================= + // resetGroupMetric + // ========================================================================= + + @Test + void resetGroupMetric_managerIsNull_returnsFalse() { + assertFalse(serviceWith(null).resetGroupMetric("G1")); + } + + @Test + void resetGroupMetric_groupNotFound_returnsFalse() { + DualInflowControlManager manager = mock(DualInflowControlManager.class); + when(manager.getGroupInflowThreshold("G1")).thenReturn(null); + + assertFalse(serviceWith(manager).resetGroupMetric("G1")); + verify(manager, never()).resetGroupMetrics(any()); + } + + @Test + void resetGroupMetric_success_returnsTrueAndCallsManager() { + DualInflowControlManager manager = mock(DualInflowControlManager.class); + when(manager.getGroupInflowThreshold("G1")).thenReturn(makeGroupVO("G1", "그룹1")); + + assertTrue(serviceWith(manager).resetGroupMetric("G1")); + verify(manager).resetGroupMetrics("G1"); + } + + // ========================================================================= + // resetAllGroupMetrics + // ========================================================================= + + @Test + void resetAllGroupMetrics_managerIsNull_noException() { + assertDoesNotThrow(() -> serviceWith(null).resetAllGroupMetrics()); + } + + @Test + void resetAllGroupMetrics_callsManagerReset() { + DualInflowControlManager manager = mock(DualInflowControlManager.class); + + serviceWith(manager).resetAllGroupMetrics(); + + verify(manager).resetAllGroupMetrics(); + } +}