fix: AbstractInflowControlManager Bandwidth.simple → classic+intervally 버킷 생성 버그 수정 및 테스트 추가

- makeBucketUsingInflowVO: 어댑터/인터페이스 단일 버킷 threshold 리필을 greedy → intervally 변경
- makeGroupBucket: 그룹 threshold 버킷 동일하게 수정
- DualInflowControlManagerMakeBucketTest: 버킷 생성 로직 직접 검증 테스트 추가
- InflowControlManagerTest: @BeforeEach reload + spike/burst 경계 케이스 테스트 추가
- SQL 픽스처: MIN 단위 TEST_ADAPTER/TEST_INTERFACE/test-group-quota 항목 추가

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
curry772
2026-05-08 10:21:57 +09:00
parent 8900966d71
commit 43548a90c4
5 changed files with 138 additions and 14 deletions
@@ -1,5 +1,7 @@
package com.eactive.eai.common.inflow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -36,9 +38,13 @@ class InflowControlManagerTest {
InflowControlManager inflowControlManager;
@BeforeEach
void setUp() throws LifecycleException {
void setUp() throws Exception {
if (!inflowControlManager.isStarted()) {
inflowControlManager.start();
} else {
inflowControlManager.reloadAdapter();
inflowControlManager.reloadInterface();
inflowControlManager.reloadGroup();
}
}
@@ -57,4 +63,22 @@ class InflowControlManagerTest {
assertNull(inflowControlManager.getGroupBucket("non-existent-group"));
}
@Test
void adapter_spikePasses_thenBurstBlocked() {
for (int i = 0; i < 5; i++) assertTrue(inflowControlManager.isAdapterPass("TEST_ADAPTER"));
assertFalse(inflowControlManager.isAdapterPass("TEST_ADAPTER"));
}
@Test
void interface_spikePasses_thenBurstBlocked() {
for (int i = 0; i < 5; i++) assertTrue(inflowControlManager.isInterfacePass("TEST_INTERFACE"));
assertFalse(inflowControlManager.isInterfacePass("TEST_INTERFACE"));
}
@Test
void group_spikePasses_thenBurstBlocked() {
for (int i = 0; i < 5; i++) assertNull(inflowControlManager.isGroupPass("test-group-quota"));
assertEquals(CustomGroupBucket.RESULT_BLOCKED_THRESHOLD, inflowControlManager.isGroupPass("test-group-quota"));
}
}
@@ -0,0 +1,92 @@
package com.eactive.eai.common.inflow.dual;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
import com.eactive.eai.common.inflow.InflowTargetVO;
/**
* DualInflowControlManager.makeBucket() 버킷 생성 동작 검증.
*
* <p>검증 포인트:
* - threshold/perSecond 버킷이 실제로 생성되는지
* - 할당량 이내 spike는 통과하고 초과 burst는 차단되는지
* - perSecond/threshold 중 어느 쪽이 binding 제약인지 구분되는지
*/
class DualInflowControlManagerMakeBucketTest {
private final DualInflowControlManager manager = new DualInflowControlManager();
private InflowTargetVO vo(long perSecond, long threshold, String timeUnit) {
InflowTargetVO vo = new InflowTargetVO();
vo.setName("TEST");
vo.setThresholdPerSecond(perSecond);
vo.setThreshold(threshold);
vo.setThresholdTimeUnit(timeUnit);
vo.setActivate(true);
return vo;
}
// =========================================================================
// threshold 단독 (5건/분)
// =========================================================================
@Test
void thresholdOnly_spikePasses_thenBurstBlocked() {
DualCustomBucket bucket = manager.makeBucket(vo(0, 5, "MIN"));
for (int i = 0; i < 5; i++) assertNull(bucket.tryConsume());
assertEquals(DualCustomBucket.RESULT_BLOCKED_THRESHOLD, bucket.tryConsume());
}
// =========================================================================
// perSecond 단독
// =========================================================================
@Test
void perSecondOnly_spikePasses_thenBurstBlocked() {
DualCustomBucket bucket = manager.makeBucket(vo(5, 0, null));
for (int i = 0; i < 5; i++) assertNull(bucket.tryConsume());
assertEquals(DualCustomBucket.RESULT_BLOCKED_PER_SECOND, bucket.tryConsume());
}
// =========================================================================
// perSecond + threshold 동시 설정 — binding 제약 구분
// =========================================================================
@Test
void both_perSecondIsBinding_blockedByPerSecond() {
// perSecond=3 이 binding: threshold=100/MIN은 여유 있음
DualCustomBucket bucket = manager.makeBucket(vo(3, 100, "MIN"));
for (int i = 0; i < 3; i++) assertNull(bucket.tryConsume());
assertEquals(DualCustomBucket.RESULT_BLOCKED_PER_SECOND, bucket.tryConsume());
}
@Test
void both_thresholdIsBinding_blockedByThreshold() {
// threshold=3/MIN 이 binding: perSecond=100은 여유 있음
DualCustomBucket bucket = manager.makeBucket(vo(100, 3, "MIN"));
for (int i = 0; i < 3; i++) assertNull(bucket.tryConsume());
assertEquals(DualCustomBucket.RESULT_BLOCKED_THRESHOLD, bucket.tryConsume());
}
// =========================================================================
// 비활성 / 조건 미충족 → null 반환
// =========================================================================
@Test
void inactiveVO_returnsNull() {
InflowTargetVO vo = vo(5, 0, null);
vo.setActivate(false);
assertNull(manager.makeBucket(vo));
}
@Test
void noLimitsSet_returnsNull() {
assertNull(manager.makeBucket(vo(0, 0, null)));
}
}