diff --git a/src/main/java/com/eactive/eai/agent/inflow/ReloadInflowClientControlCommand.java b/src/main/java/com/eactive/eai/agent/inflow/ReloadInflowClientControlCommand.java index 8afe1e1..becbfeb 100644 --- a/src/main/java/com/eactive/eai/agent/inflow/ReloadInflowClientControlCommand.java +++ b/src/main/java/com/eactive/eai/agent/inflow/ReloadInflowClientControlCommand.java @@ -4,7 +4,7 @@ import com.eactive.eai.agent.command.Command; import com.eactive.eai.agent.command.CommandException; import com.eactive.eai.common.inflow.AbstractInflowControlManager; import com.eactive.eai.common.inflow.InflowControlUtil; -import com.eactive.eai.common.inflow.dual.DualInflowControlManager; +import com.eactive.eai.common.inflow.dual.ClientDualInflowControlManager; import com.eactive.eai.common.util.Logger; public class ReloadInflowClientControlCommand extends Command { @@ -31,10 +31,10 @@ public class ReloadInflowClientControlCommand extends Command { String keyName = (String) args; try { AbstractInflowControlManager base = InflowControlUtil.getInflowControlManager(); - if (!(base instanceof DualInflowControlManager)) { - throw new IllegalStateException("DualInflowControlManager 가 활성화되지 않았습니다."); + if (!(base instanceof ClientDualInflowControlManager)) { + throw new IllegalStateException("ClientDualInflowControlManager 가 활성화되지 않았습니다."); } - DualInflowControlManager manager = (DualInflowControlManager) base; + ClientDualInflowControlManager manager = (ClientDualInflowControlManager) base; if (keyName != null) { if ("ALL".equals(keyName)) { diff --git a/src/main/java/com/eactive/eai/agent/inflow/RemoveInflowClientControlCommand.java b/src/main/java/com/eactive/eai/agent/inflow/RemoveInflowClientControlCommand.java index fe02028..97fe72c 100644 --- a/src/main/java/com/eactive/eai/agent/inflow/RemoveInflowClientControlCommand.java +++ b/src/main/java/com/eactive/eai/agent/inflow/RemoveInflowClientControlCommand.java @@ -4,7 +4,7 @@ import com.eactive.eai.agent.command.Command; import com.eactive.eai.agent.command.CommandException; import com.eactive.eai.common.inflow.AbstractInflowControlManager; import com.eactive.eai.common.inflow.InflowControlUtil; -import com.eactive.eai.common.inflow.dual.DualInflowControlManager; +import com.eactive.eai.common.inflow.dual.ClientDualInflowControlManager; import com.eactive.eai.common.util.Logger; public class RemoveInflowClientControlCommand extends Command { @@ -31,10 +31,10 @@ public class RemoveInflowClientControlCommand extends Command { String key = (String) args; try { AbstractInflowControlManager base = InflowControlUtil.getInflowControlManager(); - if (!(base instanceof DualInflowControlManager)) { - throw new IllegalStateException("DualInflowControlManager 가 활성화되지 않았습니다."); + if (!(base instanceof ClientDualInflowControlManager)) { + throw new IllegalStateException("ClientDualInflowControlManager 가 활성화되지 않았습니다."); } - DualInflowControlManager manager = (DualInflowControlManager) base; + ClientDualInflowControlManager manager = (ClientDualInflowControlManager) base; manager.removeClient(key); if (logger.isWarn()) logger.warn(this.name + "] " + key + " removed."); diff --git a/src/main/java/com/eactive/eai/common/inflow/AbstractInflowControlManager.java b/src/main/java/com/eactive/eai/common/inflow/AbstractInflowControlManager.java index 12c23b1..8898d1f 100644 --- a/src/main/java/com/eactive/eai/common/inflow/AbstractInflowControlManager.java +++ b/src/main/java/com/eactive/eai/common/inflow/AbstractInflowControlManager.java @@ -21,6 +21,7 @@ import com.eactive.eai.common.util.Logger; import io.github.bucket4j.Bandwidth; import io.github.bucket4j.Bucket4j; +import io.github.bucket4j.Refill; import io.github.bucket4j.local.LocalBucket; import io.github.bucket4j.local.LocalBucketBuilder; @@ -345,12 +346,14 @@ public abstract class AbstractInflowControlManager implements Lifecycle, Bucket if (AbstractInflowControlManager.isInflowTarget(inflowTarget)) { LocalBucketBuilder builder = Bucket4j.builder(); if (inflowTarget.getThresholdPerSecond() > 0) { - builder.addLimit(Bandwidth.simple(inflowTarget.getThresholdPerSecond(), Duration.ofSeconds(1))); + builder.addLimit(Bandwidth.classic(inflowTarget.getThresholdPerSecond(), + Refill.greedy(inflowTarget.getThresholdPerSecond(), Duration.ofSeconds(1)))); } if (inflowTarget.getThreshold() > 0 && StringUtils.isNotBlank(inflowTarget.getThresholdTimeUnit())) { - builder.addLimit(Bandwidth.simple(inflowTarget.getThreshold(), - AbstractInflowControlManager.getPeriod(inflowTarget.getThresholdTimeUnit()))); + builder.addLimit(Bandwidth.classic(inflowTarget.getThreshold(), + Refill.intervally(inflowTarget.getThreshold(), + AbstractInflowControlManager.getPeriod(inflowTarget.getThresholdTimeUnit())))); } return new CustomBucket(builder.build(), inflowTarget); @@ -369,14 +372,16 @@ public abstract class AbstractInflowControlManager implements Lifecycle, Bucket if (groupVo.getThresholdPerSecond() > 0) { perSecondBucket = Bucket4j.builder() - .addLimit(Bandwidth.simple(groupVo.getThresholdPerSecond(), Duration.ofSeconds(1))) + .addLimit(Bandwidth.classic(groupVo.getThresholdPerSecond(), + Refill.greedy(groupVo.getThresholdPerSecond(), Duration.ofSeconds(1)))) .build(); } if (groupVo.getThreshold() > 0 && StringUtils.isNotBlank(groupVo.getThresholdTimeUnit())) { thresholdBucket = Bucket4j.builder() - .addLimit(Bandwidth.simple(groupVo.getThreshold(), - AbstractInflowControlManager.getPeriod(groupVo.getThresholdTimeUnit()))) + .addLimit(Bandwidth.classic(groupVo.getThreshold(), + Refill.intervally(groupVo.getThreshold(), + AbstractInflowControlManager.getPeriod(groupVo.getThresholdTimeUnit())))) .build(); } diff --git a/src/main/java/com/eactive/eai/common/inflow/dual/ClientDualBucket.java b/src/main/java/com/eactive/eai/common/inflow/dual/ClientDualBucket.java new file mode 100644 index 0000000..da8d686 --- /dev/null +++ b/src/main/java/com/eactive/eai/common/inflow/dual/ClientDualBucket.java @@ -0,0 +1,22 @@ +package com.eactive.eai.common.inflow.dual; + +import com.eactive.eai.common.inflow.InflowTargetVO; + +/** + * DualBucket에 클라이언트 유량제어 메서드를 추가한 인터페이스. + * {@link ClientDualInflowControlManager}가 구현한다. + */ +public interface ClientDualBucket extends DualBucket { + + /** + * 클라이언트 유량제어 체크 — 차단 원인 포함. + * @return null이면 통과, "PER_SECOND" 또는 "THRESHOLD"이면 해당 버킷에서 차단 + */ + String isClientPassDetail(String clientId); + + /** + * 클라이언트 유량제어 설정 조회 — 에러 메시지 생성용. + * @return 등록된 설정이 없으면 null + */ + InflowTargetVO getClientInflowThreshold(String clientId); +} diff --git a/src/main/java/com/eactive/eai/common/inflow/dual/ClientDualInflowControlManager.java b/src/main/java/com/eactive/eai/common/inflow/dual/ClientDualInflowControlManager.java new file mode 100644 index 0000000..606d1e7 --- /dev/null +++ b/src/main/java/com/eactive/eai/common/inflow/dual/ClientDualInflowControlManager.java @@ -0,0 +1,112 @@ +package com.eactive.eai.common.inflow.dual; + +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.stereotype.Component; + +import com.eactive.eai.common.inflow.Bucket; +import com.eactive.eai.common.inflow.InflowTargetVO; +import com.eactive.eai.common.inflow.InflowType; +import com.eactive.eai.common.lifecycle.LifecycleException; +import com.eactive.eai.common.util.ApplicationContextProvider; +import com.eactive.eai.common.util.Logger; + +/** + * 어댑터/인터페이스 이중 버킷(DualInflowControlManager)에 클라이언트 유량제어를 추가한 관리자. + * + *
적용 방법: INFLOW.properties에 아래 한 줄 추가. + *
inflow.control.bucket.className=com.eactive.eai.common.inflow.dual.ClientDualInflowControlManager+ */ +@Component +public class ClientDualInflowControlManager extends DualInflowControlManager implements ClientDualBucket { + + private static final Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT); + + public static synchronized Bucket getBucket() { + return ApplicationContextProvider.getContext().getBean(ClientDualInflowControlManager.class); + } + + private volatile Map
클라이언트 유량제어는 {@link ClientDualBucket}으로 분리됨. */ public interface DualBucket extends Bucket { @@ -22,16 +22,4 @@ public interface DualBucket extends Bucket { * @return null이면 통과, "PER_SECOND" 또는 "THRESHOLD"이면 해당 버킷에서 차단 */ String isInterfacePassDetail(String inter); - - /** - * 클라이언트 유량제어 체크 — 차단 원인 포함. - * @return null이면 통과, "PER_SECOND" 또는 "THRESHOLD"이면 해당 버킷에서 차단 - */ - String isClientPassDetail(String clientId); - - /** - * 클라이언트 유량제어 설정 조회 — 에러 메시지 생성용. - * @return 등록된 설정이 없으면 null - */ - InflowTargetVO getClientInflowThreshold(String clientId); } diff --git a/src/main/java/com/eactive/eai/common/inflow/dual/DualInflowControlManager.java b/src/main/java/com/eactive/eai/common/inflow/dual/DualInflowControlManager.java index de1b827..d6c415a 100644 --- a/src/main/java/com/eactive/eai/common/inflow/dual/DualInflowControlManager.java +++ b/src/main/java/com/eactive/eai/common/inflow/dual/DualInflowControlManager.java @@ -9,7 +9,6 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; import org.apache.commons.lang3.StringUtils; -import org.springframework.stereotype.Component; import com.eactive.eai.common.inflow.AbstractInflowControlManager; import com.eactive.eai.common.inflow.Bucket; @@ -22,21 +21,17 @@ import com.eactive.eai.common.util.Logger; import io.github.bucket4j.Bandwidth; import io.github.bucket4j.Bucket4j; +import io.github.bucket4j.Refill; import io.github.bucket4j.local.LocalBucket; /** - * 어댑터/인터페이스/클라이언트 버킷을 이중 구조(perSecond + threshold)로 관리하는 유량제어 관리자. + * 어댑터/인터페이스 버킷을 이중 구조(perSecond + threshold)로 관리하는 유량제어 관리자. * - *
기존 InflowControlManager는 어댑터/인터페이스에 단일 LocalBucket(복수 Bandwidth)을 사용하여 - * 차단 시 어느 한도(초당/기간)를 초과했는지 알 수 없었다. - * 이 클래스는 그룹 버킷(CustomGroupBucket)과 동일하게 두 버킷을 분리하고 - * {@link DualBucket#isAdapterPassDetail}/{@link DualBucket#isInterfacePassDetail}로 - * 차단 원인을 반환한다. 그룹 버킷 통과 여부는 {@link TargetMetrics}로 카운팅된다. + *
클라이언트 유량제어는 {@link ClientDualInflowControlManager}로 분리됨. * *
적용 방법: INFLOW.properties에 아래 한 줄 추가. - *
inflow.control.bucket.className=com.eactive.eai.common.inflow.dual.DualInflowControlManager+ *
inflow.control.bucket.className=com.eactive.eai.common.inflow.dual.ClientDualInflowControlManager*/ -@Component public class DualInflowControlManager extends AbstractInflowControlManager implements DualBucket { private static final Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT); @@ -48,11 +43,9 @@ public class DualInflowControlManager extends AbstractInflowControlManager imple private volatile Map
DualInflowControlManager와 함께 사용: - *
- * inflow.control.bucket.className=com.eactive.eai.common.inflow.dual.DualInflowControlManager - *- * - *
Spring Bean 등록 후 기존 RequestProcessor 대신 이 클래스를 어댑터에 설정한다. - */ -public class DualRequestProcessor extends RequestProcessor { - - @Override - protected String checkClientInflow(Bucket bucket, String clientId) { - if (StringUtils.isBlank(clientId)) return null; - if (bucket instanceof DualBucket) { - return ((DualBucket) bucket).isClientPassDetail(clientId); - } - return null; - } - - @Override - protected InflowTargetVO resolveClientInflowThreshold(Bucket bucket, String clientId) { - if (bucket instanceof DualBucket) { - return ((DualBucket) bucket).getClientInflowThreshold(clientId); - } - return null; - } - - @Override - protected String checkAdapterInflow(Bucket bucket, String adapterGroupName) { - if (bucket instanceof DualBucket) { - return ((DualBucket) bucket).isAdapterPassDetail(adapterGroupName); - } - return super.checkAdapterInflow(bucket, adapterGroupName); - } - - @Override - protected String checkInterfaceInflow(Bucket bucket, String eaiSvcCd) { - if (bucket instanceof DualBucket) { - return ((DualBucket) bucket).isInterfacePassDetail(eaiSvcCd); - } - return super.checkInterfaceInflow(bucket, eaiSvcCd); - } - - /** - * 차단 원인에 따라 에러 메시지에 한도 정보를 포함. - *
검증 포인트: + * - 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))); + } +} diff --git a/src/test/java/com/eactive/eai/common/inflow/dual/DualInflowControlManagerMetricsTest.java b/src/test/java/com/eactive/eai/common/inflow/dual/DualInflowControlManagerMetricsTest.java index c52f309..be4280c 100644 --- a/src/test/java/com/eactive/eai/common/inflow/dual/DualInflowControlManagerMetricsTest.java +++ b/src/test/java/com/eactive/eai/common/inflow/dual/DualInflowControlManagerMetricsTest.java @@ -21,9 +21,8 @@ import io.github.bucket4j.local.LocalBucket; /** * DualInflowControlManager 메트릭 단위 테스트. * - *
start() 없이 버킷 맵을 ReflectionTestUtils로 직접 주입하여 - * isAdapterPassDetail / isInterfacePassDetail / isClientPassDetail / isGroupPass() 의 - * 카운팅 동작과 getXxxMetrics / resetXxxMetrics 메서드를 검증한다. + *
어댑터/인터페이스/그룹 메트릭을 검증한다.
+ * 클라이언트 메트릭은 ClientDualInflowControlManagerMetricsTest 에서 검증한다.
*/
class DualInflowControlManagerMetricsTest {
@@ -100,10 +99,6 @@ class DualInflowControlManagerMetricsTest {
ReflectionTestUtils.setField(manager, "interfaceBucketMap", map);
}
- private void injectClientMap(Map RequestProcessor 클래스는 static 초기화 블록에서 EAIServerManager.getInstance()를 호출한다.
- * @BeforeAll에서 mock ApplicationContext를 주입한 뒤 DualRequestProcessor를 최초 로딩시킨다.
- *
- * 주의: DualRequestProcessor를 필드 타입으로 선언하면 테스트 클래스 로딩 시 함께 로딩되어
- * @BeforeAll 실행 전에 NPE가 발생한다. 반드시 메서드 본문 내에서만 참조해야 한다.
- */
-class DualRequestProcessorTest {
-
- @BeforeAll
- static void setupMockApplicationContext() {
- ApplicationContext mockContext = mock(ApplicationContext.class);
- EAIServerManager mockServerManager = mock(EAIServerManager.class);
- when(mockContext.getBean(EAIServerManager.class)).thenReturn(mockServerManager);
- when(mockServerManager.getLocalServerName()).thenReturn("TEST-SERVER");
- when(mockServerManager.getGroupInstId()).thenReturn("TEST-INST");
-
- ReflectionTestUtils.setField(ApplicationContextProvider.class, "context", mockContext);
- }
-
- private InflowTargetVO makeVO(String name, long perSec, long threshold, String unit) {
- InflowTargetVO vo = new InflowTargetVO();
- vo.setName(name);
- vo.setThresholdPerSecond(perSec);
- vo.setThreshold(threshold);
- vo.setThresholdTimeUnit(unit);
- vo.setActivate(true);
- return vo;
- }
-
- // -------------------------------------------------------------------------
- // checkAdapterInflow
- // -------------------------------------------------------------------------
-
- @Test
- void checkAdapterInflow_dualBucket_pass_returnsNull() {
- DualRequestProcessor processor = new DualRequestProcessor();
- DualBucket mockBucket = mock(DualBucket.class);
- when(mockBucket.isAdapterPassDetail("ADAPTER_A")).thenReturn(null);
-
- assertNull(processor.checkAdapterInflow(mockBucket, "ADAPTER_A"));
- verify(mockBucket).isAdapterPassDetail("ADAPTER_A");
- }
-
- @Test
- void checkAdapterInflow_dualBucket_blockedPerSecond() {
- DualRequestProcessor processor = new DualRequestProcessor();
- DualBucket mockBucket = mock(DualBucket.class);
- when(mockBucket.isAdapterPassDetail("ADAPTER_A")).thenReturn(DualCustomBucket.RESULT_BLOCKED_PER_SECOND);
-
- assertEquals(DualCustomBucket.RESULT_BLOCKED_PER_SECOND,
- processor.checkAdapterInflow(mockBucket, "ADAPTER_A"));
- }
-
- @Test
- void checkAdapterInflow_dualBucket_blockedThreshold() {
- DualRequestProcessor processor = new DualRequestProcessor();
- DualBucket mockBucket = mock(DualBucket.class);
- when(mockBucket.isAdapterPassDetail("ADAPTER_A")).thenReturn(DualCustomBucket.RESULT_BLOCKED_THRESHOLD);
-
- assertEquals(DualCustomBucket.RESULT_BLOCKED_THRESHOLD,
- processor.checkAdapterInflow(mockBucket, "ADAPTER_A"));
- }
-
- @Test
- void checkAdapterInflow_nonDualBucket_pass_returnsNull() {
- DualRequestProcessor processor = new DualRequestProcessor();
- Bucket mockBucket = mock(Bucket.class);
- when(mockBucket.isAdapterPass("ADAPTER_A")).thenReturn(true);
-
- assertNull(processor.checkAdapterInflow(mockBucket, "ADAPTER_A"));
- }
-
- @Test
- void checkAdapterInflow_nonDualBucket_blocked_returnsBlocked() {
- DualRequestProcessor processor = new DualRequestProcessor();
- Bucket mockBucket = mock(Bucket.class);
- when(mockBucket.isAdapterPass("ADAPTER_A")).thenReturn(false);
-
- assertEquals("BLOCKED", processor.checkAdapterInflow(mockBucket, "ADAPTER_A"));
- }
-
- // -------------------------------------------------------------------------
- // checkInterfaceInflow
- // -------------------------------------------------------------------------
-
- @Test
- void checkInterfaceInflow_dualBucket_pass_returnsNull() {
- DualRequestProcessor processor = new DualRequestProcessor();
- DualBucket mockBucket = mock(DualBucket.class);
- when(mockBucket.isInterfacePassDetail("IF_001")).thenReturn(null);
-
- assertNull(processor.checkInterfaceInflow(mockBucket, "IF_001"));
- verify(mockBucket).isInterfacePassDetail("IF_001");
- }
-
- @Test
- void checkInterfaceInflow_dualBucket_blockedPerSecond() {
- DualRequestProcessor processor = new DualRequestProcessor();
- DualBucket mockBucket = mock(DualBucket.class);
- when(mockBucket.isInterfacePassDetail("IF_001")).thenReturn(DualCustomBucket.RESULT_BLOCKED_PER_SECOND);
-
- assertEquals(DualCustomBucket.RESULT_BLOCKED_PER_SECOND,
- processor.checkInterfaceInflow(mockBucket, "IF_001"));
- }
-
- @Test
- void checkInterfaceInflow_nonDualBucket_pass_returnsNull() {
- DualRequestProcessor processor = new DualRequestProcessor();
- Bucket mockBucket = mock(Bucket.class);
- when(mockBucket.isInterfacePass("IF_001")).thenReturn(true);
-
- assertNull(processor.checkInterfaceInflow(mockBucket, "IF_001"));
- }
-
- @Test
- void checkInterfaceInflow_nonDualBucket_blocked_returnsBlocked() {
- DualRequestProcessor processor = new DualRequestProcessor();
- Bucket mockBucket = mock(Bucket.class);
- when(mockBucket.isInterfacePass("IF_001")).thenReturn(false);
-
- assertEquals("BLOCKED", processor.checkInterfaceInflow(mockBucket, "IF_001"));
- }
-
- // -------------------------------------------------------------------------
- // buildInflowTargetErrorMsg
- // -------------------------------------------------------------------------
-
- @Test
- void buildInflowTargetErrorMsg_perSecond_includesReqPerSec() {
- DualRequestProcessor processor = new DualRequestProcessor();
- InflowTargetVO vo = makeVO("결제API", 30, 1000, "MIN");
-
- String msg = processor.buildInflowTargetErrorMsg(vo, DualCustomBucket.RESULT_BLOCKED_PER_SECOND);
-
- assertEquals("API 호출 한도 초과 [결제API: 30req/sec]", msg);
- }
-
- @Test
- void buildInflowTargetErrorMsg_threshold_includesReqPerUnit() {
- DualRequestProcessor processor = new DualRequestProcessor();
- InflowTargetVO vo = makeVO("결제API", 30, 1000, "MIN");
-
- String msg = processor.buildInflowTargetErrorMsg(vo, DualCustomBucket.RESULT_BLOCKED_THRESHOLD);
-
- assertEquals("API 호출 한도 초과 [결제API: 1000req/MIN]", msg);
- }
-
- @Test
- void buildInflowTargetErrorMsg_blockedFallback_simpleFormat() {
- DualRequestProcessor processor = new DualRequestProcessor();
- InflowTargetVO vo = makeVO("결제API", 30, 1000, "MIN");
-
- String msg = processor.buildInflowTargetErrorMsg(vo, "BLOCKED");
-
- assertEquals("API 호출 한도 초과 [결제API]", msg);
- }
-
- @Test
- void buildInflowTargetErrorMsg_nullBlockedType_simpleFormat() {
- DualRequestProcessor processor = new DualRequestProcessor();
- InflowTargetVO vo = makeVO("결제API", 30, 1000, "MIN");
-
- String msg = processor.buildInflowTargetErrorMsg(vo, null);
-
- assertEquals("API 호출 한도 초과 [결제API]", msg);
- }
-
- @Test
- void buildInflowTargetErrorMsg_hourUnit() {
- DualRequestProcessor processor = new DualRequestProcessor();
- InflowTargetVO vo = makeVO("조회API", 10, 500, "HOUR");
-
- String msg = processor.buildInflowTargetErrorMsg(vo, DualCustomBucket.RESULT_BLOCKED_THRESHOLD);
-
- assertEquals("API 호출 한도 초과 [조회API: 500req/HOUR]", msg);
- }
-}
diff --git a/src/test/resources/com/eactive/eai/common/inflow/init_inflow_control_group.sql b/src/test/resources/com/eactive/eai/common/inflow/init_inflow_control_group.sql
index d55aeb7..3b97c89 100644
--- a/src/test/resources/com/eactive/eai/common/inflow/init_inflow_control_group.sql
+++ b/src/test/resources/com/eactive/eai/common/inflow/init_inflow_control_group.sql
@@ -1,2 +1,3 @@
INSERT INTO inflow_control_group (group_id, group_name, threshold, threshold_per_second, threshold_time_unit, use_yn) VALUES
- ('test-group-001', '테스트 그룹', 100, 10, 'MIN', '1');
+ ('test-group-001', '테스트 그룹', 100, 10, 'MIN', '1'),
+ ('test-group-quota', '쿼터전용 그룹', 5, 0, 'MIN', '1');
diff --git a/src/test/resources/com/eactive/eai/common/inflow/init_tseaifr09.sql b/src/test/resources/com/eactive/eai/common/inflow/init_tseaifr09.sql
index ac25a7f..ec220ab 100644
--- a/src/test/resources/com/eactive/eai/common/inflow/init_tseaifr09.sql
+++ b/src/test/resources/com/eactive/eai/common/inflow/init_tseaifr09.sql
@@ -1,6 +1,8 @@
-INSERT INTO tseaifr09 (type,name,threshold,useyn,thresholdpersecond,thresholdtimeunit) VALUES
- ('01','_AA0_IN_NET_AsS',10,'1',0,NULL),
- ('01','_AB0_IN_NET_AsS',3,'1',0,NULL),
- ('01','_TST_IN_NET_AsS',1,'1',0,NULL),
- ('02','FASSFEP00000004S2',10,'1',0,NULL),
- ('02','FDASFEP00000009S2',2,'1',0,NULL);
\ No newline at end of file
+INSERT INTO tseaifr09 (type,name,threshold,useyn,thresholdpersecond,thresholdtimeunit) VALUES
+ ('01','_AA0_IN_NET_AsS',10,'1',0,NULL),
+ ('01','_AB0_IN_NET_AsS',3,'1',0,NULL),
+ ('01','_TST_IN_NET_AsS',1,'1',0,NULL),
+ ('02','FASSFEP00000004S2',10,'1',0,NULL),
+ ('02','FDASFEP00000009S2',2,'1',0,NULL),
+ ('01','TEST_ADAPTER',5,'1',0,'MIN'),
+ ('02','TEST_INTERFACE',5,'1',0,'MIN');
\ No newline at end of file