A
This commit is contained in:
@@ -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<String> 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;
|
||||
}
|
||||
|
||||
@@ -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<String, Boolean> boolFields = resolveConstantBooleanFields(enumDecl, constantName);
|
||||
Boolean methodResult = evaluateBooleanMethod(
|
||||
enumDecl, methodName, constantName, boolFields, context);
|
||||
if (methodResult == null) {
|
||||
return null;
|
||||
}
|
||||
return negated ? !methodResult : methodResult;
|
||||
}
|
||||
|
||||
public static List<String> filterEnumConstants(
|
||||
List<String> candidates,
|
||||
String constraint,
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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<String> typeAccessors = typeAccessorMethods(trigger, predicate.receiverName(), receiverTypeFqn, machineEnumFqn, context);
|
||||
|
||||
Set<String> 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<String> typeAccessorMethods(
|
||||
TriggerPoint trigger,
|
||||
String receiverName,
|
||||
String receiverTypeFqn,
|
||||
String machineEnumFqn,
|
||||
CodebaseContext context) {
|
||||
LinkedHashSet<String> 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<String> enumReturningMethods(
|
||||
String receiverTypeFqn,
|
||||
String machineEnumFqn,
|
||||
CodebaseContext context) {
|
||||
TypeDeclaration type = context.getTypeDeclaration(receiverTypeFqn);
|
||||
if (type == null) {
|
||||
return List.of();
|
||||
}
|
||||
TypeResolver typeResolver = new TypeResolver(context);
|
||||
LinkedHashSet<String> 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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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<String> 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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user