Extract machine routing helpers and gate Spring DI to ambiguous generics.
Keep package heuristics for single-machine codebases and fail closed only when String/Object generic triggers are provably ambiguous across multiple beans. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
package click.kamil.springstatemachineexporter.analysis.enricher.routing;
|
||||
|
||||
import click.kamil.springstatemachineexporter.ast.common.AstUtils;
|
||||
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
|
||||
/**
|
||||
* Maps {@code @EnableStateMachine(name=…)} bean names to configuration class FQNs.
|
||||
*/
|
||||
final class EnableStateMachineBeanRouting {
|
||||
|
||||
private EnableStateMachineBeanRouting() {
|
||||
}
|
||||
|
||||
static Boolean matchesNamedBean(String beanName, String currentMachineConfigFqn, CodebaseContext context) {
|
||||
String normalized = stripQuotes(beanName);
|
||||
if (normalized == null || normalized.isEmpty() || context == null || currentMachineConfigFqn == null) {
|
||||
return null;
|
||||
}
|
||||
String matchingConfig = findConfigFqnByBeanName(normalized, context);
|
||||
if (matchingConfig == null) {
|
||||
return null;
|
||||
}
|
||||
return currentMachineConfigFqn.equals(matchingConfig);
|
||||
}
|
||||
|
||||
static String findConfigFqnByBeanName(String beanName, CodebaseContext context) {
|
||||
for (TypeDeclaration configType : context.findEntryPointClasses(java.util.List.of("EnableStateMachine"))) {
|
||||
String configFqn = context.getFqn(configType);
|
||||
String configuredName = extractEnableStateMachineName(configType);
|
||||
if (configuredName == null || configuredName.isEmpty()) {
|
||||
configuredName = defaultBeanName(configType.getName().getIdentifier());
|
||||
}
|
||||
if (beanName.equals(configuredName)) {
|
||||
return configFqn;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static String extractEnableStateMachineName(TypeDeclaration configType) {
|
||||
for (Object modifierObj : configType.modifiers()) {
|
||||
if (!(modifierObj instanceof Annotation annotation)) {
|
||||
continue;
|
||||
}
|
||||
if (!annotation.getTypeName().toString().endsWith("EnableStateMachine")) {
|
||||
continue;
|
||||
}
|
||||
String name = AstUtils.extractAnnotationMember(annotation, "name");
|
||||
if (name != null && !name.isBlank()) {
|
||||
return stripQuotes(name);
|
||||
}
|
||||
String value = AstUtils.extractAnnotationMember(annotation, "value");
|
||||
if (value != null && !value.isBlank()) {
|
||||
return stripQuotes(value);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static String defaultBeanNameFromFqn(String fqn) {
|
||||
if (fqn == null || !fqn.contains(".")) {
|
||||
return fqn;
|
||||
}
|
||||
String simple = fqn.substring(fqn.lastIndexOf('.') + 1);
|
||||
return defaultBeanName(simple);
|
||||
}
|
||||
|
||||
private static String defaultBeanName(String simpleClassName) {
|
||||
if (simpleClassName == null || simpleClassName.isEmpty()) {
|
||||
return simpleClassName;
|
||||
}
|
||||
return Character.toLowerCase(simpleClassName.charAt(0)) + simpleClassName.substring(1);
|
||||
}
|
||||
|
||||
private static String stripQuotes(String raw) {
|
||||
if (raw == null || raw.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
String trimmed = raw.trim();
|
||||
if (trimmed.length() >= 2 && trimmed.startsWith("\"") && trimmed.endsWith("\"")) {
|
||||
return trimmed.substring(1, trimmed.length() - 1);
|
||||
}
|
||||
return trimmed;
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,8 @@ import click.kamil.springstatemachineexporter.analysis.enricher.matching.StrictF
|
||||
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
|
||||
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
|
||||
import click.kamil.springstatemachineexporter.analysis.resolver.StateMachineTypeResolver;
|
||||
import click.kamil.springstatemachineexporter.ast.common.AstUtils;
|
||||
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||
import click.kamil.springstatemachineexporter.model.Transition;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
|
||||
import java.util.HashSet;
|
||||
@@ -37,9 +35,12 @@ public class HeuristicBeanResolutionEngine implements BeanResolutionEngine {
|
||||
return isRoutedToCorrectMachine(chain, currentMachineName, context);
|
||||
}
|
||||
|
||||
String explicitTarget = resolveExplicitTarget(chain);
|
||||
MachineRoutingEvidence evidence = MachineRoutingEvidence.from(chain);
|
||||
|
||||
String explicitTarget = evidence.explicitBeanName();
|
||||
if (explicitTarget != null && !explicitTarget.isEmpty() && context != null) {
|
||||
Boolean namedTarget = resolveNamedBeanTarget(explicitTarget, currentMachineName, context);
|
||||
Boolean namedTarget = EnableStateMachineBeanRouting.matchesNamedBean(
|
||||
explicitTarget, currentMachineName, context);
|
||||
if (namedTarget != null) {
|
||||
return namedTarget;
|
||||
}
|
||||
@@ -70,9 +71,12 @@ public class HeuristicBeanResolutionEngine implements BeanResolutionEngine {
|
||||
|
||||
@Override
|
||||
public boolean isRoutedToCorrectMachine(CallChain chain, String currentMachineName, CodebaseContext context) {
|
||||
String explicitTarget = resolveExplicitTarget(chain);
|
||||
MachineRoutingEvidence evidence = MachineRoutingEvidence.from(chain);
|
||||
|
||||
String explicitTarget = evidence.explicitBeanName();
|
||||
if (explicitTarget != null && !explicitTarget.isEmpty() && context != null && currentMachineName != null) {
|
||||
Boolean namedTarget = resolveNamedBeanTarget(explicitTarget, currentMachineName, context);
|
||||
Boolean namedTarget = EnableStateMachineBeanRouting.matchesNamedBean(
|
||||
explicitTarget, currentMachineName, context);
|
||||
if (namedTarget != null) {
|
||||
return namedTarget;
|
||||
}
|
||||
@@ -126,6 +130,15 @@ public class HeuristicBeanResolutionEngine implements BeanResolutionEngine {
|
||||
}
|
||||
}
|
||||
|
||||
if (!isSingleStateMachine(context) && isAmbiguousSharedGenericTrigger(chain.getTriggerPoint())) {
|
||||
Boolean injectionMatch = SpringInjectionRouting.matchesMachineConfig(
|
||||
chain.getTriggerPoint(), currentMachineName, context);
|
||||
if (injectionMatch != null) {
|
||||
return injectionMatch;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
String targetVar = hasExplicitBeanTarget ? explicitTarget : chain.getContextMachineId();
|
||||
if (targetVar == null && chain.getTriggerPoint() != null) {
|
||||
targetVar = chain.getTriggerPoint().getStateMachineId();
|
||||
@@ -201,17 +214,6 @@ public class HeuristicBeanResolutionEngine implements BeanResolutionEngine {
|
||||
return context == null || isSingleStateMachine(context);
|
||||
}
|
||||
|
||||
private static String resolveExplicitTarget(CallChain chain) {
|
||||
if (chain == null) {
|
||||
return null;
|
||||
}
|
||||
String explicitTarget = chain.getContextMachineId();
|
||||
if (explicitTarget == null && chain.getTriggerPoint() != null) {
|
||||
explicitTarget = chain.getTriggerPoint().getStateMachineId();
|
||||
}
|
||||
return explicitTarget;
|
||||
}
|
||||
|
||||
private DistinctTypeAffinity resolveDistinctTypeAffinity(
|
||||
TriggerPoint trigger, String machineName, CodebaseContext context) {
|
||||
String triggerEventFqn = trigger.getEventTypeFqn();
|
||||
@@ -301,64 +303,15 @@ public class HeuristicBeanResolutionEngine implements BeanResolutionEngine {
|
||||
}
|
||||
|
||||
private Boolean resolveNamedBeanTarget(String beanName, String currentMachineName, CodebaseContext context) {
|
||||
String normalized = stripAnnotationQuotes(beanName);
|
||||
if (normalized == null || normalized.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
String matchingConfig = null;
|
||||
for (TypeDeclaration configType : context.findEntryPointClasses(java.util.List.of("EnableStateMachine"))) {
|
||||
String configFqn = context.getFqn(configType);
|
||||
String configuredName = extractEnableStateMachineName(configType);
|
||||
if (configuredName == null || configuredName.isEmpty()) {
|
||||
configuredName = defaultBeanName(configType.getName().getIdentifier());
|
||||
}
|
||||
if (normalized.equals(configuredName)) {
|
||||
matchingConfig = configFqn;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (matchingConfig == null) {
|
||||
return null;
|
||||
}
|
||||
return currentMachineName.equals(matchingConfig);
|
||||
return EnableStateMachineBeanRouting.matchesNamedBean(beanName, currentMachineName, context);
|
||||
}
|
||||
|
||||
private static String extractEnableStateMachineName(TypeDeclaration configType) {
|
||||
for (Object modifierObj : configType.modifiers()) {
|
||||
if (!(modifierObj instanceof Annotation annotation)) {
|
||||
continue;
|
||||
}
|
||||
if (!annotation.getTypeName().toString().endsWith("EnableStateMachine")) {
|
||||
continue;
|
||||
}
|
||||
String name = AstUtils.extractAnnotationMember(annotation, "name");
|
||||
if (name != null && !name.isBlank()) {
|
||||
return stripAnnotationQuotes(name);
|
||||
}
|
||||
String value = AstUtils.extractAnnotationMember(annotation, "value");
|
||||
if (value != null && !value.isBlank()) {
|
||||
return stripAnnotationQuotes(value);
|
||||
}
|
||||
private boolean isAmbiguousSharedGenericTrigger(TriggerPoint trigger) {
|
||||
if (trigger == null) {
|
||||
return false;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String defaultBeanName(String simpleClassName) {
|
||||
if (simpleClassName == null || simpleClassName.isEmpty()) {
|
||||
return simpleClassName;
|
||||
}
|
||||
return Character.toLowerCase(simpleClassName.charAt(0)) + simpleClassName.substring(1);
|
||||
}
|
||||
|
||||
private static String stripAnnotationQuotes(String raw) {
|
||||
if (raw == null || raw.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
String trimmed = raw.trim();
|
||||
if (trimmed.length() >= 2 && trimmed.startsWith("\"") && trimmed.endsWith("\"")) {
|
||||
return trimmed.substring(1, trimmed.length() - 1);
|
||||
}
|
||||
return trimmed;
|
||||
return isAmbiguousSharedGenericType(trigger.getEventTypeFqn())
|
||||
|| isAmbiguousSharedGenericType(trigger.getStateTypeFqn());
|
||||
}
|
||||
|
||||
private int countStateMachines(CodebaseContext context) {
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package click.kamil.springstatemachineexporter.analysis.enricher.routing;
|
||||
|
||||
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
|
||||
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
|
||||
|
||||
/**
|
||||
* Source-derived evidence used to decide whether a call chain belongs to a state-machine export.
|
||||
*/
|
||||
public record MachineRoutingEvidence(
|
||||
String explicitBeanName,
|
||||
String stateMachineId,
|
||||
String eventTypeFqn,
|
||||
String stateTypeFqn,
|
||||
String triggerClassFqn,
|
||||
String triggerMethod,
|
||||
Integer triggerLine) {
|
||||
|
||||
public static MachineRoutingEvidence from(CallChain chain) {
|
||||
if (chain == null) {
|
||||
return new MachineRoutingEvidence(null, null, null, null, null, null, null);
|
||||
}
|
||||
TriggerPoint trigger = chain.getTriggerPoint();
|
||||
String explicit = chain.getContextMachineId();
|
||||
String stateMachineId = null;
|
||||
Integer line = null;
|
||||
if (trigger != null) {
|
||||
if (explicit == null) {
|
||||
explicit = trigger.getStateMachineId();
|
||||
}
|
||||
stateMachineId = trigger.getStateMachineId();
|
||||
if (trigger.getLineNumber() > 0) {
|
||||
line = trigger.getLineNumber();
|
||||
}
|
||||
}
|
||||
return new MachineRoutingEvidence(
|
||||
explicit,
|
||||
stateMachineId,
|
||||
trigger != null ? trigger.getEventTypeFqn() : null,
|
||||
trigger != null ? trigger.getStateTypeFqn() : null,
|
||||
trigger != null ? trigger.getClassName() : null,
|
||||
trigger != null ? trigger.getMethodName() : null,
|
||||
line);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package click.kamil.springstatemachineexporter.analysis.enricher.routing;
|
||||
|
||||
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
|
||||
import click.kamil.springstatemachineexporter.analysis.spring.InjectionPointAnalyzer;
|
||||
import click.kamil.springstatemachineexporter.analysis.spring.SpringBeanRegistry;
|
||||
import click.kamil.springstatemachineexporter.analysis.spring.SpringContextScanner;
|
||||
import click.kamil.springstatemachineexporter.analysis.spring.SpringDependencyResolver;
|
||||
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||
import org.eclipse.jdt.core.dom.*;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Resolves which {@code @EnableStateMachine} configuration owns a trigger's {@code StateMachine} receiver
|
||||
* using Spring injection analysis (JDT bindings), not package-name guessing.
|
||||
*/
|
||||
final class SpringInjectionRouting {
|
||||
|
||||
private static final Set<String> TRIGGER_METHODS = Set.of(
|
||||
"sendEvent", "sendEvents", "sendEventCollect", "sendEventMono", "fire", "trigger");
|
||||
|
||||
private SpringInjectionRouting() {
|
||||
}
|
||||
|
||||
static Boolean matchesMachineConfig(TriggerPoint trigger, String machineConfigFqn, CodebaseContext context) {
|
||||
if (trigger == null || machineConfigFqn == null || context == null || !context.isResolveBindings()) {
|
||||
return null;
|
||||
}
|
||||
String configFqn = resolveConfigurationFqn(trigger, context);
|
||||
if (configFqn == null) {
|
||||
return null;
|
||||
}
|
||||
return machineConfigFqn.equals(configFqn);
|
||||
}
|
||||
|
||||
static String resolveConfigurationFqn(TriggerPoint trigger, CodebaseContext context) {
|
||||
if (trigger == null || trigger.getClassName() == null || trigger.getMethodName() == null) {
|
||||
return null;
|
||||
}
|
||||
if (!context.isResolveBindings()) {
|
||||
return null;
|
||||
}
|
||||
InjectionPointAnalyzer analyzer = createAnalyzer(context);
|
||||
TypeDeclaration type = context.getTypeDeclaration(trigger.getClassName());
|
||||
if (type == null) {
|
||||
return null;
|
||||
}
|
||||
MethodDeclaration method = context.findMethodDeclaration(type, trigger.getMethodName(), true);
|
||||
if (method == null || method.getBody() == null) {
|
||||
return null;
|
||||
}
|
||||
CompilationUnit cu = (CompilationUnit) type.getRoot();
|
||||
final IVariableBinding[] receiverBinding = new IVariableBinding[1];
|
||||
method.getBody().accept(new ASTVisitor() {
|
||||
@Override
|
||||
public boolean visit(MethodInvocation node) {
|
||||
if (trigger.getLineNumber() > 0 && cu != null
|
||||
&& cu.getLineNumber(node.getStartPosition()) != trigger.getLineNumber()) {
|
||||
return true;
|
||||
}
|
||||
if (!TRIGGER_METHODS.contains(node.getName().getIdentifier())) {
|
||||
return true;
|
||||
}
|
||||
Expression receiver = node.getExpression();
|
||||
if (receiver instanceof SimpleName sn) {
|
||||
IBinding binding = sn.resolveBinding();
|
||||
if (binding instanceof IVariableBinding vb) {
|
||||
receiverBinding[0] = vb;
|
||||
}
|
||||
} else if (receiver instanceof FieldAccess fa) {
|
||||
receiverBinding[0] = fa.resolveFieldBinding();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (receiverBinding[0] == null) {
|
||||
return null;
|
||||
}
|
||||
String injectedFqn = analyzer.resolveInjectedBeanFqn(receiverBinding[0]);
|
||||
if (injectedFqn == null) {
|
||||
return null;
|
||||
}
|
||||
TypeDeclaration injectedType = context.getTypeDeclaration(injectedFqn);
|
||||
if (injectedType != null && context.extendsStateMachineConfigurerAdapter(injectedType)) {
|
||||
return injectedFqn;
|
||||
}
|
||||
return EnableStateMachineBeanRouting.findConfigFqnByBeanName(
|
||||
EnableStateMachineBeanRouting.defaultBeanNameFromFqn(injectedFqn), context);
|
||||
}
|
||||
|
||||
private static InjectionPointAnalyzer createAnalyzer(CodebaseContext context) {
|
||||
SpringBeanRegistry registry = new SpringBeanRegistry();
|
||||
SpringContextScanner scanner = new SpringContextScanner(registry);
|
||||
for (CompilationUnit cu : context.getCompilationUnits()) {
|
||||
cu.accept(scanner);
|
||||
}
|
||||
return new InjectionPointAnalyzer(new SpringDependencyResolver(registry));
|
||||
}
|
||||
}
|
||||
@@ -194,6 +194,10 @@ public class CodebaseContext {
|
||||
this.resolveBindings = resolveBindings;
|
||||
}
|
||||
|
||||
public boolean isResolveBindings() {
|
||||
return resolveBindings;
|
||||
}
|
||||
|
||||
public void setActiveProfiles(List<String> profiles) {
|
||||
this.activeProfiles.clear();
|
||||
if (profiles != null) {
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
package click.kamil.springstatemachineexporter.analysis.enricher.routing;
|
||||
|
||||
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
|
||||
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
|
||||
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class SpringInjectionRoutingTest {
|
||||
|
||||
@Test
|
||||
void shouldRouteByQualifierWithoutPackageNameHeuristics(@TempDir Path tempDir) throws Exception {
|
||||
Files.writeString(tempDir.resolve("PaymentService.java"), """
|
||||
package com.example;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.stereotype.Service;
|
||||
@Service
|
||||
public class PaymentService {
|
||||
@Qualifier("paymentStateMachine")
|
||||
private final StateMachine<String, String> stateMachine;
|
||||
public PaymentService(StateMachine<String, String> stateMachine) {
|
||||
this.stateMachine = stateMachine;
|
||||
}
|
||||
public void capture() {
|
||||
stateMachine.sendEvent("CAPTURE");
|
||||
}
|
||||
}
|
||||
@org.springframework.context.annotation.Configuration
|
||||
@org.springframework.statemachine.config.EnableStateMachine(name = "paymentStateMachine")
|
||||
class PaymentStateMachineConfig
|
||||
extends org.springframework.statemachine.config.StateMachineConfigurerAdapter<String, String> {}
|
||||
@org.springframework.context.annotation.Configuration
|
||||
@org.springframework.statemachine.config.EnableStateMachine(name = "orderStateMachine")
|
||||
class OrderStateMachineConfig
|
||||
extends org.springframework.statemachine.config.StateMachineConfigurerAdapter<String, String> {}
|
||||
""");
|
||||
|
||||
CodebaseContext context = new CodebaseContext();
|
||||
context.setResolveBindings(true);
|
||||
context.scan(tempDir);
|
||||
|
||||
HeuristicBeanResolutionEngine engine = new HeuristicBeanResolutionEngine();
|
||||
CallChain chain = CallChain.builder()
|
||||
.triggerPoint(TriggerPoint.builder()
|
||||
.className("com.example.PaymentService")
|
||||
.methodName("capture")
|
||||
.lineNumber(12)
|
||||
.event("CAPTURE")
|
||||
.eventTypeFqn("java.lang.String")
|
||||
.stateTypeFqn("java.lang.String")
|
||||
.stateMachineId("paymentStateMachine")
|
||||
.build())
|
||||
.methodChain(List.of("com.example.PaymentService.capture()"))
|
||||
.build();
|
||||
|
||||
assertThat(engine.isRoutedToCorrectMachine(
|
||||
chain, "com.example.PaymentStateMachineConfig", context)).isTrue();
|
||||
assertThat(engine.isRoutedToCorrectMachine(
|
||||
chain, "com.example.OrderStateMachineConfig", context)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailClosedForAmbiguousStringStateMachineWithoutQualifier(@TempDir Path tempDir) throws Exception {
|
||||
Files.writeString(tempDir.resolve("OrderService.java"), """
|
||||
package com.example;
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.stereotype.Service;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class OrderService {
|
||||
private final StateMachine<String, String> stateMachine;
|
||||
public void finish() {
|
||||
stateMachine.sendEvent("FINISH");
|
||||
}
|
||||
}
|
||||
@org.springframework.context.annotation.Configuration
|
||||
@org.springframework.statemachine.config.EnableStateMachine(name = "machineA")
|
||||
class MachineAConfig
|
||||
extends org.springframework.statemachine.config.StateMachineConfigurerAdapter<String, String> {}
|
||||
@org.springframework.context.annotation.Configuration
|
||||
@org.springframework.statemachine.config.EnableStateMachine(name = "machineB")
|
||||
class MachineBConfig
|
||||
extends org.springframework.statemachine.config.StateMachineConfigurerAdapter<String, String> {}
|
||||
""");
|
||||
|
||||
CodebaseContext context = new CodebaseContext();
|
||||
context.setResolveBindings(true);
|
||||
context.scan(tempDir);
|
||||
|
||||
HeuristicBeanResolutionEngine engine = new HeuristicBeanResolutionEngine();
|
||||
CallChain chain = CallChain.builder()
|
||||
.triggerPoint(TriggerPoint.builder()
|
||||
.className("com.example.OrderService")
|
||||
.methodName("finish")
|
||||
.lineNumber(11)
|
||||
.event("FINISH")
|
||||
.eventTypeFqn("java.lang.String")
|
||||
.stateTypeFqn("java.lang.String")
|
||||
.build())
|
||||
.methodChain(List.of("com.example.OrderService.finish()"))
|
||||
.build();
|
||||
|
||||
assertThat(engine.isRoutedToCorrectMachine(chain, "com.example.MachineAConfig", context)).isFalse();
|
||||
assertThat(engine.isRoutedToCorrectMachine(chain, "com.example.MachineBConfig", context)).isFalse();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user