diff --git a/WebContent/WEB-INF/authserver-servlet.xml b/WebContent/WEB-INF/authserver-servlet.xml index 58f4c31..a0f4a9c 100644 --- a/WebContent/WEB-INF/authserver-servlet.xml +++ b/WebContent/WEB-INF/authserver-servlet.xml @@ -25,6 +25,9 @@ base-package="com.eactive.eai.adapter" /> + + diff --git a/WebContent/WEB-INF/web.xml b/WebContent/WEB-INF/web.xml index 1265260..754053a 100644 --- a/WebContent/WEB-INF/web.xml +++ b/WebContent/WEB-INF/web.xml @@ -19,6 +19,7 @@ /agent/* /common/* /management/* + /manage/* /mgr/* diff --git a/elink-online-common b/elink-online-common index 5806868..e64319e 160000 --- a/elink-online-common +++ b/elink-online-common @@ -1 +1 @@ -Subproject commit 580686886c0dbcbe7bbd8165e027f0b6777acfd2 +Subproject commit e64319e7d27d19d191623816e8c27dca0f437cac diff --git a/src/main/java/com/eactive/eai/custom/inbound/processor/DJBRequestProcessor.java b/src/main/java/com/eactive/eai/custom/inbound/processor/DJBRequestProcessor.java new file mode 100644 index 0000000..d2f8759 --- /dev/null +++ b/src/main/java/com/eactive/eai/custom/inbound/processor/DJBRequestProcessor.java @@ -0,0 +1,81 @@ +package com.eactive.eai.custom.inbound.processor; + +import org.apache.commons.lang3.StringUtils; + +import com.eactive.eai.common.inflow.Bucket; +import com.eactive.eai.common.inflow.InflowTargetVO; +import com.eactive.eai.common.inflow.dual.ClientDualBucket; +import com.eactive.eai.common.inflow.dual.DualBucket; +import com.eactive.eai.common.inflow.dual.DualCustomBucket; +import com.eactive.eai.inbound.processor.RequestProcessor; + +/** + * 어댑터/인터페이스/클라이언트 유량제어 차단 시 어느 버킷(PER_SECOND/THRESHOLD)에서 + * 차단됐는지 에러 메시지에 포함하는 RequestProcessor 확장. + * + *

ClientDualInflowControlManager와 함께 사용: + *

+ * inflow.control.bucket.className=com.eactive.eai.common.inflow.dual.ClientDualInflowControlManager
+ * 
+ * + *

Spring Bean 등록 후 기존 RequestProcessor 대신 이 클래스를 어댑터에 설정한다. + */ +public class DJBRequestProcessor extends RequestProcessor { + + @Override + protected String checkClientInflow(Bucket bucket, String clientId) { + if (StringUtils.isBlank(clientId)) return null; + if (bucket instanceof ClientDualBucket) { + return ((ClientDualBucket) bucket).isClientPassDetail(clientId); + } + return null; + } + + @Override + protected InflowTargetVO resolveClientInflowThreshold(Bucket bucket, String clientId) { + if (bucket instanceof ClientDualBucket) { + return ((ClientDualBucket) 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); + } + + /** + * 차단 원인에 따라 에러 메시지에 한도 정보를 포함. + *

    + *
  • PER_SECOND: "API 호출 한도 초과 [name: Nreq/sec]"
  • + *
  • THRESHOLD: "API 호출 한도 초과 [name: Nreq/UNIT]"
  • + *
  • 그 외: "API 호출 한도 초과 [name]" (기존 동작)
  • + *
+ */ + @Override + protected String buildInflowTargetErrorMsg(InflowTargetVO inflowTargetVO, String blockedType) { + if (DualCustomBucket.RESULT_BLOCKED_PER_SECOND.equals(blockedType)) { + return String.format("API 호출 한도 초과 [%s: %dreq/sec]", + inflowTargetVO.getName(), + inflowTargetVO.getThresholdPerSecond()); + } + if (DualCustomBucket.RESULT_BLOCKED_THRESHOLD.equals(blockedType)) { + return String.format("API 호출 한도 초과 [%s: %dreq/%s]", + inflowTargetVO.getName(), + inflowTargetVO.getThreshold(), + inflowTargetVO.getThresholdTimeUnit()); + } + return super.buildInflowTargetErrorMsg(inflowTargetVO, blockedType); + } +} 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/custom/inflow/ClientInflowTargetMetricController.java b/src/main/java/com/eactive/eai/custom/inflow/ClientInflowTargetMetricController.java new file mode 100644 index 0000000..b1a71f6 --- /dev/null +++ b/src/main/java/com/eactive/eai/custom/inflow/ClientInflowTargetMetricController.java @@ -0,0 +1,94 @@ +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.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.eactive.eai.manage.inflow.TargetMetricDTO; + +/** + * 클라이언트 유량제어 메트릭 API. + * + * ClientDualInflowControlManager 가 활성화된 환경에서만 정상 응답한다. + * + * GET /manage/inflow/client/metric → 전체 클라이언트 메트릭 + * GET /manage/inflow/client/{clientId}/metric → 특정 클라이언트 메트릭 + * POST /manage/inflow/client/metric/reset → 전체 클라이언트 메트릭 초기화 + * POST /manage/inflow/client/{clientId}/metric/reset → 특정 클라이언트 메트릭 초기화 + */ +@RestController +@RequestMapping("/manage/inflow") +public class ClientInflowTargetMetricController { + + @Autowired + private ClientInflowTargetMetricService metricService; + + @GetMapping("/client/metric") + public ResponseEntity getAllClientMetrics() { + List list = metricService.getAllClientMetrics(); + return okList(list); + } + + @GetMapping("/client/{clientId}/metric") + public ResponseEntity getClientMetric(@PathVariable String clientId) { + TargetMetricDTO dto = metricService.getClientMetric(clientId); + return okSingle(dto, "클라이언트를 찾을 수 없습니다: " + clientId); + } + + @PostMapping("/client/metric/reset") + public ResponseEntity resetAllClientMetrics() { + metricService.resetAllClientMetrics(); + return okReset("전체 클라이언트 메트릭 초기화 완료"); + } + + @PostMapping("/client/{clientId}/metric/reset") + public ResponseEntity resetClientMetric(@PathVariable String clientId) { + boolean ok = metricService.resetClientMetric(clientId); + return okResetSingle(ok, clientId, "클라이언트"); + } + + private ResponseEntity okList(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 okSingle(TargetMetricDTO 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); + } + + private ResponseEntity okReset(String message) { + Map result = new HashMap<>(); + result.put("success", true); + result.put("message", message); + return ResponseEntity.ok(result); + } + + private ResponseEntity okResetSingle(boolean ok, String targetId, String targetLabel) { + Map result = new HashMap<>(); + result.put("success", ok); + result.put("message", ok + ? "초기화 완료: " + targetId + : targetLabel + "를 찾을 수 없습니다: " + targetId); + return ResponseEntity.ok(result); + } +} diff --git a/src/main/java/com/eactive/eai/custom/inflow/ClientInflowTargetMetricService.java b/src/main/java/com/eactive/eai/custom/inflow/ClientInflowTargetMetricService.java new file mode 100644 index 0000000..39df98e --- /dev/null +++ b/src/main/java/com/eactive/eai/custom/inflow/ClientInflowTargetMetricService.java @@ -0,0 +1,79 @@ +package com.eactive.eai.custom.inflow; + +import java.util.ArrayList; +import java.util.List; + +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.DualInflowControlManager; +import com.eactive.eai.manage.inflow.TargetMetricDTO; + +@Service +public class ClientInflowTargetMetricService { + + public TargetMetricDTO getClientMetric(String clientId) { + ClientDualInflowControlManager manager = getClientDualManager(); + if (manager == null) return null; + InflowTargetVO vo = manager.getClientInflowThreshold(clientId); + if (vo == null) return null; + return buildDTO(clientId, "CLIENT", vo, manager.getClientMetrics(clientId)); + } + + public List getAllClientMetrics() { + ClientDualInflowControlManager manager = getClientDualManager(); + if (manager == null) return new ArrayList<>(); + List result = new ArrayList<>(); + for (String id : manager.getClientBucketMap().keySet()) { + TargetMetricDTO dto = getClientMetric(id); + if (dto != null) result.add(dto); + } + return result; + } + + public boolean resetClientMetric(String clientId) { + ClientDualInflowControlManager manager = getClientDualManager(); + if (manager == null) return false; + if (manager.getClientInflowThreshold(clientId) == null) return false; + manager.resetClientMetrics(clientId); + return true; + } + + public void resetAllClientMetrics() { + ClientDualInflowControlManager manager = getClientDualManager(); + if (manager != null) manager.resetAllClientMetrics(); + } + + protected ClientDualInflowControlManager getClientDualManager() { + AbstractInflowControlManager base = InflowControlUtil.getInflowControlManager(); + return (base instanceof ClientDualInflowControlManager) ? (ClientDualInflowControlManager) base : null; + } + + private TargetMetricDTO buildDTO(String targetId, String targetType, + InflowTargetVO vo, + DualInflowControlManager.TargetMetrics m) { + TargetMetricDTO dto = new TargetMetricDTO(); + dto.setTargetId(targetId); + dto.setTargetType(targetType); + dto.setActivate(vo.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/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 5773a9c..c34ea5d 100644 --- a/src/main/java/com/eactive/eai/manage/inflow/InflowTargetBucketService.java +++ b/src/main/java/com/eactive/eai/manage/inflow/InflowTargetBucketService.java @@ -57,24 +57,6 @@ public class InflowTargetBucketService { return toDtoList("INTERFACE", manager.getInterfaceBucketMap()); } - // ------------------------------------------------------------------------- - // 클라이언트 - // ------------------------------------------------------------------------- - - public TargetBucketStatusDTO getClientBucketStatus(String clientId) { - DualInflowControlManager manager = getDualManager(); - if (manager == null) return null; - DualCustomBucket bucket = manager.getClientBucket(clientId); - if (bucket == null) return null; - return toDto(clientId, "CLIENT", bucket); - } - - public List getAllClientBucketStatus() { - DualInflowControlManager manager = getDualManager(); - if (manager == null) return new ArrayList<>(); - return toDtoList("CLIENT", manager.getClientBucketMap()); - } - // ------------------------------------------------------------------------- // Private helpers // ------------------------------------------------------------------------- diff --git a/src/main/java/com/eactive/eai/manage/inflow/InflowTargetMetricController.java b/src/main/java/com/eactive/eai/manage/inflow/InflowTargetMetricController.java index 31bfc90..b73c832 100644 --- a/src/main/java/com/eactive/eai/manage/inflow/InflowTargetMetricController.java +++ b/src/main/java/com/eactive/eai/manage/inflow/InflowTargetMetricController.java @@ -13,7 +13,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** - * 어댑터/인터페이스/클라이언트 유량제어 메트릭 API. + * 어댑터/인터페이스 유량제어 메트릭 API. * * DualInflowControlManager 가 활성화된 환경에서만 정상 응답한다. * @@ -26,11 +26,6 @@ import org.springframework.web.bind.annotation.RestController; * GET /manage/inflow/interface/{interfaceId}/metric → 특정 인터페이스 메트릭 * POST /manage/inflow/interface/metric/reset → 전체 인터페이스 메트릭 초기화 * POST /manage/inflow/interface/{interfaceId}/metric/reset → 특정 인터페이스 메트릭 초기화 - * - * GET /manage/inflow/client/metric → 전체 클라이언트 메트릭 - * GET /manage/inflow/client/{clientId}/metric → 특정 클라이언트 메트릭 - * POST /manage/inflow/client/metric/reset → 전체 클라이언트 메트릭 초기화 - * POST /manage/inflow/client/{clientId}/metric/reset → 특정 클라이언트 메트릭 초기화 */ @RestController @RequestMapping("/manage/inflow") @@ -95,34 +90,6 @@ public class InflowTargetMetricController { return okResetSingle(ok, interfaceId, "인터페이스"); } - // ========================================================================= - // 클라이언트 - // ========================================================================= - - @GetMapping("/client/metric") - public ResponseEntity getAllClientMetrics() { - List list = metricService.getAllClientMetrics(); - return okList(list); - } - - @GetMapping("/client/{clientId}/metric") - public ResponseEntity getClientMetric(@PathVariable String clientId) { - TargetMetricDTO dto = metricService.getClientMetric(clientId); - return okSingle(dto, "클라이언트를 찾을 수 없습니다: " + clientId); - } - - @PostMapping("/client/metric/reset") - public ResponseEntity resetAllClientMetrics() { - metricService.resetAllClientMetrics(); - return okReset("전체 클라이언트 메트릭 초기화 완료"); - } - - @PostMapping("/client/{clientId}/metric/reset") - public ResponseEntity resetClientMetric(@PathVariable String clientId) { - boolean ok = metricService.resetClientMetric(clientId); - return okResetSingle(ok, clientId, "클라이언트"); - } - // ========================================================================= // Helpers // ========================================================================= diff --git a/src/main/java/com/eactive/eai/manage/inflow/InflowTargetMetricService.java b/src/main/java/com/eactive/eai/manage/inflow/InflowTargetMetricService.java index 5eadad3..9134e3d 100644 --- a/src/main/java/com/eactive/eai/manage/inflow/InflowTargetMetricService.java +++ b/src/main/java/com/eactive/eai/manage/inflow/InflowTargetMetricService.java @@ -85,42 +85,6 @@ public class InflowTargetMetricService { if (manager != null) manager.resetAllInterfaceMetrics(); } - // ------------------------------------------------------------------------- - // 클라이언트 - // ------------------------------------------------------------------------- - - public TargetMetricDTO getClientMetric(String clientId) { - DualInflowControlManager manager = getDualManager(); - if (manager == null) return null; - InflowTargetVO vo = manager.getClientInflowThreshold(clientId); - if (vo == null) return null; - return buildDTO(clientId, "CLIENT", vo, manager.getClientMetrics(clientId)); - } - - public List getAllClientMetrics() { - DualInflowControlManager manager = getDualManager(); - if (manager == null) return new ArrayList<>(); - List result = new ArrayList<>(); - for (String id : manager.getClientBucketMap().keySet()) { - TargetMetricDTO dto = getClientMetric(id); - if (dto != null) result.add(dto); - } - return result; - } - - public boolean resetClientMetric(String clientId) { - DualInflowControlManager manager = getDualManager(); - if (manager == null) return false; - if (manager.getClientInflowThreshold(clientId) == null) return false; - manager.resetClientMetrics(clientId); - return true; - } - - public void resetAllClientMetrics() { - DualInflowControlManager manager = getDualManager(); - if (manager != null) manager.resetAllClientMetrics(); - } - // ------------------------------------------------------------------------- // Private helpers // ------------------------------------------------------------------------- diff --git a/src/main/resources/standard-message-config.properties b/src/main/resources/standard-message-config.properties index c28227a..a3fec4f 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.DualRequestProcessor +requestProcessor.class=com.eactive.eai.custom.inbound.processor.DJBRequestProcessor # 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/custom/inbound/processor/DJBRequestProcessorTest.java b/src/test/java/com/eactive/eai/custom/inbound/processor/DJBRequestProcessorTest.java new file mode 100644 index 0000000..9b796b6 --- /dev/null +++ b/src/test/java/com/eactive/eai/custom/inbound/processor/DJBRequestProcessorTest.java @@ -0,0 +1,239 @@ +package com.eactive.eai.custom.inbound.processor; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.context.ApplicationContext; +import org.springframework.test.util.ReflectionTestUtils; + +import com.eactive.eai.common.inflow.Bucket; +import com.eactive.eai.common.inflow.InflowTargetVO; +import com.eactive.eai.common.inflow.dual.ClientDualBucket; +import com.eactive.eai.common.inflow.dual.DualBucket; +import com.eactive.eai.common.inflow.dual.DualCustomBucket; +import com.eactive.eai.common.server.EAIServerManager; +import com.eactive.eai.common.util.ApplicationContextProvider; + +/** + * DJBRequestProcessor 단위 테스트. + * + *

RequestProcessor 클래스는 static 초기화 블록에서 EAIServerManager.getInstance()를 호출한다. + * @BeforeAll에서 mock ApplicationContext를 주입한 뒤 DJBRequestProcessor를 최초 로딩시킨다. + * + *

주의: DJBRequestProcessor를 필드 타입으로 선언하면 테스트 클래스 로딩 시 함께 로딩되어 + * @BeforeAll 실행 전에 NPE가 발생한다. 반드시 메서드 본문 내에서만 참조해야 한다. + */ +class DJBRequestProcessorTest { + + @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() { + DJBRequestProcessor processor = new DJBRequestProcessor(); + 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() { + DJBRequestProcessor processor = new DJBRequestProcessor(); + 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() { + DJBRequestProcessor processor = new DJBRequestProcessor(); + 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() { + DJBRequestProcessor processor = new DJBRequestProcessor(); + Bucket mockBucket = mock(Bucket.class); + when(mockBucket.isAdapterPass("ADAPTER_A")).thenReturn(true); + + assertNull(processor.checkAdapterInflow(mockBucket, "ADAPTER_A")); + } + + @Test + void checkAdapterInflow_nonDualBucket_blocked_returnsBlocked() { + DJBRequestProcessor processor = new DJBRequestProcessor(); + 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() { + DJBRequestProcessor processor = new DJBRequestProcessor(); + 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() { + DJBRequestProcessor processor = new DJBRequestProcessor(); + 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() { + DJBRequestProcessor processor = new DJBRequestProcessor(); + Bucket mockBucket = mock(Bucket.class); + when(mockBucket.isInterfacePass("IF_001")).thenReturn(true); + + assertNull(processor.checkInterfaceInflow(mockBucket, "IF_001")); + } + + @Test + void checkInterfaceInflow_nonDualBucket_blocked_returnsBlocked() { + DJBRequestProcessor processor = new DJBRequestProcessor(); + Bucket mockBucket = mock(Bucket.class); + when(mockBucket.isInterfacePass("IF_001")).thenReturn(false); + + assertEquals("BLOCKED", processor.checkInterfaceInflow(mockBucket, "IF_001")); + } + + // ------------------------------------------------------------------------- + // checkClientInflow — ClientDualBucket 기반 + // ------------------------------------------------------------------------- + + @Test + void checkClientInflow_clientDualBucket_pass_returnsNull() { + DJBRequestProcessor processor = new DJBRequestProcessor(); + ClientDualBucket mockBucket = mock(ClientDualBucket.class); + when(mockBucket.isClientPassDetail("CLIENT_A")).thenReturn(null); + + assertNull(processor.checkClientInflow(mockBucket, "CLIENT_A")); + verify(mockBucket).isClientPassDetail("CLIENT_A"); + } + + @Test + void checkClientInflow_clientDualBucket_blocked() { + DJBRequestProcessor processor = new DJBRequestProcessor(); + ClientDualBucket mockBucket = mock(ClientDualBucket.class); + when(mockBucket.isClientPassDetail("CLIENT_A")).thenReturn(DualCustomBucket.RESULT_BLOCKED_THRESHOLD); + + assertEquals(DualCustomBucket.RESULT_BLOCKED_THRESHOLD, + processor.checkClientInflow(mockBucket, "CLIENT_A")); + } + + @Test + void checkClientInflow_nonClientDualBucket_returnsNull() { + DJBRequestProcessor processor = new DJBRequestProcessor(); + Bucket mockBucket = mock(Bucket.class); + + assertNull(processor.checkClientInflow(mockBucket, "CLIENT_A")); + } + + @Test + void checkClientInflow_blankClientId_returnsNull() { + DJBRequestProcessor processor = new DJBRequestProcessor(); + ClientDualBucket mockBucket = mock(ClientDualBucket.class); + + assertNull(processor.checkClientInflow(mockBucket, "")); + verify(mockBucket, never()).isClientPassDetail(any()); + } + + // ------------------------------------------------------------------------- + // buildInflowTargetErrorMsg + // ------------------------------------------------------------------------- + + @Test + void buildInflowTargetErrorMsg_perSecond_includesReqPerSec() { + DJBRequestProcessor processor = new DJBRequestProcessor(); + 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() { + DJBRequestProcessor processor = new DJBRequestProcessor(); + 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() { + DJBRequestProcessor processor = new DJBRequestProcessor(); + InflowTargetVO vo = makeVO("결제API", 30, 1000, "MIN"); + + String msg = processor.buildInflowTargetErrorMsg(vo, "BLOCKED"); + + assertEquals("API 호출 한도 초과 [결제API]", msg); + } + + @Test + void buildInflowTargetErrorMsg_nullBlockedType_simpleFormat() { + DJBRequestProcessor processor = new DJBRequestProcessor(); + InflowTargetVO vo = makeVO("결제API", 30, 1000, "MIN"); + + String msg = processor.buildInflowTargetErrorMsg(vo, null); + + assertEquals("API 호출 한도 초과 [결제API]", msg); + } + + @Test + void buildInflowTargetErrorMsg_hourUnit() { + DJBRequestProcessor processor = new DJBRequestProcessor(); + 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/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/custom/inflow/ClientInflowTargetMetricControllerTest.java b/src/test/java/com/eactive/eai/custom/inflow/ClientInflowTargetMetricControllerTest.java new file mode 100644 index 0000000..ce996b9 --- /dev/null +++ b/src/test/java/com/eactive/eai/custom/inflow/ClientInflowTargetMetricControllerTest.java @@ -0,0 +1,144 @@ +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.TargetMetricDTO; + +/** + * ClientInflowTargetMetricController 단위 테스트. + * + *

Spring MVC를 기동하지 않고 컨트롤러를 직접 인스턴스화하여 + * 응답 구조(success, data, count, message)를 검증한다. + */ +class ClientInflowTargetMetricControllerTest { + + private ClientInflowTargetMetricController controller; + private ClientInflowTargetMetricService mockService; + + @BeforeEach + void setUp() { + controller = new ClientInflowTargetMetricController(); + mockService = mock(ClientInflowTargetMetricService.class); + ReflectionTestUtils.setField(controller, "metricService", mockService); + } + + private TargetMetricDTO makeDTO(String targetId, String targetType, + long allowed, long rejPs, long rejTh) { + TargetMetricDTO dto = new TargetMetricDTO(); + dto.setTargetId(targetId); + dto.setTargetType(targetType); + 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); + return dto; + } + + @SuppressWarnings("unchecked") + private Map body(ResponseEntity response) { + return (Map) response.getBody(); + } + + // ========================================================================= + // 단일 조회 + // ========================================================================= + + @Test + void getClientMetric_found_successTrueAndDataPresent() { + TargetMetricDTO dto = makeDTO("C1", "CLIENT", 300, 50, 20); + when(mockService.getClientMetric("C1")).thenReturn(dto); + + Map body = body(controller.getClientMetric("C1")); + + assertEquals(Boolean.TRUE, body.get("success")); + assertSame(dto, body.get("data")); + assertFalse(body.containsKey("message")); + } + + @Test + void getClientMetric_notFound_successFalseWithMessage() { + when(mockService.getClientMetric("GHOST")).thenReturn(null); + + Map body = body(controller.getClientMetric("GHOST")); + + assertEquals(Boolean.FALSE, body.get("success")); + assertTrue(body.get("message").toString().contains("GHOST")); + } + + // ========================================================================= + // 전체 조회 + // ========================================================================= + + @Test + void getAllClientMetrics_returnsListAndCount() { + List list = Arrays.asList( + makeDTO("C1", "CLIENT", 100, 5, 5), + makeDTO("C2", "CLIENT", 200, 0, 0), + makeDTO("C3", "CLIENT", 50, 10, 0) + ); + when(mockService.getAllClientMetrics()).thenReturn(list); + + Map body = body(controller.getAllClientMetrics()); + + assertEquals(Boolean.TRUE, body.get("success")); + assertSame(list, body.get("data")); + assertEquals(3, body.get("count")); + } + + @Test + void getAllClientMetrics_emptyList_countZero() { + when(mockService.getAllClientMetrics()).thenReturn(Collections.emptyList()); + + Map body = body(controller.getAllClientMetrics()); + + assertEquals(Boolean.TRUE, body.get("success")); + assertEquals(0, body.get("count")); + } + + // ========================================================================= + // reset + // ========================================================================= + + @Test + void resetClientMetric_success_successTrueWithTargetId() { + when(mockService.resetClientMetric("C1")).thenReturn(true); + + Map body = body(controller.resetClientMetric("C1")); + + assertEquals(Boolean.TRUE, body.get("success")); + assertTrue(body.get("message").toString().contains("C1")); + } + + @Test + void resetClientMetric_notFound_successFalseWithTargetId() { + when(mockService.resetClientMetric("GHOST")).thenReturn(false); + + Map body = body(controller.resetClientMetric("GHOST")); + + assertEquals(Boolean.FALSE, body.get("success")); + assertTrue(body.get("message").toString().contains("GHOST")); + } + + @Test + void resetAllClientMetrics_callsDelegateOnce() { + doNothing().when(mockService).resetAllClientMetrics(); + + controller.resetAllClientMetrics(); + + verify(mockService, times(1)).resetAllClientMetrics(); + } +} diff --git a/src/test/java/com/eactive/eai/custom/inflow/ClientInflowTargetMetricServiceTest.java b/src/test/java/com/eactive/eai/custom/inflow/ClientInflowTargetMetricServiceTest.java new file mode 100644 index 0000000..5e9325b --- /dev/null +++ b/src/test/java/com/eactive/eai/custom/inflow/ClientInflowTargetMetricServiceTest.java @@ -0,0 +1,177 @@ +package com.eactive.eai.custom.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.InflowTargetVO; +import com.eactive.eai.common.inflow.dual.ClientDualInflowControlManager; +import com.eactive.eai.common.inflow.dual.DualInflowControlManager; +import com.eactive.eai.manage.inflow.TargetMetricDTO; + +class ClientInflowTargetMetricServiceTest { + + private static ClientInflowTargetMetricService serviceWith(ClientDualInflowControlManager manager) { + return new ClientInflowTargetMetricService() { + @Override + protected ClientDualInflowControlManager getClientDualManager() { return manager; } + }; + } + + private InflowTargetVO makeVO(String name) { + InflowTargetVO vo = new InflowTargetVO(); + vo.setName(name); + 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; + } + + // ========================================================================= + // null 관리자 (비활성 환경) + // ========================================================================= + + @Test + void getClientMetric_managerIsNull_returnsNull() { + assertNull(serviceWith(null).getClientMetric("C1")); + } + + @Test + void getAllClientMetrics_managerIsNull_returnsEmptyList() { + assertTrue(serviceWith(null).getAllClientMetrics().isEmpty()); + } + + @Test + void resetClientMetric_managerIsNull_returnsFalse() { + assertFalse(serviceWith(null).resetClientMetric("C1")); + } + + @Test + void resetAllClientMetrics_managerIsNull_noException() { + assertDoesNotThrow(() -> serviceWith(null).resetAllClientMetrics()); + } + + // ========================================================================= + // 단일 조회 + // ========================================================================= + + @Test + void getClientMetric_found_mapsFields() { + ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class); + when(manager.getClientInflowThreshold("C1")).thenReturn(makeVO("C1")); + when(manager.getClientMetrics("C1")).thenReturn(makeMetrics(200, 30, 20)); + + TargetMetricDTO dto = serviceWith(manager).getClientMetric("C1"); + + assertNotNull(dto); + assertEquals("C1", dto.getTargetId()); + assertEquals("CLIENT", dto.getTargetType()); + assertTrue(dto.isActivate()); + assertEquals(200, dto.getAllowed()); + assertEquals(30, dto.getRejectedPerSecond()); + assertEquals(20, dto.getRejectedThreshold()); + assertEquals(250, dto.getTotalRequests()); + } + + @Test + void getClientMetric_notRegistered_returnsNull() { + ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class); + when(manager.getClientInflowThreshold("C1")).thenReturn(null); + + assertNull(serviceWith(manager).getClientMetric("C1")); + } + + @Test + void getClientMetric_noMetricsYet_returnsZeroCounters() { + ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class); + when(manager.getClientInflowThreshold("C1")).thenReturn(makeVO("C1")); + when(manager.getClientMetrics("C1")).thenReturn(null); + + TargetMetricDTO dto = serviceWith(manager).getClientMetric("C1"); + + assertNotNull(dto); + assertEquals(0, dto.getAllowed()); + assertEquals(0, dto.getTotalRequests()); + assertEquals(0.0, dto.getRejectRatio(), 0.001); + } + + @Test + void getClientMetric_rejectRatioCalculation() { + ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class); + when(manager.getClientInflowThreshold("C1")).thenReturn(makeVO("C1")); + // allowed=90, rejPs=5, rejTh=5 → reject=10/100 = 10.00% + when(manager.getClientMetrics("C1")).thenReturn(makeMetrics(90, 5, 5)); + + TargetMetricDTO dto = serviceWith(manager).getClientMetric("C1"); + + assertEquals(10.0, dto.getRejectRatio(), 0.01); + } + + // ========================================================================= + // 전체 조회 + // ========================================================================= + + @Test + void getAllClientMetrics_twoEntries_returnsBoth() { + ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class); + java.util.Map bucketMap = + new java.util.LinkedHashMap<>(); + bucketMap.put("C1", null); + bucketMap.put("C2", null); + when(manager.getClientBucketMap()).thenReturn(bucketMap); + when(manager.getClientInflowThreshold("C1")).thenReturn(makeVO("C1")); + when(manager.getClientInflowThreshold("C2")).thenReturn(makeVO("C2")); + when(manager.getClientMetrics("C1")).thenReturn(makeMetrics(10, 0, 0)); + when(manager.getClientMetrics("C2")).thenReturn(makeMetrics(20, 5, 0)); + + List result = serviceWith(manager).getAllClientMetrics(); + + assertEquals(2, result.size()); + } + + @Test + void getAllClientMetrics_noKeys_returnsEmptyList() { + ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class); + when(manager.getClientBucketMap()).thenReturn(new java.util.HashMap<>()); + + assertTrue(serviceWith(manager).getAllClientMetrics().isEmpty()); + } + + // ========================================================================= + // reset + // ========================================================================= + + @Test + void resetClientMetric_success_returnsTrueAndCallsManager() { + ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class); + when(manager.getClientInflowThreshold("C1")).thenReturn(makeVO("C1")); + + assertTrue(serviceWith(manager).resetClientMetric("C1")); + verify(manager).resetClientMetrics("C1"); + } + + @Test + void resetClientMetric_notRegistered_returnsFalseAndNoReset() { + ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class); + when(manager.getClientInflowThreshold("C1")).thenReturn(null); + + assertFalse(serviceWith(manager).resetClientMetric("C1")); + verify(manager, never()).resetClientMetrics(any()); + } + + @Test + void resetAllClientMetrics_callsManager() { + ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class); + serviceWith(manager).resetAllClientMetrics(); + verify(manager).resetAllClientMetrics(); + } +} 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 2037ced..8aedcad 100644 --- a/src/test/java/com/eactive/eai/manage/inflow/InflowTargetBucketServiceTest.java +++ b/src/test/java/com/eactive/eai/manage/inflow/InflowTargetBucketServiceTest.java @@ -21,8 +21,8 @@ import io.github.bucket4j.local.LocalBucket; /** * InflowTargetBucketService 단위 테스트. * - *

getDualManager()를 protected로 오버라이드하는 내부 테스트 서브클래스를 사용하여 - * DualInflowControlManager 의존성을 주입한다. + *

어댑터/인터페이스: getDualManager()를 오버라이드하는 서브클래스 사용. + * 클라이언트 관련 테스트는 ClientInflowTargetBucketServiceTest 에서 검증한다. */ class InflowTargetBucketServiceTest { @@ -33,9 +33,7 @@ class InflowTargetBucketServiceTest { private static InflowTargetBucketService serviceWith(DualInflowControlManager manager) { return new InflowTargetBucketService() { @Override - protected DualInflowControlManager getDualManager() { - return manager; - } + protected DualInflowControlManager getDualManager() { return manager; } }; } @@ -96,16 +94,6 @@ class InflowTargetBucketServiceTest { assertTrue(serviceWith(null).getAllInterfaceBucketStatus().isEmpty()); } - @Test - void getClientBucketStatus_managerIsNull_returnsNull() { - assertNull(serviceWith(null).getClientBucketStatus("CLIENT_A")); - } - - @Test - void getAllClientBucketStatus_managerIsNull_returnsEmptyList() { - assertTrue(serviceWith(null).getAllClientBucketStatus().isEmpty()); - } - // ========================================================================= // 버킷 미존재 // ========================================================================= @@ -126,14 +114,6 @@ class InflowTargetBucketServiceTest { assertNull(serviceWith(manager).getInterfaceBucketStatus("IF_001")); } - @Test - void getClientBucketStatus_notFound_returnsNull() { - DualInflowControlManager manager = mock(DualInflowControlManager.class); - when(manager.getClientBucket("CLIENT_A")).thenReturn(null); - - assertNull(serviceWith(manager).getClientBucketStatus("CLIENT_A")); - } - // ========================================================================= // 단일 조회 — targetType, targetId 매핑 // ========================================================================= @@ -166,98 +146,10 @@ class InflowTargetBucketServiceTest { assertEquals(2, dto.getBuckets().size()); } - @Test - void getClientBucketStatus_found_mapsClientType() { - DualInflowControlManager manager = mock(DualInflowControlManager.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 타입별 포함 여부 + // BucketInfo 용량/시간단위 매핑 // ========================================================================= - @Test - void toDto_onlyThreshold_bucketInfoHasOneThresholdEntry() { - InflowTargetVO vo = makeVO("C1", 0, 1000, "DAY", true); - DualCustomBucket bucket = new DualCustomBucket(null, freshBucket(1000), vo); - - DualInflowControlManager manager = mock(DualInflowControlManager.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, "SEC", true); - DualCustomBucket bucket = new DualCustomBucket(freshBucket(100), null, vo); - - DualInflowControlManager manager = mock(DualInflowControlManager.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() { - DualInflowControlManager manager = mock(DualInflowControlManager.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); - - DualInflowControlManager manager = mock(DualInflowControlManager.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); - - DualInflowControlManager manager = mock(DualInflowControlManager.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 toDto_capacity_perSecondBucketInfoCapacityMatchesVO() { DualInflowControlManager manager = mock(DualInflowControlManager.class); @@ -278,19 +170,6 @@ class InflowTargetBucketServiceTest { // 전체 목록 조회 // ========================================================================= - @Test - void getAllClientBucketStatus_twoEntries_returnsBoth() { - DualInflowControlManager manager = mock(DualInflowControlManager.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()); - } - @Test void getAllAdapterBucketStatus_emptyMap_returnsEmptyList() { DualInflowControlManager manager = mock(DualInflowControlManager.class); diff --git a/src/test/java/com/eactive/eai/manage/inflow/InflowTargetMetricControllerTest.java b/src/test/java/com/eactive/eai/manage/inflow/InflowTargetMetricControllerTest.java index a7dcd96..8de70fd 100644 --- a/src/test/java/com/eactive/eai/manage/inflow/InflowTargetMetricControllerTest.java +++ b/src/test/java/com/eactive/eai/manage/inflow/InflowTargetMetricControllerTest.java @@ -210,92 +210,4 @@ class InflowTargetMetricControllerTest { verify(mockService, times(1)).resetAllInterfaceMetrics(); } - - // ========================================================================= - // 클라이언트 — 단일 조회 - // ========================================================================= - - @Test - void getClientMetric_found_successTrueAndDataPresent() { - TargetMetricDTO dto = makeDTO("C1", "CLIENT", 300, 50, 20); - when(mockService.getClientMetric("C1")).thenReturn(dto); - - Map body = body(controller.getClientMetric("C1")); - - assertEquals(Boolean.TRUE, body.get("success")); - assertSame(dto, body.get("data")); - } - - @Test - void getClientMetric_notFound_successFalseWithMessage() { - when(mockService.getClientMetric("GHOST")).thenReturn(null); - - Map body = body(controller.getClientMetric("GHOST")); - - assertEquals(Boolean.FALSE, body.get("success")); - assertTrue(body.get("message").toString().contains("GHOST")); - } - - // ========================================================================= - // 클라이언트 — 전체 조회 - // ========================================================================= - - @Test - void getAllClientMetrics_returnsListAndCount() { - List list = Arrays.asList( - makeDTO("C1", "CLIENT", 100, 5, 5), - makeDTO("C2", "CLIENT", 200, 0, 0), - makeDTO("C3", "CLIENT", 50, 10, 0) - ); - when(mockService.getAllClientMetrics()).thenReturn(list); - - Map body = body(controller.getAllClientMetrics()); - - assertEquals(Boolean.TRUE, body.get("success")); - assertSame(list, body.get("data")); - assertEquals(3, body.get("count")); - } - - @Test - void getAllClientMetrics_emptyList_countZero() { - when(mockService.getAllClientMetrics()).thenReturn(Collections.emptyList()); - - Map body = body(controller.getAllClientMetrics()); - - assertEquals(Boolean.TRUE, body.get("success")); - assertEquals(0, body.get("count")); - } - - // ========================================================================= - // 클라이언트 — reset - // ========================================================================= - - @Test - void resetClientMetric_success_successTrue() { - when(mockService.resetClientMetric("C1")).thenReturn(true); - - Map body = body(controller.resetClientMetric("C1")); - - assertEquals(Boolean.TRUE, body.get("success")); - assertTrue(body.get("message").toString().contains("C1")); - } - - @Test - void resetClientMetric_notFound_successFalse() { - when(mockService.resetClientMetric("GHOST")).thenReturn(false); - - Map body = body(controller.resetClientMetric("GHOST")); - - assertEquals(Boolean.FALSE, body.get("success")); - assertTrue(body.get("message").toString().contains("GHOST")); - } - - @Test - void resetAllClientMetrics_callsDelegateOnce() { - doNothing().when(mockService).resetAllClientMetrics(); - - controller.resetAllClientMetrics(); - - verify(mockService, times(1)).resetAllClientMetrics(); - } } diff --git a/src/test/java/com/eactive/eai/manage/inflow/InflowTargetMetricServiceTest.java b/src/test/java/com/eactive/eai/manage/inflow/InflowTargetMetricServiceTest.java index 91c13cb..34a7ed5 100644 --- a/src/test/java/com/eactive/eai/manage/inflow/InflowTargetMetricServiceTest.java +++ b/src/test/java/com/eactive/eai/manage/inflow/InflowTargetMetricServiceTest.java @@ -13,12 +13,12 @@ import com.eactive.eai.common.inflow.dual.DualInflowControlManager; /** * InflowTargetMetricService 단위 테스트. * - *

getDualManager()를 오버라이드하여 DualInflowControlManager 의존성을 주입한다. + *

어댑터/인터페이스 테스트는 getDualManager()를 오버라이드하여 DualInflowControlManager 주입. */ class InflowTargetMetricServiceTest { // ------------------------------------------------------------------------- - // 테스트용 서브클래스 + // 테스트용 서브클래스 — 어댑터/인터페이스용 // ------------------------------------------------------------------------- private static InflowTargetMetricService serviceWith(DualInflowControlManager manager) { @@ -76,11 +76,6 @@ class InflowTargetMetricServiceTest { assertNull(serviceWith(null).getInterfaceMetric("IF1")); } - @Test - void getClientMetric_managerIsNull_returnsNull() { - assertNull(serviceWith(null).getClientMetric("C1")); - } - // ========================================================================= // 어댑터 — 단일 조회 // ========================================================================= @@ -271,55 +266,4 @@ class InflowTargetMetricServiceTest { serviceWith(manager).resetAllInterfaceMetrics(); verify(manager).resetAllInterfaceMetrics(); } - - // ========================================================================= - // 클라이언트 — 단일 조회 - // ========================================================================= - - @Test - void getClientMetric_found_mapsFields() { - DualInflowControlManager manager = mock(DualInflowControlManager.class); - when(manager.getClientInflowThreshold("C1")).thenReturn(makeVO("C1")); - when(manager.getClientMetrics("C1")).thenReturn(makeMetrics(200, 30, 20)); - - TargetMetricDTO dto = serviceWith(manager).getClientMetric("C1"); - - assertNotNull(dto); - assertEquals("C1", dto.getTargetId()); - assertEquals("CLIENT", dto.getTargetType()); - assertEquals(200, dto.getAllowed()); - assertEquals(250, dto.getTotalRequests()); - } - - @Test - void getClientMetric_notRegistered_returnsNull() { - DualInflowControlManager manager = mock(DualInflowControlManager.class); - when(manager.getClientInflowThreshold("C1")).thenReturn(null); - - assertNull(serviceWith(manager).getClientMetric("C1")); - } - - @Test - void resetClientMetric_success_returnsTrueAndCallsManager() { - DualInflowControlManager manager = mock(DualInflowControlManager.class); - when(manager.getClientInflowThreshold("C1")).thenReturn(makeVO("C1")); - - assertTrue(serviceWith(manager).resetClientMetric("C1")); - verify(manager).resetClientMetrics("C1"); - } - - @Test - void resetClientMetric_notRegistered_returnsFalse() { - DualInflowControlManager manager = mock(DualInflowControlManager.class); - when(manager.getClientInflowThreshold("C1")).thenReturn(null); - - assertFalse(serviceWith(manager).resetClientMetric("C1")); - } - - @Test - void resetAllClientMetrics_callsManager() { - DualInflowControlManager manager = mock(DualInflowControlManager.class); - serviceWith(manager).resetAllClientMetrics(); - verify(manager).resetAllClientMetrics(); - } }