A
This commit is contained in:
@@ -2,7 +2,6 @@ package click.kamil.springstatemachineexporter.analysis.enricher;
|
|||||||
|
|
||||||
import click.kamil.springstatemachineexporter.analysis.enricher.path.SymbolicPathValueEstimator;
|
import click.kamil.springstatemachineexporter.analysis.enricher.path.SymbolicPathValueEstimator;
|
||||||
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
|
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
|
||||||
import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
|
|
||||||
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
|
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
|
||||||
import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver;
|
import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver;
|
||||||
import click.kamil.springstatemachineexporter.analysis.resolver.MachineEnumCanonicalizer;
|
import click.kamil.springstatemachineexporter.analysis.resolver.MachineEnumCanonicalizer;
|
||||||
@@ -29,10 +28,12 @@ import java.util.regex.Matcher;
|
|||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Narrows wide call-graph polymorphic widens to endpoint-specific enum constants.
|
* Narrows wide call-graph polymorphic widens using dataflow evidence only.
|
||||||
*
|
*
|
||||||
* <p>Uses multiple independent evidence sources (REST path, command keys, constraints, static trigger
|
* <p>Evidence sources: static trigger events, path-binding map values, branch constraints,
|
||||||
* events, method-chain literals, camelCase method names) and only narrows when evidence converges.
|
* sendEvent enum literals along the method chain, and AST path-value estimation.
|
||||||
|
* REST URL path segments and method-name tokens are intentionally not used — those are not
|
||||||
|
* dataflow and falsely link endpoints to transitions.
|
||||||
*/
|
*/
|
||||||
public final class CallChainPolyNarrower {
|
public final class CallChainPolyNarrower {
|
||||||
|
|
||||||
@@ -122,10 +123,9 @@ public final class CallChainPolyNarrower {
|
|||||||
CodebaseContext context,
|
CodebaseContext context,
|
||||||
HintVotes votes) {
|
HintVotes votes) {
|
||||||
collectFromStaticTriggerEvent(trigger, configuredConstants, votes);
|
collectFromStaticTriggerEvent(trigger, configuredConstants, votes);
|
||||||
collectFromEntryPoint(chain != null ? chain.getEntryPoint() : null, configuredConstants, votes);
|
|
||||||
collectFromConstraint(trigger.getConstraint(), configuredConstants, votes);
|
collectFromConstraint(trigger.getConstraint(), configuredConstants, votes);
|
||||||
if (chain != null) {
|
if (chain != null) {
|
||||||
collectFromMethodChain(chain.getMethodChain(), configuredConstants, votes);
|
collectFromPathBindings(chain.getPathBindings(), configuredConstants, votes);
|
||||||
collectFromSendEventLiterals(chain.getMethodChain(), configuredConstants, context, votes);
|
collectFromSendEventLiterals(chain.getMethodChain(), configuredConstants, context, votes);
|
||||||
collectFromPathEstimator(chain, configuredConstants, context, votes);
|
collectFromPathEstimator(chain, configuredConstants, context, votes);
|
||||||
}
|
}
|
||||||
@@ -145,41 +145,33 @@ public final class CallChainPolyNarrower {
|
|||||||
addEnumReference(event, configuredConstants, votes, STRONG);
|
addEnumReference(event, configuredConstants, votes, STRONG);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void collectFromEntryPoint(
|
/**
|
||||||
EntryPoint entryPoint,
|
* Path bindings come from {@code PathBindingEvaluator} / call-graph dataflow, not from URL text.
|
||||||
|
*/
|
||||||
|
private static void collectFromPathBindings(
|
||||||
|
Map<String, String> pathBindings,
|
||||||
Set<String> configuredConstants,
|
Set<String> configuredConstants,
|
||||||
HintVotes votes) {
|
HintVotes votes) {
|
||||||
if (entryPoint == null) {
|
if (pathBindings == null || pathBindings.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (entryPoint.getName() != null) {
|
for (Map.Entry<String, String> entry : pathBindings.entrySet()) {
|
||||||
collectFromPath(entryPoint.getName(), configuredConstants, votes);
|
String key = entry.getKey();
|
||||||
}
|
String value = entry.getValue();
|
||||||
if (entryPoint.getMetadata() != null) {
|
if (value == null || value.isBlank()) {
|
||||||
Object path = entryPoint.getMetadata().get("path");
|
|
||||||
if (path instanceof String pathValue) {
|
|
||||||
collectFromPath(pathValue, configuredConstants, votes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (entryPoint.getMethodName() != null) {
|
|
||||||
addUniqueIdentifierMatch(entryPoint.getMethodName(), configuredConstants, votes, WEAK);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void collectFromPath(String rawPath, Set<String> configuredConstants, HintVotes votes) {
|
|
||||||
if (rawPath == null || rawPath.isBlank()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int space = rawPath.indexOf(' ');
|
|
||||||
String path = space >= 0 ? rawPath.substring(space + 1).trim() : rawPath.trim();
|
|
||||||
if (path.isBlank()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (String segment : path.split("/")) {
|
|
||||||
if (segment.isBlank() || segment.contains("{")) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
addPathToken(segment, configuredConstants, votes, STRONG);
|
boolean eventLikeKey = key != null && (
|
||||||
|
key.equalsIgnoreCase("event")
|
||||||
|
|| key.equalsIgnoreCase("eventString")
|
||||||
|
|| key.equalsIgnoreCase("command")
|
||||||
|
|| key.equalsIgnoreCase("commandKey")
|
||||||
|
|| key.equalsIgnoreCase("action")
|
||||||
|
|| key.equalsIgnoreCase("actionKey")
|
||||||
|
|| key.toLowerCase(Locale.ROOT).endsWith("event"));
|
||||||
|
int weight = eventLikeKey ? STRONG : MEDIUM;
|
||||||
|
addEnumReference(value, configuredConstants, votes, weight);
|
||||||
|
addPathToken(value, configuredConstants, votes, weight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,30 +214,6 @@ public final class CallChainPolyNarrower {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void collectFromMethodChain(
|
|
||||||
List<String> methodChain,
|
|
||||||
Set<String> configuredConstants,
|
|
||||||
HintVotes votes) {
|
|
||||||
if (methodChain == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (String methodFqn : methodChain) {
|
|
||||||
if (methodFqn == null || !methodFqn.contains(".")) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1);
|
|
||||||
addUniqueIdentifierMatch(methodName, configuredConstants, votes, WEAK);
|
|
||||||
for (String token : splitCamelCase(methodName)) {
|
|
||||||
addPathToken(token, configuredConstants, votes, WEAK);
|
|
||||||
}
|
|
||||||
String className = methodFqn.substring(0, methodFqn.lastIndexOf('.'));
|
|
||||||
int nestedDot = className.lastIndexOf('.');
|
|
||||||
if (nestedDot >= 0) {
|
|
||||||
addUniqueIdentifierMatch(className.substring(nestedDot + 1), configuredConstants, votes, WEAK);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void collectFromSendEventLiterals(
|
private static void collectFromSendEventLiterals(
|
||||||
List<String> methodChain,
|
List<String> methodChain,
|
||||||
Set<String> configuredConstants,
|
Set<String> configuredConstants,
|
||||||
@@ -280,18 +248,28 @@ public final class CallChainPolyNarrower {
|
|||||||
methodDeclaration.getBody().accept(new ASTVisitor() {
|
methodDeclaration.getBody().accept(new ASTVisitor() {
|
||||||
@Override
|
@Override
|
||||||
public boolean visit(MethodInvocation node) {
|
public boolean visit(MethodInvocation node) {
|
||||||
if (!FIRE_METHOD_NAMES.contains(node.getName().getIdentifier())) {
|
boolean fireSite = FIRE_METHOD_NAMES.contains(node.getName().getIdentifier());
|
||||||
return super.visit(node);
|
|
||||||
}
|
|
||||||
for (Object argument : node.arguments()) {
|
for (Object argument : node.arguments()) {
|
||||||
if (!(argument instanceof Expression expression)) {
|
if (!(argument instanceof Expression expression)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (expression instanceof QualifiedName qualifiedName) {
|
if (expression instanceof QualifiedName qualifiedName) {
|
||||||
addEnumReference(qualifiedName.getFullyQualifiedName(), configuredConstants, votes, STRONG);
|
// Enum constant passed into the next hop (dataflow at the call site).
|
||||||
} else if (expression instanceof SimpleName simpleName) {
|
addEnumReference(
|
||||||
addEnumReference(simpleName.getIdentifier(), configuredConstants, votes, STRONG);
|
qualifiedName.getFullyQualifiedName(),
|
||||||
} else {
|
configuredConstants,
|
||||||
|
votes,
|
||||||
|
STRONG);
|
||||||
|
} else if (fireSite && expression instanceof SimpleName simpleName) {
|
||||||
|
// Only accept UPPERCASE identifiers that are configured constants —
|
||||||
|
// plain parameter names like "event" must not vote.
|
||||||
|
String identifier = simpleName.getIdentifier();
|
||||||
|
if (identifier != null
|
||||||
|
&& identifier.equals(identifier.toUpperCase(Locale.ROOT))
|
||||||
|
&& configuredConstants.contains(identifier)) {
|
||||||
|
addEnumReference(identifier, configuredConstants, votes, STRONG);
|
||||||
|
}
|
||||||
|
} else if (fireSite) {
|
||||||
String resolved = resolver.resolve(expression, context);
|
String resolved = resolver.resolve(expression, context);
|
||||||
if (resolved != null) {
|
if (resolved != null) {
|
||||||
addEnumReference(resolved, configuredConstants, votes, STRONG);
|
addEnumReference(resolved, configuredConstants, votes, STRONG);
|
||||||
|
|||||||
@@ -1136,9 +1136,7 @@ public final class MachineEnumCanonicalizer {
|
|||||||
List<String> boundEventLiterals = constraint != null && !constraint.isBlank()
|
List<String> boundEventLiterals = constraint != null && !constraint.isBlank()
|
||||||
? extractBoundEventLiteralsFromConstraint(constraint)
|
? extractBoundEventLiteralsFromConstraint(constraint)
|
||||||
: List.of();
|
: List.of();
|
||||||
if (boundEventLiterals.size() != 1) {
|
// Dataflow-only: never invent event constants from REST URL path segments.
|
||||||
boundEventLiterals = extractEventLiteralsFromEntryPointPath(entryPoint);
|
|
||||||
}
|
|
||||||
if (boundEventLiterals.size() != 1) {
|
if (boundEventLiterals.size() != 1) {
|
||||||
return trigger;
|
return trigger;
|
||||||
}
|
}
|
||||||
@@ -1146,19 +1144,13 @@ public final class MachineEnumCanonicalizer {
|
|||||||
if (eventTypeFqn == null || eventTypeFqn.isBlank()) {
|
if (eventTypeFqn == null || eventTypeFqn.isBlank()) {
|
||||||
return trigger;
|
return trigger;
|
||||||
}
|
}
|
||||||
String literal = boundEventLiterals.get(0).toUpperCase();
|
String literal = boundEventLiterals.get(0);
|
||||||
if (entryPoint != null && entryPoint.getName() != null
|
// Command keys like "order.pay" are not enum constants — take the trailing segment.
|
||||||
&& !entryPoint.getName().toUpperCase().contains("/" + literal + "/")
|
int lastDot = literal.lastIndexOf('.');
|
||||||
&& !entryPoint.getName().toUpperCase().endsWith("/" + literal)) {
|
if (lastDot >= 0 && lastDot + 1 < literal.length()) {
|
||||||
return trigger;
|
literal = literal.substring(lastDot + 1);
|
||||||
}
|
|
||||||
String machineTypeLiteral = constraint != null
|
|
||||||
? extractBoundParamLiteralFromConstraint(constraint, "machineType")
|
|
||||||
: null;
|
|
||||||
if (machineTypeLiteral != null && entryPoint != null && entryPoint.getName() != null
|
|
||||||
&& !entryPoint.getName().toUpperCase().contains("/" + machineTypeLiteral.toUpperCase() + "/")) {
|
|
||||||
return trigger;
|
|
||||||
}
|
}
|
||||||
|
literal = literal.toUpperCase();
|
||||||
String constantFqn = eventTypeFqn + "." + literal;
|
String constantFqn = eventTypeFqn + "." + literal;
|
||||||
if (context != null) {
|
if (context != null) {
|
||||||
List<String> machineEnumValues = context.getEnumValues(eventTypeFqn);
|
List<String> machineEnumValues = context.getEnumValues(eventTypeFqn);
|
||||||
@@ -1173,49 +1165,21 @@ public final class MachineEnumCanonicalizer {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String extractBoundParamLiteralFromConstraint(String constraint, String paramName) {
|
|
||||||
java.util.regex.Matcher matcher = java.util.regex.Pattern
|
|
||||||
.compile("\"([^\"]+)\"\\.equalsIgnoreCase\\(" + paramName + "\\)")
|
|
||||||
.matcher(constraint);
|
|
||||||
if (matcher.find()) {
|
|
||||||
return matcher.group(1);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<String> extractBoundEventLiteralsFromConstraint(String constraint) {
|
private static List<String> extractBoundEventLiteralsFromConstraint(String constraint) {
|
||||||
List<String> literals = new ArrayList<>();
|
List<String> literals = new ArrayList<>();
|
||||||
java.util.regex.Matcher matcher = java.util.regex.Pattern
|
java.util.regex.Matcher matcher = java.util.regex.Pattern
|
||||||
.compile("\"([^\"]+)\"\\.equalsIgnoreCase\\((\\w+)\\)")
|
.compile("\"([^\"]+)\"\\.equalsIgnoreCase\\((\\w+)\\)")
|
||||||
.matcher(constraint);
|
.matcher(constraint);
|
||||||
while (matcher.find()) {
|
while (matcher.find()) {
|
||||||
if ("event".equals(matcher.group(2))) {
|
String param = matcher.group(2);
|
||||||
|
if ("event".equals(param)
|
||||||
|
|| "eventString".equals(param)
|
||||||
|
|| "actionKey".equals(param)
|
||||||
|
|| "action".equals(param)
|
||||||
|
|| "commandKey".equals(param)) {
|
||||||
literals.add(matcher.group(1));
|
literals.add(matcher.group(1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return literals;
|
return literals;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<String> extractEventLiteralsFromEntryPointPath(EntryPoint entryPoint) {
|
|
||||||
if (entryPoint == null || entryPoint.getName() == null || entryPoint.getName().isBlank()) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
String rawPath = entryPoint.getName();
|
|
||||||
int space = rawPath.indexOf(' ');
|
|
||||||
String path = space >= 0 ? rawPath.substring(space + 1).trim() : rawPath.trim();
|
|
||||||
if (path.isBlank()) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
String[] segments = path.split("/");
|
|
||||||
for (int i = segments.length - 1; i >= 0; i--) {
|
|
||||||
String segment = segments[i];
|
|
||||||
if (segment.isBlank() || segment.contains("{")) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (segment.matches("[A-Z_][A-Z0-9_]*")) {
|
|
||||||
return List.of(segment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,10 +24,11 @@ class CallChainPolyNarrowerTest {
|
|||||||
private final TransitionLinkerEnricher enricher = new TransitionLinkerEnricher();
|
private final TransitionLinkerEnricher enricher = new TransitionLinkerEnricher();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldNarrowWidePolyUsingExpandedRestPathSegment() {
|
void shouldNotNarrowUsingRestPathSegmentAlone() {
|
||||||
Transition pay = transition("NEW", "PAID", "com.example.OrderEvent.PAY");
|
Transition pay = transition("NEW", "PAID", "com.example.OrderEvent.PAY");
|
||||||
Transition ship = transition("PAID", "SHIPPED", "com.example.OrderEvent.SHIP");
|
Transition ship = transition("PAID", "SHIPPED", "com.example.OrderEvent.SHIP");
|
||||||
|
|
||||||
|
// Wide poly + URL containing /PAY must NOT invent a narrow — that is not dataflow.
|
||||||
CallChain chain = CallChain.builder()
|
CallChain chain = CallChain.builder()
|
||||||
.entryPoint(EntryPoint.builder()
|
.entryPoint(EntryPoint.builder()
|
||||||
.name("POST /api/machine/ORDER/transition/PAY")
|
.name("POST /api/machine/ORDER/transition/PAY")
|
||||||
@@ -58,6 +59,47 @@ class CallChainPolyNarrowerTest {
|
|||||||
|
|
||||||
enricher.enrich(result, null, null);
|
enricher.enrich(result, null, null);
|
||||||
|
|
||||||
|
CallChain linked = result.getMetadata().getCallChains().get(0);
|
||||||
|
assertThat(linked.getMatchedTransitions()).isNullOrEmpty();
|
||||||
|
assertThat(linked.getLinkResolution()).isEqualTo(LinkResolution.AMBIGUOUS_WIDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNarrowUsingConstraintBindingEvidence() {
|
||||||
|
Transition pay = transition("NEW", "PAID", "com.example.OrderEvent.PAY");
|
||||||
|
Transition ship = transition("PAID", "SHIPPED", "com.example.OrderEvent.SHIP");
|
||||||
|
|
||||||
|
CallChain chain = CallChain.builder()
|
||||||
|
.entryPoint(EntryPoint.builder()
|
||||||
|
.name("POST /api/machine/ORDER/transition/PAY")
|
||||||
|
.className("com.example.MachineController")
|
||||||
|
.methodName("transition")
|
||||||
|
.build())
|
||||||
|
.methodChain(List.of(
|
||||||
|
"com.example.MachineController.transition",
|
||||||
|
"com.example.StateMachineDispatcher.fireOrder",
|
||||||
|
"com.example.StateMachine.send"))
|
||||||
|
.triggerPoint(TriggerPoint.builder()
|
||||||
|
.ambiguous(true)
|
||||||
|
.event("OrderEvent.valueOf(eventStr)")
|
||||||
|
.constraint("\"PAY\".equalsIgnoreCase(event)")
|
||||||
|
.polymorphicEvents(List.of(
|
||||||
|
"com.example.OrderEvent.PAY",
|
||||||
|
"com.example.OrderEvent.SHIP",
|
||||||
|
"com.example.OrderEvent.CANCEL"))
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
AnalysisResult result = AnalysisResult.builder()
|
||||||
|
.name("com.example.config.OrderStateMachineConfig")
|
||||||
|
.eventTypeFqn("com.example.OrderEvent")
|
||||||
|
.stateTypeFqn("com.example.OrderState")
|
||||||
|
.transitions(List.of(pay, ship))
|
||||||
|
.metadata(CodebaseMetadata.builder().callChains(List.of(chain)).build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
enricher.enrich(result, null, null);
|
||||||
|
|
||||||
CallChain linked = result.getMetadata().getCallChains().get(0);
|
CallChain linked = result.getMetadata().getCallChains().get(0);
|
||||||
assertThat(linked.getLinkResolution()).isEqualTo(LinkResolution.RESOLVED);
|
assertThat(linked.getLinkResolution()).isEqualTo(LinkResolution.RESOLVED);
|
||||||
assertThat(linked.getMatchedTransitions()).hasSize(1);
|
assertThat(linked.getMatchedTransitions()).hasSize(1);
|
||||||
@@ -68,7 +110,7 @@ class CallChainPolyNarrowerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldNarrowWidePolyUsingRichDispatcherMethodName() {
|
void shouldNotNarrowUsingMethodNameTokensAlone() {
|
||||||
Transition pay = transition("NEW", "PAID", "com.example.OrderEvent.PAY");
|
Transition pay = transition("NEW", "PAID", "com.example.OrderEvent.PAY");
|
||||||
Transition ship = transition("PAID", "SHIPPED", "com.example.OrderEvent.SHIP");
|
Transition ship = transition("PAID", "SHIPPED", "com.example.OrderEvent.SHIP");
|
||||||
|
|
||||||
@@ -103,6 +145,43 @@ class CallChainPolyNarrowerTest {
|
|||||||
|
|
||||||
enricher.enrich(result, null, null);
|
enricher.enrich(result, null, null);
|
||||||
|
|
||||||
|
CallChain linked = result.getMetadata().getCallChains().get(0);
|
||||||
|
assertThat(linked.getMatchedTransitions()).isNullOrEmpty();
|
||||||
|
assertThat(linked.getLinkResolution()).isEqualTo(LinkResolution.AMBIGUOUS_WIDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNarrowUsingPathBindingsFromDataflow() {
|
||||||
|
Transition pay = transition("NEW", "PAID", "com.example.OrderEvent.PAY");
|
||||||
|
Transition ship = transition("PAID", "SHIPPED", "com.example.OrderEvent.SHIP");
|
||||||
|
|
||||||
|
CallChain chain = CallChain.builder()
|
||||||
|
.entryPoint(EntryPoint.builder()
|
||||||
|
.name("POST /api/orders/pay")
|
||||||
|
.className("com.example.OrderController")
|
||||||
|
.methodName("pay")
|
||||||
|
.build())
|
||||||
|
.pathBindings(java.util.Map.of("event", "com.example.OrderEvent.PAY"))
|
||||||
|
.triggerPoint(TriggerPoint.builder()
|
||||||
|
.ambiguous(true)
|
||||||
|
.event("OrderEvent.valueOf(eventStr)")
|
||||||
|
.polymorphicEvents(List.of(
|
||||||
|
"com.example.OrderEvent.PAY",
|
||||||
|
"com.example.OrderEvent.SHIP",
|
||||||
|
"com.example.OrderEvent.CANCEL"))
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
AnalysisResult result = AnalysisResult.builder()
|
||||||
|
.name("com.example.config.OrderStateMachineConfig")
|
||||||
|
.eventTypeFqn("com.example.OrderEvent")
|
||||||
|
.stateTypeFqn("com.example.OrderState")
|
||||||
|
.transitions(List.of(pay, ship))
|
||||||
|
.metadata(CodebaseMetadata.builder().callChains(List.of(chain)).build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
enricher.enrich(result, null, null);
|
||||||
|
|
||||||
CallChain linked = result.getMetadata().getCallChains().get(0);
|
CallChain linked = result.getMetadata().getCallChains().get(0);
|
||||||
assertThat(linked.getLinkResolution()).isEqualTo(LinkResolution.RESOLVED);
|
assertThat(linked.getLinkResolution()).isEqualTo(LinkResolution.RESOLVED);
|
||||||
assertThat(linked.getMatchedTransitions()).hasSize(1);
|
assertThat(linked.getMatchedTransitions()).hasSize(1);
|
||||||
@@ -293,15 +372,18 @@ class CallChainPolyNarrowerTest {
|
|||||||
Transition pay = transition("NEW", "PAID", "com.example.OrderEvent.PAY");
|
Transition pay = transition("NEW", "PAID", "com.example.OrderEvent.PAY");
|
||||||
Transition ship = transition("PAID", "SHIPPED", "com.example.OrderEvent.SHIP");
|
Transition ship = transition("PAID", "SHIPPED", "com.example.OrderEvent.SHIP");
|
||||||
|
|
||||||
|
// Without eventTypeFqn, path text must not invent PAY — require dataflow bindings/constraints.
|
||||||
CallChain chain = CallChain.builder()
|
CallChain chain = CallChain.builder()
|
||||||
.entryPoint(EntryPoint.builder()
|
.entryPoint(EntryPoint.builder()
|
||||||
.name("POST /api/machine/ORDER/transition/PAY")
|
.name("POST /api/machine/ORDER/transition/PAY")
|
||||||
.className("com.example.MachineController")
|
.className("com.example.MachineController")
|
||||||
.methodName("transition")
|
.methodName("transition")
|
||||||
.build())
|
.build())
|
||||||
|
.pathBindings(java.util.Map.of("event", "PAY"))
|
||||||
.triggerPoint(TriggerPoint.builder()
|
.triggerPoint(TriggerPoint.builder()
|
||||||
.ambiguous(true)
|
.ambiguous(true)
|
||||||
.event("OrderEvent.valueOf(eventStr)")
|
.event("OrderEvent.valueOf(eventStr)")
|
||||||
|
.eventTypeFqn("com.example.OrderEvent")
|
||||||
.polymorphicEvents(List.of(
|
.polymorphicEvents(List.of(
|
||||||
"com.example.OrderEvent.PAY",
|
"com.example.OrderEvent.PAY",
|
||||||
"com.example.OrderEvent.SHIP",
|
"com.example.OrderEvent.SHIP",
|
||||||
|
|||||||
Reference in New Issue
Block a user