# 첫 가드가 SINGLE 아닌 전략을 전부 거부한다 private static void validateRouteShape(NotificationRouteDescriptor route) { if (route.routeStrategy() != dev.caskeleton.application.notification.NotificationRouteStrategy.SINGLE) { throw new NotificationCatalogException( "initial notification catalog supports SINGLE route strategy only"); } # 그 뒤 전략을 보지 않는 검사 둘 if (route.targets().size() != route.maximumTargets()) { throw new NotificationCatalogException( "route target count must equal its maximum target bound"); } long worstCaseCalls = Math.addExact( Math.multiplyExact( (long) route.maximumTargets(), (long) route.maximumPhysicalAttempts()), route.maximumReconcileCalls()); if (worstCaseCalls > route.maximumTotalProviderCalls()) { throw new NotificationCatalogException( "route amplification exceeds maximum total provider calls"); } # switch 네 갈래 — SINGLE 을 뺀 셋은 도달할 수 없다 switch (route.routeStrategy()) { case SINGLE -> { if (route.maximumTargets() != 1 || route.maximumFallbackActivations() != 0 || route.targets().stream().anyMatch(target -> target.fallbackTargetId().isPresent())) { throw new NotificationCatalogException( "SINGLE route requires one target and no fallback"); } } case FAN_OUT_ALL -> { if (route.maximumFallbackActivations() != 0 || route.targets().stream().anyMatch(target -> target.fallbackTargetId().isPresent())) { throw new NotificationCatalogException("FAN_OUT_ALL route cannot define fallback"); } } case ORDERED_FALLBACK -> { if (route.maximumTargets() < 2 || route.maximumFallbackActivations() < 1 || route.maximumFallbackActivations() > route.maximumTargets() - 1) { throw new NotificationCatalogException("ORDERED_FALLBACK route has invalid bounds"); } } default -> throw new NotificationCatalogException( "unknown notification route strategy: " + route.routeStrategy()); } validateFallbackGraph(route); # 그리고 폴백 그래프 검사가 던질 수 있는 두 문구 204: "fallback references unknown target"); 213: throw new NotificationCatalogException("cyclic notification fallback graph"); # 순환을 거부한다는 테스트 189: void legacyReceiptUnsafeFallbackBoundsAndCyclesAreRejected() { # 그 테스트가 만든 팬아웃 라우트 NotificationRouteDescriptor unsupportedFanOut = new NotificationRouteDescriptor( new NotificationRouteId("security-slack"), 3, NotificationChannel.SLACK, NotificationMode.DURABLE_ASYNC, NotificationAdmissionClass.TRANSACTIONAL, NotificationRouteStrategy.FAN_OUT_ALL, # 그리고 순환 라우트 — 전략과 범위 값 NotificationRouteDescriptor cyclic = new NotificationRouteDescriptor( new NotificationRouteId("security-slack"), 3, NotificationChannel.SLACK, NotificationMode.DURABLE_ASYNC, NotificationAdmissionClass.TRANSACTIONAL, NotificationRouteStrategy.ORDERED_FALLBACK, new NotificationTemplateRef("security-slack", 1), false, false, 2, 1, 1, 0, 2, Duration.ofSeconds(5), List.of( new NotificationRouteDescriptor.Target( "target-a", "slack-runtime-r1", Optional.of("target-b")), new NotificationRouteDescriptor.Target( "target-b", "slack-runtime-r1", Optional.of("target-a")))); # 그 둘에 붙은 단언 175: .hasMessageContaining("explicit active revision"); 211: .hasMessageContaining("legacy"); # 이 두 분기를 태울 라우트를 만드는 곳 — 저장소 전수 NotificationKindPolicyTest.java:44: NotificationRouteStrategy.FAN_OUT_ALL); NotificationKindPolicyTest.java:76: NotificationRouteStrategy.FAN_OUT_ALL, NotificationKindPolicyTest.java:105: strategy == NotificationRouteStrategy.ORDERED_FALLBACK ? 1 : 0, NotificationKindPolicy.java:115: if (strategy == NotificationRouteStrategy.FAN_OUT_ALL && maximumFallbacks != 0) { NotificationKindPolicy.java:118: if (strategy == NotificationRouteStrategy.ORDERED_FALLBACK NotificationBindingCompilerTest.java:253: NotificationRouteStrategy.FAN_OUT_ALL, NotificationBindingCompilerTest.java:286: NotificationRouteStrategy.ORDERED_FALLBACK, # 설정 파일의 비-SINGLE 전략: 0 # 그 테스트를 실제로 돌린 결과 BUILD SUCCESSFUL in 1s tests="5" skipped="0" failures="0" errors="0"