Match HTML sidebar chains by entry point name when paths differ.
Expanded REST endpoints share the same controller method, so class/method matching grouped unrelated call chains; prefer entry point name when present and cover with enterprise HTML tests. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -394,10 +394,20 @@
|
|||||||
return isLifecycleChain(c) || isHighlightableChain(c) || isUnresolvedExternalChain(c);
|
return isLifecycleChain(c) || isHighlightableChain(c) || isUnresolvedExternalChain(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function entryPointsMatch(chainEntryPoint, sidebarEntryPoint) {
|
||||||
|
if (!chainEntryPoint || !sidebarEntryPoint) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (chainEntryPoint.name && sidebarEntryPoint.name) {
|
||||||
|
return chainEntryPoint.name === sidebarEntryPoint.name;
|
||||||
|
}
|
||||||
|
return chainEntryPoint.className === sidebarEntryPoint.className
|
||||||
|
&& chainEntryPoint.methodName === sidebarEntryPoint.methodName;
|
||||||
|
}
|
||||||
|
|
||||||
function chainsForEntryPoint(ep) {
|
function chainsForEntryPoint(ep) {
|
||||||
return (metadata.metadata.callChains || []).filter(c =>
|
return (metadata.metadata.callChains || []).filter(c =>
|
||||||
c.entryPoint.className === ep.className &&
|
entryPointsMatch(c.entryPoint, ep) &&
|
||||||
c.entryPoint.methodName === ep.methodName &&
|
|
||||||
isSidebarChain(c)
|
isSidebarChain(c)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,16 +13,16 @@ import java.util.List;
|
|||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enterprise dedicated REST endpoints must produce HTML explorer link keys end-to-end;
|
* Enterprise dedicated and expanded generic REST endpoints must produce HTML explorer link keys end-to-end.
|
||||||
* generic {@code {event}} dispatchers remain unresolved external.
|
|
||||||
*/
|
*/
|
||||||
class EnterpriseDedicatedEndpointHtmlTest {
|
class EnterpriseDedicatedEndpointHtmlTest {
|
||||||
|
|
||||||
private static final String PAY_ENDPOINT = "POST /api/machine/order/pay";
|
private static final String PAY_ENDPOINT = "POST /api/machine/order/pay";
|
||||||
private static final String GENERIC_ENDPOINT = "POST /api/machine/{machineType}/transition/{event}";
|
private static final String ORDER_PAY = "POST /api/machine/ORDER/transition/PAY";
|
||||||
|
private static final String ORDER_SHIP = "POST /api/machine/ORDER/transition/SHIP";
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void htmlShouldExposeLinkKeysForDedicatedPayEndpoint(@TempDir Path tempDir) throws Exception {
|
void htmlShouldExposeLinkKeysForDedicatedAndExpandedOrderEndpoints(@TempDir Path tempDir) throws Exception {
|
||||||
Path enterprise = findProjectRoot().resolve("state_machines/state_machine_enterprise");
|
Path enterprise = findProjectRoot().resolve("state_machines/state_machine_enterprise");
|
||||||
ExportService exportService = new ExportService(List.of(new HtmlExporter(), new JsonExporter()));
|
ExportService exportService = new ExportService(List.of(new HtmlExporter(), new JsonExporter()));
|
||||||
exportService.runExporter(enterprise, tempDir, List.of("html"), true, List.of(), null, null,
|
exportService.runExporter(enterprise, tempDir, List.of("html"), true, List.of(), null, null,
|
||||||
@@ -33,11 +33,14 @@ class EnterpriseDedicatedEndpointHtmlTest {
|
|||||||
String html = Files.readString(htmlFile);
|
String html = Files.readString(htmlFile);
|
||||||
|
|
||||||
assertThat(html).contains(PAY_ENDPOINT);
|
assertThat(html).contains(PAY_ENDPOINT);
|
||||||
|
assertThat(html).contains(ORDER_PAY);
|
||||||
|
assertThat(html).contains(ORDER_SHIP);
|
||||||
|
assertThat(html).contains("function entryPointsMatch(chainEntryPoint, sidebarEntryPoint)");
|
||||||
assertThat(html).contains("\"linkResolution\" : \"RESOLVED\"");
|
assertThat(html).contains("\"linkResolution\" : \"RESOLVED\"");
|
||||||
assertThat(html).contains("#link_");
|
assertThat(html).contains("#link_OrderState_NEW__OrderEvent_PAY");
|
||||||
assertThat(html).contains("\"linkKey\"");
|
assertThat(html).contains("#link_OrderState_PENDING__OrderEvent_SHIP");
|
||||||
assertThat(html).contains(GENERIC_ENDPOINT);
|
assertThat(html).contains("\"linkKey\" : \"OrderState_NEW__OrderEvent_PAY\"");
|
||||||
assertThat(html).contains("linkResolution === 'UNRESOLVED_EXTERNAL'");
|
assertThat(html).contains("\"linkKey\" : \"OrderState_PENDING__OrderEvent_SHIP\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Path findOrderMachineHtml(Path outputDir) throws Exception {
|
private static Path findOrderMachineHtml(Path outputDir) throws Exception {
|
||||||
|
|||||||
@@ -410,6 +410,78 @@ class HtmlTransitionLinkConsistencyTest {
|
|||||||
assertThat(html).contains("#link_OrderState_NEW__OrderEvent_PAY");
|
assertThat(html).contains("#link_OrderState_NEW__OrderEvent_PAY");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void htmlShouldMatchCallChainsByEntryPointNameWhenControllerMethodIsShared() {
|
||||||
|
EntryPoint payTransition = EntryPoint.builder()
|
||||||
|
.type(EntryPoint.Type.REST)
|
||||||
|
.name("POST /api/machine/ORDER/transition/PAY")
|
||||||
|
.className("com.example.web.StateMachineController")
|
||||||
|
.methodName("transition")
|
||||||
|
.build();
|
||||||
|
EntryPoint shipTransition = EntryPoint.builder()
|
||||||
|
.type(EntryPoint.Type.REST)
|
||||||
|
.name("POST /api/machine/ORDER/transition/SHIP")
|
||||||
|
.className("com.example.web.StateMachineController")
|
||||||
|
.methodName("transition")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
CallChain payChain = CallChain.builder()
|
||||||
|
.entryPoint(payTransition)
|
||||||
|
.linkResolution(LinkResolution.RESOLVED)
|
||||||
|
.triggerPoint(TriggerPoint.builder()
|
||||||
|
.event("com.example.order.OrderEvent.PAY")
|
||||||
|
.build())
|
||||||
|
.matchedTransitions(List.of(MatchedTransition.builder()
|
||||||
|
.sourceState("com.example.order.OrderState.NEW")
|
||||||
|
.targetState("com.example.order.OrderState.PAID")
|
||||||
|
.event("com.example.order.OrderEvent.PAY")
|
||||||
|
.linkKey("OrderState_NEW__OrderEvent_PAY")
|
||||||
|
.build()))
|
||||||
|
.build();
|
||||||
|
CallChain shipChain = CallChain.builder()
|
||||||
|
.entryPoint(shipTransition)
|
||||||
|
.linkResolution(LinkResolution.RESOLVED)
|
||||||
|
.triggerPoint(TriggerPoint.builder()
|
||||||
|
.event("com.example.order.OrderEvent.SHIP")
|
||||||
|
.build())
|
||||||
|
.matchedTransitions(List.of(MatchedTransition.builder()
|
||||||
|
.sourceState("com.example.order.OrderState.PAID")
|
||||||
|
.targetState("com.example.order.OrderState.SHIPPED")
|
||||||
|
.event("com.example.order.OrderEvent.SHIP")
|
||||||
|
.linkKey("OrderState_PAID__OrderEvent_SHIP")
|
||||||
|
.build()))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
AnalysisResult result = AnalysisResult.builder()
|
||||||
|
.name("com.example.config.OrderStateMachineConfiguration")
|
||||||
|
.stateTypeFqn("com.example.order.OrderState")
|
||||||
|
.eventTypeFqn("com.example.order.OrderEvent")
|
||||||
|
.transitions(List.of(
|
||||||
|
shortTransition(
|
||||||
|
"com.example.order.OrderEvent.PAY",
|
||||||
|
"com.example.order.OrderState.NEW",
|
||||||
|
"com.example.order.OrderState.PAID"),
|
||||||
|
shortTransition(
|
||||||
|
"com.example.order.OrderEvent.SHIP",
|
||||||
|
"com.example.order.OrderState.PAID",
|
||||||
|
"com.example.order.OrderState.SHIPPED")))
|
||||||
|
.startStates(Set.of("com.example.order.OrderState.NEW"))
|
||||||
|
.endStates(Set.of("com.example.order.OrderState.SHIPPED"))
|
||||||
|
.metadata(CodebaseMetadata.builder()
|
||||||
|
.entryPoints(List.of(payTransition, shipTransition))
|
||||||
|
.callChains(List.of(payChain, shipChain))
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
String html = new HtmlExporter().export(result, ExportOptions.builder().includeMetadataPane(true).build());
|
||||||
|
|
||||||
|
assertThat(html).contains("function entryPointsMatch(chainEntryPoint, sidebarEntryPoint)");
|
||||||
|
assertThat(html).contains("POST /api/machine/ORDER/transition/PAY");
|
||||||
|
assertThat(html).contains("POST /api/machine/ORDER/transition/SHIP");
|
||||||
|
assertThat(html).contains("\"linkKey\" : \"OrderState_NEW__OrderEvent_PAY\"");
|
||||||
|
assertThat(html).contains("\"linkKey\" : \"OrderState_PAID__OrderEvent_SHIP\"");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void ambiguousWidenHtmlShouldExposeMatchedTransitionLinkKeys() {
|
void ambiguousWidenHtmlShouldExposeMatchedTransitionLinkKeys() {
|
||||||
EntryPoint ambiguousEp = EntryPoint.builder()
|
EntryPoint ambiguousEp = EntryPoint.builder()
|
||||||
|
|||||||
Reference in New Issue
Block a user