diff --git a/elink-online-common b/elink-online-common index 8900966..43548a9 160000 --- a/elink-online-common +++ b/elink-online-common @@ -1 +1 @@ -Subproject commit 8900966d71dbde9ca8a09b5e97e5629c4fbe69fa +Subproject commit 43548a90c49407f31e132d44c966d35be7f74a5d diff --git a/src/main/java/com/eactive/eai/custom/inflow/ClientInflowTargetBucketController.java b/src/main/java/com/eactive/eai/custom/inflow/ClientInflowTargetBucketController.java new file mode 100644 index 0000000..6870a94 --- /dev/null +++ b/src/main/java/com/eactive/eai/custom/inflow/ClientInflowTargetBucketController.java @@ -0,0 +1,63 @@ +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.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.eactive.eai.manage.inflow.TargetBucketStatusDTO; + +/** + * 클라이언트 버킷 상태 모니터링 API. + * + * ClientDualInflowControlManager 가 활성화된 환경에서만 정상 응답한다. + * + * GET /manage/inflow/client/bucket-status → 전체 클라이언트 버킷 상태 + * GET /manage/inflow/client/{clientId}/bucket-status → 특정 클라이언트 버킷 상태 + */ +@RestController +@RequestMapping("/manage/inflow") +public class ClientInflowTargetBucketController { + + @Autowired + private ClientInflowTargetBucketService bucketService; + + @GetMapping("/client/bucket-status") + public ResponseEntity getAllClientBucketStatus() { + List list = bucketService.getAllClientBucketStatus(); + return ok(list); + } + + @GetMapping("/client/{clientId}/bucket-status") + public ResponseEntity getClientBucketStatus(@PathVariable String clientId) { + TargetBucketStatusDTO dto = bucketService.getClientBucketStatus(clientId); + return single(dto, "클라이언트를 찾을 수 없습니다: " + clientId); + } + + private ResponseEntity ok(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 single(TargetBucketStatusDTO 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); + } +} diff --git a/src/main/java/com/eactive/eai/custom/inflow/ClientInflowTargetBucketService.java b/src/main/java/com/eactive/eai/custom/inflow/ClientInflowTargetBucketService.java new file mode 100644 index 0000000..cb5e388 --- /dev/null +++ b/src/main/java/com/eactive/eai/custom/inflow/ClientInflowTargetBucketService.java @@ -0,0 +1,79 @@ +package com.eactive.eai.custom.inflow; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +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.manage.inflow.GroupBucketStatusDTO; +import com.eactive.eai.manage.inflow.TargetBucketStatusDTO; + +/** + * 클라이언트 버킷 상태 조회 서비스. + * + * ClientDualInflowControlManager 가 활성화된 경우에만 동작한다. + */ +@Service +public class ClientInflowTargetBucketService { + + public TargetBucketStatusDTO getClientBucketStatus(String clientId) { + ClientDualInflowControlManager manager = getClientDualManager(); + if (manager == null) return null; + DualCustomBucket bucket = manager.getClientBucket(clientId); + if (bucket == null) return null; + return toDto(clientId, "CLIENT", bucket); + } + + public List getAllClientBucketStatus() { + ClientDualInflowControlManager manager = getClientDualManager(); + if (manager == null) return new ArrayList<>(); + return toDtoList("CLIENT", manager.getClientBucketMap()); + } + + 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<>(); + + if (vo.getThresholdPerSecond() > 0) { + long capacity = vo.getThresholdPerSecond(); + long available = bucket.getPerSecondAvailableTokens(); + if (available < 0) available = capacity; + buckets.add(new GroupBucketStatusDTO.BucketInfo( + "perSecond", capacity, Math.min(available, capacity), "SEC")); + } + + if (vo.getThreshold() > 0) { + long capacity = vo.getThreshold(); + long available = bucket.getThresholdAvailableTokens(); + if (available < 0) available = capacity; + buckets.add(new GroupBucketStatusDTO.BucketInfo( + "threshold", capacity, Math.min(available, capacity), vo.getThresholdTimeUnit())); + } + + TargetBucketStatusDTO dto = new TargetBucketStatusDTO(); + dto.setTargetId(targetId); + dto.setTargetType(targetType); + dto.setActivate(vo.isActivate()); + dto.setBuckets(buckets); + return dto; + } + + private List toDtoList(String targetType, Map bucketMap) { + List result = new ArrayList<>(); + for (Map.Entry entry : bucketMap.entrySet()) { + result.add(toDto(entry.getKey(), targetType, entry.getValue())); + } + return result; + } +} diff --git a/src/main/java/com/eactive/eai/manage/inflow/InflowTargetBucketController.java b/src/main/java/com/eactive/eai/manage/inflow/InflowTargetBucketController.java index 1d26c14..fb43384 100644 --- a/src/main/java/com/eactive/eai/manage/inflow/InflowTargetBucketController.java +++ b/src/main/java/com/eactive/eai/manage/inflow/InflowTargetBucketController.java @@ -21,8 +21,6 @@ import org.springframework.web.bind.annotation.RestController; * GET /manage/inflow/adapter/{adapterId}/bucket-status → 특정 어댑터 버킷 상태 * GET /manage/inflow/interface/bucket-status → 전체 인터페이스 버킷 상태 * GET /manage/inflow/interface/{interfaceId}/bucket-status → 특정 인터페이스 버킷 상태 - * GET /manage/inflow/client/bucket-status → 전체 클라이언트 버킷 상태 - * GET /manage/inflow/client/{clientId}/bucket-status → 특정 클라이언트 버킷 상태 */ @RestController @RequestMapping("/manage/inflow") @@ -63,22 +61,6 @@ public class InflowTargetBucketController { return single(dto, "인터페이스를 찾을 수 없습니다: " + interfaceId); } - // ========================================================================= - // 클라이언트 - // ========================================================================= - - @GetMapping("/client/bucket-status") - public ResponseEntity getAllClientBucketStatus() { - List list = bucketService.getAllClientBucketStatus(); - return ok(list); - } - - @GetMapping("/client/{clientId}/bucket-status") - public ResponseEntity getClientBucketStatus(@PathVariable String clientId) { - TargetBucketStatusDTO dto = bucketService.getClientBucketStatus(clientId); - return single(dto, "클라이언트를 찾을 수 없습니다: " + clientId); - } - // ========================================================================= // Helpers // ========================================================================= 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 148d6de..c34ea5d 100644 --- a/src/main/java/com/eactive/eai/manage/inflow/InflowTargetBucketService.java +++ b/src/main/java/com/eactive/eai/manage/inflow/InflowTargetBucketService.java @@ -9,7 +9,6 @@ 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; @@ -58,24 +57,6 @@ public class InflowTargetBucketService { return toDtoList("INTERFACE", manager.getInterfaceBucketMap()); } - // ------------------------------------------------------------------------- - // 클라이언트 - // ------------------------------------------------------------------------- - - public TargetBucketStatusDTO getClientBucketStatus(String clientId) { - ClientDualInflowControlManager manager = getClientDualManager(); - if (manager == null) return null; - DualCustomBucket bucket = manager.getClientBucket(clientId); - if (bucket == null) return null; - return toDto(clientId, "CLIENT", bucket); - } - - public List getAllClientBucketStatus() { - ClientDualInflowControlManager manager = getClientDualManager(); - if (manager == null) return new ArrayList<>(); - return toDtoList("CLIENT", manager.getClientBucketMap()); - } - // ------------------------------------------------------------------------- // Private helpers // ------------------------------------------------------------------------- @@ -85,11 +66,6 @@ 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/test/java/com/eactive/eai/custom/inflow/ClientInflowTargetBucketControllerTest.java b/src/test/java/com/eactive/eai/custom/inflow/ClientInflowTargetBucketControllerTest.java new file mode 100644 index 0000000..4cff51c --- /dev/null +++ b/src/test/java/com/eactive/eai/custom/inflow/ClientInflowTargetBucketControllerTest.java @@ -0,0 +1,106 @@ +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.TargetBucketStatusDTO; + +/** + * ClientInflowTargetBucketController 단위 테스트. + */ +class ClientInflowTargetBucketControllerTest { + + private ClientInflowTargetBucketController controller; + private ClientInflowTargetBucketService mockService; + + @BeforeEach + void setUp() { + controller = new ClientInflowTargetBucketController(); + mockService = mock(ClientInflowTargetBucketService.class); + ReflectionTestUtils.setField(controller, "bucketService", mockService); + } + + private TargetBucketStatusDTO makeDTO(String id, String type) { + TargetBucketStatusDTO dto = new TargetBucketStatusDTO(); + dto.setTargetId(id); + dto.setTargetType(type); + dto.setActivate(true); + dto.setBuckets(Collections.emptyList()); + return dto; + } + + @SuppressWarnings("unchecked") + private Map body(ResponseEntity response) { + return (Map) response.getBody(); + } + + // ========================================================================= + // 단일 조회 + // ========================================================================= + + @Test + void getClientBucketStatus_found_successTrueAndDataPresent() { + TargetBucketStatusDTO dto = makeDTO("CLIENT_A", "CLIENT"); + when(mockService.getClientBucketStatus("CLIENT_A")).thenReturn(dto); + + ResponseEntity response = controller.getClientBucketStatus("CLIENT_A"); + 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 getClientBucketStatus_notFound_successFalseWithClientId() { + when(mockService.getClientBucketStatus("CLIENT_X")).thenReturn(null); + + ResponseEntity response = controller.getClientBucketStatus("CLIENT_X"); + Map body = body(response); + + assertEquals(Boolean.FALSE, body.get("success")); + assertTrue(body.get("message").toString().contains("CLIENT_X")); + } + + // ========================================================================= + // 전체 조회 + // ========================================================================= + + @Test + void getAllClientBucketStatus_returnsListAndCount() { + List list = Arrays.asList( + makeDTO("CLIENT_A", "CLIENT"), + makeDTO("CLIENT_B", "CLIENT"), + makeDTO("CLIENT_C", "CLIENT") + ); + when(mockService.getAllClientBucketStatus()).thenReturn(list); + + ResponseEntity response = controller.getAllClientBucketStatus(); + Map body = body(response); + + assertEquals(Boolean.TRUE, body.get("success")); + assertSame(list, body.get("data")); + assertEquals(3, body.get("count")); + } + + @Test + void getAllClientBucketStatus_emptyList_countZero() { + when(mockService.getAllClientBucketStatus()).thenReturn(Collections.emptyList()); + + Map body = body(controller.getAllClientBucketStatus()); + + assertEquals(Boolean.TRUE, body.get("success")); + assertEquals(0, body.get("count")); + } +} diff --git a/src/test/java/com/eactive/eai/custom/inflow/ClientInflowTargetBucketServiceTest.java b/src/test/java/com/eactive/eai/custom/inflow/ClientInflowTargetBucketServiceTest.java new file mode 100644 index 0000000..d40cbe1 --- /dev/null +++ b/src/test/java/com/eactive/eai/custom/inflow/ClientInflowTargetBucketServiceTest.java @@ -0,0 +1,208 @@ +package com.eactive.eai.custom.inflow; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.time.Duration; +import java.util.List; +import java.util.Map; +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.manage.inflow.GroupBucketStatusDTO; +import com.eactive.eai.manage.inflow.TargetBucketStatusDTO; + +import io.github.bucket4j.Bandwidth; +import io.github.bucket4j.Bucket4j; +import io.github.bucket4j.local.LocalBucket; + +/** + * ClientInflowTargetBucketService 단위 테스트. + */ +class ClientInflowTargetBucketServiceTest { + + // ------------------------------------------------------------------------- + // 테스트용 서브클래스 + // ------------------------------------------------------------------------- + + private static ClientInflowTargetBucketService serviceWith(ClientDualInflowControlManager manager) { + return new ClientInflowTargetBucketService() { + @Override + protected ClientDualInflowControlManager getClientDualManager() { return manager; } + }; + } + + // ------------------------------------------------------------------------- + // Helpers + // ------------------------------------------------------------------------- + + private InflowTargetVO makeVO(String name, long perSec, long threshold, String unit, boolean activate) { + InflowTargetVO vo = new InflowTargetVO(); + vo.setName(name); + vo.setThresholdPerSecond(perSec); + vo.setThreshold(threshold); + vo.setThresholdTimeUnit(unit); + vo.setActivate(activate); + return vo; + } + + private LocalBucket freshBucket(long capacity) { + return Bucket4j.builder() + .addLimit(Bandwidth.simple(capacity, Duration.ofSeconds(1))) + .build(); + } + + private LocalBucket exhaustedBucket(long capacity) { + LocalBucket b = freshBucket(capacity); + b.tryConsume(capacity); + return b; + } + + private DualCustomBucket makeDualBucket(String name, long perSec, long threshold) { + return new DualCustomBucket(freshBucket(perSec), freshBucket(threshold), + makeVO(name, perSec, threshold, "DAY", true)); + } + + // ========================================================================= + // ClientDualManager null (비활성 환경) + // ========================================================================= + + @Test + void getClientBucketStatus_managerIsNull_returnsNull() { + assertNull(serviceWith(null).getClientBucketStatus("CLIENT_A")); + } + + @Test + void getAllClientBucketStatus_managerIsNull_returnsEmptyList() { + assertTrue(serviceWith(null).getAllClientBucketStatus().isEmpty()); + } + + // ========================================================================= + // 버킷 미존재 + // ========================================================================= + + @Test + void getClientBucketStatus_notFound_returnsNull() { + ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class); + when(manager.getClientBucket("CLIENT_A")).thenReturn(null); + + assertNull(serviceWith(manager).getClientBucketStatus("CLIENT_A")); + } + + // ========================================================================= + // 단일 조회 — targetType, targetId 매핑 + // ========================================================================= + + @Test + void getClientBucketStatus_found_mapsClientType() { + ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class); + when(manager.getClientBucket("CLIENT_A")).thenReturn(makeDualBucket("CLIENT_A", 50, 5000)); + + TargetBucketStatusDTO dto = serviceWith(manager).getClientBucketStatus("CLIENT_A"); + + assertNotNull(dto); + assertEquals("CLIENT_A", dto.getTargetId()); + assertEquals("CLIENT", dto.getTargetType()); + assertTrue(dto.isActivate()); + assertEquals(2, dto.getBuckets().size()); + } + + // ========================================================================= + // BucketInfo 타입별 포함 여부 + // ========================================================================= + + @Test + void toDto_onlyThreshold_bucketInfoHasOneThresholdEntry() { + InflowTargetVO vo = makeVO("C1", 0, 1000, "DAY", true); + DualCustomBucket bucket = new DualCustomBucket(null, freshBucket(1000), vo); + + ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class); + when(manager.getClientBucket("C1")).thenReturn(bucket); + + TargetBucketStatusDTO dto = serviceWith(manager).getClientBucketStatus("C1"); + + assertEquals(1, dto.getBuckets().size()); + assertEquals("threshold", dto.getBuckets().get(0).getType()); + } + + @Test + void toDto_onlyPerSecond_bucketInfoHasOnePerSecondEntry() { + InflowTargetVO vo = makeVO("C2", 100, 0, null, true); + DualCustomBucket bucket = new DualCustomBucket(freshBucket(100), null, vo); + + ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class); + when(manager.getClientBucket("C2")).thenReturn(bucket); + + TargetBucketStatusDTO dto = serviceWith(manager).getClientBucketStatus("C2"); + + assertEquals(1, dto.getBuckets().size()); + assertEquals("perSecond", dto.getBuckets().get(0).getType()); + } + + @Test + void toDto_bothBuckets_bucketInfoHasTwoEntries() { + ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class); + when(manager.getClientBucket("C3")).thenReturn(makeDualBucket("C3", 100, 5000)); + + TargetBucketStatusDTO dto = serviceWith(manager).getClientBucketStatus("C3"); + + assertEquals(2, dto.getBuckets().size()); + assertEquals("perSecond", dto.getBuckets().get(0).getType()); + assertEquals("threshold", dto.getBuckets().get(1).getType()); + } + + // ========================================================================= + // 사용률 계산 + // ========================================================================= + + @Test + void toDto_exhaustedThreshold_usagePercentIs100() { + InflowTargetVO vo = makeVO("C4", 0, 100, "DAY", true); + DualCustomBucket bucket = new DualCustomBucket(null, exhaustedBucket(100), vo); + + ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class); + when(manager.getClientBucket("C4")).thenReturn(bucket); + + TargetBucketStatusDTO dto = serviceWith(manager).getClientBucketStatus("C4"); + + GroupBucketStatusDTO.BucketInfo info = dto.getBuckets().get(0); + assertEquals(0, info.getAvailableTokens()); + assertEquals(100.0, info.getUsagePercent(), 0.01); + } + + @Test + void toDto_freshBucket_usagePercentIsZero() { + InflowTargetVO vo = makeVO("C5", 0, 100, "DAY", true); + DualCustomBucket bucket = new DualCustomBucket(null, freshBucket(100), vo); + + ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class); + when(manager.getClientBucket("C5")).thenReturn(bucket); + + TargetBucketStatusDTO dto = serviceWith(manager).getClientBucketStatus("C5"); + + GroupBucketStatusDTO.BucketInfo info = dto.getBuckets().get(0); + assertEquals(100, info.getAvailableTokens()); + assertEquals(0.0, info.getUsagePercent(), 0.01); + } + + // ========================================================================= + // 전체 목록 조회 + // ========================================================================= + + @Test + void getAllClientBucketStatus_twoEntries_returnsBoth() { + 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(); + + assertEquals(2, result.size()); + } +} diff --git a/src/test/java/com/eactive/eai/manage/inflow/InflowTargetBucketControllerTest.java b/src/test/java/com/eactive/eai/manage/inflow/InflowTargetBucketControllerTest.java index e028725..8384e8d 100644 --- a/src/test/java/com/eactive/eai/manage/inflow/InflowTargetBucketControllerTest.java +++ b/src/test/java/com/eactive/eai/manage/inflow/InflowTargetBucketControllerTest.java @@ -18,6 +18,7 @@ import org.springframework.test.util.ReflectionTestUtils; * *

