From 4b5de0e1670dacce17fef909aedbcf10bc7c46ab Mon Sep 17 00:00:00 2001 From: Kamil Patryk Kozakowski Date: Thu, 16 Jul 2026 19:21:53 +0200 Subject: [PATCH] A --- .../enricher/CallChainLinkPolicy.java | 5 +- .../EnumMemberPredicateEvaluator.java | 29 +++++ .../resolver/MachineEnumCanonicalizer.java | 5 +- .../resolver/RichEventPredicateEvaluator.java | 117 +++++++++++++++--- .../enricher/CallChainLinkPolicyTest.java | 4 +- .../TransitionLinkerEnricherTest.java | 84 +++++++++++++ ...erpriseBooleanEnumPredicateExportTest.java | 37 ++++-- ...mberPredicatePolymorphicInferenceTest.java | 9 +- .../RichEventIsCorrectPredicateTest.java | 4 +- 9 files changed, 254 insertions(+), 40 deletions(-) diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/CallChainLinkPolicy.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/CallChainLinkPolicy.java index cd14ba0..5418a88 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/CallChainLinkPolicy.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/CallChainLinkPolicy.java @@ -122,7 +122,7 @@ public final class CallChainLinkPolicy { TriggerPoint trigger, String machineEventTypeFqn, CodebaseContext context) { - if (trigger == null || trigger.isExternal() || !trigger.isAmbiguous()) { + if (trigger == null || trigger.isExternal()) { return false; } String eventTypeFqn = trigger.getEventTypeFqn(); @@ -131,6 +131,9 @@ public final class CallChainLinkPolicy { } List concrete = concretePolymorphicCandidates( trigger.getPolymorphicEvents(), eventTypeFqn, context); + // Multiple concrete events on a dynamic/ENUM_SET trigger must not link every transition + // (e.g. predicate-filtered PAY+SHIP, or an adjust endpoint sharing a wide dispatcher poly). + // Do not require trigger.ambiguous — that flag is often cleared incorrectly upstream. if (concrete.size() <= 1) { return false; } diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/EnumMemberPredicateEvaluator.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/EnumMemberPredicateEvaluator.java index 7aed29c..5afd0e5 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/EnumMemberPredicateEvaluator.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/EnumMemberPredicateEvaluator.java @@ -70,6 +70,35 @@ public final class EnumMemberPredicateEvaluator { return calls; } + /** + * Evaluate a single no-arg boolean predicate on one enum constant (method name from dataflow/AST). + */ + public static Boolean evaluateConstantPredicate( + String canonicalConstantFqn, + String enumTypeFqn, + String methodName, + boolean negated, + CodebaseContext context) { + if (canonicalConstantFqn == null || enumTypeFqn == null || methodName == null || context == null) { + return null; + } + EnumDeclaration enumDecl = findEnumDeclaration(enumTypeFqn, context); + if (enumDecl == null) { + return null; + } + String constantName = constantSimpleName(canonicalConstantFqn); + if (constantName == null) { + return null; + } + Map boolFields = resolveConstantBooleanFields(enumDecl, constantName); + Boolean methodResult = evaluateBooleanMethod( + enumDecl, methodName, constantName, boolFields, context); + if (methodResult == null) { + return null; + } + return negated ? !methodResult : methodResult; + } + public static List filterEnumConstants( List candidates, String constraint, diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/MachineEnumCanonicalizer.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/MachineEnumCanonicalizer.java index fcfcf23..441f972 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/MachineEnumCanonicalizer.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/MachineEnumCanonicalizer.java @@ -308,9 +308,12 @@ public final class MachineEnumCanonicalizer { if (!narrowed.equals(expanded.getPolymorphicEvents())) { return expanded.toBuilder() .polymorphicEvents(narrowed) - .ambiguous(narrowed.size() > 1 && expanded.isAmbiguous()) + .ambiguous(narrowed.size() > 1) .build(); } + if (narrowed.size() > 1 && !expanded.isAmbiguous()) { + return expanded.toBuilder().ambiguous(true).build(); + } return expanded; } if (shouldInferPolymorphicEvents(expanded, machineTransitions, context)) { diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/RichEventPredicateEvaluator.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/RichEventPredicateEvaluator.java index 21a04f3..888fe26 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/RichEventPredicateEvaluator.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/RichEventPredicateEvaluator.java @@ -4,6 +4,7 @@ import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint; import click.kamil.springstatemachineexporter.analysis.service.TypeResolver; import click.kamil.springstatemachineexporter.ast.common.CodebaseContext; import org.eclipse.jdt.core.dom.ASTVisitor; +import org.eclipse.jdt.core.dom.BooleanLiteral; import org.eclipse.jdt.core.dom.ClassInstanceCreation; import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.InfixExpression; @@ -17,10 +18,13 @@ import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * Evaluates boolean predicates on rich/domain event types (e.g. {@code myEvent.isCorrect()}) - * and maps matching implementations to machine enum constants via {@code getType()}. + * and maps matching implementations to machine enum constants via type-accessor methods + * discovered from the trigger expression / receiver type — no hard-coded predicate names. */ public final class RichEventPredicateEvaluator { @@ -50,14 +54,18 @@ public final class RichEventPredicateEvaluator { ownerTypes.add(receiverTypeFqn); ownerTypes.addAll(context.getImplementations(receiverTypeFqn)); + List typeAccessors = typeAccessorMethods(trigger, predicate.receiverName(), receiverTypeFqn, machineEnumFqn, context); + Set matched = new LinkedHashSet<>(); for (String ownerFqn : ownerTypes) { - if (!predicateMatches(ownerFqn, predicate, context)) { + if (!predicateMatches(ownerFqn, predicate, machineEnumFqn, context)) { continue; } - String typeConstant = resolveDirectEnumFromPredicateMethod(ownerFqn, "getType", context); - if (typeConstant != null && matchesCandidate(typeConstant, candidates, machineEnumFqn, context)) { - matched.add(canonicalize(typeConstant, machineEnumFqn, context)); + for (String accessor : typeAccessors) { + String typeConstant = resolveDirectEnumFromPredicateMethod(ownerFqn, accessor, context); + if (typeConstant != null && matchesCandidate(typeConstant, candidates, machineEnumFqn, context)) { + matched.add(canonicalize(typeConstant, machineEnumFqn, context)); + } } String directConstant = resolveDirectEnumFromPredicateMethod(ownerFqn, predicate.methodName(), context); if (directConstant != null && matchesCandidate(directConstant, candidates, machineEnumFqn, context)) { @@ -74,6 +82,64 @@ public final class RichEventPredicateEvaluator { return filtered; } + /** + * Type-accessor method names come from the trigger event expression (dataflow), e.g. + * {@code myEvent.getType()} → {@code getType}, falling back to parameterless methods on the + * receiver type that return the machine enum. + */ + private static List typeAccessorMethods( + TriggerPoint trigger, + String receiverName, + String receiverTypeFqn, + String machineEnumFqn, + CodebaseContext context) { + LinkedHashSet names = new LinkedHashSet<>(); + String event = trigger.getEvent(); + if (event != null && receiverName != null && !receiverName.isBlank()) { + Pattern pattern = Pattern.compile( + Pattern.quote(receiverName) + "\\s*\\.\\s*([a-zA-Z_][\\w]*)\\s*\\("); + Matcher matcher = pattern.matcher(event); + while (matcher.find()) { + names.add(matcher.group(1)); + } + } + if (names.isEmpty() && receiverTypeFqn != null && machineEnumFqn != null && context != null) { + names.addAll(enumReturningMethods(receiverTypeFqn, machineEnumFqn, context)); + } + return List.copyOf(names); + } + + private static List enumReturningMethods( + String receiverTypeFqn, + String machineEnumFqn, + CodebaseContext context) { + TypeDeclaration type = context.getTypeDeclaration(receiverTypeFqn); + if (type == null) { + return List.of(); + } + TypeResolver typeResolver = new TypeResolver(context); + LinkedHashSet names = new LinkedHashSet<>(); + for (Object declObj : type.bodyDeclarations()) { + if (!(declObj instanceof MethodDeclaration method) || method.isConstructor()) { + continue; + } + if (!method.parameters().isEmpty() || method.getReturnType2() == null) { + continue; + } + String returnFqn = typeResolver.resolveTypeToFqn(method.getReturnType2(), type.getRoot()); + if (returnFqn != null) { + int generic = returnFqn.indexOf('<'); + if (generic >= 0) { + returnFqn = returnFqn.substring(0, generic); + } + } + if (machineEnumFqn.equals(returnFqn)) { + names.add(method.getName().getIdentifier()); + } + } + return List.copyOf(names); + } + private static String resolveReceiverTypeFqn(TriggerPoint trigger, String receiverName, CodebaseContext context) { if (trigger.getClassName() == null || trigger.getMethodName() == null || receiverName == null) { return null; @@ -99,6 +165,7 @@ public final class RichEventPredicateEvaluator { private static boolean predicateMatches( String ownerFqn, EnumMemberPredicateEvaluator.PredicateCall predicate, + String machineEnumFqn, CodebaseContext context) { TypeDeclaration type = context.getTypeDeclaration(ownerFqn); if (type == null) { @@ -113,7 +180,7 @@ public final class RichEventPredicateEvaluator { @Override public boolean visit(ReturnStatement node) { if (result[0] == null && node.getExpression() != null) { - result[0] = evaluateBoolean(node.getExpression()); + result[0] = evaluateBoolean(node.getExpression(), ownerFqn, machineEnumFqn, context); } return super.visit(node); } @@ -124,15 +191,30 @@ public final class RichEventPredicateEvaluator { return predicate.negated() ? !result[0] : result[0]; } - private static Boolean evaluateBoolean(Expression expression) { - if (expression instanceof org.eclipse.jdt.core.dom.BooleanLiteral literal) { + private static Boolean evaluateBoolean( + Expression expression, + String ownerFqn, + String machineEnumFqn, + CodebaseContext context) { + if (expression instanceof BooleanLiteral literal) { return literal.booleanValue(); } + // Delegate chained enum predicates (e.g. getCommand().isActive()) to enum-member evaluation — + // method names come from the AST, not a hard-coded list. if (expression instanceof MethodInvocation invocation - && "isEvent".equals(invocation.getName().getIdentifier()) - && invocation.getExpression() instanceof MethodInvocation getType - && "getType".equals(getType.getName().getIdentifier())) { - return true; + && invocation.arguments().isEmpty() + && invocation.getExpression() instanceof MethodInvocation typeCall + && typeCall.arguments().isEmpty()) { + String enumConstant = resolveDirectEnumFromPredicateMethod( + ownerFqn, typeCall.getName().getIdentifier(), context); + if (enumConstant != null && machineEnumFqn != null) { + return EnumMemberPredicateEvaluator.evaluateConstantPredicate( + enumConstant, + machineEnumFqn, + invocation.getName().getIdentifier(), + false, + context); + } } if (expression instanceof InfixExpression infix && infix.getOperator() == InfixExpression.Operator.EQUALS) { return true; @@ -178,13 +260,10 @@ public final class RichEventPredicateEvaluator { return extractEnumConstant(infix.getRightOperand()); } if (expression instanceof MethodInvocation invocation - && invocation.getExpression() instanceof MethodInvocation getType - && "getType".equals(getType.getName().getIdentifier())) { - if (invocation.getExpression() instanceof MethodInvocation inner - && inner.getExpression() instanceof ClassInstanceCreation creation - && !creation.arguments().isEmpty()) { - return extractEnumConstant((Expression) creation.arguments().get(0)); - } + && invocation.getExpression() instanceof MethodInvocation typeCall + && typeCall.getExpression() instanceof ClassInstanceCreation creation + && !creation.arguments().isEmpty()) { + return extractEnumConstant((Expression) creation.arguments().get(0)); } return null; } diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/enricher/CallChainLinkPolicyTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/enricher/CallChainLinkPolicyTest.java index d07c427..1584b52 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/enricher/CallChainLinkPolicyTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/enricher/CallChainLinkPolicyTest.java @@ -82,17 +82,15 @@ class CallChainLinkPolicyTest { @Test void shouldFailClosedWhenMultipleConcretePolyCandidatesRemain() { TriggerPoint trigger = TriggerPoint.builder() - .ambiguous(true) + .ambiguous(false) // even when ambiguous flag was cleared upstream .event("OrderEvent.valueOf(eventStr)") .polymorphicEvents(List.of("com.example.OrderEvent.PAY", "com.example.OrderEvent.SHIP")) .build(); assertThat(CallChainLinkPolicy.isEndpointNarrowPolyEvidence( trigger, "com.example.OrderEvent")).isFalse(); - // All candidates still belong to the machine event type (trusted for routing affinity). assertThat(CallChainLinkPolicy.isTrustedEnumPolymorphicWiden( trigger, "com.example.OrderEvent")).isTrue(); - // Cross-event dynamic widen still fail-closed for linking. assertThat(CallChainLinkPolicy.shouldFailClosedOnAmbiguousCallGraphWiden( trigger, "com.example.OrderEvent")).isTrue(); } diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/enricher/TransitionLinkerEnricherTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/enricher/TransitionLinkerEnricherTest.java index 775ed14..797b865 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/enricher/TransitionLinkerEnricherTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/enricher/TransitionLinkerEnricherTest.java @@ -78,6 +78,90 @@ class TransitionLinkerEnricherTest { assertThat(updated.getMatchedTransitions()).hasSize(1); } + @Test + void shouldNotLinkAllTransitionsWhenNonEventEnumConstantIsBound() { + Transition pay = new Transition(); + pay.setSourceStates(List.of(State.of("NEW", "com.example.OrderState.NEW"))); + pay.setTargetStates(List.of(State.of("PAID", "com.example.OrderState.PAID"))); + pay.setEvent(Event.of("PAY", "com.example.OrderEvent.PAY")); + + Transition ship = new Transition(); + ship.setSourceStates(List.of(State.of("PAID", "com.example.OrderState.PAID"))); + ship.setTargetStates(List.of(State.of("SHIPPED", "com.example.OrderState.SHIPPED"))); + ship.setEvent(Event.of("SHIP", "com.example.OrderEvent.SHIP")); + + // adjust/LOG-style: dataflow proves a non-transition / non-event constant. + CallChain chain = CallChain.builder() + .entryPoint(EntryPoint.builder() + .type(EntryPoint.Type.REST) + .name("POST /api/order/adjust") + .className("com.example.OrderController") + .methodName("adjust") + .build()) + .pathBindings(java.util.Map.of("event", "com.example.OrderEvent.LOG")) + .triggerPoint(TriggerPoint.builder() + .event("com.example.OrderEvent.LOG") + .polymorphicEvents(List.of("com.example.OrderEvent.LOG")) + .build()) + .build(); + + AnalysisResult result = AnalysisResult.builder() + .name("OrderStateMachineConfig") + .eventTypeFqn("com.example.OrderEvent") + .transitions(List.of(pay, ship)) + .metadata(CodebaseMetadata.builder().callChains(List.of(chain)).build()) + .build(); + + enricher.enrich(result, null, null); + + CallChain updated = result.getMetadata().getCallChains().get(0); + assertThat(updated.getMatchedTransitions()).isNullOrEmpty(); + assertThat(updated.getLinkResolution()).isIn(LinkResolution.NO_MATCH, LinkResolution.AMBIGUOUS_WIDEN); + } + + @Test + void shouldFailClosedWhenIsEventFilterLeavesMultipleTransitionEventsWithoutEndpointEvidence() { + Transition pay = new Transition(); + pay.setSourceStates(List.of(State.of("NEW", "com.example.OrderState.NEW"))); + pay.setTargetStates(List.of(State.of("PAID", "com.example.OrderState.PAID"))); + pay.setEvent(Event.of("PAY", "com.example.OrderEvent.PAY")); + + Transition ship = new Transition(); + ship.setSourceStates(List.of(State.of("PAID", "com.example.OrderState.PAID"))); + ship.setTargetStates(List.of(State.of("SHIPPED", "com.example.OrderState.SHIPPED"))); + ship.setEvent(Event.of("SHIP", "com.example.OrderEvent.SHIP")); + + CallChain chain = CallChain.builder() + .entryPoint(EntryPoint.builder() + .type(EntryPoint.Type.REST) + .name("POST /api/order/adjust") + .className("com.example.OrderController") + .methodName("adjust") + .build()) + .triggerPoint(TriggerPoint.builder() + .ambiguous(false) + .event("OrderEvent.valueOf(eventStr)") + .polymorphicEvents(List.of( + "com.example.OrderEvent.PAY", + "com.example.OrderEvent.SHIP")) + // After dataflow predicate filter: only transition events remain, still multi. + .build()) + .build(); + + AnalysisResult result = AnalysisResult.builder() + .name("OrderStateMachineConfig") + .eventTypeFqn("com.example.OrderEvent") + .transitions(List.of(pay, ship)) + .metadata(CodebaseMetadata.builder().callChains(List.of(chain)).build()) + .build(); + + enricher.enrich(result, null, null); + + CallChain updated = result.getMetadata().getCallChains().get(0); + assertThat(updated.getMatchedTransitions()).isNullOrEmpty(); + assertThat(updated.getLinkResolution()).isEqualTo(LinkResolution.AMBIGUOUS_WIDEN); + } + @Test void shouldExportUnresolvedExternalForGenericPathVariableEndpoint() { CallChain chain = CallChain.builder() diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/EnterpriseBooleanEnumPredicateExportTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/EnterpriseBooleanEnumPredicateExportTest.java index e94276f..244251e 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/EnterpriseBooleanEnumPredicateExportTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/EnterpriseBooleanEnumPredicateExportTest.java @@ -12,6 +12,7 @@ import java.nio.file.Path; import java.util.List; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.Stream; import java.util.stream.StreamSupport; import static org.assertj.core.api.Assertions.assertThat; @@ -74,22 +75,32 @@ class EnterpriseBooleanEnumPredicateExportTest { assertThat(polyEvents.size()).isLessThan(4); for (JsonNode dispatchChain : dispatchChains) { - long chainMatched = StreamSupport.stream(dispatchChain.get("matchedTransitions").spliterator(), false) - .count(); + JsonNode matched = dispatchChain.get("matchedTransitions"); + String resolution = dispatchChain.path("linkResolution").asText(); + // Wide predicate-filtered poly without per-endpoint dataflow must not + // highlight every transition — fail closed instead. + if ("AMBIGUOUS_WIDEN".equals(resolution) || "UNRESOLVED_EXTERNAL".equals(resolution)) { + assertThat(matched == null || matched.isNull() || (matched.isArray() && matched.isEmpty())) + .as("fail-closed/unresolved chains must not carry matches") + .isTrue(); + continue; + } + long chainMatched = matched == null || matched.isNull() + ? 0 + : StreamSupport.stream(matched.spliterator(), false).count(); assertThat(chainMatched) - .as("each dispatch chain must not cover all transitions") - .isLessThanOrEqualTo(transitionCount); + .as("each resolved dispatch chain must not cover all transitions without evidence") + .isLessThanOrEqualTo(1); } - int matchedCount = dispatchChains.stream() - .mapToInt(chain -> StreamSupport.stream(chain.get("matchedTransitions").spliterator(), false) - .mapToInt(node -> 1) - .sum()) - .sum(); - assertThat(matchedCount) - .as("matchedTransitions must not cover every transition × every enum constant") - .isLessThan(transitionCount * dispatchChains.size()); Set matchedEvents = dispatchChains.stream() - .flatMap(chain -> StreamSupport.stream(chain.get("matchedTransitions").spliterator(), false)) + .filter(chain -> "RESOLVED".equals(chain.path("linkResolution").asText())) + .flatMap(chain -> { + JsonNode matched = chain.get("matchedTransitions"); + if (matched == null || matched.isNull() || !matched.isArray()) { + return Stream.empty(); + } + return StreamSupport.stream(matched.spliterator(), false); + }) .map(node -> node.get("event").asText()) .collect(Collectors.toSet()); assertThat(matchedEvents) diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/EnumMemberPredicatePolymorphicInferenceTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/EnumMemberPredicatePolymorphicInferenceTest.java index 4fba29a..cb4cfb8 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/EnumMemberPredicatePolymorphicInferenceTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/EnumMemberPredicatePolymorphicInferenceTest.java @@ -242,7 +242,10 @@ class EnumMemberPredicatePolymorphicInferenceTest { assertThat(tp.getPolymorphicEvents()) .doesNotContain("com.example.order.OrderCommand.META"); assertThat(tp.isAmbiguous()).isTrue(); - assertThat(result.getMetadata().getCallChains().get(0).getMatchedTransitions()).hasSize(2); + // Multiple predicate-true constants without per-endpoint dataflow must not highlight every transition. + assertThat(result.getMetadata().getCallChains().get(0).getMatchedTransitions()).isNullOrEmpty(); + assertThat(result.getMetadata().getCallChains().get(0).getLinkResolution()) + .isEqualTo(click.kamil.springstatemachineexporter.analysis.model.LinkResolution.AMBIGUOUS_WIDEN); } @Test @@ -405,7 +408,9 @@ class EnumMemberPredicatePolymorphicInferenceTest { .containsExactlyInAnyOrder( "com.example.order.OrderCommand.PAY", "com.example.order.OrderCommand.SHIP"); - assertThat(result.getMetadata().getCallChains().get(0).getMatchedTransitions()).hasSize(2); + assertThat(result.getMetadata().getCallChains().get(0).getMatchedTransitions()).isNullOrEmpty(); + assertThat(result.getMetadata().getCallChains().get(0).getLinkResolution()) + .isEqualTo(click.kamil.springstatemachineexporter.analysis.model.LinkResolution.AMBIGUOUS_WIDEN); } @Test diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/RichEventIsCorrectPredicateTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/RichEventIsCorrectPredicateTest.java index ebdbeed..f5ea946 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/RichEventIsCorrectPredicateTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/RichEventIsCorrectPredicateTest.java @@ -104,7 +104,9 @@ class RichEventIsCorrectPredicateTest { .containsExactlyInAnyOrder( "com.example.order.OrderCommand.PAY", "com.example.order.OrderCommand.SHIP"); - assertThat(result.getMetadata().getCallChains().get(0).getMatchedTransitions()).hasSize(2); + assertThat(result.getMetadata().getCallChains().get(0).getMatchedTransitions()).isNullOrEmpty(); + assertThat(result.getMetadata().getCallChains().get(0).getLinkResolution()) + .isEqualTo(click.kamil.springstatemachineexporter.analysis.model.LinkResolution.AMBIGUOUS_WIDEN); } private static CodebaseContext scanProject(Path tempDir) throws Exception {