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()); } }