Spring MVC를 기동하지 않고 컨트롤러를 직접 인스턴스화하여 * 응답 구조(success, data, count, message)를 검증한다. + * 클라이언트 관련 테스트는 ClientInflowTargetBucketControllerTest 에서 검증한다. */ class InflowTargetBucketControllerTest { @@ -148,64 +149,4 @@ class InflowTargetBucketControllerTest { assertEquals(Boolean.TRUE, body.get("success")); assertEquals(1, body.get("count")); } - - // ========================================================================= - // 클라이언트 — 단일 조회 - // ========================================================================= - - @Test - void getClientBucketStatus_found_successTrueAndDataPresent() { - TargetBucketStatusDTO dto = makeDTO("CLIENT_A", "CLIENT"); - when(mockService.getClientBucketStatus("CLIENT_A")).thenReturn(dto); - - ResponseEntity response = controller.getClientBucketStatus("CLIENT_A"); - 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 getClientBucketStatus_notFound_successFalseWithClientId() { - when(mockService.getClientBucketStatus("CLIENT_X")).thenReturn(null); - - ResponseEntity response = controller.getClientBucketStatus("CLIENT_X"); - Map body = body(response); - - assertEquals(Boolean.FALSE, body.get("success")); - assertTrue(body.get("message").toString().contains("CLIENT_X")); - } - - // ========================================================================= - // 클라이언트 — 전체 조회 - // ========================================================================= - - @Test - void getAllClientBucketStatus_returnsListAndCount() { - List list = Arrays.asList( - makeDTO("CLIENT_A", "CLIENT"), - makeDTO("CLIENT_B", "CLIENT"), - makeDTO("CLIENT_C", "CLIENT") - ); - when(mockService.getAllClientBucketStatus()).thenReturn(list); - - ResponseEntity response = controller.getAllClientBucketStatus(); - Map body = body(response); - - assertEquals(Boolean.TRUE, body.get("success")); - assertSame(list, body.get("data")); - assertEquals(3, body.get("count")); - } - - @Test - void getAllClientBucketStatus_emptyList_countZero() { - when(mockService.getAllClientBucketStatus()).thenReturn(Collections.emptyList()); - - Map body = body(controller.getAllClientBucketStatus()); - - assertEquals(Boolean.TRUE, body.get("success")); - assertEquals(0, body.get("count")); - } } 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 cde43ce..8aedcad 100644 --- a/src/test/java/com/eactive/eai/manage/inflow/InflowTargetBucketServiceTest.java +++ b/src/test/java/com/eactive/eai/manage/inflow/InflowTargetBucketServiceTest.java @@ -11,7 +11,6 @@ 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; @@ -23,7 +22,7 @@ import io.github.bucket4j.local.LocalBucket; * InflowTargetBucketService 단위 테스트. * *

어댑터/인터페이스: getDualManager()를 오버라이드하는 서브클래스 사용. - * 클라이언트: getClientDualManager()를 오버라이드하는 서브클래스 사용. + * 클라이언트 관련 테스트는 ClientInflowTargetBucketServiceTest 에서 검증한다. */ class InflowTargetBucketServiceTest { @@ -35,17 +34,6 @@ class InflowTargetBucketServiceTest { return new InflowTargetBucketService() { @Override 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; } }; } @@ -106,16 +94,6 @@ class InflowTargetBucketServiceTest { assertTrue(serviceWith(null).getAllInterfaceBucketStatus().isEmpty()); } - @Test - void getClientBucketStatus_managerIsNull_returnsNull() { - assertNull(serviceWithClient(null).getClientBucketStatus("CLIENT_A")); - } - - @Test - void getAllClientBucketStatus_managerIsNull_returnsEmptyList() { - assertTrue(serviceWithClient(null).getAllClientBucketStatus().isEmpty()); - } - // ========================================================================= // 버킷 미존재 // ========================================================================= @@ -136,14 +114,6 @@ class InflowTargetBucketServiceTest { assertNull(serviceWith(manager).getInterfaceBucketStatus("IF_001")); } - @Test - void getClientBucketStatus_notFound_returnsNull() { - ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class); - when(manager.getClientBucket("CLIENT_A")).thenReturn(null); - - assertNull(serviceWithClient(manager).getClientBucketStatus("CLIENT_A")); - } - // ========================================================================= // 단일 조회 — targetType, targetId 매핑 // ========================================================================= @@ -176,98 +146,10 @@ class InflowTargetBucketServiceTest { assertEquals(2, dto.getBuckets().size()); } - @Test - void getClientBucketStatus_found_mapsClientType() { - ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class); - when(manager.getClientBucket("CLIENT_A")).thenReturn(makeDualBucket("CLIENT_A", 50, 5000)); - - TargetBucketStatusDTO dto = serviceWithClient(manager).getClientBucketStatus("CLIENT_A"); - - assertNotNull(dto); - assertEquals("CLIENT_A", dto.getTargetId()); - assertEquals("CLIENT", dto.getTargetType()); - assertTrue(dto.isActivate()); - assertEquals(2, dto.getBuckets().size()); - } - // ========================================================================= - // BucketInfo 타입별 포함 여부 + // BucketInfo 용량/시간단위 매핑 // ========================================================================= - @Test - void toDto_onlyThreshold_bucketInfoHasOneThresholdEntry() { - InflowTargetVO vo = makeVO("C1", 0, 1000, "DAY", true); - DualCustomBucket bucket = new DualCustomBucket(null, freshBucket(1000), vo); - - ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class); - when(manager.getClientBucket("C1")).thenReturn(bucket); - - TargetBucketStatusDTO dto = serviceWithClient(manager).getClientBucketStatus("C1"); - - assertEquals(1, dto.getBuckets().size()); - assertEquals("threshold", dto.getBuckets().get(0).getType()); - } - - @Test - void toDto_onlyPerSecond_bucketInfoHasOnePerSecondEntry() { - InflowTargetVO vo = makeVO("C2", 100, 0, "SEC", true); - DualCustomBucket bucket = new DualCustomBucket(freshBucket(100), null, vo); - - ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class); - when(manager.getClientBucket("C2")).thenReturn(bucket); - - TargetBucketStatusDTO dto = serviceWithClient(manager).getClientBucketStatus("C2"); - - assertEquals(1, dto.getBuckets().size()); - assertEquals("perSecond", dto.getBuckets().get(0).getType()); - } - - @Test - void toDto_bothBuckets_bucketInfoHasTwoEntries() { - ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class); - when(manager.getClientBucket("C3")).thenReturn(makeDualBucket("C3", 100, 5000)); - - TargetBucketStatusDTO dto = serviceWithClient(manager).getClientBucketStatus("C3"); - - assertEquals(2, dto.getBuckets().size()); - assertEquals("perSecond", dto.getBuckets().get(0).getType()); - assertEquals("threshold", dto.getBuckets().get(1).getType()); - } - - // ========================================================================= - // 사용률 계산 - // ========================================================================= - - @Test - void toDto_exhaustedThreshold_usagePercentIs100() { - InflowTargetVO vo = makeVO("C4", 0, 100, "DAY", true); - DualCustomBucket bucket = new DualCustomBucket(null, exhaustedBucket(100), vo); - - ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class); - when(manager.getClientBucket("C4")).thenReturn(bucket); - - TargetBucketStatusDTO dto = serviceWithClient(manager).getClientBucketStatus("C4"); - - GroupBucketStatusDTO.BucketInfo info = dto.getBuckets().get(0); - assertEquals(0, info.getAvailableTokens()); - assertEquals(100.0, info.getUsagePercent(), 0.01); - } - - @Test - void toDto_freshBucket_usagePercentIsZero() { - InflowTargetVO vo = makeVO("C5", 0, 100, "DAY", true); - DualCustomBucket bucket = new DualCustomBucket(null, freshBucket(100), vo); - - ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class); - when(manager.getClientBucket("C5")).thenReturn(bucket); - - TargetBucketStatusDTO dto = serviceWithClient(manager).getClientBucketStatus("C5"); - - GroupBucketStatusDTO.BucketInfo info = dto.getBuckets().get(0); - assertEquals(100, info.getAvailableTokens()); - assertEquals(0.0, info.getUsagePercent(), 0.01); - } - @Test void toDto_capacity_perSecondBucketInfoCapacityMatchesVO() { DualInflowControlManager manager = mock(DualInflowControlManager.class); @@ -288,19 +170,6 @@ class InflowTargetBucketServiceTest { // 전체 목록 조회 // ========================================================================= - @Test - void getAllClientBucketStatus_twoEntries_returnsBoth() { - 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 = serviceWithClient(manager).getAllClientBucketStatus(); - - assertEquals(2, result.size()); - } - @Test void getAllAdapterBucketStatus_emptyMap_returnsEmptyList() { DualInflowControlManager manager = mock(DualInflowControlManager.class);